From 658953df1514d662c3081168f7fe3757e3ffa83d Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 19 Oct 2023 02:54:22 +0200 Subject: [PATCH 01/27] Add note about pkgutils dependency on PyQt6 for qtlupdate --- i18n/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/i18n/README.md b/i18n/README.md index 159eee73..1c5303a1 100644 --- a/i18n/README.md +++ b/i18n/README.md @@ -48,6 +48,7 @@ instance `en_GB` for British English. You will need the translation tool Qt 5 Linguist on your system. For Ubuntu/Debian, run: + ```bash sudo apt install qttools5-dev-tools ``` @@ -75,6 +76,7 @@ The `i18n` folder at the root of the repository contains the `nw_XX.ts` translat Whether to add a new language to the translation framework, or to update the file against the current source code, you must first run the `qtlupdate` command: + ```bash python3 pkgutils.py qtlupdate i18n/nw_XX.ts ``` @@ -86,6 +88,10 @@ be a valid ISO language code, otherwise novelWriter will not accept the file. For instance, the French translation uses the language code `fr_FR`, so its translation file will be `nw_fr_FR.ts` +Note: The `qtlupdate` command needs the `lupdate` tool provided by PyQt6, which uses the latest +TS file format. The tool in PyQt5 generates an older file format. On Debian/Ubuntu it is provided +by the package `pyqt6-dev-tools`. + ### Edit the Translation File in Qt Linguist @@ -100,6 +106,7 @@ Please select "English" and "United Kingdom" as the _source_ language if prompte The application does not use `.ts` files directly. The `novelwriter/assets/i18n/nw_XX.qm` files are the actual files used to translate the GUI into another language other than the default British English. These files are not generated by default, but they can be built with: + ```bash python3 pkgutils.py qtlrelease ``` From bc1b5f24a0517e6c357de2d3cc729a265a227745 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 19 Oct 2023 02:58:29 +0200 Subject: [PATCH 02/27] Add more information in error output from pkgutils --- pkgutils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgutils.py b/pkgutils.py index 840ffef7..99f77521 100755 --- a/pkgutils.py +++ b/pkgutils.py @@ -325,6 +325,7 @@ def buildQtI18nTS(sysArgs): from PyQt6.lupdate import lupdate except ImportError: print("ERROR: This command requires lupdate from PyQt6") + print("On Debian/Ubuntu, install: pyqt6-dev-tools") sys.exit(1) print("") From 69af58227a9b80db63d1f073af9c1a0e929ab81e Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 21 Oct 2023 15:41:49 +0200 Subject: [PATCH 03/27] Fix broken docs build --- .readthedocs.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.readthedocs.yml b/.readthedocs.yml index 448aca8b..f59b1673 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,6 +1,10 @@ # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details version: 2 +build: + os: "ubuntu-22.04" + tools: + python: "3.11" sphinx: configuration: docs/source/conf.py formats: From ddd5e841bf2f4b02c1a998b7bfcbba8ae59805e8 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 22 Oct 2023 00:13:21 +0200 Subject: [PATCH 04/27] Clean up unneeded docs build settings --- .readthedocs.yml | 15 --------------- docs/source/conf.py | 14 ++------------ 2 files changed, 2 insertions(+), 27 deletions(-) delete mode 100644 .readthedocs.yml diff --git a/.readthedocs.yml b/.readthedocs.yml deleted file mode 100644 index f59b1673..00000000 --- a/.readthedocs.yml +++ /dev/null @@ -1,15 +0,0 @@ -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details -version: 2 -build: - os: "ubuntu-22.04" - tools: - python: "3.11" -sphinx: - configuration: docs/source/conf.py -formats: - - htmlzip - - pdf -python: - install: - - requirements: docs/source/requirements.txt diff --git a/docs/source/conf.py b/docs/source/conf.py index 176c13b8..72ddad50 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -27,7 +27,7 @@ with open(initFile) as inFile: else: release = "unknown" -version = release.split("-")[0] +version = release.partition("-")[0] # -- General Configuration --------------------------------------------------- @@ -67,11 +67,6 @@ html_sidebars = { "**": ["navbar-logo", "sidebar-title", "sbt-sidebar-nav"], } -# -- Options for HTMLHelp Output --------------------------------------------- - -# Output file base name for HTML help builder. -htmlhelp_basename = "novelWriterDoc" - # -- Options for LaTeX Output ------------------------------------------------ latex_elements = { @@ -84,11 +79,6 @@ latex_elements = { "figure_align": "htbp", } latex_logo = "_static/novelwriter-pdf.png" - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). latex_documents = [( - master_doc, "manual.tex", "User Guide", - author, "manual" + master_doc, "manual.tex", "User Guide", author, "manual" )] From bce6f9543a7db150ce595292849fd38a97b8a349 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Mon, 23 Oct 2023 23:58:29 +0200 Subject: [PATCH 05/27] Make sure icon columns on build settings dialog are narrow (#1551) --- novelwriter/tools/manussettings.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py index 842114a9..c4b211fb 100644 --- a/novelwriter/tools/manussettings.py +++ b/novelwriter/tools/manussettings.py @@ -332,14 +332,16 @@ class _FilterTab(QWidget): treeHeader = self.optTree.header() treeHeader.setStretchLastSection(False) + treeHeader.setMinimumSectionSize(iPx + cMg) # See Issue #1551 treeHeader.setSectionResizeMode(self.C_NAME, QHeaderView.Stretch) treeHeader.setSectionResizeMode(self.C_ACTIVE, QHeaderView.Fixed) treeHeader.setSectionResizeMode(self.C_STATUS, QHeaderView.Fixed) treeHeader.resizeSection(self.C_ACTIVE, iPx + cMg) treeHeader.resizeSection(self.C_STATUS, iPx + cMg) - self.optTree.setSelectionMode(QAbstractItemView.ExtendedSelection) self.optTree.setDragDropMode(QAbstractItemView.NoDragDrop) + self.optTree.setSelectionMode(QAbstractItemView.ExtendedSelection) + self.optTree.setSelectionBehavior(QAbstractItemView.SelectRows) # Filters # ======= @@ -390,11 +392,11 @@ class _FilterTab(QWidget): self.mainSplit.addWidget(self.filterOpt) self.mainSplit.setCollapsible(0, False) self.mainSplit.setCollapsible(1, False) - self.mainSplit.setStretchFactor(0, 0) - self.mainSplit.setStretchFactor(1, 1) + self.mainSplit.setStretchFactor(0, 1) + self.mainSplit.setStretchFactor(1, 0) self.mainSplit.setSizes([ - CONFIG.pxInt(pOptions.getInt("GuiBuildSettings", "treeWidth", 1)), - CONFIG.pxInt(pOptions.getInt("GuiBuildSettings", "filterWidth", 1)) + CONFIG.pxInt(pOptions.getInt("GuiBuildSettings", "treeWidth", 300)), + CONFIG.pxInt(pOptions.getInt("GuiBuildSettings", "filterWidth", 300)) ]) self.outerBox = QHBoxLayout() From 6df591c8036c87acb36738136415c332e637db6b Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 2 Nov 2023 23:01:41 +0100 Subject: [PATCH 06/27] Fix untranslated strings in Build tool (#1563) --- novelwriter/core/buildsettings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/novelwriter/core/buildsettings.py b/novelwriter/core/buildsettings.py index f506cef5..931525ea 100644 --- a/novelwriter/core/buildsettings.py +++ b/novelwriter/core/buildsettings.py @@ -32,7 +32,7 @@ from enum import Enum from typing import Iterable from pathlib import Path -from PyQt5.QtCore import QT_TRANSLATE_NOOP +from PyQt5.QtCore import QT_TRANSLATE_NOOP, QCoreApplication from novelwriter import CONFIG from novelwriter.enum import nwBuildFmt @@ -209,7 +209,7 @@ class BuildSettings: @staticmethod def getLabel(key: str) -> str: """Extract the GUI label for a specific setting.""" - return SETTINGS_LABELS.get(key, "ERROR") + return QCoreApplication.translate("Builds", SETTINGS_LABELS.get(key, "ERROR")) def getStr(self, key: str) -> str: """Type safe value access for strings.""" From 08a012ef8d9d94d3a6f09260261ca9dcd217e7c8 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 2 Nov 2023 23:34:42 +0100 Subject: [PATCH 07/27] Only default to system locale if a translation file exists (#1564) --- novelwriter/config.py | 14 ++++++++------ tests/test_base/test_base_config.py | 6 +++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/novelwriter/config.py b/novelwriter/config.py index d0bd7fc3..ed4eee65 100644 --- a/novelwriter/config.py +++ b/novelwriter/config.py @@ -81,10 +81,12 @@ class Config: # Localisation # Note that these paths must be strings - self._qLocale = QLocale.system() - self._qtTrans = {} + self._nwLangPath = self._appPath / "assets" / "i18n" self._qtLangPath = QLibraryInfo.location(QLibraryInfo.TranslationsPath) - self._nwLangPath = str(self._appPath / "assets" / "i18n") + + wantedLocale = self._nwLangPath / f"nw_{QLocale.system().name()}.qm" + self._qLocale = QLocale.system() if wantedLocale.exists() else QLocale("en_GB") + self._qtTrans = {} # PDF Manual pdfDocs = self._appPath / "assets" / "manual.pdf" @@ -427,7 +429,7 @@ class Config: else: return [] - for qmFile in Path(self._nwLangPath).iterdir(): + for qmFile in self._nwLangPath.iterdir(): qmName = qmFile.name if not (qmFile.is_file() and qmName.startswith(fPre) and qmName.endswith(fExt)): continue @@ -495,8 +497,8 @@ class Config: self._qtTrans = {} langList = [ - (self._qtLangPath, "qtbase"), # Qt 5.x - (self._nwLangPath, "nw"), # novelWriter + (self._qtLangPath, "qtbase"), # Qt 5.x + (str(self._nwLangPath), "nw"), # novelWriter ] for lngPath, lngBase in langList: for lngCode in self._qLocale.uiLanguages(): diff --git a/tests/test_base/test_base_config.py b/tests/test_base/test_base_config.py index 0f953f81..2b29444b 100644 --- a/tests/test_base/test_base_config.py +++ b/tests/test_base/test_base_config.py @@ -166,21 +166,21 @@ def testBaseConfig_Localisation(fncPath, tstPaths): i18nDir = fncPath / "i18n" i18nDir.mkdir() - tstConf._nwLangPath = str(i18nDir) + tstConf._nwLangPath = i18nDir copyfile(tstPaths.filesDir / "nw_en_GB.qm", i18nDir / "nw_en_GB.qm") writeFile(i18nDir / "nw_en_GB.ts", "") writeFile(i18nDir / "nw_abcd.qm", "") tstApp = MockApp() - tstConf.initLocalisation(tstApp) + tstConf.initLocalisation(tstApp) # type: ignore # Check Lists theList = tstConf.listLanguages(tstConf.LANG_NW) assert theList == [("en_GB", "British English")] theList = tstConf.listLanguages(tstConf.LANG_PROJ) assert theList == [("en_GB", "British English")] - theList = tstConf.listLanguages(None) + theList = tstConf.listLanguages(None) # type: ignore assert theList == [] # Add Language From 12d7512441641aa9fedf0c5179aac53e607c3596 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 2 Nov 2023 23:41:34 +0100 Subject: [PATCH 08/27] Make sure also Preferences defaults to British English --- novelwriter/dialogs/preferences.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index 72120448..c355fd83 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -176,6 +176,8 @@ class GuiPreferencesGeneral(QWidget): for lang, langName in theLangs: self.guiLocale.addItem(langName, lang) langIdx = self.guiLocale.findData(CONFIG.guiLocale) + if langIdx < 0: + langIdx = self.guiLocale.findData("en_GB") if langIdx != -1: self.guiLocale.setCurrentIndex(langIdx) From b3018bb392bea4add1798a236937c20ad9c8101d Mon Sep 17 00:00:00 2001 From: nisemono-neko <148933960+nisemono-neko@users.noreply.github.com> Date: Fri, 3 Nov 2023 13:27:03 +1000 Subject: [PATCH 09/27] Fix typos --- docs/source/int_introduction.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/int_introduction.rst b/docs/source/int_introduction.rst index d4c99188..ca344eb8 100644 --- a/docs/source/int_introduction.rst +++ b/docs/source/int_introduction.rst @@ -10,7 +10,7 @@ Key Features At its core, novelWriter is a multi-document plain text editor. It uses a markup syntax inspired by Markdown_ to apply simple formatting to the text. It is designed for writing fiction, so the formatting features available are limited to those relevant for this purpose. It is *not* suitable -for technical writing, and it is *not* a full-feature Markdown editor. +for technical writing, and it is *not* a full-featured Markdown editor. Your novel project is organised as a collection of separate plain text documents instead of a single, large document. The idea here is to make it easier to reorganise your project structure @@ -32,11 +32,11 @@ Below are some key features of novelWriter. formatting text. Formatting is therefore limited to a small set of formatting tags for simple things like text emphasis and paragraph alignment. When you really want to focus on just writing, you can switch the editor into :guilabel:`Focus Mode` where only the text editor panel - itself is vissible, and the project structure view is hidden away. + itself is visible, and the project structure view is hidden away. **Keep an eye on your notes** The main window can optionally show a document viewer to the right of the editor. This view - panel is intended for displaying another scene document, you character notes, plot notes, or any + panel is intended for displaying another scene document, your character notes, plot notes, or any other document you may need to reference while writing. It is not intended as a preview panel for the document you're editing, but if you wish, you can also use it for this purpose. From 3896d0fbf49affdb7a10915c00fbe1357746b8f1 Mon Sep 17 00:00:00 2001 From: nisemono-neko <148933960+nisemono-neko@users.noreply.github.com> Date: Fri, 3 Nov 2023 13:40:24 +1000 Subject: [PATCH 10/27] Fix typos --- docs/source/int_overview.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/int_overview.rst b/docs/source/int_overview.rst index 09d8358f..6cba93ff 100644 --- a/docs/source/int_overview.rst +++ b/docs/source/int_overview.rst @@ -14,8 +14,8 @@ novelWriter is built as a cross-platform application using `Python 3 `_ for the user interface. novelWriter is built for Linux first, and this is where it works best. However, it also runs fine -on Windows and MacOS due to the cross-platform framework it's build on. The author of the -application doesn't own a Mac, so on-going Mac support is dependant on user feedback and user +on Windows and MacOS due to the cross-platform framework it's built on. The author of the +application doesn't own a Mac, so on-going Mac support is dependent on user feedback and user contributions. Spell checking in novelWriter is provided by a third party library called @@ -36,7 +36,7 @@ read on. This chapter explains the basics of how the application works and what it can and cannot do. :ref:`a_ui_project` – Recommended Reading - This chapter will give you a more detailed explanation of how you the user interface components + This chapter will give you a more detailed explanation of how you can use the user interface components to organise and view your project work. :ref:`a_ui_writing` – Recommended Reading @@ -68,7 +68,7 @@ read on. Organising Your Projects ======================== -In addition to manage a collection of plain text files, novelWriter can interpret and map the +In addition to managing a collection of plain text files, novelWriter can interpret and map the structure of your novel and show you additional information about its flow and content. In order to take advantage of these features, you must structure your text in a specific way and add some meta data for it to extract. From 0c249d201039e66b9036577ef6fc88b99874a988 Mon Sep 17 00:00:00 2001 From: nisemono-neko <148933960+nisemono-neko@users.noreply.github.com> Date: Fri, 3 Nov 2023 16:07:14 +1000 Subject: [PATCH 11/27] Fix typos --- docs/source/usage_breakdown.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/usage_breakdown.rst b/docs/source/usage_breakdown.rst index b6899f44..b0b0c8f7 100644 --- a/docs/source/usage_breakdown.rst +++ b/docs/source/usage_breakdown.rst @@ -105,7 +105,7 @@ much all collected meta data is available here in different columns. You can select which novel root folder to display from the dropdown box, and you can select which columns to show or hide from the menu button. You can also rearrange the columns by drag and drop. -The app will remember you column order and size between sessions, and for each individual project. +The app will remember your column order and size between sessions, and for each individual project. Colour Themes @@ -220,7 +220,7 @@ makes it easier to track them with version control software. Drive is mapped on Linux Gnome desktops using gvfs/gio. .. caution:: - You should not add additional files to the project folder yourself. Nor should you,a s a rule, + You should not add additional files to the project folder yourself. Nor should you, as a rule, manually edit files within it. If you really must manually edit the text files, e.g. with some automated task you want to perform, you need to rebuild the :term:`Project Index` when you open the project again. From 4c7fbdb77ab59061cc03dd04a5a47ab7282020ef Mon Sep 17 00:00:00 2001 From: nisemono-neko <148933960+nisemono-neko@users.noreply.github.com> Date: Fri, 3 Nov 2023 16:12:53 +1000 Subject: [PATCH 12/27] Fix typos --- docs/source/int_customise.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/int_customise.rst b/docs/source/int_customise.rst index 9098db7d..d54afc2a 100644 --- a/docs/source/int_customise.rst +++ b/docs/source/int_customise.rst @@ -87,7 +87,7 @@ set as ``name`` inside the file. Gustom GUI and Icons Theme -------------------------- -A GUI theme ``.conf`` file consists of the follwing settings: +A GUI theme ``.conf`` file consists of the following settings: .. code-block:: cfg @@ -127,7 +127,7 @@ In the Main section you must at least define the ``name`` and ``icontheme`` sett ``typicons_light`` or ``typicons_dark``, or to an icon theme in your custom icons directory. The setting must match the icon theme's folder name. -The Palette values correspond the the Qt enum values for QPalette::ColorRole, see the +The Palette values correspond to the Qt enum values for QPalette::ColorRole, see the `Qt documentation `_ for more details. The colour values are RGB numbers on the format ``r, g, b`` where each is an integer from to 255. Omitted values are not loaded and will use default values. @@ -167,5 +167,5 @@ A syntax theme ``.conf`` file consists of the follwing settings: modifier = 0, 0, 0 In the Main section, you must define at least the ``name`` setting. The Syntax colour values are -RGB numbers on the format ``r, g, b`` where each is an integer from to 255. Omitted values default +RGB numbers of the format ``r, g, b`` where each is an integer from to 255. Omitted values default to black, except ``background`` which defaults to white, From 7080703481be997cf660ebb65a7614395d8d8826 Mon Sep 17 00:00:00 2001 From: nisemono-neko <148933960+nisemono-neko@users.noreply.github.com> Date: Fri, 3 Nov 2023 16:16:18 +1000 Subject: [PATCH 13/27] Fix typos --- docs/source/int_glossary.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/int_glossary.rst b/docs/source/int_glossary.rst index a57ac414..2cead9cf 100644 --- a/docs/source/int_glossary.rst +++ b/docs/source/int_glossary.rst @@ -32,7 +32,7 @@ Glossary for more details. Reference - A references is one of a set of :term:`keywords` that can be used to link to a + A reference is one of a set of :term:`keywords` that can be used to link to a :term:`tag` in another document. The reference keywords are specific to the different :term:`root folder` types. A full overview is available in the :ref:`a_references` chapter. @@ -55,7 +55,7 @@ Glossary Keyword A keyword in novelWriter is a special command you put in the text of your documents. They are - not standard Markdown, but is used in novelWriter to add information that is interpreted by + not standard Markdown, but are used in novelWriter to add information that is interpreted by the application. For instance, keywords are used for :term:`tags` and :term:`references`. From 552249d145ad1039a752632d485919d64c75ac92 Mon Sep 17 00:00:00 2001 From: nisemono-neko <148933960+nisemono-neko@users.noreply.github.com> Date: Fri, 3 Nov 2023 16:18:43 +1000 Subject: [PATCH 14/27] Fix typos --- docs/source/usage_shortcuts.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/usage_shortcuts.rst b/docs/source/usage_shortcuts.rst index 764b5240..86e60afa 100644 --- a/docs/source/usage_shortcuts.rst +++ b/docs/source/usage_shortcuts.rst @@ -4,7 +4,7 @@ Keyboard Shortcuts ****************** -Most features in novelWriter are available as keyboard shortcuts. This is a reference list if these +Most features in novelWriter are available as keyboard shortcuts. This is a reference list of those shortcuts. Most of them are also listed in the application's user interface. .. note:: From 36fd64e1a3af33d8b5d44ac9f8e376ac54642fab Mon Sep 17 00:00:00 2001 From: nisemono-neko <148933960+nisemono-neko@users.noreply.github.com> Date: Fri, 3 Nov 2023 16:29:30 +1000 Subject: [PATCH 15/27] Fix typos --- docs/source/usage_format.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/source/usage_format.rst b/docs/source/usage_format.rst index 4b159491..567695ff 100644 --- a/docs/source/usage_format.rst +++ b/docs/source/usage_format.rst @@ -34,7 +34,7 @@ have a distinct colour, and the references themselves will get a colour if they references will get a squiggly error line underneath. The same applies to duplicate tags. There are a number of syntax highlighter colour themes available, both for light and dark GUIs. You -can select them for :guilabel:`Preferences`. +can select them from :guilabel:`Preferences`. .. _a_fmt_head: @@ -72,7 +72,7 @@ For headers level one and two, adding a ``!`` modifies the behaviour of the head ``#! Title Text`` This tells the build tool that the level one heading is intended to be used for the novel's main title, like for instance on the front page. When building the manuscript, this will use a - different styling and will exclude the title from for instance a Table of Contents in Libre + different styling and will exclude the title from, for instance, a Table of Contents in Libre Office. ``##! Title Text`` @@ -170,7 +170,7 @@ in a special manner and will show up in the :ref:`a_ui_outline` in a dedicated c indicate this by altering the colour of the word. ``% text...`` - This is a comment. The text is not renderred by default (this can be overridden), seen in the + This is a comment. The text is not rendered by default (this can be overridden), seen in the document viewer, or counted towards word counts. ``% Synopsis: text...`` @@ -196,10 +196,10 @@ Tags use the command ``@tag:`` to define a tag. The tag can be set once per sect heading. Setting it multiple times under the same heading will just override the previous setting. ``@tag: value`` - A tag commad followed by the tag value, like for instance the name of a character. + A tag command followed by the tag value, like for instance the name of a character. References can be set anywhere within a section, and are collected according to their category. -References are on the form: +References are in the form: ``@keyword: value`` A reference keyword followed by a value, or a comma separated list of values. @@ -236,7 +236,7 @@ Examples: "``>> Centred text <<``", "The text paragraph is centred." "``> Left indented text``", "The text has an increased left margin." "``Right indented text <``", "The text has an increased right margin." - "``> Left/right indented text <``", "The text has an both margins increased." + "``> Left/right indented text <``", "The text has both margins increased." .. note:: The text editor will not show the alignment and indentation live. But the viewer will show them From 4582ef8b8326a4819a9bae2aba4dd3d6ab0dc8a6 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Fri, 3 Nov 2023 10:13:35 +0100 Subject: [PATCH 16/27] Fix anchor tags in html output (#1566) --- novelwriter/core/tohtml.py | 4 ++-- novelwriter/core/tokenizer.py | 41 ++++++++++++++++++++--------------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/novelwriter/core/tohtml.py b/novelwriter/core/tohtml.py index 8459ae59..9784f281 100644 --- a/novelwriter/core/tohtml.py +++ b/novelwriter/core/tohtml.py @@ -164,7 +164,7 @@ class ToHtml(Tokenizer): parStyle = None tmpResult = [] - for tType, tLine, tText, tFormat, tStyle in self._tokens: + for tType, nHead, tText, tFormat, tStyle in self._tokens: # Replace < and > with HTML entities if tFormat: @@ -225,7 +225,7 @@ class ToHtml(Tokenizer): hStyle = "" if self._linkHeaders: - aNm = f"" + aNm = f"" else: aNm = "" diff --git a/novelwriter/core/tokenizer.py b/novelwriter/core/tokenizer.py index 6fc281b3..e1f94f07 100644 --- a/novelwriter/core/tokenizer.py +++ b/novelwriter/core/tokenizer.py @@ -390,7 +390,7 @@ class Tokenizer(ABC): The format of the token list is an entry with a five-tuple for each line in the file. The tuple is as follows: 1: The type of the block, self.T_* - 2: The line in the file where this block occurred + 2: The header number under which the text is placed 3: The text content of the block, without leading tags 4: The internal formatting map of the text, self.FMT_* 5: The style of the block, self.A_* @@ -404,16 +404,15 @@ class Tokenizer(ABC): self._tokens = [] tmpMarkdown = [] - nLine = 0 + nHead = 0 breakNext = False for aLine in self._text.splitlines(): - nLine += 1 sLine = aLine.strip() # Check for blank lines if len(sLine) == 0: self._tokens.append(( - self.T_EMPTY, nLine, "", None, self.A_NONE + self.T_EMPTY, nHead, "", None, self.A_NONE )) if self._keepMarkdown: tmpMarkdown.append("\n") @@ -438,7 +437,7 @@ class Tokenizer(ABC): elif sLine == "[VSPACE]": self._tokens.append( - (self.T_SKIP, nLine, "", None, sAlign) + (self.T_SKIP, nHead, "", None, sAlign) ) continue @@ -446,11 +445,11 @@ class Tokenizer(ABC): nSkip = checkInt(sLine[8:-1], 0) if nSkip >= 1: self._tokens.append( - (self.T_SKIP, nLine, "", None, sAlign) + (self.T_SKIP, nHead, "", None, sAlign) ) if nSkip > 1: self._tokens += (nSkip - 1) * [ - (self.T_SKIP, nLine, "", None, self.A_NONE) + (self.T_SKIP, nHead, "", None, self.A_NONE) ] continue @@ -459,20 +458,20 @@ class Tokenizer(ABC): synTag = cLine[:9].lower() if synTag == "synopsis:": self._tokens.append(( - self.T_SYNOPSIS, nLine, cLine[9:].strip(), None, sAlign + self.T_SYNOPSIS, nHead, cLine[9:].strip(), None, sAlign )) if self._doSynopsis and self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) else: self._tokens.append(( - self.T_COMMENT, nLine, aLine[1:].strip(), None, sAlign + self.T_COMMENT, nHead, aLine[1:].strip(), None, sAlign )) if self._doComments and self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) elif aLine[0] == "@": self._tokens.append(( - self.T_KEYWORD, nLine, aLine[1:].strip(), None, sAlign + self.T_KEYWORD, nHead, aLine[1:].strip(), None, sAlign )) if self._doKeywords and self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) @@ -482,8 +481,9 @@ class Tokenizer(ABC): sAlign |= self.A_CENTRE sAlign |= self.A_PBB + nHead += 1 self._tokens.append(( - self.T_HEAD1, nLine, aLine[2:].strip(), None, sAlign + self.T_HEAD1, nHead, aLine[2:].strip(), None, sAlign )) if self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) @@ -492,39 +492,44 @@ class Tokenizer(ABC): if self._isNovel: sAlign |= self.A_PBB + nHead += 1 self._tokens.append(( - self.T_HEAD2, nLine, aLine[3:].strip(), None, sAlign + self.T_HEAD2, nHead, aLine[3:].strip(), None, sAlign )) if self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) elif aLine[:4] == "### ": + nHead += 1 self._tokens.append(( - self.T_HEAD3, nLine, aLine[4:].strip(), None, sAlign + self.T_HEAD3, nHead, aLine[4:].strip(), None, sAlign )) if self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) elif aLine[:5] == "#### ": + nHead += 1 self._tokens.append(( - self.T_HEAD4, nLine, aLine[5:].strip(), None, sAlign + self.T_HEAD4, nHead, aLine[5:].strip(), None, sAlign )) if self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) elif aLine[:3] == "#! ": + nHead += 1 if self._isNovel: tStyle = self.T_TITLE else: tStyle = self.T_HEAD1 self._tokens.append(( - tStyle, nLine, aLine[3:].strip(), None, sAlign | self.A_CENTRE + tStyle, nHead, aLine[3:].strip(), None, sAlign | self.A_CENTRE )) if self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) elif aLine[:4] == "##! ": + nHead += 1 if self._isNovel: tStyle = self.T_UNNUM sAlign |= self.A_PBB @@ -532,7 +537,7 @@ class Tokenizer(ABC): tStyle = self.T_HEAD2 self._tokens.append(( - tStyle, nLine, aLine[4:].strip(), None, sAlign + tStyle, nHead, aLine[4:].strip(), None, sAlign )) if self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) @@ -589,7 +594,7 @@ class Tokenizer(ABC): # sorted by position fmtPos = sorted(fmtPos, key=itemgetter(0)) self._tokens.append(( - self.T_TEXT, nLine, aLine, fmtPos, sAlign + self.T_TEXT, nHead, aLine, fmtPos, sAlign )) if self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) @@ -608,7 +613,7 @@ class Tokenizer(ABC): # Always add an empty line at the end of the file self._tokens.append(( - self.T_EMPTY, nLine, "", None, self.A_NONE + self.T_EMPTY, nHead, "", None, self.A_NONE )) if self._keepMarkdown: tmpMarkdown.append("\n") From b0476955010451229ccdc5da0347cc7c99f8d014 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Fri, 3 Nov 2023 10:28:54 +0100 Subject: [PATCH 17/27] Update tests --- tests/test_core/test_core_tohtml.py | 528 ++++++++++++------------- tests/test_core/test_core_tokenizer.py | 204 +++++----- 2 files changed, 363 insertions(+), 369 deletions(-) diff --git a/tests/test_core/test_core_tohtml.py b/tests/test_core/test_core_tohtml.py index 6c7dee61..771040ab 100644 --- a/tests/test_core/test_core_tohtml.py +++ b/tests/test_core/test_core_tohtml.py @@ -29,177 +29,176 @@ from novelwriter.core.project import NWProject @pytest.mark.core def testCoreToHtml_ConvertFormat(mockGUI): - """Test the tokenizer and converter chain using the ToHtml class. - """ - theProject = NWProject() - theHtml = ToHtml(theProject) + """Test the tokenizer and converter chain using the ToHtml class.""" + project = NWProject() + html = ToHtml(project) # Novel Files Headers # =================== - theHtml._isNovel = True - theHtml._isNote = False - theHtml._isFirst = True + html._isNovel = True + html._isNote = False + html._isFirst = True # Header 1 - theHtml._text = "# Partition\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = "# Partition\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Partition

