From bbde3159bbf4a6560d822836acc32617df8e9ea9 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 4 Jan 2021 23:45:47 +0100 Subject: [PATCH 01/10] Updated install intructions in readme --- README.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 074641f7..4d3d1ae7 100644 --- a/README.md +++ b/README.md @@ -71,16 +71,16 @@ needed are `pyqt5`, `lxml` and `pyenchant`. You can also install novelWriter from source with: ```bash -python3 setup.py sample -sudo python3 setup.py install -sudo python3 setup.py launcher +python setup.py sample +sudo python setup.py install +sudo python setup.py xdg-install ``` -The last line will install the application icons and set up a launcher for novelWriter. The method -uses hardcoded paths, so it may or may not work for your Linux distro. If you have any issues, -please submit a ticket so the script can be tuned. +The last line will install the application icons and set up a launcher for novelWriter. The script +may prompt you to choose which executable to configure if it finds more than one. -The script may prompt you to choose which executable to configure if it finds more than one. +**Note:** If your distro has multiple versions of Python installed, you may need to use the +`python3` command instead of `python`. ### Running from Source @@ -162,7 +162,8 @@ particularly useful for increasing logging output for debugging. # Key Features -Some features of novelWriter are listed below. Consult the documentation for more information. +Some features of novelWriter are listed below. Consult the +[documentation](https://novelwriter.readthedocs.io/) for more information. ### Markdown Flavour @@ -170,7 +171,7 @@ novelWriter is _not_ a full-feature Markdown editor. It allows for a minimal set needed for writing text documents for novels. These are currently limited to: * Headings level 1 to 4 using the `#` syntax only. -* Emphasised and strong text. These are rendered as italicised and bold. +* Emphasised and strongly emphasised text. These are rendered as italicised and bold text. * Strikethrough text. * Hard line breaks using two or more spaces at the end of a line. From 6ebe235dc3a5f5463a77fe47c790581d3643c53f Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 4 Jan 2021 23:58:57 +0100 Subject: [PATCH 02/10] Rewritten "Getting Started" section in the documentation --- docs/source/int_started.rst | 310 +++++++++++++++++++++--------------- 1 file changed, 180 insertions(+), 130 deletions(-) diff --git a/docs/source/int_started.rst b/docs/source/int_started.rst index c9874c08..eac1c0b7 100644 --- a/docs/source/int_started.rst +++ b/docs/source/int_started.rst @@ -7,105 +7,65 @@ Getting Started This is a brief guide to how you can get novelWriter running on your computer. These are the methods currently supported by the developer. Packages may also be available in other package managers, but those are not managed by the developer. A Windows installer file is also provided on -the GitHub page and main website. +the GitHub page and on the main website. As novelWriter matures, more options for how to install it and get it running will be added. For non-Windows users the install process is at the present time best suited for people used to working -with the command line. +with the command line. But even if you're not, the install process is fairly straight forward. .. note:: The text below assumes the command ``python`` corresponds to a Python 3 executable. For operating systems with both Python 2 and 3, the command ``python3`` may be needed instead. On - Linux, the scripts can also be made executable and run without the ``python`` command. + Linux, the scripts can also be made executable and run without the ``python`` command. Likewise, + ``pip`` may need to be replaced with ``pip3``. .. _a_started_install: -Installation -============ +Installing and Running +====================== -You can download the latest version of novelWriter from the source repository on GitHub_. You can -also install it directly from PyPi with ``pip install novelwriter``, or download the packages -directly from the PyPi_ project page. +The application is written in Python 3 using Qt5 via 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. -Latest version of novelWriter is |release|. +You can download the latest version of novelWriter from the source repository on GitHub_. +novelWriter is also hosted on PyPi_, and can be installed on all operating systems that support Qt5 +and Python 3. It is regularly tested on Linux, Windows and macOS. + +To install from PyPi you must first have the ``python`` and ``pip`` commands available on your +system. If you don't, see specific instructions for your operating system later in this document. +To install novelWriter from PyPi, use the following command: + +.. code-block:: console + + pip install novelwriter + +To upgrade an existing installation, use: + +.. code-block:: console + + pip install --upgrade novelwriter + +The latest version of novelWriter is |release|. .. _GitHub: https://github.com/vkbo/novelWriter/releases .. _PyPi: https://pypi.org/project/novelWriter/ -.. _a_started_install_win: - -Windows Installer ------------------ - -You can run novelWriter directly from source on Windows, but a Windows installer is also provided -for 64-bit Windows on the `main website`_ and GitHub_ page. This installer bundles all that is -needed for novelWriter to run, including Python and the xml and Qt libraries. - -.. _main website: https://novelwriter.io - - -.. _a_started_install_source: - -Install from Source on Linux ----------------------------- - -For Linux systems, novelWriter can be installed from source with the provided ``setup.py`` script. -To install novelWriter into the system's default Python install locations, run: - -.. code-block:: console - - python setup.py install - -To set up the novelWriter desktop launcher, the icons and the project file association, run: - -.. code-block:: console - - python setup.py xdg-install - -By default, these commands installs novelWriter and its icons for the current user only. To install -for all users, run the script with the ``sudo`` command. Other options are also available. Run -``python setup.py help`` for a full list of install options. - - .. _a_started_depend: -Dependencies -============ +Python Dependencies +------------------- novelWriter has been designed to rely on as few dependencies as possible. Aside from the package(s) needed to communicate with the Qt GUI libraries, only one package is required for handling the XML format of the main project file. Everything else is handled with standard Python libraries. -Optionally, a package can be installed to interface with the Enchant spell checking libaries, but -this isn't strictly required. If no external spell checking library is available, novelWriter falls -back to using the internal ``difflib`` of Python to check spelling. This is a much slower approach, -and it is less sophisticated than full spell checking libaries, but if you only work with small -files, the performance loss is not noticeable. +The following Python packages are needed to run novelWriter: - -.. _a_started_depend_packages: - -Package Installation --------------------- - -If you already have Python installed, all you need to do is install the dependencies. To do this, -you need to open your command line tool, find the folder where you extracted novelWriter, and run -the following command: - -.. code-block:: console - - pip install -r requirements.txt - -This will install all the dependencies and recommended packages. - -The following Python packages are required to run novelWriter: - -* ``pyqt5``, needed for connecting with the Qt5 libraries. -* ``lxml``, needed full XML support. - -You can of course also install these packages from your operating system's package repository. +* ``pyqt5`` – needed for connecting with the Qt5 libraries. +* ``lxml`` – needed for full XML support. +* ``pyenchant`` – needed for efficient spell checking (optional). PyQt/Qt should be at least 5.2.1, but ideally 5.10 or higher for nearly all features to work. Exporting to standard Markdown, for instance, requires PyQt/Qt 5.14. Searching using regular @@ -113,18 +73,158 @@ expressions requires 5.3, and for full Unicode support, 5.13. There is no known requirement for package ``lxml``, but the code was originally written with 4.2, which is therefore set as the minimum. It may work on lower versions. You have to test it. -The spell checking extension is optional, but recommended: +Optionally, a package can be installed to interface with the Enchant spell checking libaries, but +this isn't strictly required. If no external spell checking library is available, novelWriter falls +back to using the internal ``difflib`` of Python to check spelling. This is a much slower approach, +and it is less sophisticated than full spell checking libaries, but if you only work with small +files, the performance loss is not noticeable. The spell check library must be at least 3.0 to work +with Windows. On Linux, 2.0 also works fine. -* ``pyenchant``, needed for efficient spell checking. +If you install from PyPi, these dependencies should be installed automatically. They can be +manually installed with: -The optional spell check library must be at least 3.0 to work with Windows. On Linux, 2.0 also -works fine. +.. code-block:: console + + pip install -r requirements.txt -.. _a_started_depend_docs: +.. _a_started_running: + +Running from Source +------------------- + +If all the required dependencies are met, you can run novelWriter from the command line with: + +.. code-block:: console + + python novelWriter.py + +A few switches are supported from the command line, mostly to assist in debugging if an error is +encountered. To list all options, run: + +.. code-block:: console + + python novelWriter.py --help + + +.. _a_started_linux: + +Setup on Linux +============== + +The dependencies of novelWriter are generally available from Linux distro repositories. For Debian +and Ubuntu, they can be installed with: + +.. code-block:: console + + sudo apt install python3-pyqt5 python3-lxml python3-enchant + +If you downloaded the source, you can use the provided ``setup.py`` script to install novelWriter +into the system's default Python install locations. If so, run: + +.. code-block:: console + + python setup.py install + +To set up the novelWriter desktop launcher, the icons, and the project file association, run: + +.. code-block:: console + + python setup.py xdg-install + +By default, these commands install novelWriter and its icons for the current user only. To install +for all users, run the script with the ``sudo`` command. Other options are also available. Run +``python setup.py help`` for a full list of install options. + + +.. _a_started_macos: + +Setup on macOS +============== + +These instructions assume you're using brew, and have Python and pip set up. If not, see the +`brew docs`_ for help. Main requirements are installed via the requirements file. You also need to +install the ``pyobjc`` package, so you must run: + +.. code-block:: console + + pip3 install --user -r requirements.txt + pip3 install --user pyobjc + +For spell checking you may also need to install the enchant package. It comes with a lot of default +dictionaries. + +.. code-block:: console + + brew install enchant + +.. _brew docs: https://docs.brew.sh/Homebrew-and-Python + + +.. _a_started_windows: + +Setup on Windows +================ + +On Windows, you have two options: You can either run from source, or install novelWriter via a +Windows installer. + + +.. _a_started_win_installer: + +Windows Installer +----------------- + +You can install novelWriter with the Windows installer for 64-bit Windows available on the +`main website`_ and GitHub_ page. This installer bundles all that is needed for novelWriter to run, +including Python and the xml and Qt libraries. When installing novelWriter this way, you don't need +to install any of the dependencies manually. The installer is made with pyinstaller and Inno Setup. + +.. _main website: https://novelwriter.io + + +.. _a_started_win_source: + +From Source +----------- + +To run from source, you may first need to install Python. If you don't have it installed, you can +download it from the python.org_ website. novelWriter should work with Python 3.6 or higher, but it +is recommended that you install the latest version of Python. + +Also, make sure you select the "Add Python to PATH" option during installation. + +.. image:: images/python_win_install.png + :width: 600 + +Once Python is set up and running, you can either run novelWriter from the folder where you +extracted it, or you can build an executable and run that from a desktop icon instead. + +The PyPi installer should come bundled with the Python installation, so to install dependencies, +run: + +.. code-block:: console + + pip install --user -r requirements.txt + +.. tip:: + + To create a desktop shortcut to launch novelWriter, you can right click the ``novelWriter.py`` + file, create a shortcut, then right click again, select "Properties" and change the target to + your python executable followed by ``novelWriter.py``. It should look something like this: + ``C:\...\AppData\Local\Programs\Python\Python38\python.exe novelWriter.py`` + +You can also run the ``make.py`` script to generate a single executable, or an installer. +See `Build and Install novelWriter`_ for more details or run: ``python make.py help``. + +.. _python.org: https://www.python.org/downloads/windows/ +.. _Build and Install novelWriter: https://github.com/vkbo/novelWriter/blob/main/setup/README.md + + +.. _a_started_docs: Building the Documentation --------------------------- +========================== If you installed novelWriter from a package, the documentation should be included. If you're running novelWriter from the source code, a local copy of this documentation can be generated. It @@ -145,7 +245,7 @@ The documentation can then be built from the ``docs`` folder in the source code make html -If successful, the documentation should then be available in the ``docs/build/html`` folder. +If successful, the documentation should be available in the ``docs/build/html`` folder. The documentation can also be built for the Qt Assistant. To build the help packages from the documentation source, run the following from the root source folder: @@ -164,53 +264,3 @@ locally first, then send you to the website as a fallback. computer. If it isn't available, or novelWriter cannot find it, the help feature will fall back to redirecting you to the documentation website. - -.. _a_started_running: - -Running from Source -=================== - -If all the required dependencies are met, you can run novelWriter from the command line: - -.. code-block:: console - - python novelWriter.py - -A few switches are supported from the command line, mostly to assist in debugging if an error is -encountered. To list all options, run: - -.. code-block:: console - - python novelWriter.py --help - - -.. _a_started_standalone: - -Building a Standalone Executable -================================ - -A standalone executable can be built with ``pyinstaller``, using the provided python script -``make.py`` in the source folder. This script can install dependencies, build a standalone -executable of novelWriter, or build a ``setup.exe`` file with Inno Setup. - -For a full list of the script's options, run ``python make.py help``. - - -.. _a_started_standalone_win: - -Additional Instructions for Windows ------------------------------------ - -If you don't have Python installed, you can download it from the python.org website. The installers -for Windows are available at https://www.python.org/downloads/windows/ - -novelWriter should work with Python 3.6 or higher, and the executable installer is the easiest to -install. - -Also, make sure you select the "Add Python to PATH" option. - -.. image:: images/python_win_install.png - :width: 600 - -Once Python is set up and running, you can either run novelWriter from the folder where you -extracted it, or you can build an executable and run that from a desktop icon instead. From 4a4ac1d6c8450544cb81ed9546d40284a5847ba4 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Wed, 6 Jan 2021 21:53:42 +0100 Subject: [PATCH 03/10] Add a new constants class named nwLists --- nw/constants/__init__.py | 19 ++++++++++--------- nw/constants/constants.py | 13 +++++++++++-- nw/guimain.py | 4 ++-- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/nw/constants/__init__.py b/nw/constants/__init__.py index 1ffe2540..b58b56c3 100644 --- a/nw/constants/__init__.py +++ b/nw/constants/__init__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from nw.constants.iso import isoLanguage, isoCountry from nw.constants.constants import ( - nwConst, nwRegEx, nwFiles, nwKeyWords, nwLabels, nwQuotes, nwUnicode + nwConst, nwLists, nwRegEx, nwFiles, nwKeyWords, nwLabels, nwQuotes, nwUnicode ) from nw.constants.enum import ( nwAlert, nwDocAction, nwItemClass, nwItemLayout, nwItemType, nwOutline, @@ -11,18 +11,19 @@ from nw.constants.enum import ( __all__ = [ "isoCountry", "isoLanguage", - "nwAlert", "nwConst", - "nwDocAction", - "nwDocInsert", + "nwLists", + "nwRegEx", "nwFiles", + "nwKeyWords", + "nwLabels", + "nwQuotes", + "nwUnicode", + "nwAlert", + "nwDocAction", "nwItemClass", "nwItemLayout", "nwItemType", - "nwKeyWords", - "nwLabels", "nwOutline", - "nwQuotes", - "nwRegEx", - "nwUnicode", + "nwDocInsert", ] diff --git a/nw/constants/constants.py b/nw/constants/constants.py index b0ea61c5..a39bb8e1 100644 --- a/nw/constants/constants.py +++ b/nw/constants/constants.py @@ -45,10 +45,19 @@ class nwConst(): SP_INTERNAL = "internal" SP_ENCHANT = "enchant" - # Check Lists +# END Class nwConst + +class nwLists(): + """Lists used for grouping various other constants. + """ + + # Regular user-accessible item types REG_TYPES = {nwItemType.ROOT, nwItemType.FOLDER, nwItemType.FILE} -# END Class nwConst + # Item classes where the full list of novel layouts are allowed + CLS_NOVEL = {nwItemClass.NOVEL, nwItemClass.ARCHIVE} + +# END Class nwLists class nwRegEx(): diff --git a/nw/guimain.py b/nw/guimain.py index 920a689e..c7ad7e55 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -47,7 +47,7 @@ from nw.gui import ( GuiTheme, GuiWritingStats ) from nw.core import NWProject, NWDoc, NWIndex -from nw.constants import nwItemType, nwItemClass, nwAlert, nwConst +from nw.constants import nwItemType, nwItemClass, nwAlert, nwLists from nw.common import getGuiItem logger = logging.getLogger(__name__) @@ -725,7 +725,7 @@ class GuiMain(QMainWindow): tItem = self.theProject.projTree[tHandle] if tItem is None: return - if tItem.itemType not in nwConst.REG_TYPES: + if tItem.itemType not in nwLists.REG_TYPES: return logger.verbose("Requesting change to item %s" % tHandle) From 8b963f3fe4302589fe468da28a412465f9e487a4 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Wed, 6 Jan 2021 22:08:34 +0100 Subject: [PATCH 04/10] Use new novel class list to select status or importance list in Item Editor --- nw/gui/itemeditor.py | 47 +++++++++++++++++++++++++++++--------------- nw/gui/projtree.py | 20 +++++++------------ 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/nw/gui/itemeditor.py b/nw/gui/itemeditor.py index 9815ed59..3e46a11a 100644 --- a/nw/gui/itemeditor.py +++ b/nw/gui/itemeditor.py @@ -28,13 +28,14 @@ import nw import logging +from PyQt5.QtCore import pyqtSlot from PyQt5.QtWidgets import ( QDialog, QVBoxLayout, QGridLayout, QLineEdit, QComboBox, QLabel, QDialogButtonBox ) from nw.gui.custom import QSwitch -from nw.constants import nwLabels, nwItemLayout, nwItemClass, nwItemType +from nw.constants import nwLabels, nwItemLayout, nwItemType, nwLists logger = logging.getLogger(__name__) @@ -50,14 +51,15 @@ class GuiItemEditor(QDialog): self.theProject = theProject self.theParent = theParent - self.outerBox = QVBoxLayout() + ## + # Build GUI + ## self.theItem = self.theProject.projTree[tHandle] if self.theItem is None: self._doClose() self.setWindowTitle("Item Settings") - self.setLayout(self.outerBox) # Item Label self.editName = QLineEdit() @@ -66,7 +68,7 @@ class GuiItemEditor(QDialog): # Item Status self.editStatus = QComboBox() - if self.theItem.itemClass == nwItemClass.NOVEL: + if self.theItem.itemClass in nwLists.CLS_NOVEL: for sLabel, _, _ in self.theProject.statusItems: self.editStatus.addItem( self.theParent.statusIcons[sLabel], sLabel, sLabel @@ -81,7 +83,7 @@ class GuiItemEditor(QDialog): self.editLayout = QComboBox() self.validLayouts = [] if self.theItem.itemType == nwItemType.FILE: - if self.theItem.itemClass == nwItemClass.NOVEL: + if self.theItem.itemClass in nwLists.CLS_NOVEL: self.validLayouts.append(nwItemLayout.TITLE) self.validLayouts.append(nwItemLayout.BOOK) self.validLayouts.append(nwItemLayout.PAGE) @@ -109,20 +111,27 @@ class GuiItemEditor(QDialog): self.editExport.setEnabled(False) self.editExport.setChecked(False) - self.editName.setText(self.theItem.itemName) - statusIdx = self.editStatus.findData(self.theItem.itemStatus) - if statusIdx != -1: - self.editStatus.setCurrentIndex(statusIdx) - layoutIdx = self.editLayout.findData(self.theItem.itemLayout) - if layoutIdx != -1: - self.editLayout.setCurrentIndex(layoutIdx) - # Buttons self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.buttonBox.accepted.connect(self._doSave) self.buttonBox.rejected.connect(self._doClose) - # Assemble + # Set Current Values + self.editName.setText(self.theItem.itemName) + self.editName.selectAll() + + statusIdx = self.editStatus.findData(self.theItem.itemStatus) + if statusIdx != -1: + self.editStatus.setCurrentIndex(statusIdx) + + layoutIdx = self.editLayout.findData(self.theItem.itemLayout) + if layoutIdx != -1: + self.editLayout.setCurrentIndex(layoutIdx) + + ## + # Assemble + ## + self.mainForm = QGridLayout() self.mainForm.setVerticalSpacing(self.mainConf.pxInt(4)) self.mainForm.setHorizontalSpacing(self.mainConf.pxInt(16)) @@ -135,6 +144,7 @@ class GuiItemEditor(QDialog): self.mainForm.addWidget(self.textExport, 3, 0, 1, 2) self.mainForm.addWidget(self.editExport, 3, 2, 1, 1) + self.outerBox = QVBoxLayout() self.outerBox.setSpacing(self.mainConf.pxInt(16)) self.outerBox.addLayout(self.mainForm) self.outerBox.addStretch(1) @@ -142,12 +152,16 @@ class GuiItemEditor(QDialog): self.setLayout(self.outerBox) self.rejected.connect(self._doClose) - self.editName.selectAll() logger.debug("GuiItemEditor initialisation complete") return + ## + # Slots + ## + + @pyqtSlot() def _doSave(self): """Save the setting to the item. """ @@ -170,10 +184,11 @@ class GuiItemEditor(QDialog): return + @pyqtSlot() def _doClose(self): """Close the dialog without saving the settings. """ - logger.verbose("ItemEditor close button clicked") + logger.verbose("ItemEditor cancel button clicked") self.close() return diff --git a/nw/gui/projtree.py b/nw/gui/projtree.py index 7d7e7e54..49ca0fcb 100644 --- a/nw/gui/projtree.py +++ b/nw/gui/projtree.py @@ -535,36 +535,30 @@ class GuiProjectTree(QTreeWidget): def setTreeItemValues(self, tHandle): """Set the name and flag values for a tree item. """ - trItem = self._getTreeItem(tHandle) - nwItem = self.theProject.projTree[tHandle] - tName = nwItem.itemName - tClass = nwItem.itemClass - tHandle = nwItem.itemHandle + trItem = self._getTreeItem(tHandle) + nwItem = self.theProject.projTree[tHandle] expIcon = QIcon() - - stClass = nwLabels.CLASS_FLAG[nwItem.itemClass] - stLayout = "" + stFlags = nwLabels.CLASS_FLAG[nwItem.itemClass] if nwItem.itemType == nwItemType.FILE: - stLayout = "."+nwLabels.LAYOUT_FLAG[nwItem.itemLayout] + stFlags += "."+nwLabels.LAYOUT_FLAG[nwItem.itemLayout] if nwItem.isExported: expIcon = self.theTheme.getIcon("check") else: expIcon = self.theTheme.getIcon("cross") - tStatus = stClass+stLayout iStatus = nwItem.itemStatus - if tClass == nwItemClass.NOVEL: + if nwItem.itemClass == nwItemClass.NOVEL: iStatus = self.theProject.statusItems.checkEntry(iStatus) # Make sure it's valid flagIcon = self.theParent.statusIcons[iStatus] else: iStatus = self.theProject.importItems.checkEntry(iStatus) # Make sure it's valid flagIcon = self.theParent.importIcons[iStatus] - trItem.setText(self.C_NAME, tName) + trItem.setText(self.C_NAME, nwItem.itemName) trItem.setIcon(self.C_EXPORT, expIcon) - trItem.setText(self.C_FLAGS, tStatus) trItem.setIcon(self.C_FLAGS, flagIcon) + trItem.setText(self.C_FLAGS, stFlags) return From 88776b4e00d78990766c8f7976a3bc6b8be2a397 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Wed, 6 Jan 2021 22:09:20 +0100 Subject: [PATCH 05/10] Fix max length bug in Item Editor and let input boxes expand with dialog width --- nw/gui/itemeditor.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nw/gui/itemeditor.py b/nw/gui/itemeditor.py index 3e46a11a..592f33cb 100644 --- a/nw/gui/itemeditor.py +++ b/nw/gui/itemeditor.py @@ -64,7 +64,7 @@ class GuiItemEditor(QDialog): # Item Label self.editName = QLineEdit() self.editName.setMinimumWidth(self.mainConf.pxInt(220)) - self.editName.setMaxLength(self.mainConf.pxInt(200)) + self.editName.setMaxLength(200) # Item Status self.editStatus = QComboBox() @@ -143,6 +143,9 @@ class GuiItemEditor(QDialog): self.mainForm.addWidget(self.editLayout, 2, 1, 1, 2) self.mainForm.addWidget(self.textExport, 3, 0, 1, 2) self.mainForm.addWidget(self.editExport, 3, 2, 1, 1) + self.mainForm.setColumnStretch(0, 0) + self.mainForm.setColumnStretch(1, 1) + self.mainForm.setColumnStretch(2, 0) self.outerBox = QVBoxLayout() self.outerBox.setSpacing(self.mainConf.pxInt(16)) From 885caa52db6f383184e8cab25cd86d9336fc5a79 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Thu, 7 Jan 2021 21:15:03 +0100 Subject: [PATCH 06/10] Code cleanup in the Item Settings constructor --- nw/gui/itemeditor.py | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/nw/gui/itemeditor.py b/nw/gui/itemeditor.py index 592f33cb..218accee 100644 --- a/nw/gui/itemeditor.py +++ b/nw/gui/itemeditor.py @@ -61,13 +61,18 @@ class GuiItemEditor(QDialog): self.setWindowTitle("Item Settings") + mVd = self.mainConf.pxInt(220) + mSp = self.mainConf.pxInt(16) + vSp = self.mainConf.pxInt(4) + # Item Label self.editName = QLineEdit() - self.editName.setMinimumWidth(self.mainConf.pxInt(220)) + self.editName.setMinimumWidth(mVd) self.editName.setMaxLength(200) # Item Status self.editStatus = QComboBox() + self.editStatus.setMinimumWidth(mVd) if self.theItem.itemClass in nwLists.CLS_NOVEL: for sLabel, _, _ in self.theProject.statusItems: self.editStatus.addItem( @@ -81,24 +86,24 @@ class GuiItemEditor(QDialog): # Item Layout self.editLayout = QComboBox() - self.validLayouts = [] + self.editLayout.setMinimumWidth(mVd) + validLayouts = [] if self.theItem.itemType == nwItemType.FILE: if self.theItem.itemClass in nwLists.CLS_NOVEL: - self.validLayouts.append(nwItemLayout.TITLE) - self.validLayouts.append(nwItemLayout.BOOK) - self.validLayouts.append(nwItemLayout.PAGE) - self.validLayouts.append(nwItemLayout.PARTITION) - self.validLayouts.append(nwItemLayout.UNNUMBERED) - self.validLayouts.append(nwItemLayout.CHAPTER) - self.validLayouts.append(nwItemLayout.SCENE) - self.validLayouts.append(nwItemLayout.NOTE) - else: - self.validLayouts.append(nwItemLayout.NOTE) + validLayouts.append(nwItemLayout.TITLE) + validLayouts.append(nwItemLayout.BOOK) + validLayouts.append(nwItemLayout.PAGE) + validLayouts.append(nwItemLayout.PARTITION) + validLayouts.append(nwItemLayout.UNNUMBERED) + validLayouts.append(nwItemLayout.CHAPTER) + validLayouts.append(nwItemLayout.SCENE) + validLayouts.append(nwItemLayout.NOTE) else: - self.validLayouts.append(nwItemLayout.NO_LAYOUT) + validLayouts.append(nwItemLayout.NO_LAYOUT) + self.editLayout.setEnabled(False) for itemLayout in nwItemLayout: - if itemLayout in self.validLayouts: + if itemLayout in validLayouts: self.editLayout.addItem(nwLabels.LAYOUT_NAME[itemLayout], itemLayout) # Export Switch @@ -133,8 +138,8 @@ class GuiItemEditor(QDialog): ## self.mainForm = QGridLayout() - self.mainForm.setVerticalSpacing(self.mainConf.pxInt(4)) - self.mainForm.setHorizontalSpacing(self.mainConf.pxInt(16)) + self.mainForm.setVerticalSpacing(vSp) + self.mainForm.setHorizontalSpacing(mSp) self.mainForm.addWidget(QLabel("Label"), 0, 0, 1, 1) self.mainForm.addWidget(self.editName, 0, 1, 1, 2) self.mainForm.addWidget(QLabel("Status"), 1, 0, 1, 1) @@ -148,7 +153,7 @@ class GuiItemEditor(QDialog): self.mainForm.setColumnStretch(2, 0) self.outerBox = QVBoxLayout() - self.outerBox.setSpacing(self.mainConf.pxInt(16)) + self.outerBox.setSpacing(mSp) self.outerBox.addLayout(self.mainForm) self.outerBox.addStretch(1) self.outerBox.addWidget(self.buttonBox) From 7c643ea698ba21271ed293b029ee636af6f7a0ef Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 9 Jan 2021 19:53:42 +0100 Subject: [PATCH 07/10] Make sure document edit status is not set on non-text changes --- nw/gui/doceditor.py | 12 ++++++++++-- nw/guimain.py | 7 +++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index f782fa62..c2978b00 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -316,7 +316,6 @@ class GuiDocEditor(QTextEdit): self.lastEdit = time() self._runCounter() self.wcTimer.start() - self.setDocumentChanged(False) self.theHandle = tHandle self.setReadOnly(False) @@ -341,6 +340,9 @@ class GuiDocEditor(QTextEdit): self.docFooter.updateLineCount() self.lengthLast = self.qDocument.characterCount() + qApp.processEvents() + self.setDocumentChanged(False) + qApp.restoreOverrideCursor() return True @@ -373,8 +375,8 @@ class GuiDocEditor(QTextEdit): qApp.setOverrideCursor(QCursor(Qt.WaitCursor)) self.setPlainText(theText) - self.setDocumentChanged(True) self.updateDocMargins() + self.setDocumentChanged(True) qApp.restoreOverrideCursor() return True @@ -450,11 +452,17 @@ class GuiDocEditor(QTextEdit): lM = max(cM, fH) self.setViewportMargins(tM, uM, tM, lM) + docChanged = self.docChanged if self.mainConf.scrollPastEnd: docFrame = self.qDocument.rootFrame().frameFormat() docFrame.setBottomMargin(max(0, 0.6*(wH - uM - lM - 4*tB))) self.qDocument.rootFrame().setFrameFormat(docFrame) + # This is needed as the setFrameFormat function itself will + # trigger the contetsChanged signal which sets docChanged, so we + # set it back to whatever it was before. + self.setDocumentChanged(docChanged) + return def updateDocInfo(self, tHandle): diff --git a/nw/guimain.py b/nw/guimain.py index c7ad7e55..11ebbdff 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -432,6 +432,7 @@ class GuiMain(QMainWindow): # Restore previously open documents, if any if self.theProject.lastEdited is not None: self.openDocument(self.theProject.lastEdited, doScroll=True) + if self.theProject.lastViewed is not None: self.viewDocument(self.theProject.lastViewed) @@ -439,6 +440,12 @@ class GuiMain(QMainWindow): if self.theIndex.indexBroken: self.rebuildIndex() + # Make sure the changed status is set to false on all that was + # just opened + qApp.processEvents() + self.docEditor.setDocumentChanged(False) + self.theProject.setProjectChanged(False) + logger.debug("Project load complete") return True From a6cbdfc559e58e85b0debe1be81c244d7e985ecf Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 9 Jan 2021 20:07:09 +0100 Subject: [PATCH 08/10] Fix initial setting of project word count --- nw/core/project.py | 5 ++++- nw/guimain.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/nw/core/project.py b/nw/core/project.py index d6b6da78..75d86aaa 100644 --- a/nw/core/project.py +++ b/nw/core/project.py @@ -606,10 +606,13 @@ class NWProject(): self.theParent.setStatus("Opened Project: %s" % self.projName) self._scanProjectFolder() - self.setProjectChanged(False) + + self.currWCount = self.lastWCount self.projOpened = time() self.projAltered = False + self._writeLockFile() + self.setProjectChanged(False) return True diff --git a/nw/guimain.py b/nw/guimain.py index 11ebbdff..b9816c42 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -428,6 +428,7 @@ class GuiMain(QMainWindow): self.docEditor.setSpellCheck(self.theProject.spellCheck) self.mainMenu.setAutoOutline(self.theProject.autoOutline) self.statusBar.setRefTime(self.theProject.projOpened) + self.statusBar.setStats(self.theProject.currWCount, 0) # Restore previously open documents, if any if self.theProject.lastEdited is not None: From e2d553495998bb4ee0a388a9fe37a7c967ea6c6d Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 9 Jan 2021 20:21:32 +0100 Subject: [PATCH 09/10] Some minor code cleanup and redundant code removed --- nw/gui/statusbar.py | 24 +++++++----------------- nw/guimain.py | 2 +- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/nw/gui/statusbar.py b/nw/gui/statusbar.py index 8fceafa5..bbebb7cb 100644 --- a/nw/gui/statusbar.py +++ b/nw/gui/statusbar.py @@ -51,9 +51,6 @@ class GuiMainStatus(QStatusBar): self.theTheme = theParent.theTheme self.refTime = None - self.projWords = 0 - self.sessWords = 0 - colNone = QColor(*self.theTheme.statNone) colTrue = QColor(*self.theTheme.statUnsaved) colFalse = QColor(*self.theTheme.statSaved) @@ -182,26 +179,19 @@ class GuiMainStatus(QStatusBar): def setStats(self, pWC, sWC): """Set the current project statistics. """ - self.projWords = pWC - self.sessWords = sWC - self._updateStats() + self.statsText.setToolTip( + "Project word count (session change)" + ) + self.statsText.setText( + f"Words: {pWC:n} ({sWC:+n})" + ) + return ## # Internal Functions ## - def _updateStats(self): - """Update statistics. - """ - self.statsText.setToolTip( - "Project word count (session change)" - ) - self.statsText.setText( - f"Words: {self.projWords:n} ({self.sessWords:+n})" - ) - return - def _updateTime(self): """Update the session clock. """ diff --git a/nw/guimain.py b/nw/guimain.py index b9816c42..cc8f61bd 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -467,7 +467,7 @@ class GuiMain(QMainWindow): return False self.treeView.saveTreeOrder() - self.theProject.saveProject(autoSave) + self.theProject.saveProject(autoSave=autoSave) self.theIndex.saveIndex() return True From 73cd7f7513e7eb9ed9baf08ca77f11cea881af2d Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 9 Jan 2021 20:26:05 +0100 Subject: [PATCH 10/10] Improve doc changed checking and some code cleanup --- nw/gui/doceditor.py | 11 ++++++++--- nw/gui/statusbar.py | 9 ++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index c2978b00..3421276c 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -903,12 +903,13 @@ class GuiDocEditor(QTextEdit): ## @pyqtSlot(int, int, int) - def _docChange(self, thePos, charsRemoved, charsAdded): + def _docChange(self, thePos, chrRem, chrAdd): """Triggered by QTextDocument->contentsChanged. This also triggers the syntax highlighter. """ self.lastEdit = time() self.lastFind = None + if self.qDocument.characterCount() > nwConst.MAX_DOCSIZE: self.theParent.makeAlert(( "The document has grown too big and you cannot add more text to it. " @@ -916,12 +917,16 @@ class GuiDocEditor(QTextEdit): ) % (nwConst.MAX_DOCSIZE/1.0e6), nwAlert.ERROR) self.undo() return + if not self.docChanged: - self.setDocumentChanged(True) + self.setDocumentChanged(chrRem != 0 or chrAdd != 0) + if not self.wcTimer.isActive(): self.wcTimer.start() - if self.doReplace and charsAdded == 1: + + if self.doReplace and chrAdd == 1: self._docAutoReplace(self.qDocument.findBlock(thePos)) + return @pyqtSlot("QPoint") diff --git a/nw/gui/statusbar.py b/nw/gui/statusbar.py index bbebb7cb..e1738447 100644 --- a/nw/gui/statusbar.py +++ b/nw/gui/statusbar.py @@ -179,13 +179,8 @@ class GuiMainStatus(QStatusBar): def setStats(self, pWC, sWC): """Set the current project statistics. """ - self.statsText.setToolTip( - "Project word count (session change)" - ) - self.statsText.setText( - f"Words: {pWC:n} ({sWC:+n})" - ) - + self.statsText.setText(f"Words: {pWC:n} ({sWC:+n})") + self.statsText.setToolTip("Project word count (session change)") return ##