Merge branch 'main' into dev

This commit is contained in:
Veronica Berglyd Olsen
2023-11-05 18:54:52 +01:00
29 changed files with 576 additions and 464 deletions
+2 -2
View File
@@ -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)
-11
View File
@@ -1,11 +0,0 @@
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
version: 2
sphinx:
configuration: docs/source/conf.py
formats:
- htmlzip
- pdf
python:
install:
- requirements: docs/source/requirements.txt
+57
View File
@@ -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
+2 -12
View File
@@ -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"
)]
+3 -3
View File
@@ -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 <https://doc.qt.io/qt-5.15/qpalette.html#ColorRole-enum>`_ 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,
+2 -2
View File
@@ -32,7 +32,7 @@ Glossary
for more details.
Reference
A references is one of a set of :term:`keywords<keyword>` that can be used to link to a
A reference is one of a set of :term:`keywords<keyword>` 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<tag>` and
:term:`references<reference>`.
+3 -3
View File
@@ -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.
+4 -4
View File
@@ -14,8 +14,8 @@ novelWriter is built as a cross-platform application using `Python 3 <https://ww
the programming language, and `Qt 5 <https://www.qt.io/>`_ 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.
+2 -2
View File
@@ -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.
+6 -6
View File
@@ -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
+1 -1
View File
@@ -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::
+7
View File
@@ -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
```
+13
View File
@@ -5,6 +5,8 @@
<h2>Release Notes for 2.1</h2>
<p><i>Released on 17 October 2023</i></p>
<p>Scroll down for <a href="#patch">Patch Notes</a></p>
<p>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.</p>
<p><i>See also the <a href="https://github.com/vkbo/novelWriter/releases">Releases</a> page.</i></p>
<a name="patch"></a><h2>Patch Notes</h2>
<h3>Patch 2.2.1 &ndash; 5 November 2023</h3>
<p>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.</p>
<p>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.</p>
</body>
</html>
+8 -6
View File
@@ -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"
@@ -425,7 +427,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
@@ -493,8 +495,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():
+2 -2
View File
@@ -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."""
+2 -2
View File
@@ -162,7 +162,7 @@ class ToHtml(Tokenizer):
pStyle = None
lines = []
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:
@@ -223,7 +223,7 @@ class ToHtml(Tokenizer):
hStyle = ""
if self._linkHeaders:
aNm = f"<a name='T{tLine:06d}'></a>"
aNm = f"<a name='T{nHead:04d}'></a>"
else:
aNm = ""
+23 -18
View File
@@ -382,7 +382,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_*
@@ -396,16 +396,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")
@@ -430,7 +429,7 @@ class Tokenizer(ABC):
elif sLine == "[VSPACE]":
self._tokens.append(
(self.T_SKIP, nLine, "", None, sAlign)
(self.T_SKIP, nHead, "", None, sAlign)
)
continue
@@ -438,11 +437,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
@@ -451,20 +450,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)
@@ -474,8 +473,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)
@@ -484,39 +484,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
@@ -524,7 +529,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)
@@ -581,7 +586,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)
@@ -600,7 +605,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")
+2
View File
@@ -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)
+3 -1
View File
@@ -339,6 +339,8 @@ class GuiDocEditor(QPlainTextEdit):
self.clearEditor()
else:
self.redrawText()
if not self._bigDoc:
self.highLight.rehighlight()
return
@@ -980,7 +982,7 @@ class GuiDocEditor(QPlainTextEdit):
uCursor = self.textCursor()
pCursor = self.cursorForPosition(pos)
ctxMenu = QMenu()
ctxMenu = QMenu(self)
# Follow
if self._followTag(cursor=pCursor, loadTag=False):
+1 -1
View File
@@ -381,7 +381,7 @@ class GuiDocViewer(QTextBrowser):
userCursor = self.textCursor()
userSelection = userCursor.hasSelection()
mnuContext = QMenu()
mnuContext = QMenu(self)
# Cut, Copy and Paste
# ===================
+1 -1
View File
@@ -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)
+54 -7
View File
@@ -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,
@@ -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))
@@ -507,8 +507,15 @@ class GuiProjectTree(QTreeWidget):
# Allow Move by Drag & Drop
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 (see #1569)
trRoot = self.invisibleRootItem()
trRoot.setFlags(trRoot.flags() ^ Qt.ItemIsDropEnabled)
@@ -524,6 +531,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()
@@ -1196,7 +1210,7 @@ class GuiProjectTree(QTreeWidget):
logger.debug("No item found")
return False
ctxMenu = QMenu()
ctxMenu = QMenu(self)
# Trash Folder
# ============
@@ -1343,6 +1357,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
##
@@ -1374,14 +1399,36 @@ 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 < self._scrollMargin:
if not self._scrollTimer.isActive():
self._scrollDirection = -1
self._scrollTimer.start()
elif y > self.height() - self._scrollMargin:
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.
"""
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)
+1
View File
@@ -991,6 +991,7 @@ class GuiMain(QMainWindow):
if dlgWords.result() == QDialog.Accepted:
logger.debug("Reloading word list")
SHARED.updateSpellCheckLanguage(reload=True)
self.docEditor.spellCheckDocument()
return True
+8 -6
View File
@@ -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()
@@ -718,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)"))
+1
View File
@@ -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("")
+2 -2
View File
@@ -1,6 +1,6 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.2-alpha1" hexVersion="0x020200a1" fileVersion="1.5" fileRevision="1" timeStamp="2023-09-07 19:14:50">
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="1531" autoCount="238" editTime="75619">
<novelWriterXML appVersion="2.2-alpha1" hexVersion="0x020200a1" fileVersion="1.5" fileRevision="1" timeStamp="2023-11-05 18:54:42">
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="1532" autoCount="238" editTime="75633">
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jane Smith</author>
+3 -3
View File
@@ -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
+261 -267
View File
@@ -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 == (
"<h1 class='title' style='text-align: center;'>Partition</h1>\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 == (
"<h1 style='page-break-before: always;'>Chapter Title</h1>\n"
)
# Header 3
theHtml._text = "### Scene Title\n"
theHtml.tokenizeText()
theHtml.doConvert()
assert theHtml.theResult == "<h2>Scene Title</h2>\n"
html._text = "### Scene Title\n"
html.tokenizeText()
html.doConvert()
assert html.theResult == "<h2>Scene Title</h2>\n"
# Header 4
theHtml._text = "#### Section Title\n"
theHtml.tokenizeText()
theHtml.doConvert()
assert theHtml.theResult == "<h3>Section Title</h3>\n"
html._text = "#### Section Title\n"
html.tokenizeText()
html.doConvert()
assert html.theResult == "<h3>Section Title</h3>\n"
# Title
theHtml._text = "#! Title\n"
theHtml.tokenizeText()
theHtml.doConvert()
assert theHtml.theResult == (
html._text = "#! Title\n"
html.tokenizeText()
html.doConvert()
assert html.theResult == (
"<h1 class='title' style='text-align: center;'>Title</h1>\n"
)
# Unnumbered
theHtml._text = "##! Prologue\n"
theHtml.tokenizeText()
theHtml.doConvert()
assert theHtml.theResult == "<h1 style='page-break-before: always;'>Prologue</h1>\n"
html._text = "##! Prologue\n"
html.tokenizeText()
html.doConvert()
assert html.theResult == "<h1 style='page-break-before: always;'>Prologue</h1>\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 == "<h1><a name='T000001'></a>Heading One</h1>\n"
html._text = "# Heading One\n"
html.tokenizeText()
html.doConvert()
assert html.theResult == "<h1><a name='T0001'></a>Heading One</h1>\n"
# Header 2
theHtml._text = "## Heading Two\n"
theHtml.tokenizeText()
theHtml.doConvert()
assert theHtml.theResult == "<h2><a name='T000001'></a>Heading Two</h2>\n"
html._text = "## Heading Two\n"
html.tokenizeText()
html.doConvert()
assert html.theResult == "<h2><a name='T0001'></a>Heading Two</h2>\n"
# Header 3
theHtml._text = "### Heading Three\n"
theHtml.tokenizeText()
theHtml.doConvert()
assert theHtml.theResult == "<h3><a name='T000001'></a>Heading Three</h3>\n"
html._text = "### Heading Three\n"
html.tokenizeText()
html.doConvert()
assert html.theResult == "<h3><a name='T0001'></a>Heading Three</h3>\n"
# Header 4
theHtml._text = "#### Heading Four\n"
theHtml.tokenizeText()
theHtml.doConvert()
assert theHtml.theResult == "<h4><a name='T000001'></a>Heading Four</h4>\n"
html._text = "#### Heading Four\n"
html.tokenizeText()
html.doConvert()
assert html.theResult == "<h4><a name='T0001'></a>Heading Four</h4>\n"
# Title
theHtml._text = "#! Heading One\n"
theHtml.tokenizeText()
theHtml.doConvert()
assert theHtml.theResult == (
"<h1 style='text-align: center;'><a name='T000001'></a>Heading One</h1>\n"
html._text = "#! Heading One\n"
html.tokenizeText()
html.doConvert()
assert html.theResult == (
"<h1 style='text-align: center;'><a name='T0001'></a>Heading One</h1>\n"
)
# Unnumbered
theHtml._text = "##! Heading Two\n"
theHtml.tokenizeText()
theHtml.doConvert()
assert theHtml.theResult == "<h2><a name='T000001'></a>Heading Two</h2>\n"
html._text = "##! Heading Two\n"
html.tokenizeText()
html.doConvert()
assert html.theResult == "<h2><a name='T0001'></a>Heading Two</h2>\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 == (
"<p>Some <strong>nested bold and <em>italic</em> and "
"<del>strikethrough</del> text</strong> here</p>\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 == (
"<p class='break'>Line one<br/>Line two<br/>Line three</p>\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 == (
"<p class='synopsis'><strong>Synopsis:</strong> The synopsis ...</p>\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 == (
"<p class='comment'><strong>Comment:</strong> A comment ...</p>\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 == (
"<p><span class='tags'>Characters:</span> "
"<a href='#tag_Bod'>Bod</a>, <a href='#tag_Jane'>Jane</a></p>\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 == (
"<h2>"
"<a name='T000001'></a>Chapter</h2>\n"
"<a name='T0001'></a>Chapter</h2>\n"
"<p style='margin-bottom: 0;'>"
"<span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a>"
"</p>\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 == (
"<p>Some <b>nested bold and <i>italic</i> and "
"<span style='text-decoration: line-through;'>strikethrough</span> "
"text</b> here</p>\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 == (
"<h1 class='title' style='text-align: center; page-break-before: always;'>"
"<a name='T000001'></a>A Title</h1>\n"
"<a name='T0001'></a>A Title</h1>\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 == (
"<h1 style='page-break-before: always;'>"
"<a name='T000001'></a>Prologue</h1>\n"
"<a name='T0001'></a>Prologue</h1>\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 == "<p class='sep' style='text-align: center;'>* * *</p>\n"
html.doConvert()
assert html.theResult == "<p class='sep' style='text-align: center;'>* * *</p>\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 == "<p class='skip'>&nbsp;</p>\n"
html.doConvert()
assert html.theResult == "<p class='skip'>&nbsp;</p>\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 == (
"<h1 class='title'>A Title</h1>\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 == (
"<h1 class='title' style='text-align: left;'>A Title</h1>\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 == (
"<h1 class='title' style='text-align: right;'>A Title</h1>\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 == (
"<h1 class='title' style='text-align: center;'>A Title</h1>\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 == (
"<h1 class='title' style='text-align: justify;'>A Title</h1>\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 == (
"<h1 class='title' "
"style='page-break-before: always; page-break-after: always;'>A Title</h1>\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 == (
"<p style='margin-left: 40px;'>Some text ...</p>\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 == (
"<p style='margin-right: 40px;'>Some text ...</p>\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 == (
"<p>Text with &gt; and &lt; with some <strong>bold text</strong> in it.</p>\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 == (
"<p>Text with some &lt;<strong>bold text</strong>&gt; in it.</p>\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 == (
"<p>Let's &gt; be &gt; <em>difficult <strong>shall</strong> &gt; we</em>?</p>\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 == (
"<p>Test &gt; text <em>&lt;<strong>bold</strong>&gt;</em> and more.</p>\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 == (
"<p class='comment'>"
"<strong>Comment:</strong> Test &gt; text _&lt;**bold**&gt;_ and more."
"</p>\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 == (
"<h1 style='page-break-before: always;'>Heading &lt;1&gt;</h1>\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 == (
"<p>Test text **<em>bold</em>** and more.</p>\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="&nbsp;")
html.replaceTabs(nSpaces=2, spaceChar="&nbsp;")
resText[6] = "<h3>A Section</h3>\n<p>&nbsp;&nbsp;More text in scene two.</p>\n"
# Check File
# ==========
theStyle = theHtml.getStyleSheet()
theStyle = html.getStyleSheet()
htmlDoc = (
"<!DOCTYPE html>\n"
"<html>\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 <brackets> & shortdash, 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 == (
"<p>Text with &lt;brackets&gt; &amp; shortdash, long—dash …</p>\n"
)
# Auto-Replace, replace Unicode
docText = "Text with <brackets> & shortdash, 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 == (
"<p>Text with &lt;brackets&gt; &amp; short&ndash;dash, long&mdash;dash &hellip;</p>\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 <brackets> &amp; short&ndash;dash, long&mdash;dash &hellip;\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") == (
"<p class='synopsis'><strong>Synopsis:</strong> synopsis text</p>\n"
)
assert theHtml._formatComments("comment text") == (
assert html._formatComments("comment text") == (
"<p class='comment'><strong>Comment:</strong> comment text</p>\n"
)
assert theHtml._formatKeywords("") == ""
assert theHtml._formatKeywords("tag: Jane") == (
assert html._formatKeywords("") == ""
assert html._formatKeywords("tag: Jane") == (
"<span class='tags'>Tag:</span> <a name='tag_Jane'>Jane</a>"
)
assert theHtml._formatKeywords("char: Bod, Jane") == (
assert html._formatKeywords("char: Bod, Jane") == (
"<span class='tags'>Characters:</span> "
"<a href='#tag_Bod'>Bod</a>, "
"<a href='#tag_Jane'>Jane</a>"
@@ -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") == (
"<p class='comment'><span class='synopsis'>Synopsis:</span> synopsis text</p>\n"
)
assert theHtml._formatComments("comment text") == (
assert html._formatComments("comment text") == (
"<p class='comment'>comment text</p>\n"
)
assert theHtml._formatKeywords("") == ""
assert theHtml._formatKeywords("tag: Jane") == (
assert html._formatKeywords("") == ""
assert html._formatKeywords("tag: Jane") == (
"<span class='tags'>Tag:</span> <a name='tag_Jane'>Jane</a>"
)
assert theHtml._formatKeywords("char: Bod, Jane") == (
assert html._formatKeywords("char: Bod, Jane") == (
"<span class='tags'>Characters:</span> "
"<a href='#char=Bod'>Bod</a>, "
"<a href='#char=Jane'>Jane</a>"
+102 -102
View File
@@ -434,8 +434,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"
@@ -447,14 +447,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"
@@ -466,8 +466,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"
@@ -481,10 +481,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"
@@ -513,23 +513,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"
@@ -556,20 +556,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)
@@ -579,7 +579,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],
@@ -587,7 +587,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"
@@ -595,7 +595,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],
@@ -603,7 +603,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"
@@ -611,7 +611,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],
@@ -621,7 +621,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"
@@ -629,7 +629,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],
@@ -637,7 +637,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"
@@ -645,7 +645,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],
@@ -657,7 +657,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"
@@ -679,11 +679,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
@@ -727,12 +727,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
@@ -747,12 +747,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
@@ -764,14 +764,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
@@ -783,11 +783,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
@@ -799,11 +799,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
@@ -815,11 +815,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
@@ -835,13 +835,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
@@ -854,15 +854,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