\n" ) # Header 2 - theHtml._text = "## Chapter Title\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = "## Chapter Title\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Chapter Title

\n" ) # Header 3 - theHtml._text = "### Scene Title\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == "

Scene Title

\n" + html._text = "### Scene Title\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == "

Scene Title

\n" # Header 4 - theHtml._text = "#### Section Title\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == "

Section Title

\n" + html._text = "#### Section Title\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == "

Section Title

\n" # Title - theHtml._text = "#! Title\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = "#! Title\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Title

\n" ) # Unnumbered - theHtml._text = "##! Prologue\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == "

Prologue

\n" + html._text = "##! Prologue\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == "

Prologue

\n" # Note Files Headers # ================== - theHtml._isNovel = False - theHtml._isNote = True - theHtml._isFirst = True - theHtml.setLinkHeaders(True) + html._isNovel = False + html._isNote = True + html._isFirst = True + html.setLinkHeaders(True) # Header 1 - theHtml._text = "# Heading One\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == "

Heading One

\n" + html._text = "# Heading One\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == "

Heading One

\n" # Header 2 - theHtml._text = "## Heading Two\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == "

Heading Two

\n" + html._text = "## Heading Two\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == "

Heading Two

\n" # Header 3 - theHtml._text = "### Heading Three\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == "

