Merge branch 'main' into dev

This commit is contained in:
Veronica K. B. Olsen
2021-04-23 22:02:59 +02:00
41 changed files with 1958 additions and 1966 deletions
+6 -6
View File
@@ -29,7 +29,7 @@ The full documentation is available on [readthedocs.io](https://novelwriter.read
## Implementation ## Implementation
The application is written in Python 3 using Qt5 via PyQt5. It is developed on Linux, but it should The application is written in Python 3 using Qt5 and PyQt5. It is developed on Linux, but it should
in principle work fine on other operating systems as well, as long as dependencies are met. It is in principle work fine on other operating systems as well, as long as dependencies are met. It is
regularly tested on Ubuntu Linux, Windows, and macOS. regularly tested on Ubuntu Linux, Windows, and macOS.
@@ -142,7 +142,7 @@ space only.
Dependencies can generally be installed from PyPi with: Dependencies can generally be installed from PyPi with:
```bash ```bash
pip install pyqt5 lxml pyenchant pip install -r requirements.txt
``` ```
### Additional Steps for Linux ### Additional Steps for Linux
@@ -153,7 +153,7 @@ and Debian, run:
sudo apt install python3-pyqt5 python3-lxml python3-enchant sudo apt install python3-pyqt5 python3-lxml python3-enchant
``` ```
If you want to set up a launcher on Linux, you can run: If you want to set up a launcher and icons on Linux, you can run:
```bash ```bash
python setup.py xdg-install python setup.py xdg-install
``` ```
@@ -171,12 +171,12 @@ pip3 install --user pyobjc
### Additional Steps for Windows ### Additional Steps for Windows
Windows doesn't by default come with Python installed. If you haven't installed it already, get it Windows does not by default come with Python installed. If you haven't installed it already, get it
from [python.org/downloads](https://www.python.org/downloads/). Remember to select "Add Python to from [python.org/downloads](https://www.python.org/downloads/). Remember to select "Add Python to
PATH" during the installation. PATH" during the installation.
The script `setup_windows.bat` can be used to create desktop and start menu icons for novelWriter. The script `windows_install.bat` in the `setup` folder can be used to create desktop and start menu
The script will also install dependencies for you from PyPi. icons for novelWriter. The script will also install dependencies for you from PyPi.
## Internationalisation ## Internationalisation
+2 -2
View File
@@ -76,7 +76,7 @@ Step 2: Dependencies and Icons
**Alternative A: By Script** **Alternative A: By Script**
Open the folder where you extracted novelWriter, and double-click the file named Open the folder where you extracted novelWriter, and double-click the file named
``setup_windows.bat``. This should open a command line window and run the setup script to install ``windows_install.bat``. This should open a command line window and run the setup script to install
dependencies, and add desktop and start menu icons. dependencies, and add desktop and start menu icons.
.. note:: .. note::
@@ -119,7 +119,7 @@ Uninstalling
**Alternative A: By Script** **Alternative A: By Script**
Open the folder where you keep the novelWriter files, and double-click the file named Open the folder where you keep the novelWriter files, and double-click the file named
``uninstall_windows.bat``. This should open a command line window and run the setup script to ``windows_uninstall.bat``. This should open a command line window and run the setup script to
remove the main dependency packages and remove desktop and start menu icons. remove the main dependency packages and remove desktop and start menu icons.
.. note:: .. note::
+408 -409
View File
File diff suppressed because it is too large Load Diff
+408 -409
View File
File diff suppressed because it is too large Load Diff
+408 -409
View File
File diff suppressed because it is too large Load Diff
+408 -409
View File
File diff suppressed because it is too large Load Diff
+11 -11
View File
@@ -3,29 +3,29 @@ SOURCES += \
nw/core/document.py \ nw/core/document.py \
nw/core/project.py \ nw/core/project.py \
nw/core/tokenizer.py \ nw/core/tokenizer.py \
nw/gui/about.py \ nw/dialogs/about.py \
nw/gui/build.py \ nw/dialogs/docmerge.py \
nw/dialogs/docsplit.py \
nw/dialogs/itemeditor.py \
nw/dialogs/preferences.py \
nw/dialogs/projload.py \
nw/dialogs/projsettings.py \
nw/dialogs/wordlist.py \
nw/gui/custom.py \ nw/gui/custom.py \
nw/gui/doceditor.py \ nw/gui/doceditor.py \
nw/gui/docmerge.py \
nw/gui/docsplit.py \
nw/gui/docviewer.py \ nw/gui/docviewer.py \
nw/gui/itemdetails.py \ nw/gui/itemdetails.py \
nw/gui/itemeditor.py \
nw/gui/mainmenu.py \ nw/gui/mainmenu.py \
nw/gui/noveltree.py \ nw/gui/noveltree.py \
nw/gui/outline.py \ nw/gui/outline.py \
nw/gui/outlinedetails.py \ nw/gui/outlinedetails.py \
nw/gui/preferences.py \
nw/gui/projdetails.py \ nw/gui/projdetails.py \
nw/gui/projload.py \
nw/gui/projsettings.py \
nw/gui/projtree.py \ nw/gui/projtree.py \
nw/gui/projwizard.py \
nw/gui/statusbar.py \ nw/gui/statusbar.py \
nw/gui/theme.py \ nw/gui/theme.py \
nw/gui/wordlist.py \ nw/tools/build.py \
nw/gui/writingstats.py \ nw/tools/projwizard.py \
nw/tools/writingstats.py \
nw/common.py \ nw/common.py \
nw/constants.py \ nw/constants.py \
nw/error.py \ nw/error.py \
+23
View File
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
from nw.dialogs.about import GuiAbout
from nw.dialogs.docmerge import GuiDocMerge
from nw.dialogs.docsplit import GuiDocSplit
from nw.dialogs.itemeditor import GuiItemEditor
from nw.dialogs.preferences import GuiPreferences
from nw.dialogs.projload import GuiProjectLoad
from nw.dialogs.projsettings import GuiProjectSettings
from nw.dialogs.quotes import GuiQuoteSelect
from nw.dialogs.wordlist import GuiWordList
__all__ = [
"GuiAbout",
"GuiDocMerge",
"GuiDocSplit",
"GuiItemEditor",
"GuiPreferences",
"GuiProjectLoad",
"GuiProjectSettings",
"GuiQuoteSelect",
"GuiWordList",
]
@@ -38,7 +38,8 @@ from PyQt5.QtWidgets import (
from nw.core import NWSpellSimple, NWSpellEnchant from nw.core import NWSpellSimple, NWSpellEnchant
from nw.enum import nwAlert from nw.enum import nwAlert
from nw.constants import nwConst from nw.constants import nwConst
from nw.gui.custom import QSwitch, QConfigLayout, PagedDialog, QuotesDialog from nw.gui.custom import QSwitch, QConfigLayout, PagedDialog
from nw.dialogs.quotes import GuiQuoteSelect
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -1223,7 +1224,7 @@ class GuiPreferencesQuotes(QWidget):
def _getQuote(self, qType): def _getQuote(self, qType):
"""Dialog for single quote open. """Dialog for single quote open.
""" """
qtBox = QuotesDialog(self, currentQuote=self.quoteSym[qType].text()) qtBox = GuiQuoteSelect(self, currentQuote=self.quoteSym[qType].text())
if qtBox.exec_() == QDialog.Accepted: if qtBox.exec_() == QDialog.Accepted:
self.quoteSym[qType].setText(qtBox.selectedQuote) self.quoteSym[qType].setText(qtBox.selectedQuote)
+133
View File
@@ -0,0 +1,133 @@
# -*- coding: utf-8 -*-
"""
novelWriter GUI Quotes Dialog
===============================
GUI class for quotes dialog
File History:
Created: 2020-06-18 [0.9]
This file is a part of novelWriter
Copyright 20182021, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import nw
import logging
from PyQt5.QtGui import QFontMetrics
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtWidgets import (
QLabel, QVBoxLayout, QHBoxLayout, QDialog, QDialogButtonBox,
QListWidget, QListWidgetItem, QFrame
)
from nw.constants import trConst, nwQuotes
logger = logging.getLogger(__name__)
class GuiQuoteSelect(QDialog):
selectedQuote = ""
def __init__(self, theParent=None, currentQuote="\""):
QDialog.__init__(self, parent=theParent)
self.mainConf = nw.CONFIG
self.outerBox = QVBoxLayout()
self.innerBox = QHBoxLayout()
self.labelBox = QVBoxLayout()
self.selectedQuote = currentQuote
qMetrics = QFontMetrics(self.font())
pxW = 7*qMetrics.boundingRectChar("M").width()
pxH = 7*qMetrics.boundingRectChar("M").height()
pxH = 7*qMetrics.boundingRectChar("M").height()
lblFont = self.font()
lblFont.setPointSizeF(4*lblFont.pointSizeF())
# Preview Label
self.previewLabel = QLabel(currentQuote)
self.previewLabel.setFont(lblFont)
self.previewLabel.setFixedSize(QSize(pxW, pxH))
self.previewLabel.setAlignment(Qt.AlignCenter)
self.previewLabel.setFrameStyle(QFrame.Box | QFrame.Plain)
# Quote Symbols
self.listBox = QListWidget()
self.listBox.itemSelectionChanged.connect(self._selectedSymbol)
minSize = 100
for sKey, sLabel in nwQuotes.SYMBOLS.items():
theText = "[ %s ] %s" % (sKey, trConst(sLabel))
minSize = max(minSize, qMetrics.boundingRect(theText).width())
qtItem = QListWidgetItem(theText)
qtItem.setData(Qt.UserRole, sKey)
self.listBox.addItem(qtItem)
if sKey == currentQuote:
self.listBox.setCurrentItem(qtItem)
self.listBox.setMinimumWidth(minSize + self.mainConf.pxInt(40))
self.listBox.setMinimumHeight(self.mainConf.pxInt(150))
# Buttons
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.buttonBox.accepted.connect(self._doAccept)
self.buttonBox.rejected.connect(self._doReject)
# Assemble
self.labelBox.addWidget(self.previewLabel, 0, Qt.AlignTop)
self.labelBox.addStretch(1)
self.innerBox.addLayout(self.labelBox)
self.innerBox.addWidget(self.listBox)
self.outerBox.addLayout(self.innerBox)
self.outerBox.addWidget(self.buttonBox)
self.setLayout(self.outerBox)
return
##
# Slots
##
def _selectedSymbol(self):
"""Update the preview label and the selected quote style.
"""
selItems = self.listBox.selectedItems()
if selItems:
theSymbol = selItems[0].data(Qt.UserRole)
self.previewLabel.setText(theSymbol)
self.selectedQuote = theSymbol
return
def _doAccept(self):
"""Ok button clicked.
"""
self.accept()
return
def _doReject(self):
"""Cancel button clicked.
"""
self.reject()
return
# END Class GuiQuoteSelect
+1 -23
View File
@@ -1,50 +1,28 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from nw.gui.about import GuiAbout
from nw.gui.build import GuiBuildNovel
from nw.gui.doceditor import GuiDocEditor from nw.gui.doceditor import GuiDocEditor
from nw.gui.docmerge import GuiDocMerge
from nw.gui.docsplit import GuiDocSplit
from nw.gui.docviewer import GuiDocViewer, GuiDocViewDetails from nw.gui.docviewer import GuiDocViewer, GuiDocViewDetails
from nw.gui.itemdetails import GuiItemDetails from nw.gui.itemdetails import GuiItemDetails
from nw.gui.itemeditor import GuiItemEditor
from nw.gui.mainmenu import GuiMainMenu from nw.gui.mainmenu import GuiMainMenu
from nw.gui.noveltree import GuiNovelTree from nw.gui.noveltree import GuiNovelTree
from nw.gui.outline import GuiOutline from nw.gui.outline import GuiOutline
from nw.gui.outlinedetails import GuiOutlineDetails from nw.gui.outlinedetails import GuiOutlineDetails
from nw.gui.preferences import GuiPreferences
from nw.gui.projdetails import GuiProjectDetails from nw.gui.projdetails import GuiProjectDetails
from nw.gui.projload import GuiProjectLoad
from nw.gui.projsettings import GuiProjectSettings
from nw.gui.projtree import GuiProjectTree from nw.gui.projtree import GuiProjectTree
from nw.gui.projwizard import GuiProjectWizard
from nw.gui.statusbar import GuiMainStatus from nw.gui.statusbar import GuiMainStatus
from nw.gui.theme import GuiTheme from nw.gui.theme import GuiTheme
from nw.gui.wordlist import GuiWordList
from nw.gui.writingstats import GuiWritingStats
__all__ = [ __all__ = [
"GuiAbout",
"GuiBuildNovel",
"GuiDocEditor", "GuiDocEditor",
"GuiDocMerge",
"GuiDocSplit",
"GuiDocViewDetails", "GuiDocViewDetails",
"GuiDocViewer", "GuiDocViewer",
"GuiItemDetails", "GuiItemDetails",
"GuiItemEditor",
"GuiMainMenu", "GuiMainMenu",
"GuiNovelTree",
"GuiMainStatus", "GuiMainStatus",
"GuiNovelTree",
"GuiOutline", "GuiOutline",
"GuiOutlineDetails", "GuiOutlineDetails",
"GuiPreferences",
"GuiProjectDetails", "GuiProjectDetails",
"GuiProjectLoad",
"GuiProjectSettings",
"GuiProjectTree", "GuiProjectTree",
"GuiProjectWizard",
"GuiTheme", "GuiTheme",
"GuiWordList",
"GuiWritingStats",
] ]
+5 -104
View File
@@ -29,18 +29,17 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
import nw import nw
import logging import logging
from PyQt5.QtGui import QColor, QPalette, QPainter, QFontMetrics from PyQt5.QtGui import QColor, QPalette, QPainter
from PyQt5.QtCore import ( from PyQt5.QtCore import (
Qt, QRect, QPoint, QSize, QRectF, QPropertyAnimation, pyqtProperty Qt, QRect, QPoint, QRectF, QPropertyAnimation, pyqtProperty
) )
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QGridLayout, QLabel, QWidget, QVBoxLayout, QHBoxLayout, QSizePolicy, QGridLayout, QLabel, QWidget, QVBoxLayout, QHBoxLayout, QSizePolicy,
QAbstractButton, QDialog, QTabWidget, QTabBar, QStyle, QDialogButtonBox, QAbstractButton, QDialog, QTabWidget, QTabBar, QStyle, QStylePainter,
QStylePainter, QStyleOptionTab, QListWidget, QListWidgetItem, QFrame, QStyleOptionTab, QLineEdit
QLineEdit
) )
from nw.constants import trConst, nwQuotes, nwUnicode from nw.constants import nwUnicode
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -461,101 +460,3 @@ class VerticalTabBar(QTabBar):
return return
# END Class VerticalTabBar # END Class VerticalTabBar
# =============================================================================================== #
# Quotes Dialog
# =============================================================================================== #
class QuotesDialog(QDialog):
selectedQuote = ""
def __init__(self, theParent=None, currentQuote="\""):
QDialog.__init__(self, parent=theParent)
self.mainConf = nw.CONFIG
self.outerBox = QVBoxLayout()
self.innerBox = QHBoxLayout()
self.labelBox = QVBoxLayout()
self.selectedQuote = currentQuote
qMetrics = QFontMetrics(self.font())
pxW = 7*qMetrics.boundingRectChar("M").width()
pxH = 7*qMetrics.boundingRectChar("M").height()
pxH = 7*qMetrics.boundingRectChar("M").height()
lblFont = self.font()
lblFont.setPointSizeF(4*lblFont.pointSizeF())
# Preview Label
self.previewLabel = QLabel(currentQuote)
self.previewLabel.setFont(lblFont)
self.previewLabel.setFixedSize(QSize(pxW, pxH))
self.previewLabel.setAlignment(Qt.AlignCenter)
self.previewLabel.setFrameStyle(QFrame.Box | QFrame.Plain)
# Quote Symbols
self.listBox = QListWidget()
self.listBox.itemSelectionChanged.connect(self._selectedSymbol)
minSize = 100
for sKey, sLabel in nwQuotes.SYMBOLS.items():
theText = "[ %s ] %s" % (sKey, trConst(sLabel))
minSize = max(minSize, qMetrics.boundingRect(theText).width())
qtItem = QListWidgetItem(theText)
qtItem.setData(Qt.UserRole, sKey)
self.listBox.addItem(qtItem)
if sKey == currentQuote:
self.listBox.setCurrentItem(qtItem)
self.listBox.setMinimumWidth(minSize + self.mainConf.pxInt(40))
self.listBox.setMinimumHeight(self.mainConf.pxInt(150))
# Buttons
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.buttonBox.accepted.connect(self._doAccept)
self.buttonBox.rejected.connect(self._doReject)
# Assemble
self.labelBox.addWidget(self.previewLabel, 0, Qt.AlignTop)
self.labelBox.addStretch(1)
self.innerBox.addLayout(self.labelBox)
self.innerBox.addWidget(self.listBox)
self.outerBox.addLayout(self.innerBox)
self.outerBox.addWidget(self.buttonBox)
self.setLayout(self.outerBox)
return
##
# Slots
##
def _selectedSymbol(self):
"""Update the preview label and the selected quote style.
"""
selItems = self.listBox.selectedItems()
if selItems:
theSymbol = selItems[0].data(Qt.UserRole)
self.previewLabel.setText(theSymbol)
self.selectedQuote = theSymbol
return
def _doAccept(self):
"""Ok button clicked.
"""
self.accept()
return
def _doReject(self):
"""Cancel button clicked.
"""
self.reject()
return
# END Class QuotesDialog
+8 -5
View File
@@ -39,12 +39,15 @@ from PyQt5.QtWidgets import (
) )
from nw.gui import ( from nw.gui import (
GuiAbout, GuiBuildNovel, GuiDocEditor, GuiDocMerge, GuiDocSplit, GuiDocEditor, GuiDocViewDetails, GuiDocViewer, GuiItemDetails, GuiMainMenu,
GuiDocViewDetails, GuiDocViewer, GuiItemDetails, GuiItemEditor, GuiMainStatus, GuiNovelTree, GuiOutline, GuiOutlineDetails,
GuiMainMenu, GuiMainStatus, GuiNovelTree, GuiOutline, GuiOutlineDetails, GuiProjectDetails, GuiProjectTree, GuiTheme
GuiPreferences, GuiProjectDetails, GuiProjectLoad, GuiProjectSettings,
GuiProjectTree, GuiProjectWizard, GuiTheme, GuiWordList, GuiWritingStats
) )
from nw.dialogs import (
GuiAbout, GuiDocMerge, GuiDocSplit, GuiItemEditor, GuiPreferences,
GuiProjectLoad, GuiProjectSettings, GuiWordList
)
from nw.tools import GuiBuildNovel, GuiProjectWizard, GuiWritingStats
from nw.core import NWProject, NWDoc, NWIndex from nw.core import NWProject, NWDoc, NWIndex
from nw.enum import nwItemType, nwItemClass, nwAlert, nwWidget from nw.enum import nwItemType, nwItemClass, nwAlert, nwWidget
from nw.common import getGuiItem, hexToInt from nw.common import getGuiItem, hexToInt
+11
View File
@@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
from nw.tools.build import GuiBuildNovel
from nw.tools.projwizard import GuiProjectWizard
from nw.tools.writingstats import GuiWritingStats
__all__ = [
"GuiBuildNovel",
"GuiProjectWizard",
"GuiWritingStats",
]
+60 -50
View File
@@ -367,10 +367,10 @@ def makeMinimalPackage(targetOS):
if targetOS == OS_WIN: if targetOS == OS_WIN:
zipObj.write("novelWriter.py", "novelWriter.pyw") zipObj.write("novelWriter.py", "novelWriter.pyw")
print("Adding File: novelWriter.pyw") print("Adding File: novelWriter.pyw")
zipObj.write(os.path.join("setup", "setup_windows.bat"), "setup_windows.bat") zipObj.write(os.path.join("setup", "windows_install.bat"), "windows_install.bat")
print("Adding File: setup_windows.bat") print("Adding File: windows_install.bat")
zipObj.write(os.path.join("setup", "uninstall_windows.bat"), "uninstall_windows.bat") zipObj.write(os.path.join("setup", "windows_uninstall.bat"), "windows_uninstall.bat")
print("Adding File: uninstall_windows.bat") print("Adding File: windows_uninstall.bat")
else: else:
zipObj.write("novelWriter.py") zipObj.write("novelWriter.py")
print("Adding File: novelWriter.py") print("Adding File: novelWriter.py")
@@ -1056,51 +1056,61 @@ if __name__ == "__main__":
else: else:
targetOS = hostOS targetOS = hostOS
helpMsg = ( helpMsg = [
"\n" "",
"novelWriter Setup Tool\n" "novelWriter Setup Tool",
"======================\n" "======================",
"\n" "",
"This tool provides setup and build commands for installing or distibuting novelWriter\n" "This tool provides setup and build commands for installing or distibuting",
"as a package on Linux, Mac and Windows. The available options are as follows:\n" "novelWriter as a package on Linux, Mac and Windows. The available options",
"\n" "are as follows:",
"Some of the commands can be targeted towards a different OS than the host OS. To target\n" "",
"the command, add one of '--target-linux', '--target-darwin' or '--target-win'.\n" "Some of the commands can be targeted towards a different OS than the host OS.",
"\n" "To target the command, add one of '--target-linux', '--target-darwin' or",
"General:\n" "'--target-win'.",
"\n" "",
" help Print the help message.\n" "General:",
" pip Install all package dependencies for novelWriter using pip.\n" "",
" clean Will attempt to delete the 'build' and 'dist' folders.\n" " help Print the help message.",
"\n" " pip Install all package dependencies for novelWriter using pip.",
"Additional Builds:\n" " clean Will attempt to delete the 'build' and 'dist' folders.",
"\n" "",
" qthelp Build the help documentation for use with the Qt Assistant. Run before\n" "Additional Builds:",
" install to have local help enable in the the installed version.\n" "",
" qtlupdate Update the translation files for internationalisation.\n" " qthelp Build the help documentation for use with the Qt Assistant.",
" qtlrelease Build the language files for internationalisation.\n" " qtlupdate Update the translation files for internationalisation.",
" sample Build the sample project as a zip file. Run before install to enable\n" " qtlrelease Build the language files for internationalisation.",
" creating sample projects in the in-app New Project Wizard.\n" " sample Build the sample project zip file and add it to assets.",
"\n" "",
"Python Packaging:\n" "Python Packaging:",
"\n" "",
" minimal-zip Creates a minimal zip file of the core application without all the\n" " minimal-zip Creates a minimal zip file of the core application without",
" other source files. Accepts a target OS flag.\n" " all the other source files. Defaults to tailor the zip file",
" pack-pyz Creates a pyz package in a folder with all dependencies using the\n" " for the current OS, but accepts a target OS flag to build",
" zipapp tool. On Windows, python embeddable is added to the folder.\n" " for another OS.",
" setup-pyz Build a Windows installer from a zipapp package using Inno Setup.\n" " pack-pyz Creates a .pyz package in a folder with all dependencies",
"\n" " using the zipapp tool. On Windows, python embeddable is",
"System Install:\n" " added to the folder.",
"\n" " setup-pyz Build a Windows executable installer from a zipapp package",
" install Installs novelWriter to the system's Python install location. Run as \n" " using Inno Setup. Must run 'pack-pyz' first.",
" root or with sudo for system-wide install, or as user for single user \n" "",
" install.\n" "System Install:",
" xdg-install Install launcher and icons for freedesktop systems. Run as root or \n" "",
" with sudo for system-wide install, or as user for single user install.\n" " install Installs novelWriter to the system's Python install",
" Running 'xdg-uninstall' will remove the icons.\n" " location. Run as root or with sudo for system-wide install,",
" win-install Install desktop and start menu icons for Windows systems.\n" " or as user for single user install.",
" Running 'win-uninstall' will remove the icons.\n" " xdg-install Install launcher and icons for freedesktop systems. Run as",
) " root or with sudo for system-wide install, or as user for",
" single user install.",
" xdg-uninstall Remove the launcher and icons for the current system as",
" installed by the 'xdg-install' command.",
" win-install Install desktop icon, start menu icon, and registry entries",
" for file association with .nwx files for Windows systems.",
" win-uninstall Remove desktop icon, start menu icon, and registry keys,",
" for the current system. Note that it only removes icons for",
" the version number of the package the command is run from.",
"",
]
# Flags and Variables # Flags and Variables
makeSetupPyz = False makeSetupPyz = False
@@ -1112,7 +1122,7 @@ if __name__ == "__main__":
if "help" in sys.argv: if "help" in sys.argv:
sys.argv.remove("help") sys.argv.remove("help")
print(helpMsg) print("\n".join(helpMsg))
sys.exit(0) sys.exit(0)
if "version" in sys.argv: if "version" in sys.argv:
+20 -48
View File
@@ -1,56 +1,28 @@
# Build and Install novelWriter # Build and Install novelWriter
The root folder of the repository contains two scripts for setup and install: The root folder of the repository contains a standard `setup.py` script. The `setup` folder
contains necessary files for the setup process, like icons and other files, and it also contains
additional scripts that are included in the minimal zip packages.
## Setup Script
## Script `setup.py` The `setup.py` script in the root folder has all the necessary commands for generating packages,
installing icons, launcher, and file associations on Linux and Windows (no macOS support yet).
The `setup.py` is a standard Python setup script with a couple of additional In addition, it is a wrapper for `setuptools` and accept all commands associated with this tool.
options:
Some of the commands can be targeted towards a different OS than the host OS. To list all custom package and install options in this script, run:
To target the command, add one of `--target-linux`, `--target-darwin` or ```
`--target-win`. ./setup.py help
```
## Windows Scripts
### General The `windows_install.bat` and `windows_uninstall.bat` are provided as convenience scripts for
Windows users who aren't used to running commands from command line. They are both located in the
`setup` folder in the source package, but in the minimal zip package for Windows, they can be
found in the root folder.
`help` Print the help message. These scripts are designed to be double-clicked by the user, but can also be run from the command
line tool. In either case, they will check if Python is properly installed, and then run the
`pip` Install all package dependencies for novelWriter using pip. commands to install or uninstall the core dependencies of novelWriter, and then setup or remove the
desktop icon, start menu icon and registry entries.
`clean` Will attempt to delete the `build` and `dist` folders.
### Additional Builds
`qthelp` Build the help documentation for use with the Qt Assistant. Run
before install to have local help enable in the the installed version
`qtlupdate` Update the translation files for internationalisation.
`qtlrelease` Build the language files for internationalisation.
`sample` Build the sample project as a zip file. Run before install to enable
creating sample projects in the in-app New Project Wizard.
### Python Packaging
`minimal-zip` Creates a minimal zip file of the core application without all
the other source files.
`pack-pyz` Creates a pyz package in a folder with all dependencies using the
zipapp tool. On Windows, python embeddable is added to the folder.
`setup-pyz` Build a Windows installer from a zipapp package using Inno Setup.
### System Install
`install` Installs novelWriter to the system's Python install location. Run
as root or with sudo for system-wide install, or as user for single user
install.
`xdg-install` Install launcher and icons for freedesktop systems. Run as root
or with sudo for system-wide install, or as user for single user install.
Running `xdg-uninstall` will remove the icons.
`win-install` Install desktop and start menu icons for Windows systems.
Running `win-uninstall` will remove the icons.
+3 -38
View File
@@ -52,45 +52,10 @@ pytest-3 -v -m core
Available markers are: Available markers are:
* `base` for unit tests covering the non-gui classes of the `bw` folder.. * `base` for unit tests covering the non-gui classes of the `nw` folder..
* `core` for unit tests covering the classes in the `nw/core` folder. * `core` for unit tests covering the classes in the `nw/core` folder.
* `gui` for unit and integrations tests covering the classes in the `nw/gui` folder. * `gui` for unit and integrations tests covering the classes in the `nw/gui` folder.
## Tests
To filter specific groups of tests, use the `-k` switch. You can filter tests further with the `-k` switch, all the way down to a single test. You can for
The commands for the respective test categories are listed below. instance run only dialog tests with `-k testDlg` or tools with `-k testTool`.
| Type | Test Target | Source File(s) | Marker | Filter |
| :---------- | :----------------------- | :--------------------- | :-------- | :----------------------- |
| Unit | Main function | nw/\_\_init\_\_.py | `-m base` | `-k testBaseInit` |
| Unit | Common functions | nw/common.py | `-m base` | `-k testBaseCommon` |
| Unit | Config class | nw/config.py | `-m base` | `-k testBaseConfig` |
| Unit | Error handlers | nw/error.py | `-m base` | `-k testBaseError` |
| Unit | Core functions | nw/core/tools.py | `-m core` | `-k testCoreTools` |
| Unit | NWDoc class | nw/core/document.py | `-m core` | `-k testCoreDocument` |
| Unit | NWIndex class | nw/core/index.py | `-m core` | `-k testCoreIndex` |
| Unit | NWItem class | nw/core/item.py | `-m core` | `-k testCoreItem` |
| Unit | NWProject class | nw/core/project.py | `-m core` | `-k testCoreProject` |
| Unit | NWSpell* classes | nw/core/spellcheck.py | `-m core` | `-k testCoreSpell` |
| Unit | NWStatus class | nw/core/status.py | `-m core` | `-k testCoreStatus` |
| Unit | NWTree class | nw/core/tree.py | `-m core` | `-k testCoreTree` |
| Unit | OptionsState class | nw/core/options.py | `-m core` | `-k testCoreOptions` |
| Unit | ToHtml class | nw/core/tohtml.py | `-m core` | `-k testCoreToHtml` |
| Unit | Tokenizer class | nw/core/tokenizer.py | `-m core` | `-k testCoreToken` |
| Integration | About Dialogs | nw/gui/about.py | `-m gui` | `-k testGuiAbout` |
| Integration | Build Novel Project Tool | nw/gui/build.py | `-m gui` | `-k testGuiBuild` |
| Integration | Document Editor Widget | nw/gui/doceditor.py | `-m gui` | `-k testGuiEditor` |
| Integration | Document Viewer Widget | nw/gui/docviewer.py | `-m gui` | `-k testGuiViewer` |
| Integration | Item Editor Dialog | nw/gui/itemeditor.py | `-m gui` | `-k testGuiItemEditor` |
| Integration | Menu Widgets | nw/gui/mainmenu.py | `-m gui` | `-k testGuiMenu` |
| Integration | Merge Tool | nw/gui/docmerge.py | `-m gui` | `-k testGuiMergeSplit` |
| Integration | Outline Widget | nw/gui/outline.py | `-m gui` | `-k testGuiOutline` |
| Integration | Preferences Dialog | nw/gui/preferences.py | `-m gui` | `-k testGuiPreferences` |
| Integration | Project Load Dialog | nw/gui/projload.py | `-m gui` | `-k testGuiProjLoad` |
| Integration | Project Settings Dialog | nw/gui/projsettings.py | `-m gui` | `-k testGuiProjSettings` |
| Integration | Project Tree Widget | nw/gui/projtree.py | `-m gui` | `-k testGuiProjTree` |
| Integration | Theme/Icon Classes | nw/gui/theme.py | `-m gui` | `-k testGuiTheme` |
| Integration | Split Tool | nw/gui/docsplit.py | `-m gui` | `-k testGuiMergeSplit` |
| Integration | Writing Stats Dialog | nw/gui/writingstats.py | `-m gui` | `-k testGuiWritingStats` |
| Integration | Various Dialogs | N/A | `-m gui` | `-k testGuiDialogs` |
@@ -26,14 +26,14 @@ from tools import getGuiItem
from PyQt5.QtWidgets import QAction, QMessageBox from PyQt5.QtWidgets import QAction, QMessageBox
from nw.gui import GuiAbout from nw.dialogs import GuiAbout
keyDelay = 2 keyDelay = 2
typeDelay = 1 typeDelay = 1
stepDelay = 20 stepDelay = 20
@pytest.mark.gui @pytest.mark.gui
def testGuiAbout_Dialog(qtbot, monkeypatch, nwGUI): def testDlgAbout_Dialog(qtbot, monkeypatch, nwGUI):
"""Test the full about dialogs. """Test the full about dialogs.
""" """
# NW About # NW About
@@ -67,4 +67,4 @@ def testGuiAbout_Dialog(qtbot, monkeypatch, nwGUI):
# qtbot.stopForInteraction() # qtbot.stopForInteraction()
msgAbout._doClose() msgAbout._doClose()
# END Test testGuiAbout_Dialog # END Test testDlgAbout_Dialog
@@ -25,20 +25,20 @@ import pytest
from PyQt5.QtCore import QItemSelectionModel from PyQt5.QtCore import QItemSelectionModel
from PyQt5.QtWidgets import QListWidgetItem, QDialog, QMessageBox from PyQt5.QtWidgets import QListWidgetItem, QDialog, QMessageBox
from nw.gui.custom import QuotesDialog from nw.dialogs import GuiQuoteSelect
keyDelay = 2 keyDelay = 2
typeDelay = 1 typeDelay = 1
stepDelay = 20 stepDelay = 20
@pytest.mark.gui @pytest.mark.gui
def testGuiDialogs_Quotes(qtbot, monkeypatch, nwGUI, nwMinimal): def testDlgOther_QuoteSelect(qtbot, monkeypatch, nwGUI, nwMinimal):
"""Test the quote symbols dialog. """Test the quote symbols dialog.
""" """
# Block message box # Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
nwQuot = QuotesDialog(nwGUI) nwQuot = GuiQuoteSelect(nwGUI)
nwQuot.show() nwQuot.show()
lastItem = "" lastItem = ""
@@ -58,4 +58,4 @@ def testGuiDialogs_Quotes(qtbot, monkeypatch, nwGUI, nwMinimal):
nwQuot._doReject() nwQuot._doReject()
nwQuot.close() nwQuot.close()
# END Test testDialogs_Quotes # END Test testDlgOther_QuoteSelect
@@ -28,15 +28,16 @@ from tools import cmpFiles, getGuiItem
from PyQt5.QtWidgets import QAction, QMessageBox from PyQt5.QtWidgets import QAction, QMessageBox
from nw.gui import GuiItemEditor, GuiProjectTree from nw.gui import GuiProjectTree
from nw.enum import nwItemLayout from nw.enum import nwItemLayout
from nw.dialogs import GuiItemEditor
keyDelay = 2 keyDelay = 2
typeDelay = 1 typeDelay = 1
stepDelay = 20 stepDelay = 20
@pytest.mark.gui @pytest.mark.gui
def testGuiItemEditor_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj, refDir, outDir): def testDlgItemEditor_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj, refDir, outDir):
"""Test the full item editor dialog. """Test the full item editor dialog.
""" """
projFile = os.path.join(fncProj, "nwProject.nwx") projFile = os.path.join(fncProj, "nwProject.nwx")
@@ -108,4 +109,4 @@ def testGuiItemEditor_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj, refDir,
# qtbot.stopForInteraction() # qtbot.stopForInteraction()
# END Test testGuiItemEditor_Dialog # END Test testDlgItemEditor_Dialog
@@ -28,14 +28,14 @@ from tools import cmpFiles, getGuiItem
from PyQt5.QtWidgets import QAction, QMessageBox from PyQt5.QtWidgets import QAction, QMessageBox
from nw.gui import GuiDocMerge, GuiDocSplit from nw.dialogs import GuiDocMerge, GuiDocSplit
keyDelay = 2 keyDelay = 2
typeDelay = 1 typeDelay = 1
stepDelay = 20 stepDelay = 20
@pytest.mark.gui @pytest.mark.gui
def testGuiMergeSplit_Tools(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir): def testDlgMergeSplit_Tools(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir):
"""Test the full merge and split tools. """Test the full merge and split tools.
""" """
# Block message box # Block message box
@@ -182,4 +182,4 @@ def testGuiMergeSplit_Tools(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir)
# qtbot.stopForInteraction() # qtbot.stopForInteraction()
# END Test testGuiMergeSplit_Tools # END Test testDlgMergeSplit_Tools
@@ -32,9 +32,8 @@ from PyQt5.QtWidgets import (
QDialogButtonBox, QDialog, QAction, QFileDialog, QFontDialog, QMessageBox QDialogButtonBox, QDialog, QAction, QFileDialog, QFontDialog, QMessageBox
) )
from nw.gui import GuiPreferences
from nw.config import Config from nw.config import Config
from nw.gui.custom import QuotesDialog from nw.dialogs import GuiPreferences, GuiQuoteSelect
from nw.constants import nwConst from nw.constants import nwConst
keyDelay = 2 keyDelay = 2
@@ -42,7 +41,7 @@ typeDelay = 1
stepDelay = 20 stepDelay = 20
@pytest.mark.gui @pytest.mark.gui
def testGuiPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir): def testDlgPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir):
"""Test the load project wizard. """Test the load project wizard.
""" """
# Block message box # Block message box
@@ -234,8 +233,8 @@ def testGuiPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir):
tabQuote = nwPrefs.tabQuote tabQuote = nwPrefs.tabQuote
nwPrefs._tabBox.setCurrentWidget(tabQuote) nwPrefs._tabBox.setCurrentWidget(tabQuote)
monkeypatch.setattr(QuotesDialog, "selectedQuote", "'") monkeypatch.setattr(GuiQuoteSelect, "selectedQuote", "'")
monkeypatch.setattr(QuotesDialog, "exec_", lambda *args: QDialog.Accepted) monkeypatch.setattr(GuiQuoteSelect, "exec_", lambda *args: QDialog.Accepted)
qtbot.mouseClick(tabQuote.btnDoubleStyleC, Qt.LeftButton) qtbot.mouseClick(tabQuote.btnDoubleStyleC, Qt.LeftButton)
# Save and Check Config # Save and Check Config
@@ -259,4 +258,4 @@ def testGuiPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir):
# qtbot.stopForInteraction() # qtbot.stopForInteraction()
# END Test testGuiPreferences_Main # END Test testDlgPreferences_Main
@@ -31,14 +31,14 @@ from PyQt5.QtWidgets import (
QMessageBox QMessageBox
) )
from nw.gui import GuiProjectLoad from nw.dialogs import GuiProjectLoad
keyDelay = 2 keyDelay = 2
typeDelay = 1 typeDelay = 1
stepDelay = 20 stepDelay = 20
@pytest.mark.gui @pytest.mark.gui
def testGuiLoadProject_Main(qtbot, monkeypatch, nwGUI, nwMinimal): def testDlgLoadProject_Main(qtbot, monkeypatch, nwGUI, nwMinimal):
"""Test the load project wizard. """Test the load project wizard.
""" """
# Block message box # Block message box
@@ -114,4 +114,4 @@ def testGuiLoadProject_Main(qtbot, monkeypatch, nwGUI, nwMinimal):
nwLoad.close() nwLoad.close()
# qtbot.stopForInteraction() # qtbot.stopForInteraction()
# END Test testGuiLoadProject_Main # END Test testDlgLoadProject_Main
@@ -32,14 +32,14 @@ from PyQt5.QtWidgets import (
QDialog, QAction, QMessageBox, QColorDialog, QTreeWidgetItem QDialog, QAction, QMessageBox, QColorDialog, QTreeWidgetItem
) )
from nw.gui import GuiProjectSettings from nw.dialogs import GuiProjectSettings
keyDelay = 2 keyDelay = 2
typeDelay = 1 typeDelay = 1
stepDelay = 20 stepDelay = 20
@pytest.mark.gui @pytest.mark.gui
def testGuiProjSettings_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj, outDir, refDir): def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj, outDir, refDir):
"""Test the full project settings dialog. """Test the full project settings dialog.
""" """
projFile = os.path.join(fncProj, "nwProject.nwx") projFile = os.path.join(fncProj, "nwProject.nwx")
@@ -243,4 +243,4 @@ def testGuiProjSettings_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj, outDi
# qtbot.stopForInteraction() # qtbot.stopForInteraction()
# END Test testGuiProjSettings_Dialog # END Test testDlgProjSettings_Dialog
@@ -28,14 +28,14 @@ from PyQt5.QtWidgets import QDialog, QMessageBox, QAction
from tools import writeFile, readFile, getGuiItem from tools import writeFile, readFile, getGuiItem
from dummy import causeOSError from dummy import causeOSError
from nw.gui.wordlist import GuiWordList from nw.dialogs import GuiWordList
from nw.constants import nwFiles from nw.constants import nwFiles
keyDelay = 2 keyDelay = 2
typeDelay = 1 typeDelay = 1
stepDelay = 20 stepDelay = 20
def testGuiWordList_Dialog(qtbot, monkeypatch, nwGUI, nwMinimal, tmpDir): def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, nwMinimal, tmpDir):
"""test the word list editor. """test the word list editor.
""" """
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
@@ -130,4 +130,4 @@ def testGuiWordList_Dialog(qtbot, monkeypatch, nwGUI, nwMinimal, tmpDir):
# qtbot.stopForInteraction() # qtbot.stopForInteraction()
wList._doClose() wList._doClose()
# END Test testGuiWordList_Dialog # END Test testDlgWordList_Dialog
+1 -1
View File
@@ -30,7 +30,7 @@ from PyQt5.QtCore import Qt
from PyQt5.QtGui import QTextCursor from PyQt5.QtGui import QTextCursor
from PyQt5.QtWidgets import QAction, QMessageBox, QDialog from PyQt5.QtWidgets import QAction, QMessageBox, QDialog
from nw.gui.itemeditor import GuiItemEditor from nw.dialogs.itemeditor import GuiItemEditor
from nw.gui.doceditor import GuiDocEditor from nw.gui.doceditor import GuiDocEditor
from nw.gui.projtree import GuiProjectTree from nw.gui.projtree import GuiProjectTree
from nw.enum import nwItemType, nwDocAction, nwWidget from nw.enum import nwItemType, nwDocAction, nwWidget
@@ -29,14 +29,14 @@ from tools import cmpFiles, getGuiItem
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QAction, QMessageBox, QFileDialog from PyQt5.QtWidgets import QAction, QMessageBox, QFileDialog
from nw.gui import GuiBuildNovel from nw.tools import GuiBuildNovel
keyDelay = 2 keyDelay = 2
typeDelay = 1 typeDelay = 1
stepDelay = 20 stepDelay = 20
@pytest.mark.gui @pytest.mark.gui
def testGuiBuild_Tool(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir): def testToolBuild_Main(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir):
"""Test the build tool. """Test the build tool.
""" """
# Block message box # Block message box
@@ -251,4 +251,4 @@ def testGuiBuild_Tool(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir):
# qtbot.stopForInteraction() # qtbot.stopForInteraction()
# END Test testGuiBuild_Tool # END Test testToolBuild_Main
@@ -29,11 +29,10 @@ from tools import getGuiItem
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QFileDialog, QWizard, QMessageBox from PyQt5.QtWidgets import QFileDialog, QWizard, QMessageBox
from nw.gui import GuiProjectWizard
from nw.enum import nwItemClass from nw.enum import nwItemClass
from nw.gui.projwizard import ( from nw.tools.projwizard import (
ProjWizardIntroPage, ProjWizardFolderPage, ProjWizardPopulatePage, GuiProjectWizard, ProjWizardIntroPage, ProjWizardFolderPage,
ProjWizardCustomPage, ProjWizardFinalPage ProjWizardPopulatePage, ProjWizardCustomPage, ProjWizardFinalPage
) )
keyDelay = 2 keyDelay = 2
@@ -41,7 +40,7 @@ typeDelay = 1
stepDelay = 20 stepDelay = 20
@pytest.mark.gui @pytest.mark.gui
def testGuiProjectWizard_Main(qtbot, monkeypatch, nwGUI, nwMinimal): def testToolProjectWizard_Main(qtbot, monkeypatch, nwGUI, nwMinimal):
"""Test the new project wizard. """Test the new project wizard.
""" """
# Block message box # Block message box
@@ -227,4 +226,4 @@ def testGuiProjectWizard_Main(qtbot, monkeypatch, nwGUI, nwMinimal):
# qtbot.stopForInteraction() # qtbot.stopForInteraction()
# END Test testGuiProjectWizard_Main # END Test testToolProjectWizard_Main
@@ -30,7 +30,7 @@ from dummy import causeOSError
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox
from nw.gui import GuiWritingStats from nw.tools import GuiWritingStats
from nw.constants import nwFiles from nw.constants import nwFiles
keyDelay = 2 keyDelay = 2
@@ -38,7 +38,7 @@ typeDelay = 1
stepDelay = 20 stepDelay = 20
@pytest.mark.gui @pytest.mark.gui
def testGuiWritingStats_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj): def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
"""Test the full writing stats tool. """Test the full writing stats tool.
""" """
# Block message box # Block message box
@@ -428,4 +428,4 @@ def testGuiWritingStats_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
assert nwGUI.closeProject() assert nwGUI.closeProject()
qtbot.wait(stepDelay) qtbot.wait(stepDelay)
# END Test testGuiWritingStats_Dialog # END Test testToolWritingStats_Main