Heading Three

\n" + html._text = "### Heading Three\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == "

Heading Three

\n" # Header 4 - theHtml._text = "#### Heading Four\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == "

Heading Four

\n" + html._text = "#### Heading Four\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == "

Heading Four

\n" # Title - theHtml._text = "#! Heading One\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( - "

Heading One

\n" + html._text = "#! Heading One\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( + "

Heading One

\n" ) # Unnumbered - theHtml._text = "##! Heading Two\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == "

Heading Two

\n" + html._text = "##! Heading Two\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == "

Heading Two

\n" # Paragraphs # ========== # Text - theHtml._text = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Some nested bold and italic and " "strikethrough text here

\n" ) # Text w/Hard Break - theHtml._text = "Line one \nLine two \nLine three\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = "Line one \nLine two \nLine three\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Line one
Line two
Line three

\n" ) # Synopsis - theHtml._text = "%synopsis: The synopsis ...\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == "" + html._text = "%synopsis: The synopsis ...\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == "" - theHtml.setSynopsis(True) - theHtml._text = "%synopsis: The synopsis ...\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html.setSynopsis(True) + html._text = "%synopsis: The synopsis ...\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Synopsis: The synopsis ...

\n" ) # Comment - theHtml._text = "% A comment ...\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == "" + html._text = "% A comment ...\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == "" - theHtml.setComments(True) - theHtml._text = "% A comment ...\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html.setComments(True) + html._text = "% A comment ...\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Comment: A comment ...

\n" ) # Keywords - theHtml._text = "@char: Bod, Jane\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == "" + html._text = "@char: Bod, Jane\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == "" - theHtml.setKeywords(True) - theHtml._text = "@char: Bod, Jane\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html.setKeywords(True) + html._text = "@char: Bod, Jane\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Characters: " "Bod, Jane

\n" ) # Multiple Keywords - theHtml.setKeywords(True) - theHtml._text = "## Chapter\n\n@pov: Bod\n@plot: Main\n@location: Europe\n\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html.setKeywords(True) + html._text = "## Chapter\n\n@pov: Bod\n@plot: Main\n@location: Europe\n\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

" - "Chapter

\n" + "Chapter\n" "

" "Point of View: Bod" "

\n" @@ -214,13 +213,13 @@ def testCoreToHtml_ConvertFormat(mockGUI): # Preview Mode # ============ - theHtml.setPreview(True, True) + html.setPreview(True, True) # Text (HTML4) - theHtml._text = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Some nested bold and italic and " "strikethrough " "text here

\n" @@ -231,109 +230,108 @@ def testCoreToHtml_ConvertFormat(mockGUI): @pytest.mark.core def testCoreToHtml_ConvertDirect(mockGUI): - """Test the converter directly using the ToHtml class. - """ - theProject = NWProject() - theHtml = ToHtml(theProject) + """Test the converter directly using the ToHtml class.""" + project = NWProject() + html = ToHtml(project) - theHtml._isNovel = True - theHtml._isNote = False - theHtml.setLinkHeaders(True) + html._isNovel = True + html._isNote = False + html.setLinkHeaders(True) # Special Titles # ============== # Title - theHtml._tokens = [ - (theHtml.T_TITLE, 1, "A Title", None, theHtml.A_PBB | theHtml.A_CENTRE), - (theHtml.T_EMPTY, 1, "", None, theHtml.A_NONE), + html._tokens = [ + (html.T_TITLE, 1, "A Title", None, html.A_PBB | html.A_CENTRE), + (html.T_EMPTY, 1, "", None, html.A_NONE), ] - theHtml.doConvert() - assert theHtml.theResult == ( + html.doConvert() + assert html.theResult == ( "

" - "A Title

\n" + "A Title\n" ) # Unnumbered - theHtml._tokens = [ - (theHtml.T_UNNUM, 1, "Prologue", None, theHtml.A_PBB), - (theHtml.T_EMPTY, 1, "", None, theHtml.A_NONE), + html._tokens = [ + (html.T_UNNUM, 1, "Prologue", None, html.A_PBB), + (html.T_EMPTY, 1, "", None, html.A_NONE), ] - theHtml.doConvert() - assert theHtml.theResult == ( + html.doConvert() + assert html.theResult == ( "

" - "Prologue

\n" + "Prologue\n" ) # Separators # ========== # Separator - theHtml._tokens = [ - (theHtml.T_SEP, 1, "* * *", None, theHtml.A_CENTRE), - (theHtml.T_EMPTY, 1, "", None, theHtml.A_NONE), + html._tokens = [ + (html.T_SEP, 1, "* * *", None, html.A_CENTRE), + (html.T_EMPTY, 1, "", None, html.A_NONE), ] - theHtml.doConvert() - assert theHtml.theResult == "

* * *

\n" + html.doConvert() + assert html.theResult == "

* * *

\n" # Skip - theHtml._tokens = [ - (theHtml.T_SKIP, 1, "", None, theHtml.A_NONE), - (theHtml.T_EMPTY, 1, "", None, theHtml.A_NONE), + html._tokens = [ + (html.T_SKIP, 1, "", None, html.A_NONE), + (html.T_EMPTY, 1, "", None, html.A_NONE), ] - theHtml.doConvert() - assert theHtml.theResult == "

 

\n" + html.doConvert() + assert html.theResult == "

 

\n" # Alignment # ========= - theHtml.setLinkHeaders(False) + html.setLinkHeaders(False) # Align Left - theHtml.setStyles(False) - theHtml._tokens = [ - (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_LEFT), + html.setStyles(False) + html._tokens = [ + (html.T_HEAD1, 1, "A Title", None, html.A_LEFT), ] - theHtml.doConvert() - assert theHtml.theResult == ( + html.doConvert() + assert html.theResult == ( "

A Title

\n" ) - theHtml.setStyles(True) + html.setStyles(True) # Align Left - theHtml._tokens = [ - (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_LEFT), + html._tokens = [ + (html.T_HEAD1, 1, "A Title", None, html.A_LEFT), ] - theHtml.doConvert() - assert theHtml.theResult == ( + html.doConvert() + assert html.theResult == ( "

A Title

\n" ) # Align Right - theHtml._tokens = [ - (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_RIGHT), + html._tokens = [ + (html.T_HEAD1, 1, "A Title", None, html.A_RIGHT), ] - theHtml.doConvert() - assert theHtml.theResult == ( + html.doConvert() + assert html.theResult == ( "

A Title

\n" ) # Align Centre - theHtml._tokens = [ - (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_CENTRE), + html._tokens = [ + (html.T_HEAD1, 1, "A Title", None, html.A_CENTRE), ] - theHtml.doConvert() - assert theHtml.theResult == ( + html.doConvert() + assert html.theResult == ( "

A Title

\n" ) # Align Justify - theHtml._tokens = [ - (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_JUSTIFY), + html._tokens = [ + (html.T_HEAD1, 1, "A Title", None, html.A_JUSTIFY), ] - theHtml.doConvert() - assert theHtml.theResult == ( + html.doConvert() + assert html.theResult == ( "

A Title

\n" ) @@ -341,11 +339,11 @@ def testCoreToHtml_ConvertDirect(mockGUI): # ========== # Page Break Always - theHtml._tokens = [ - (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_PBB | theHtml.A_PBA), + html._tokens = [ + (html.T_HEAD1, 1, "A Title", None, html.A_PBB | html.A_PBA), ] - theHtml.doConvert() - assert theHtml.theResult == ( + html.doConvert() + assert html.theResult == ( "

A Title

\n" ) @@ -354,22 +352,22 @@ def testCoreToHtml_ConvertDirect(mockGUI): # ====== # Indent Left - theHtml._tokens = [ - (theHtml.T_TEXT, 1, "Some text ...", [], theHtml.A_IND_L), - (theHtml.T_EMPTY, 2, "", None, theHtml.A_NONE), + html._tokens = [ + (html.T_TEXT, 1, "Some text ...", [], html.A_IND_L), + (html.T_EMPTY, 2, "", None, html.A_NONE), ] - theHtml.doConvert() - assert theHtml.theResult == ( + html.doConvert() + assert html.theResult == ( "

Some text ...

\n" ) # Indent Right - theHtml._tokens = [ - (theHtml.T_TEXT, 1, "Some text ...", [], theHtml.A_IND_R), - (theHtml.T_EMPTY, 2, "", None, theHtml.A_NONE), + html._tokens = [ + (html.T_TEXT, 1, "Some text ...", [], html.A_IND_R), + (html.T_EMPTY, 2, "", None, html.A_NONE), ] - theHtml.doConvert() - assert theHtml.theResult == ( + html.doConvert() + assert html.theResult == ( "

Some text ...

\n" ) @@ -378,40 +376,39 @@ def testCoreToHtml_ConvertDirect(mockGUI): @pytest.mark.core def testCoreToHtml_SpecialCases(mockGUI): - """Test some special cases that have caused errors in the past. - """ - theProject = NWProject() - theHtml = ToHtml(theProject) - theHtml._isNovel = True + """Test some special cases that have caused errors in the past.""" + project = NWProject() + html = ToHtml(project) + html._isNovel = True # Greater/Lesser than symbols # =========================== - theHtml._text = "Text with > and < with some **bold text** in it.\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = "Text with > and < with some **bold text** in it.\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Text with > and < with some bold text in it.

\n" ) - theHtml._text = "Text with some <**bold text**> in it.\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = "Text with some <**bold text**> in it.\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Text with some <bold text> in it.

\n" ) - theHtml._text = "Let's > be > _difficult **shall** > we_?\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = "Let's > be > _difficult **shall** > we_?\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Let's > be > difficult shall > we?

\n" ) - theHtml._text = "Test > text _<**bold**>_ and more.\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = "Test > text _<**bold**>_ and more.\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Test > text <bold> and more.

\n" ) @@ -419,20 +416,20 @@ def testCoreToHtml_SpecialCases(mockGUI): # =================== # See: https://github.com/vkbo/novelWriter/issues/950 - theHtml.setComments(True) - theHtml._text = "% Test > text _<**bold**>_ and more.\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html.setComments(True) + html._text = "% Test > text _<**bold**>_ and more.\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

" "Comment: Test > text _<**bold**>_ and more." "

\n" ) - theHtml._text = "## Heading <1>\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = "## Heading <1>\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Heading <1>

\n" ) @@ -440,10 +437,10 @@ def testCoreToHtml_SpecialCases(mockGUI): # ==================== # See: https://github.com/vkbo/novelWriter/issues/1412 - theHtml._text = "Test text \\**_bold_** and more.\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = "Test text \\**_bold_** and more.\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Test text **bold** and more.

\n" ) @@ -452,11 +449,10 @@ def testCoreToHtml_SpecialCases(mockGUI): @pytest.mark.core def testCoreToHtml_Complex(mockGUI, fncPath): - """Test the save method of the ToHtml class. - """ - theProject = NWProject() - theHtml = ToHtml(theProject) - theHtml._isNovel = True + """Test the save method of the ToHtml class.""" + project = NWProject() + html = ToHtml(project) + html._isNovel = True # Build Project # ============= @@ -502,21 +498,21 @@ def testCoreToHtml_Complex(mockGUI, fncPath): ] for i in range(len(docText)): - theHtml._text = docText[i] - theHtml.doPreProcessing() - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == resText[i] + html._text = docText[i] + html.doPreProcessing() + html.tokenizeText() + html.doConvert() + assert html.theResult == resText[i] - assert theHtml.fullHTML == resText + assert html.fullHTML == resText - theHtml.replaceTabs(nSpaces=2, spaceChar=" ") + html.replaceTabs(nSpaces=2, spaceChar=" ") resText[6] = "

A Section

\n

  More text in scene two.

\n" # Check File # ========== - theStyle = theHtml.getStyleSheet() + theStyle = html.getStyleSheet() htmlDoc = ( "\n" "\n" @@ -539,7 +535,7 @@ def testCoreToHtml_Complex(mockGUI, fncPath): ) saveFile = fncPath / "outFile.htm" - theHtml.saveHtml5(saveFile) + html.saveHtml5(saveFile) assert readFile(saveFile) == htmlDoc # END Test testCoreToHtml_Complex @@ -547,86 +543,84 @@ def testCoreToHtml_Complex(mockGUI, fncPath): @pytest.mark.core def testCoreToHtml_Methods(mockGUI): - """Test all the other methods of the ToHtml class. - """ - theProject = NWProject() - theHtml = ToHtml(theProject) - theHtml.setKeepMarkdown(True) + """Test all the other methods of the ToHtml class.""" + project = NWProject() + html = ToHtml(project) + html.setKeepMarkdown(True) # Auto-Replace, keep Unicode docText = "Text with & short–dash, long—dash …\n" - theHtml._text = docText - theHtml.setReplaceUnicode(False) - theHtml.doPreProcessing() - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = docText + html.setReplaceUnicode(False) + html.doPreProcessing() + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Text with <brackets> & short–dash, long—dash …

\n" ) # Auto-Replace, replace Unicode docText = "Text with & short–dash, long—dash …\n" - theHtml._text = docText - theHtml.setReplaceUnicode(True) - theHtml.doPreProcessing() - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = docText + html.setReplaceUnicode(True) + html.doPreProcessing() + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Text with <brackets> & short–dash, long—dash …

\n" ) # With Preview - theHtml.setPreview(True, True) - theHtml._text = docText - theHtml.doPreProcessing() - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theMarkdown[-1] == ( + html.setPreview(True, True) + html._text = docText + html.doPreProcessing() + html.tokenizeText() + html.doConvert() + assert html.theMarkdown[-1] == ( "Text with & short–dash, long—dash …\n\n" ) # Result Size - assert theHtml.getFullResultSize() == 147 + assert html.getFullResultSize() == 147 # CSS # === - assert len(theHtml.getStyleSheet()) > 1 - assert "p {text-align: left;" in " ".join(theHtml.getStyleSheet()) - assert "p {text-align: justify;" not in " ".join(theHtml.getStyleSheet()) + assert len(html.getStyleSheet()) > 1 + assert "p {text-align: left;" in " ".join(html.getStyleSheet()) + assert "p {text-align: justify;" not in " ".join(html.getStyleSheet()) - theHtml.setJustify(True) - assert "p {text-align: left;" not in " ".join(theHtml.getStyleSheet()) - assert "p {text-align: justify;" in " ".join(theHtml.getStyleSheet()) + html.setJustify(True) + assert "p {text-align: left;" not in " ".join(html.getStyleSheet()) + assert "p {text-align: justify;" in " ".join(html.getStyleSheet()) - theHtml.setStyles(False) - assert theHtml.getStyleSheet() == [] + html.setStyles(False) + assert html.getStyleSheet() == [] # END Test testCoreToHtml_Methods @pytest.mark.core def testCoreToHtml_Format(mockGUI): - """Test all the formatters for the ToHtml class. - """ - theProject = NWProject() - theHtml = ToHtml(theProject) + """Test all the formatters for the ToHtml class.""" + project = NWProject() + html = ToHtml(project) # Export Mode # =========== - assert theHtml._formatSynopsis("synopsis text") == ( + assert html._formatSynopsis("synopsis text") == ( "

Synopsis: synopsis text

\n" ) - assert theHtml._formatComments("comment text") == ( + assert html._formatComments("comment text") == ( "

Comment: comment text

\n" ) - assert theHtml._formatKeywords("") == "" - assert theHtml._formatKeywords("tag: Jane") == ( + assert html._formatKeywords("") == "" + assert html._formatKeywords("tag: Jane") == ( "Tag: Jane" ) - assert theHtml._formatKeywords("char: Bod, Jane") == ( + assert html._formatKeywords("char: Bod, Jane") == ( "Characters: " "Bod, " "Jane" @@ -635,20 +629,20 @@ def testCoreToHtml_Format(mockGUI): # Preview Mode # ============ - theHtml.setPreview(True, True) + html.setPreview(True, True) - assert theHtml._formatSynopsis("synopsis text") == ( + assert html._formatSynopsis("synopsis text") == ( "

Synopsis: synopsis text

\n" ) - assert theHtml._formatComments("comment text") == ( + assert html._formatComments("comment text") == ( "

comment text

\n" ) - assert theHtml._formatKeywords("") == "" - assert theHtml._formatKeywords("tag: Jane") == ( + assert html._formatKeywords("") == "" + assert html._formatKeywords("tag: Jane") == ( "Tag: Jane" ) - assert theHtml._formatKeywords("char: Bod, Jane") == ( + assert html._formatKeywords("char: Bod, Jane") == ( "Characters: " "Bod, " "Jane" diff --git a/tests/test_core/test_core_tokenizer.py b/tests/test_core/test_core_tokenizer.py index d746418d..16f34d9d 100644 --- a/tests/test_core/test_core_tokenizer.py +++ b/tests/test_core/test_core_tokenizer.py @@ -442,8 +442,8 @@ def testCoreToken_MetaFormat(mockGUI): theToken._text = "% A comment\n" theToken.tokenizeText() assert theToken._tokens == [ - (Tokenizer.T_COMMENT, 1, "A comment", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_COMMENT, 0, "A comment", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] assert theToken.theMarkdown[-1] == "\n" @@ -455,14 +455,14 @@ def testCoreToken_MetaFormat(mockGUI): theToken._text = "%synopsis: The synopsis\n" theToken.tokenizeText() assert theToken._tokens == [ - (Tokenizer.T_SYNOPSIS, 1, "The synopsis", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_SYNOPSIS, 0, "The synopsis", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] theToken._text = "% synopsis: The synopsis\n" theToken.tokenizeText() assert theToken._tokens == [ - (Tokenizer.T_SYNOPSIS, 1, "The synopsis", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_SYNOPSIS, 0, "The synopsis", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] assert theToken.theMarkdown[-1] == "\n" @@ -474,8 +474,8 @@ def testCoreToken_MetaFormat(mockGUI): theToken._text = "@char: Bod\n" theToken.tokenizeText() assert theToken._tokens == [ - (Tokenizer.T_KEYWORD, 1, "char: Bod", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_KEYWORD, 0, "char: Bod", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] assert theToken.theMarkdown[-1] == "\n" @@ -489,10 +489,10 @@ def testCoreToken_MetaFormat(mockGUI): styMid = Tokenizer.A_NONE | Tokenizer.A_Z_BTMMRG | Tokenizer.A_Z_TOPMRG styBtm = Tokenizer.A_NONE | Tokenizer.A_Z_TOPMRG assert theToken._tokens == [ - (Tokenizer.T_KEYWORD, 1, "pov: Bod", None, styTop), - (Tokenizer.T_KEYWORD, 2, "plot: Main", None, styMid), - (Tokenizer.T_KEYWORD, 3, "location: Europe", None, styBtm), - (Tokenizer.T_EMPTY, 3, "", None, Tokenizer.A_NONE), + (Tokenizer.T_KEYWORD, 0, "pov: Bod", None, styTop), + (Tokenizer.T_KEYWORD, 0, "plot: Main", None, styMid), + (Tokenizer.T_KEYWORD, 0, "location: Europe", None, styBtm), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] assert theToken.theMarkdown[-1] == "@pov: Bod\n@plot: Main\n@location: Europe\n\n" @@ -521,23 +521,23 @@ def testCoreToken_MarginFormat(mockGUI): ) theToken.tokenizeText() assert theToken._tokens == [ - (Tokenizer.T_TEXT, 1, "Some regular text", [], Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 3, "Some left-aligned text", [], Tokenizer.A_LEFT), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 5, "Some right-aligned text", [], Tokenizer.A_RIGHT), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 7, "Some centered text", [], Tokenizer.A_CENTRE), - (Tokenizer.T_EMPTY, 8, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 9, "Left-indented block", [], Tokenizer.A_IND_L), - (Tokenizer.T_EMPTY, 10, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 11, "Right-indented block", [], Tokenizer.A_IND_R), - (Tokenizer.T_EMPTY, 12, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 13, "Double-indented block", [], dblIndent), - (Tokenizer.T_EMPTY, 14, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 15, "Right-indent, right-aligned", [], rIndAlign), - (Tokenizer.T_EMPTY, 16, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 16, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 0, "Some regular text", [], Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 0, "Some left-aligned text", [], Tokenizer.A_LEFT), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 0, "Some right-aligned text", [], Tokenizer.A_RIGHT), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 0, "Some centered text", [], Tokenizer.A_CENTRE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 0, "Left-indented block", [], Tokenizer.A_IND_L), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 0, "Right-indented block", [], Tokenizer.A_IND_R), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 0, "Double-indented block", [], dblIndent), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 0, "Right-indent, right-aligned", [], rIndAlign), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] assert theToken.theMarkdown[-1] == ( "Some regular text\n\n" @@ -564,20 +564,20 @@ def testCoreToken_TextFormat(mockGUI): theToken._text = "Some plain text\non two lines\n\n\n" theToken.tokenizeText() assert theToken._tokens == [ - (Tokenizer.T_TEXT, 1, "Some plain text", [], Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 2, "on two lines", [], Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 3, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 0, "Some plain text", [], Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 0, "on two lines", [], Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] assert theToken.theMarkdown[-1] == "Some plain text\non two lines\n\n\n\n" theToken.setBodyText(False) theToken.tokenizeText() assert theToken._tokens == [ - (Tokenizer.T_EMPTY, 3, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] assert theToken.theMarkdown[-1] == "\n\n\n" theToken.setBodyText(True) @@ -587,7 +587,7 @@ def testCoreToken_TextFormat(mockGUI): theToken.tokenizeText() assert theToken._tokens == [ ( - Tokenizer.T_TEXT, 1, + Tokenizer.T_TEXT, 0, "Some **bolded text** on this lines", [ [5, 2, Tokenizer.FMT_B_B], @@ -595,7 +595,7 @@ def testCoreToken_TextFormat(mockGUI): ], Tokenizer.A_NONE ), - (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] assert theToken.theMarkdown[-1] == "Some **bolded text** on this lines\n\n" @@ -603,7 +603,7 @@ def testCoreToken_TextFormat(mockGUI): theToken.tokenizeText() assert theToken._tokens == [ ( - Tokenizer.T_TEXT, 1, + Tokenizer.T_TEXT, 0, "Some _italic text_ on this lines", [ [5, 1, Tokenizer.FMT_I_B], @@ -611,7 +611,7 @@ def testCoreToken_TextFormat(mockGUI): ], Tokenizer.A_NONE ), - (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] assert theToken.theMarkdown[-1] == "Some _italic text_ on this lines\n\n" @@ -619,7 +619,7 @@ def testCoreToken_TextFormat(mockGUI): theToken.tokenizeText() assert theToken._tokens == [ ( - Tokenizer.T_TEXT, 1, + Tokenizer.T_TEXT, 0, "Some **_bold italic text_** on this lines", [ [5, 2, Tokenizer.FMT_B_B], @@ -629,7 +629,7 @@ def testCoreToken_TextFormat(mockGUI): ], Tokenizer.A_NONE ), - (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] assert theToken.theMarkdown[-1] == "Some **_bold italic text_** on this lines\n\n" @@ -637,7 +637,7 @@ def testCoreToken_TextFormat(mockGUI): theToken.tokenizeText() assert theToken._tokens == [ ( - Tokenizer.T_TEXT, 1, + Tokenizer.T_TEXT, 0, "Some ~~strikethrough text~~ on this lines", [ [5, 2, Tokenizer.FMT_D_B], @@ -645,7 +645,7 @@ def testCoreToken_TextFormat(mockGUI): ], Tokenizer.A_NONE ), - (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] assert theToken.theMarkdown[-1] == "Some ~~strikethrough text~~ on this lines\n\n" @@ -653,7 +653,7 @@ def testCoreToken_TextFormat(mockGUI): theToken.tokenizeText() assert theToken._tokens == [ ( - Tokenizer.T_TEXT, 1, + Tokenizer.T_TEXT, 0, "Some **nested bold and _italic_ and ~~strikethrough~~ text** here", [ [5, 2, Tokenizer.FMT_B_B], @@ -665,7 +665,7 @@ def testCoreToken_TextFormat(mockGUI): ], Tokenizer.A_NONE ), - (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] assert theToken.theMarkdown[-1] == ( "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n\n" @@ -687,11 +687,11 @@ def testCoreToken_SpecialFormat(mockGUI): correctResp = [ (Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_CENTRE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_HEAD1, 2, "Title Two", None, Tokenizer.A_CENTRE | Tokenizer.A_PBB), + (Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), - (Tokenizer.T_HEAD1, 5, "Title Two", None, Tokenizer.A_CENTRE | Tokenizer.A_PBB), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), ] # Command wo/Space @@ -735,12 +735,12 @@ def testCoreToken_SpecialFormat(mockGUI): theToken.tokenizeText() assert theToken._tokens == [ (Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE), - (Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), - (Tokenizer.T_SKIP, 3, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 5, "Some text to go here ...", [], Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), ] # Multiple Empty Paragraphs @@ -755,12 +755,12 @@ def testCoreToken_SpecialFormat(mockGUI): theToken.tokenizeText() assert theToken._tokens == [ (Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE), - (Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), - (Tokenizer.T_SKIP, 3, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 5, "Some text to go here ...", [], Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), ] # Three Skips @@ -772,14 +772,14 @@ def testCoreToken_SpecialFormat(mockGUI): theToken.tokenizeText() assert theToken._tokens == [ (Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE), - (Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), - (Tokenizer.T_SKIP, 3, "", None, Tokenizer.A_NONE), - (Tokenizer.T_SKIP, 3, "", None, Tokenizer.A_NONE), - (Tokenizer.T_SKIP, 3, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 5, "Some text to go here ...", [], Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), ] # Malformed Command, Case 1 @@ -791,11 +791,11 @@ def testCoreToken_SpecialFormat(mockGUI): theToken.tokenizeText() assert theToken._tokens == [ (Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE), - (Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 5, "Some text to go here ...", [], Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), ] # Malformed Command, Case 2 @@ -807,11 +807,11 @@ def testCoreToken_SpecialFormat(mockGUI): theToken.tokenizeText() assert theToken._tokens == [ (Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE), - (Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 5, "Some text to go here ...", [], Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), ] # Malformed Command, Case 3 @@ -823,11 +823,11 @@ def testCoreToken_SpecialFormat(mockGUI): theToken.tokenizeText() assert theToken._tokens == [ (Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE), - (Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 5, "Some text to go here ...", [], Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), ] # Empty Paragraph and Page Break @@ -843,13 +843,13 @@ def testCoreToken_SpecialFormat(mockGUI): theToken.tokenizeText() assert theToken._tokens == [ (Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE), - (Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), - (Tokenizer.T_SKIP, 5, "", None, Tokenizer.A_PBB), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 7, "Some text to go here ...", [], 0), - (Tokenizer.T_EMPTY, 8, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 8, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_PBB), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], 0), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), ] # Multiple Skip @@ -862,15 +862,15 @@ def testCoreToken_SpecialFormat(mockGUI): theToken.tokenizeText() assert theToken._tokens == [ (Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE), - (Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), - (Tokenizer.T_SKIP, 5, "", None, Tokenizer.A_PBB), - (Tokenizer.T_SKIP, 5, "", None, Tokenizer.A_NONE), - (Tokenizer.T_SKIP, 5, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 7, "Some text to go here ...", [], 0), - (Tokenizer.T_EMPTY, 8, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 8, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_PBB), + (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], 0), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), ] # END Test testCoreToken_SpecialFormat From a7e2b512252b349693dac226ee1fea243fd59a43 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 4 Nov 2023 10:05:37 +0100 Subject: [PATCH 18/27] Add blockers for illegal drop events on invisible root and for root items (#1569) --- novelwriter/gui/projtree.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index e753a828..aad2fd4d 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -507,8 +507,11 @@ class GuiProjectTree(QTreeWidget): # Allow Move by Drag & Drop self.setDragEnabled(True) self.setDragDropMode(QAbstractItemView.InternalMove) + self.setDropIndicatorShown(True) # But don't allow drop on root level + # Due to a bug, this stops working somewhere between Qt 5.15.3 + # and 5.15.8, so this is also blocked in dropEvent trRoot = self.invisibleRootItem() trRoot.setFlags(trRoot.flags() ^ Qt.ItemIsDropEnabled) @@ -1380,8 +1383,16 @@ class GuiProjectTree(QTreeWidget): """ sHandle = self.getSelectedHandle() sItem = self._getTreeItem(sHandle) if sHandle else None - if sHandle is None or sItem is None: + if sHandle is None or sItem is None or sItem.parent() is None: logger.error("Invalid drag and drop event") + event.ignore() + return + + if not self.indexAt(event.pos()).isValid(): + # Needed due to a bug somewhere around Qt 5.15.8 that + # ignores the invisible root item flags + logger.error("Invalid drop location") + event.ignore() return logger.debug("Drag'n'drop of item '%s' accepted", sHandle) @@ -1728,7 +1739,9 @@ class GuiProjectTree(QTreeWidget): newItem.setData(self.C_DATA, self.D_WORDS, 0) if pHandle is None and nwItem.isRootType(): + # newItem.setFlags(newItem.flags() ^ Qt.ItemFlag.ItemIsDragEnabled) pItem = self.invisibleRootItem() + # pItem.setFlags(pItem.flags() ^ Qt.ItemFlag.ItemIsDropEnabled) elif pHandle and pHandle in self._treeMap: pItem = self._treeMap[pHandle] else: From c5baa8d359b9853c01b676bbf5d4681e48c51ebb Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 4 Nov 2023 10:14:49 +0100 Subject: [PATCH 19/27] Remove commented out code --- novelwriter/gui/projtree.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index aad2fd4d..9ac115b4 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -1739,9 +1739,7 @@ class GuiProjectTree(QTreeWidget): newItem.setData(self.C_DATA, self.D_WORDS, 0) if pHandle is None and nwItem.isRootType(): - # newItem.setFlags(newItem.flags() ^ Qt.ItemFlag.ItemIsDragEnabled) pItem = self.invisibleRootItem() - # pItem.setFlags(pItem.flags() ^ Qt.ItemFlag.ItemIsDropEnabled) elif pHandle and pHandle in self._treeMap: pItem = self._treeMap[pHandle] else: From da3747c5f5ef104a9b2897946e5f35743516086c Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 4 Nov 2023 16:45:16 +0100 Subject: [PATCH 20/27] Implement autoscroll in project tree, and disable library autoscroll feature (#1561) --- novelwriter/gui/projtree.py | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 9ac115b4..e1fd6533 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -31,8 +31,8 @@ from enum import Enum from time import time from typing import TYPE_CHECKING -from PyQt5.QtGui import QDropEvent, QMouseEvent, QPalette -from PyQt5.QtCore import QPoint, Qt, QSize, pyqtSignal, pyqtSlot +from PyQt5.QtGui import QDragMoveEvent, QDropEvent, QMouseEvent, QPalette +from PyQt5.QtCore import QPoint, QTimer, Qt, QSize, pyqtSignal, pyqtSlot from PyQt5.QtWidgets import ( QAbstractItemView, QDialog, QFrame, QHBoxLayout, QHeaderView, QLabel, QMenu, QShortcut, QSizePolicy, QToolButton, QTreeWidget, QTreeWidgetItem, @@ -508,6 +508,7 @@ class GuiProjectTree(QTreeWidget): self.setDragEnabled(True) self.setDragDropMode(QAbstractItemView.InternalMove) self.setDropIndicatorShown(True) + self.setAutoScroll(False) # But don't allow drop on root level # Due to a bug, this stops working somewhere between Qt 5.15.3 @@ -527,6 +528,13 @@ class GuiProjectTree(QTreeWidget): self.itemDoubleClicked.connect(self._treeDoubleClick) self.itemSelectionChanged.connect(self._treeSelectionChange) + # Autoscroll + self._scrollMargin = SHARED.theme.baseIconSize + self._scrollDirection = 0 + self._scrollTimer = QTimer() + self._scrollTimer.timeout.connect(self._doAutoScroll) + self._scrollTimer.setInterval(250) + # Set custom settings self.initSettings() @@ -1346,6 +1354,17 @@ class GuiProjectTree(QTreeWidget): return True + @pyqtSlot() + def _doAutoScroll(self) -> None: + """Scroll one item up or down based on direction value.""" + if self._scrollDirection == -1: + self.scrollToItem(self.itemAbove(self.itemAt(1, 1))) + elif self._scrollDirection == 1: + self.scrollToItem(self.itemBelow(self.itemAt(1, self.height() - 1))) + self._scrollDirection = 0 + self._scrollTimer.stop() + return + ## # Events ## @@ -1377,6 +1396,20 @@ class GuiProjectTree(QTreeWidget): return + def dragMoveEvent(self, event: QDragMoveEvent) -> None: + """Capture the drag move event to enable edge autoscroll.""" + y = event.pos().y() + if y < 16: + if not self._scrollTimer.isActive(): + self._scrollDirection = -1 + self._scrollTimer.start() + elif y > self.height() - 16: + if not self._scrollTimer.isActive(): + self._scrollDirection = 1 + self._scrollTimer.start() + super().dragMoveEvent(event) + return + def dropEvent(self, event: QDropEvent) -> None: """Overload the drop item event to ensure relevant data has been updated. From 05ddca043a4d7f437f63571a1131dc2f4504f243 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 4 Nov 2023 16:48:22 +0100 Subject: [PATCH 21/27] Add comment to explain why autoscroll was added --- novelwriter/gui/projtree.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index e1fd6533..89cd4e72 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -508,11 +508,14 @@ class GuiProjectTree(QTreeWidget): self.setDragEnabled(True) self.setDragDropMode(QAbstractItemView.InternalMove) self.setDropIndicatorShown(True) + + # Disable built-in autoscroll as it isn't working in some Qt + # releases (see #1561) and instead use our own implementation self.setAutoScroll(False) # But don't allow drop on root level # Due to a bug, this stops working somewhere between Qt 5.15.3 - # and 5.15.8, so this is also blocked in dropEvent + # and 5.15.8, so this is also blocked in dropEvent (see #1569) trRoot = self.invisibleRootItem() trRoot.setFlags(trRoot.flags() ^ Qt.ItemIsDropEnabled) From 6f8109609cb7bb5984c103ba2a6dd20c5193503b Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 4 Nov 2023 16:50:41 +0100 Subject: [PATCH 22/27] Use the correct margin settings --- novelwriter/gui/projtree.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 89cd4e72..31d2ca12 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -1402,11 +1402,11 @@ class GuiProjectTree(QTreeWidget): def dragMoveEvent(self, event: QDragMoveEvent) -> None: """Capture the drag move event to enable edge autoscroll.""" y = event.pos().y() - if y < 16: + if y < self._scrollMargin: if not self._scrollTimer.isActive(): self._scrollDirection = -1 self._scrollTimer.start() - elif y > self.height() - 16: + elif y > self.height() - self._scrollMargin: if not self._scrollTimer.isActive(): self._scrollDirection = 1 self._scrollTimer.start() From 8abcada86e3ef9fa5106d53bbb4311b7165acdf2 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 4 Nov 2023 17:24:43 +0100 Subject: [PATCH 23/27] Make sure all QMenu instances have a parent widget set (#1536) --- novelwriter/gui/doceditor.py | 2 +- novelwriter/gui/docviewer.py | 2 +- novelwriter/gui/noveltree.py | 2 +- novelwriter/gui/projtree.py | 8 ++++---- novelwriter/gui/sidebar.py | 2 +- novelwriter/tools/manussettings.py | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index c93db0a8..ec4bd7d8 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -1121,7 +1121,7 @@ class GuiDocEditor(QTextEdit): userSelection = userCursor.hasSelection() posCursor = self.cursorForPosition(thePos) - mnuContext = QMenu() + mnuContext = QMenu(self) # Follow, Cut, Copy and Paste # =========================== diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py index ca835ea6..18dfdbc5 100644 --- a/novelwriter/gui/docviewer.py +++ b/novelwriter/gui/docviewer.py @@ -381,7 +381,7 @@ class GuiDocViewer(QTextBrowser): userCursor = self.textCursor() userSelection = userCursor.hasSelection() - mnuContext = QMenu() + mnuContext = QMenu(self) # Cut, Copy and Paste # =================== diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index 4d804c40..0742ab54 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -228,7 +228,7 @@ class GuiNovelToolBar(QWidget): self.tbRefresh.clicked.connect(self._refreshNovelTree) # More Options Menu - self.mMore = QMenu() + self.mMore = QMenu(self) self.mLastCol = self.mMore.addMenu(self.tr("Last Column")) self.gLastCol = QActionGroup(self.mMore) diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 31d2ca12..929f37c6 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -248,7 +248,7 @@ class GuiProjectToolBar(QWidget): self.viewLabel.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) # Quick Links - self.mQuick = QMenu() + self.mQuick = QMenu(self) self.tbQuick = QToolButton(self) self.tbQuick.setToolTip("%s [Ctrl+L]" % self.tr("Quick Links")) @@ -269,7 +269,7 @@ class GuiProjectToolBar(QWidget): self.tbMoveD.clicked.connect(lambda: self.projTree.moveTreeItem(1)) # Add Item Menu - self.mAdd = QMenu() + self.mAdd = QMenu(self) self.aAddEmpty = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["document"])) self.aAddEmpty.triggered.connect( @@ -307,7 +307,7 @@ class GuiProjectToolBar(QWidget): self.tbAdd.setPopupMode(QToolButton.InstantPopup) # More Options Menu - self.mMore = QMenu() + self.mMore = QMenu(self) self.aExpand = self.mMore.addAction(self.tr("Expand All")) self.aExpand.triggered.connect(lambda: self.projTree.setExpandedFromHandle(None, True)) @@ -1210,7 +1210,7 @@ class GuiProjectTree(QTreeWidget): logger.debug("No item found") return False - ctxMenu = QMenu() + ctxMenu = QMenu(self) # Trash Folder # ============ diff --git a/novelwriter/gui/sidebar.py b/novelwriter/gui/sidebar.py index 266d735a..8854f583 100644 --- a/novelwriter/gui/sidebar.py +++ b/novelwriter/gui/sidebar.py @@ -95,7 +95,7 @@ class GuiSideBar(QToolBar): self.aStats.triggered.connect(lambda: self.mainGui.showWritingStatsDialog()) # Settings Menu - self.mSettings = QMenu() + self.mSettings = QMenu(self) self.mSettings.addAction(self.mainGui.mainMenu.aEditWordList) self.mSettings.addAction(self.mainGui.mainMenu.aProjectSettings) diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py index c4b211fb..cfa1b038 100644 --- a/novelwriter/tools/manussettings.py +++ b/novelwriter/tools/manussettings.py @@ -720,7 +720,7 @@ class _HeadingsTab(QWidget): self.formSyntax = _HeadingSyntaxHighlighter(self.editTextBox.document()) - self.menuInsert = QMenu() + self.menuInsert = QMenu(self) self.aInsTitle = self.menuInsert.addAction(self.tr("Title")) self.aInsChNum = self.menuInsert.addAction(self.tr("Chapter Number")) self.aInsChWord = self.menuInsert.addAction(self.tr("Chapter Number (Word)")) From dd61897d11a009d013a144cdfdd85b931e162e5a Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 4 Nov 2023 17:43:02 +0100 Subject: [PATCH 24/27] Force a rehighlight on theme switch for small documents (#1535) --- novelwriter/gui/doceditor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index ec4bd7d8..75b59e97 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -359,6 +359,8 @@ class GuiDocEditor(QTextEdit): self.clearEditor() else: self.redrawText() + if not self._bigDoc: + self.highLight.rehighlight() return True From ee5f340fe2723d7121ef8e00c8ac300196158c69 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 4 Nov 2023 17:46:25 +0100 Subject: [PATCH 25/27] Refresh spell checking when word list changes (#1559) --- novelwriter/guimain.py | 1 + 1 file changed, 1 insertion(+) diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 96806648..a966346b 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -980,6 +980,7 @@ class GuiMain(QMainWindow): if dlgWords.result() == QDialog.Accepted: logger.debug("Reloading word list") SHARED.updateSpellCheckLanguage(reload=True) + self.docEditor.spellCheckDocument() return True From b126b8cf0a5e20cadca689452330937273673ec8 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 4 Nov 2023 18:19:57 +0100 Subject: [PATCH 26/27] Bumb version and update release notes for v2.1.1 --- CHANGELOG.md | 57 +++++++++++++++++++++++ novelwriter/__init__.py | 6 +-- novelwriter/assets/text/release_notes.htm | 13 ++++++ sample/nwProject.nwx | 4 +- 4 files changed, 75 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bbbfd74..4ac00b49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,62 @@ # novelWriter Changelog +## Version 2.1.1 [2023-11-05] + +### Release Notes + +This is a patch release that fixes a layout issue and internationalisation issues with the new +Manuscript Build tool. It also fixes a number of issues related to bugs in the underlying Qt +framework that affects drag and drop functionality in the project tree. These issues were mostly +only affecting Debian Linux package releases. + +Other, minor issues related to updating the editor on colour theme change and project word list +changes have been fixed as well. See the full changelog for more details. + +### Detailed Changelog + +**Bugfixes** + +* Fix an issue with width of the last two columns on Selection page of the Build Settings dialog on + Windows. They were far too wide by default. Issue #1551. PR #1553. +* Fix an issue where a lot of string were not translated to the UI language in the new Manuscript + Build tool. Issue #1563. PR #1565. +* Fix an issue in the Document Viewer where it wouldn't scroll to a heading further down the page + when following a reference pointing to it. Issue #1566. PR #1568. +* Add back in checks for illegal drag and drop moves in the project tree. In 2.0, the logic here + was changed to set certain restrictions on the elements of the project tree itself, but there are + numerous bugs in the Qt framework related to drag and drop, so the checks are ignored on at least + Qt 5.15.8. In particular, it is possible to drop items on the root level, and it's possible to + move root items to other locations. Neither should be possible and will severely mess up the + project if done. Issue #1569. PR #1570. +* Add a custom autoscroll feature when dragging an item in the project tree to near the top or + bottom. This is actually a default feature of the tree widget in the Qt library, but this too is + broken in some versions of Qt 5.15.x. The default feature has been permanently disabled and + replaced by a custom written feature that behaves similarly. Issue #1561. PR #1571. +* Fix an issue where the editor document wasn't re-highlighted when the Syntax Theme for it was + changed. Issue #1535. PR #1573. +* Fix an issue where editing the Project Word List would not refresh the spell checking of the + editor. Issue #1559. PR #1573. + +**Usability** + +* Changed how the default UI language is selected. It used to default to the system locale, but + that is now changed to British English if the system local is not available in novelWriter. The + only real effects of this is that the dropdown box in Preferences now selects British English if + the system locale is not available rather than the first in the list (currently Deutch). The + second effect is that the language on buttons and other Qt components will match the rest of the + UI. Issue #1564. PR #1565. +* There is a bug in Qt on Wayland desktops where menus don't open in the correct location. + According to one Qt ticket, QTBUG-68636, this can be mitigated by ensuring all QMenu instances + have a parent set. This does not fix all issues, but it should help. The menus without a parent + set have now been updated. Issue #1536. PR #1572. + +**Documentation** + +* Fixed a number of spelling errors and typing mistakes in the documentation for 2.1. Contributed + by @nisemono-neko. PR #1567. + +---- + ## Version 2.1 [2023-10-17] ### Release Notes diff --git a/novelwriter/__init__.py b/novelwriter/__init__.py index 2b821e98..d62201e9 100644 --- a/novelwriter/__init__.py +++ b/novelwriter/__init__.py @@ -60,9 +60,9 @@ __license__ = "GPLv3" __author__ = "Veronica Berglyd Olsen" __maintainer__ = "Veronica Berglyd Olsen" __email__ = "code@vkbo.net" -__version__ = "2.1" -__hexversion__ = "0x020100f0" -__date__ = "2023-10-17" +__version__ = "2.1.1" +__hexversion__ = "0x020101f0" +__date__ = "2023-11-05" __status__ = "Stable" __domain__ = "novelwriter.io" diff --git a/novelwriter/assets/text/release_notes.htm b/novelwriter/assets/text/release_notes.htm index 521a5f6d..c74a1574 100644 --- a/novelwriter/assets/text/release_notes.htm +++ b/novelwriter/assets/text/release_notes.htm @@ -5,6 +5,8 @@

Release Notes for 2.1

Released on 17 October 2023

+

Scroll down for Patch Notes

+

The primary focus of this release has been a complete redesign of the Build Tool, that is, the tool that assembles your project into a manuscript document. The new tool, called the "Manuscript Build Tool" allows you to define multiple build definitions for your project. The build definitions @@ -30,5 +32,16 @@ a full list of changes, see the detailed changelogs.

See also the Releases page.

+

Patch Notes

+ +

Patch 2.2.1 – 5 November 2023

+ +

This is a patch release that fixes a layout issue and internationalisation issues with the new +Manuscript Build tool. It also fixes a number of issues related to bugs in the underlying Qt +framework that affects drag and drop functionality in the project tree. These issues were mostly +only affecting Debian Linux package releases.

+

Other, minor issues related to updating the editor on colour theme change and project word list +changes have been fixed as well. See the full changelog for more details.

+ diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index 36094677..59b3a2f6 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,6 +1,6 @@ - - + + Sample Project Sample Project Jane Smith From 786c1f7d548c55414995f7f2be5c6dbc7ef4cd6d Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 5 Nov 2023 18:37:09 +0100 Subject: [PATCH 27/27] Make AppIamge build use Python 3.11 --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 91d91a86..9abf2ff6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,7 @@ jobs: - name: Python Setup uses: actions/setup-python@v4 with: - python-version: 3 + python-version: "3.11" architecture: x64 - name: Install Packages (apt) @@ -48,7 +48,7 @@ jobs: - name: Python Setup uses: actions/setup-python@v4 with: - python-version: 3 + python-version: "3.11" architecture: x64 - name: Install Packages (pip)