Compare commits

...

50 Commits
v2.5 ... v2.5.3

Author SHA1 Message Date
Veronica Berglyd Olsen
8307822e20 Release 2.5.3 (#2126) 2024-11-26 22:27:30 +01:00
Veronica Berglyd Olsen
f1548dc8ea Fix some issues in the changelog 2024-11-26 22:23:15 +01:00
Veronica Berglyd Olsen
25b33ab4d3 Bump version to 2.5.3 and update changelog 2024-11-26 22:21:30 +01:00
Veronica Berglyd Olsen
4af98dcab5 Add Russian translation 2024-11-26 22:13:55 +01:00
Veronica Berglyd Olsen
3cf916c845 Update translations 2024-11-26 22:10:43 +01:00
Veronica Berglyd Olsen
d944a70063 Fix secondary issues due to Qt tree widget bug (#2109) 2024-11-16 20:35:14 +01:00
Veronica Berglyd Olsen
c20f970405 Block drops on project tree viewport, and allow non-root items on root to be deleted (#2108) 2024-11-16 20:21:57 +01:00
Veronica Berglyd Olsen
bec082c0ae Fix HTML formatting issue (#2085) 2024-11-05 18:44:05 +01:00
Veronica Berglyd Olsen
83b244a8a3 Fix head tags order in HTML output (#2080) 2024-11-05 18:39:43 +01:00
Veronica Berglyd Olsen
bd82392d71 Add Python 3.13 and fix theme issue and broken link (#2059) 2024-10-21 17:12:09 +02:00
Veronica Berglyd Olsen
836a3d353a Update Enchant link (#2042) 2024-10-21 17:08:35 +02:00
Veronica Berglyd Olsen
a4d2a9ea6f Fix swapped colours in default light theme (#2057) 2024-10-21 17:05:19 +02:00
Veronica Berglyd Olsen
b0fc8d63a6 Add Python 3.13 support 2024-10-21 17:03:52 +02:00
Veronica Berglyd Olsen
eda1a1c4dd Add sponsor link to readme 2024-09-18 14:52:24 +02:00
Veronica Berglyd Olsen
238abdb567 Update to release certificate for windows build 2024-09-18 14:33:45 +02:00
Veronica Berglyd Olsen
c3f9ceb03a Add version numbers to all build jobs 2024-09-18 14:02:27 +02:00
Veronica Berglyd Olsen
b95e9fcda3 Update for windows runner 2024-09-18 13:39:17 +02:00
Veronica Berglyd Olsen
b3f8a2c18c Add version parameter to windows build 2024-09-18 13:11:08 +02:00
Veronica Berglyd Olsen
18bbf7d86f Switch to test signing for windows setup 2024-09-18 12:14:37 +02:00
Veronica Berglyd Olsen
7a63ac83d2 Release 2.5.2 (#2021) 2024-09-17 15:49:04 +02:00
Veronica Berglyd Olsen
8dc28ddbee Add signing request to windows build 2024-09-17 15:45:30 +02:00
Veronica Berglyd Olsen
15803cf0a3 Bump version and update changelog 2024-09-17 15:33:37 +02:00
Veronica Berglyd Olsen
5ac001781c Improve documentation (#2015) 2024-09-09 12:03:22 +02:00
Veronica Berglyd Olsen
66986b88f8 Improve the spell checking info a little 2024-09-01 21:41:26 +02:00
Veronica Berglyd Olsen
f4e12a63a5 Merge branch 'release' into improve_docs 2024-09-01 21:37:53 +02:00
Veronica Berglyd Olsen
095c005c3f Fix bugs in the document viewer (#2014) 2024-09-01 17:45:44 +02:00
Veronica Berglyd Olsen
e414b6458d Refresh tab stop distance after loading document (#1996) 2024-09-01 17:38:04 +02:00
Veronica Berglyd Olsen
ece73784c2 Only highlight dialogue in novel text (#2011 and #2013) 2024-09-01 17:32:37 +02:00
Veronica Berglyd Olsen
8dd9644c13 Fix issue with overlapping formatting (#2012) 2024-09-01 17:22:54 +02:00
Veronica Berglyd Olsen
556e8db104 Add some text about spell checking to editor docs 2024-08-16 09:33:38 +02:00
Veronica Berglyd Olsen
d2f422a441 Update Ubuntu releases (#1995) 2024-07-28 14:09:39 +02:00
Veronica Berglyd Olsen
e4daff8956 Drop Ubuntu 23.10 and add 24.10 2024-07-28 14:04:14 +02:00
Veronica Berglyd Olsen
2e1b17dcdf Release 2.5.1 (#1989) 2024-07-28 13:32:15 +02:00
Veronica Berglyd Olsen
2ef11f56e5 Update changelog 2024-07-28 13:26:34 +02:00
Veronica Berglyd Olsen
fdaeca78a7 Update translations 2024-07-28 13:21:58 +02:00
Veronica Berglyd Olsen
93e7343fe9 Update changelog with latest fix 2024-07-26 00:22:58 +02:00
Veronica Berglyd Olsen
5167608f7a Merge branch 'release' into release_2.5.1 2024-07-26 00:20:11 +02:00
Veronica Berglyd Olsen
d4a0881e77 Fix updating of importance labels (#1993) 2024-07-26 00:18:47 +02:00
Veronica Berglyd Olsen
7f42210f2f Add test coverage 2024-07-26 00:15:14 +02:00
Veronica Berglyd Olsen
33497bc92d Simplify how the project tree is populated 2024-07-25 20:47:14 +02:00
Veronica Berglyd Olsen
06aa2e6c07 Only refresh project tree values when status labels change 2024-07-25 20:39:32 +02:00
Veronica Berglyd Olsen
3bffaa01dc Update viewer panel when importance labels change (#1992) 2024-07-25 19:21:52 +02:00
Veronica Berglyd Olsen
66237c35ea Bump version to 2.5.1 and update changelog 2024-07-21 20:05:54 +02:00
Veronica Berglyd Olsen
5936e1a68b Improve test coverage 2024-07-21 19:45:40 +02:00
Veronica Berglyd Olsen
17b3b2b770 Navigate to heading when a tag is clicked for editing (#1983) 2024-07-21 19:45:28 +02:00
Veronica Berglyd Olsen
826e805b06 Make sure free form text boxes in Preferences are safe (#1986) 2024-07-21 19:20:51 +02:00
Veronica Berglyd Olsen
5419d76606 Fix test 2024-07-21 19:15:56 +02:00
Veronica Berglyd Olsen
0d84653f7d Process pad before and after settings in Preferences (#1985) 2024-07-21 19:06:45 +02:00
Veronica Berglyd Olsen
7140788b48 Lock project only after successful open (#1978) 2024-07-14 16:33:48 +02:00
Veronica Berglyd Olsen
a43bd5a50e Move project lock until all steps in project open are completed (#1977) 2024-07-14 16:28:09 +02:00
57 changed files with 9396 additions and 4077 deletions

View File

@@ -32,12 +32,15 @@ jobs:
path: novelwriter/assets
- name: Build AppImage
run: python pkgutils.py build-appimage --linux-tag $LINUX_TAG --python-version $PYTHON_VERSION
id: build
run: |
echo "BUILD_VERSION=$(python pkgutils.py version)" >> $GITHUB_OUTPUT
python pkgutils.py build-appimage --linux-tag $LINUX_TAG --python-version $PYTHON_VERSION
- name: Upload Artifacts
uses: actions/upload-artifact@v4
with:
name: Linux-AppImage
path: dist_appimage
name: novelWriter-${{ steps.build.outputs.BUILD_VERSION }}-Linux-AppImage
path: dist_appimage/*.AppImage*
if-no-files-found: error
retention-days: 14

View File

@@ -25,13 +25,16 @@ jobs:
path: novelwriter/assets
- name: Build App Bundle
run: ./setup/macos/build.sh $PYTHON_VERSION $PACKAGE_ARCH $MINICONDA_ARCH
id: build
run: |
echo "BUILD_VERSION=$(python pkgutils.py version)" >> $GITHUB_OUTPUT
./setup/macos/build.sh $PYTHON_VERSION $PACKAGE_ARCH $MINICONDA_ARCH
- name: Upload DMG
uses: actions/upload-artifact@v4
with:
name: MacOS-AMD64-DMG
path: dist_macos
name: novelWriter-${{ steps.build.outputs.BUILD_VERSION }}-MacOS-AMD64-DMG
path: dist_macos/*.dmg*
if-no-files-found: error
retention-days: 14
@@ -53,12 +56,15 @@ jobs:
path: novelwriter/assets
- name: Build App Bundle
run: ./setup/macos/build.sh $PYTHON_VERSION $PACKAGE_ARCH $MINICONDA_ARCH
id: build
run: |
echo "BUILD_VERSION=$(python pkgutils.py version)" >> $GITHUB_OUTPUT
./setup/macos/build.sh $PYTHON_VERSION $PACKAGE_ARCH $MINICONDA_ARCH
- name: Upload DMG
uses: actions/upload-artifact@v4
with:
name: MacOS-M1-DMG
path: dist_macos
name: novelWriter-${{ steps.build.outputs.BUILD_VERSION }}-MacOS-M1-DMG
path: dist_macos/*.dmg*
if-no-files-found: error
retention-days: 14

View File

@@ -26,12 +26,37 @@ jobs:
path: novelwriter/assets
- name: Build Setup Installer
run: python pkgutils.py build-win-exe
id: build
run: |
echo "BUILD_VERSION=$(python pkgutils.py version)" >> $Env:GITHUB_OUTPUT
python pkgutils.py build-win-exe
- name: Upload Artifacts
- name: Upload Unsigned Artifacts
id: upload-unsigned-artifact
uses: actions/upload-artifact@v4
with:
name: Win-Setup
name: novelWriter-${{ steps.build.outputs.BUILD_VERSION }}-Win-Setup
path: dist/*.exe
if-no-files-found: error
retention-days: 14
- name: Submit Signing Request
uses: signpath/github-action-submit-signing-request@v1
with:
api-token: "${{ secrets.SIGNPATH_API_TOKEN }}"
organization-id: "0471e52c-66fa-4e9a-bfb9-36167095ca3f"
project-slug: "novelWriter"
signing-policy-slug: "release-signing "
github-artifact-id: "${{ steps.upload-unsigned-artifact.outputs.artifact-id }}"
wait-for-completion: true
output-artifact-directory: "dist/"
parameters: |
version: "${{ steps.build.outputs.BUILD_VERSION }}"
- name: Upload Signed Artifacts
uses: actions/upload-artifact@v4
with:
name: novelWriter-${{ steps.build.outputs.BUILD_VERSION }}-Win-Setup-Signed
path: dist/*.exe
if-no-files-found: error
retention-days: 14

View File

@@ -14,7 +14,7 @@ jobs:
testLinux:
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
fail-fast: false
runs-on: ubuntu-latest
steps:

View File

@@ -1,5 +1,102 @@
# novelWriter Changelog
## Version 2.5.3 [2024-11-26]
### Release Notes
This is a patch release that fixes a few minor bugs in the user interface, and with the HTML
manuscript format. This patch also adds a Russian translation, and updated the German and
Portuguese translations.
### Detailed Changelog
**Bugfixes**
* Fixed the red/green LEDs on the statusbar for the Default Light theme. The colours were swapped.
Issue #2057. PR #2059.
* Moved the styles tag into the head tag for HTML manuscript output, which is the correct placement
for them. Issue #2080. PR #2085.
* Fixed a bug in the project tree where it was possible to drag content to root level on some Qt
versions where the disallow flag is not obeyed. The illegal action is now additionally blocked by
code. Issue #2108. PR #2109.
**Documentation**
* Updated a dead link to the spell check library with the new one. Issue #2042. PR #2059.
**Internationalisation**
* Russian translation added by Konstantin Tan (@k1kimosha). PR #2126
* Updated German and Portuguese translations. PR #2126
----
## Version 2.5.2 [2024-09-17]
### Release Notes
This is a patch release that fixes a series of issues mostly affecting dialogue highlighting in the
document viewer. The way the text is formatted in the viewer changed a lot in 2.5, and there were a
few issues with the new implementation. The tab stop distance setting was also ignored, but has now
been fixed as well.
### Detailed Changelog
**Bugfixes**
* Fixed an issue where the tab stop distance setting was being ignored in the document viewer. It
must be set again every time a document is loaded. Issue #1996. PR #2014.
* Fixed an issue with overlapping formatting markers when processing text into new formats. This
issues was introduced when dialogue highlighting was added to the document viewer and manuscript
build. It arises because start and end of dialogue can occur on the same character as other
formatting, and then the order these markers are processed matters. This was not properly handled
in the code. Issue #2012. PR #2014.
* Dialogue highlighting is now only applied to plain text paragraphs, not comments, headings, and
other places where there should be no dialogue. Issue #2013. PR #2014.
* Note documents no longer get dialogue highlighting. Issue #2011. PR #2014.
**Documentation**
* Added a section about spell checking in the documentation. PR #2015.
----
## Version 2.5.1 [2024-07-28]
### Release Notes
This is a patch release that fixes an issue with the lock file being left in the project when the
project open is cancelled, a few issues with custom input boxes in Preferences, and a usability
issue when selecting to edit a tag or reference from the Tags and References panel as well as fixed
an issue with the Importance labels not being updated in the tabs for tags.
### Detailed Changelog
**Bugfixes**
* Fixes an issue where the project lock file was created before certain checks where the user could
cancel the project open process, leaving a lock file in the project folder behind. Issue #1977.
PR #1978.
* Fixes an issue where the user could add spaces to the pad before and after Preference settings,
which would pad every space entered into the text. The Preferences settings no longer accept any
white spaces in these boxes (they are removed on save) and also strips duplicate character
entries. Issue #1985. PR #1986.
* The narrator break and dialog symbol Preference text boxes will now be cleared if the settings is
a white space. PR #1986.
* Fixed an issue where Importance labels were not updated in the viewer panel's tags tabs when they
where changed in Project Settings. Issue #1992. PR #1993.
**Usability**
* When selecting to edit a tag or reference from the panel below the Document Viewer, the editor
now scrolls to the relevant heading as it does in the viewer. Issue #1983. PR #1987.
**Internationalisation**
* Latin American Spanish translation updated by Tommy Marplatt (@tmarplatt). PR #1989.
----
## Version 2.5 [2024-07-10]
### Release Notes

View File

@@ -30,6 +30,7 @@ translators for the languages currently available:
* **Norwegian:** Veronica Berglyd Olsen (vkbo)
* **Polish:** Anna Maria Polak (Nauthiz)
* **Portuguese:** Bruno Meneguello (bkmeneguello)
* **Russian:** Konstantin Tan (k1kimosha)
* **Simplified Chinese:** Qianzhi Long (longqzh)
Additional larger translation contributions:

View File

@@ -29,6 +29,13 @@ documentation.
* PyPi Project: [pypi.org/project/novelWriter](https://pypi.org/project/novelWriter)
* Social Media: [fosstodon.org/@novelwriter](https://fosstodon.org/@novelwriter)
## Sponsors
<img align="left" style="height: 1.5em; margin: 0 0.5em 0 0;" src="https://raw.githubusercontent.com/vkbo/novelWriter/main/setup/signpath_logo.png">
Free code signing on Windows provided by [SignPath.io](https://about.signpath.io/),
certificate by [SignPath Foundation](https://signpath.org/).
## Implementation
novelWriter is written with Python 3 (3.9+) using Qt5 and PyQt5 (5.15 only), and is released on

View File

@@ -4,7 +4,7 @@
Customisations
**************
.. _Enchant: https://abiword.github.io/enchant
.. _Enchant: https://rrthomas.github.io/enchant/
.. _Free Desktop: https://cgit.freedesktop.org/libreoffice/dictionaries/tree/
There are a few ways you can customise novelWriter yourself. Currently, you can add new GUI themes,

View File

@@ -40,6 +40,24 @@ Any :term:`references<reference>` in the editor can be opened in the viewer by m
the label and pressing :kbd:`Ctrl+Return`. You can also control-click them with your mouse.
Spell Checking
--------------
A third party library called Enchant is used for spell checking in the editor. The controls for
spell checking can be found in the **Tools** menu. You can also set spell checking language in
**Project Settings**.
This spell checking library comes with support for custom words that you can add by selecting
"Add Word to Dictionary" from the context menu when a word is highlighted by the spell checker as
misspelled. The custom words are managed on a per-project basis, and can the list of words can be
edited from the **Project Word List** tool available from the **Tools** menu.
.. note::
Generally, spell checking dictionaries are collected from your operating system, but on Windows
they are not. See :ref:`a_custom_dict` for how to add spell checking dictionaries on Windows.
Editor Auto-Completer
---------------------

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

4991
i18n/nw_ru_RU.ts Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -47,9 +47,9 @@ __license__ = "GPLv3"
__author__ = "Veronica Berglyd Olsen"
__maintainer__ = "Veronica Berglyd Olsen"
__email__ = "code@vkbo.net"
__version__ = "2.5"
__hexversion__ = "0x020500f0"
__date__ = "2024-07-10"
__version__ = "2.5.3"
__hexversion__ = "0x020503f0"
__date__ = "2024-11-26"
__status__ = "Stable"
__domain__ = "novelwriter.io"

View File

@@ -5,9 +5,9 @@
"Notes": "Notizen",
"Tag": "Schlagwort",
"Point of View": "Perspektive",
"Focus": "Mittelpunkt",
"Focus": "Fokus",
"Characters": "Charaktere",
"Plot": "Handlungen",
"Plot": "Handlung",
"Timeline": "Zeitleiste",
"Locations": "Schauplätze",
"Objects": "Objekte",

View File

@@ -1,7 +1,18 @@
{
"Synopsis": "Сводка",
"Short Description": "Краткое описание",
"Comment": "Комментарий",
"Notes": "Заметки",
"Tag": "Тэг",
"Point of View": "Точка зрения",
"Focus": "Фокус",
"Characters": "Персонажей",
"Plot": "Сюжет",
"Timeline": "Хронология",
"Locations": "Локации",
"Objects": "Объекты",
"Entities": "Сущности",
"Custom": "Другое",
"0": "Ноль",
"1": "Один",
"2": "Два",

View File

@@ -35,6 +35,7 @@ translators for the languages currently available:</p>
<li><b>Norwegian:</b> Veronica Berglyd Olsen (vkbo)</li>
<li><b>Polish:</b> Anna Maria Polak (Nauthiz)</li>
<li><b>Portuguese:</b> Bruno Meneguello (bkmeneguello)</li>
<li><b>Russian:</b> Konstantin Tan (k1kimosha)</li>
<li><b>Simplified Chinese:</b> Qianzhi Long (longqzh)</li>
</ul>

View File

@@ -29,5 +29,5 @@ helptext = 92, 92, 92
fadedtext = 108, 108, 108
errortext = 255, 92, 92
statusnone = 120, 120, 120
statussaved = 200, 15, 39
statusunsaved = 2, 133, 37
statussaved = 2, 133, 37
statusunsaved = 200, 15, 39

View File

@@ -279,6 +279,16 @@ def simplified(text: str) -> str:
return " ".join(str(text).strip().split())
def compact(text: str) -> str:
"""Compact a string by removing spaces."""
return "".join(str(text).split())
def uniqueCompact(text: str) -> str:
"""Return a unique, compact and sorted string."""
return "".join(sorted(set(compact(text))))
def elide(text: str, length: int) -> str:
"""Elide a piece of text to a maximum length."""
if len(text) > (cut := max(4, length)):

View File

@@ -356,6 +356,7 @@ class NWProject:
self.setProjectChanged(False)
self._valid = True
self._state = NWProjectState.READY
self._storage.lockSession() # Lock only after a successful open. See issue #1977.
SHARED.newStatusMessage(self.tr("Opened Project: {0}").format(self._data.name))

View File

@@ -121,7 +121,7 @@ class NWStatus:
return key
def update(self, update: list[tuple[str | None, StatusEntry]]) -> None:
"""Update the list of statuses, and from removed list."""
"""Update the list of statuses."""
self._store.clear()
for key, entry in update:
self._store[self._checkKey(key)] = entry
@@ -130,6 +130,9 @@ class NWStatus:
if self._default not in self._store:
self._default = next(iter(self._store)) if self._store else None
# Emit the change signal
SHARED.projectSingalProxy({"event": "statusLabels", "kind": self._prefix})
return
def check(self, value: str) -> str:

View File

@@ -235,7 +235,6 @@ class NWStorage:
if child.is_dir() and child.name.startswith("data_"):
legacy.legacyDataFolder(basePath, child)
self._writeLockFile()
self._ready = True
return NWStorageOpen.READY
@@ -249,6 +248,12 @@ class NWStorage:
return True
return True
def lockSession(self) -> None:
"""Lock the session when the project is successfully opened."""
if self._ready:
self._writeLockFile()
return
def closeSession(self) -> None:
"""Run tasks related to closing the session."""
self._clearLockFile()

View File

@@ -299,10 +299,10 @@ class ToHtml(Tokenizer):
"<head>\n"
"<meta charset='utf-8'>\n"
"<title>{title:s}</title>\n"
"</head>\n"
"<style>\n"
"{style:s}\n"
"</style>\n"
"</head>\n"
"<body>\n"
"<article>\n"
"{body:s}\n"

View File

@@ -832,7 +832,7 @@ class Tokenizer(ABC):
sAlign |= self.A_IND_R
# Process formats
tLine, tFmt = self._extractFormats(aLine)
tLine, tFmt = self._extractFormats(aLine, hDialog=self._isNovel)
tokens.append((
self.T_TEXT, nHead, tLine, tFmt, sAlign
))
@@ -1098,8 +1098,13 @@ class Tokenizer(ABC):
# Internal Functions
##
def _extractFormats(self, text: str, skip: int = 0) -> tuple[str, T_Formats]:
"""Extract format markers from a text paragraph."""
def _extractFormats(
self, text: str, skip: int = 0, hDialog: bool = False
) -> tuple[str, T_Formats]:
"""Extract format markers from a text paragraph. In order to
also process dialogue highlighting, the hDialog flag must be set
to True. See issues #2011 and #2013.
"""
temp: list[tuple[int, int, int, str]] = []
# Match Markdown
@@ -1137,7 +1142,7 @@ class Tokenizer(ABC):
))
# Match Dialogue
if self._rxDialogue:
if self._rxDialogue and hDialog:
for regEx, fmtB, fmtE in self._rxDialogue:
rxItt = regEx.globalMatch(text, 0)
while rxItt.hasNext():
@@ -1150,8 +1155,9 @@ class Tokenizer(ABC):
formats = []
for pos, n, fmt, key in reversed(sorted(temp, key=lambda x: x[0])):
if fmt > 0:
result = result[:pos] + result[pos+n:]
formats = [(p-n, f, k) for p, f, k in formats]
if n > 0:
result = result[:pos] + result[pos+n:]
formats = [(p-n if p > pos else p, f, k) for p, f, k in formats]
formats.insert(0, (pos, fmt, key))
return result, formats

View File

@@ -34,7 +34,7 @@ from PyQt5.QtWidgets import (
)
from novelwriter import CONFIG, SHARED
from novelwriter.common import describeFont
from novelwriter.common import describeFont, uniqueCompact
from novelwriter.constants import nwUnicode
from novelwriter.dialogs.quotes import GuiQuoteSelect
from novelwriter.extensions.configlayout import NColourLabel, NScrollableForm
@@ -952,8 +952,8 @@ class GuiPreferences(NDialog):
# Text Highlighting
dialogueStyle = self.dialogStyle.currentData()
allowOpenDial = self.allowOpenDial.isChecked()
narratorBreak = self.narratorBreak.text()
dialogueLine = self.dialogLine.text()
narratorBreak = self.narratorBreak.text().strip()
dialogueLine = self.dialogLine.text().strip()
altDialogOpen = self.altDialogOpen.text()
altDialogClose = self.altDialogClose.text()
highlightEmph = self.highlightEmph.isChecked()
@@ -983,8 +983,8 @@ class GuiPreferences(NDialog):
CONFIG.doReplaceDQuote = self.doReplaceDQuote.isChecked()
CONFIG.doReplaceDash = self.doReplaceDash.isChecked()
CONFIG.doReplaceDots = self.doReplaceDots.isChecked()
CONFIG.fmtPadBefore = self.fmtPadBefore.text().strip()
CONFIG.fmtPadAfter = self.fmtPadAfter.text().strip()
CONFIG.fmtPadBefore = uniqueCompact(self.fmtPadBefore.text())
CONFIG.fmtPadAfter = uniqueCompact(self.fmtPadAfter.text())
CONFIG.fmtPadThin = self.fmtPadThin.isChecked()
# Quotation Style

View File

@@ -58,7 +58,7 @@ class GuiProjectSettings(NDialog):
PAGE_IMPORT = 2
PAGE_REPLACE = 3
newProjectSettingsReady = pyqtSignal(bool)
newProjectSettingsReady = pyqtSignal()
def __init__(self, parent: QWidget, gotoPage: int = PAGE_SETTINGS) -> None:
super().__init__(parent=parent)
@@ -175,7 +175,7 @@ class GuiProjectSettings(NDialog):
projAuthor = self.settingsPage.projAuthor.text()
projLang = self.settingsPage.projLang.currentData()
spellLang = self.settingsPage.spellLang.currentData()
doBackup = not self.settingsPage.doBackup.isChecked()
doBackup = not self.settingsPage.noBackup.isChecked()
project.data.setName(projName)
project.data.setAuthor(projAuthor)
@@ -183,23 +183,19 @@ class GuiProjectSettings(NDialog):
project.data.setSpellLang(spellLang)
project.setProjectLang(projLang)
rebuildTrees = False
if self.statusPage.changed:
logger.debug("Updating status labels")
project.data.itemStatus.update(self.statusPage.getNewList())
rebuildTrees = True
if self.importPage.changed:
logger.debug("Updating importance labels")
project.data.itemImport.update(self.importPage.getNewList())
rebuildTrees = True
if self.replacePage.changed:
logger.debug("Updating auto-replace settings")
project.data.setAutoReplace(self.replacePage.getNewList())
self.newProjectSettingsReady.emit(rebuildTrees)
self.newProjectSettingsReady.emit()
QApplication.processEvents()
self.close()
@@ -289,10 +285,10 @@ class _SettingsPage(NScrollableForm):
self.spellLang.setCurrentIndex(idx)
# Backup on Close
self.doBackup = NSwitch(self)
self.doBackup.setChecked(not data.doBackup)
self.noBackup = NSwitch(self)
self.noBackup.setChecked(not data.doBackup)
self.addRow(
self.tr("Disable backup on close"), self.doBackup,
self.tr("Disable backup on close"), self.noBackup,
self.tr("Overrides main preferences.")
)

View File

@@ -229,15 +229,13 @@ class GuiDocViewer(QTextBrowser):
QApplication.restoreOverrideCursor()
return False
# Refresh the tab stops
self.setTabStopDistance(CONFIG.getTabWidth())
# Must be before setHtml
# Must be before setDocument
if updateHistory:
self.docHistory.append(tHandle)
self.setDocumentTitle(tHandle)
self.setDocument(qDoc.document)
self.setTabStopDistance(CONFIG.getTabWidth())
if self._docHandle == tHandle:
# This is a refresh, so we set the scrollbar back to where it was

View File

@@ -189,6 +189,13 @@ class GuiDocViewerPanel(QWidget):
self._updateTabVisibility()
return
@pyqtSlot(str)
def updateStatusLabels(self, kind: str) -> None:
"""Update the importance labels."""
if kind == "i":
self._loadAllTags()
return
##
# Private Slots
##

View File

@@ -166,6 +166,7 @@ class GuiProjectView(QWidget):
def openProjectTasks(self) -> None:
"""Run open project tasks."""
self.populateTree()
self.projBar.buildQuickLinksMenu()
self.projBar.setEnabled(True)
return
@@ -213,7 +214,8 @@ class GuiProjectView(QWidget):
@pyqtSlot(str)
def updateItemValues(self, tHandle: str) -> None:
"""Update tree item."""
self.projTree.setTreeItemValues(tHandle)
if nwItem := SHARED.project.tree[tHandle]:
self.projTree.setTreeItemValues(nwItem)
return
@pyqtSlot(str)
@@ -242,6 +244,12 @@ class GuiProjectView(QWidget):
self.projTree.createNewNote(tag, itemClass)
return
@pyqtSlot(str)
def refreshUserLabels(self, kind: str) -> None:
"""Refresh status or importance labels."""
self.projTree.refreshUserLabels(kind)
return
class GuiProjectToolBar(QWidget):
@@ -792,11 +800,11 @@ class GuiProjectTree(QTreeWidget):
def renameTreeItem(self, tHandle: str, name: str = "") -> None:
"""Open a dialog to edit the label of an item."""
if tItem := SHARED.project.tree[tHandle]:
newLabel, dlgOk = GuiEditLabel.getLabel(self, text=name or tItem.itemName)
if nwItem := SHARED.project.tree[tHandle]:
newLabel, dlgOk = GuiEditLabel.getLabel(self, text=name or nwItem.itemName)
if dlgOk:
tItem.setName(newLabel)
self.setTreeItemValues(tHandle)
nwItem.setName(newLabel)
self.setTreeItemValues(nwItem)
self._alertTreeChange(tHandle, flush=False)
return
@@ -992,7 +1000,9 @@ class GuiProjectTree(QTreeWidget):
self.propagateCount(tHandle, 0)
trItemP = trItemS.parent()
# If a non-root item ends up on root due to a bug, allow it
# to still be deleted from invisible root (see #2108)
trItemP = trItemS.parent() or self.invisibleRootItem()
tIndex = trItemP.indexOfChild(trItemS)
trItemP.takeChild(tIndex)
@@ -1010,44 +1020,52 @@ class GuiProjectTree(QTreeWidget):
return True
def setTreeItemValues(self, tHandle: str) -> None:
"""Set the name and flag values for a tree item from a handle in
the project tree. Does not trigger a tree change as the data is
already coming from the project tree.
def refreshUserLabels(self, kind: str) -> None:
"""Refresh status or importance labels."""
if kind == "s":
for nwItem in SHARED.project.tree:
if nwItem.isNovelLike():
self.setTreeItemValues(nwItem)
elif kind == "i":
for nwItem in SHARED.project.tree:
if not nwItem.isNovelLike():
self.setTreeItemValues(nwItem)
return
def setTreeItemValues(self, nwItem: NWItem | None) -> None:
"""Set the name and flag values for a tree item in the project
tree. Does not trigger a tree change as the data is already
coming from project data.
"""
trItem = self._getTreeItem(tHandle)
nwItem = SHARED.project.tree[tHandle]
if trItem is None or nwItem is None:
return
if isinstance(nwItem, NWItem) and (trItem := self._getTreeItem(nwItem.itemHandle)):
itemStatus, statusIcon = nwItem.getImportStatus()
hLevel = nwItem.mainHeading
itemIcon = SHARED.theme.getItemIcon(
nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel
)
itemStatus, statusIcon = nwItem.getImportStatus()
hLevel = nwItem.mainHeading
itemIcon = SHARED.theme.getItemIcon(
nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel
)
trItem.setIcon(self.C_NAME, itemIcon)
trItem.setText(self.C_NAME, nwItem.itemName)
trItem.setIcon(self.C_STATUS, statusIcon)
trItem.setToolTip(self.C_STATUS, itemStatus)
trItem.setIcon(self.C_NAME, itemIcon)
trItem.setText(self.C_NAME, nwItem.itemName)
trItem.setIcon(self.C_STATUS, statusIcon)
trItem.setToolTip(self.C_STATUS, itemStatus)
if nwItem.isFileType():
iconName = "checked" if nwItem.isActive else "unchecked"
toolTip = self.trActive if nwItem.isActive else self.trInactive
trItem.setToolTip(self.C_ACTIVE, toolTip)
else:
iconName = "noncheckable"
if nwItem.isFileType():
iconName = "checked" if nwItem.isActive else "unchecked"
toolTip = self.trActive if nwItem.isActive else self.trInactive
trItem.setToolTip(self.C_ACTIVE, toolTip)
else:
iconName = "noncheckable"
trItem.setIcon(self.C_ACTIVE, SHARED.theme.getIcon(iconName))
trItem.setIcon(self.C_ACTIVE, SHARED.theme.getIcon(iconName))
if CONFIG.emphLabels and nwItem.isDocumentLayout():
trFont = trItem.font(self.C_NAME)
trFont.setBold(hLevel == "H1" or hLevel == "H2")
trFont.setUnderline(hLevel == "H1")
trItem.setFont(self.C_NAME, trFont)
if CONFIG.emphLabels and nwItem.isDocumentLayout():
trFont = trItem.font(self.C_NAME)
trFont.setBold(hLevel == "H1" or hLevel == "H2")
trFont.setUnderline(hLevel == "H1")
trItem.setFont(self.C_NAME, trFont)
# Emit Refresh Signal
self.itemRefreshed.emit(tHandle, nwItem, itemIcon)
# Emit Refresh Signal
self.itemRefreshed.emit(nwItem.itemHandle, nwItem, itemIcon)
return
@@ -1299,9 +1317,11 @@ class GuiProjectTree(QTreeWidget):
action is allowed, and update relevant data.
"""
tItem = self.itemAt(event.pos())
dropOn = self.dropIndicatorPosition() == QAbstractItemView.DropIndicatorPosition.OnItem
onItem = self.dropIndicatorPosition() == QAbstractItemView.DropIndicatorPosition.OnItem
onView = self.dropIndicatorPosition() == QAbstractItemView.DropIndicatorPosition.OnViewport
# Make sure nothing can be dropped on invisible root (see #1569)
if not tItem or tItem.parent() is None and not dropOn:
# Make sure nothing can be dropped on the viewport (see #2108)
if tItem is None or (tItem.parent() is None and not onItem) or onView:
logger.error("Invalid drop location")
event.ignore()
return
@@ -1353,7 +1373,8 @@ class GuiProjectTree(QTreeWidget):
SHARED.project.index.deleteHandle(mHandle)
else:
SHARED.project.index.reIndexHandle(mHandle)
self.setTreeItemValues(mHandle)
if mItem := SHARED.project.tree[mHandle]:
self.setTreeItemValues(mItem)
# Update word count
self.propagateCount(tHandle, nwItemS.wordCount, countChildren=True)
@@ -1594,7 +1615,7 @@ class GuiProjectTree(QTreeWidget):
self._treeMap[tHandle] = newItem
self.propagateCount(tHandle, nwItem.wordCount, countChildren=True)
self.setTreeItemValues(tHandle)
self.setTreeItemValues(nwItem)
newItem.setExpanded(nwItem.isExpanded)
return newItem
@@ -1971,7 +1992,7 @@ class _TreeContextMenu(QMenu):
def _toggleItemActive(self) -> None:
"""Toggle the active status of an item."""
self._item.setActive(not self._item.isActive)
self.projTree.setTreeItemValues(self._handle)
self.projTree.setTreeItemValues(self._item)
self.projTree._alertTreeChange(self._handle, flush=False)
return
@@ -1984,14 +2005,14 @@ class _TreeContextMenu(QMenu):
for tItem in self._items:
if tItem and tItem.isFileType():
tItem.setActive(isActive)
self.projTree.setTreeItemValues(tItem.itemHandle)
self.projTree.setTreeItemValues(tItem)
self.projTree._alertTreeChange(tItem.itemHandle, flush=False)
return
def _changeItemStatus(self, key: str) -> None:
"""Set a new status value of an item."""
self._item.setStatus(key)
self.projTree.setTreeItemValues(self._handle)
self.projTree.setTreeItemValues(self._item)
self.projTree._alertTreeChange(self._handle, flush=False)
return
@@ -2000,14 +2021,14 @@ class _TreeContextMenu(QMenu):
for tItem in self._items:
if tItem and tItem.isNovelLike():
tItem.setStatus(key)
self.projTree.setTreeItemValues(tItem.itemHandle)
self.projTree.setTreeItemValues(tItem)
self.projTree._alertTreeChange(tItem.itemHandle, flush=False)
return
def _changeItemImport(self, key: str) -> None:
"""Set a new importance value of an item."""
self._item.setImport(key)
self.projTree.setTreeItemValues(self._handle)
self.projTree.setTreeItemValues(self._item)
self.projTree._alertTreeChange(self._handle, flush=False)
return
@@ -2016,7 +2037,7 @@ class _TreeContextMenu(QMenu):
for tItem in self._items:
if tItem and not tItem.isNovelLike():
tItem.setImport(key)
self.projTree.setTreeItemValues(tItem.itemHandle)
self.projTree.setTreeItemValues(tItem)
self.projTree._alertTreeChange(tItem.itemHandle, flush=False)
return
@@ -2024,11 +2045,11 @@ class _TreeContextMenu(QMenu):
"""Set a new item layout value of an item."""
if itemLayout == nwItemLayout.DOCUMENT and self._item.documentAllowed():
self._item.setLayout(nwItemLayout.DOCUMENT)
self.projTree.setTreeItemValues(self._handle)
self.projTree.setTreeItemValues(self._item)
self.projTree._alertTreeChange(self._handle, flush=False)
elif itemLayout == nwItemLayout.NOTE:
self._item.setLayout(nwItemLayout.NOTE)
self.projTree.setTreeItemValues(self._handle)
self.projTree.setTreeItemValues(self._item)
self.projTree._alertTreeChange(self._handle, flush=False)
return
@@ -2042,12 +2063,12 @@ class _TreeContextMenu(QMenu):
if msgYes and itemLayout == nwItemLayout.DOCUMENT and self._item.documentAllowed():
self._item.setType(nwItemType.FILE)
self._item.setLayout(nwItemLayout.DOCUMENT)
self.projTree.setTreeItemValues(self._handle)
self.projTree.setTreeItemValues(self._item)
self.projTree._alertTreeChange(self._handle, flush=False)
elif msgYes and itemLayout == nwItemLayout.NOTE:
self._item.setType(nwItemType.FILE)
self._item.setLayout(nwItemLayout.NOTE)
self.projTree.setTreeItemValues(self._handle)
self.projTree.setTreeItemValues(self._item)
self.projTree._alertTreeChange(self._handle, flush=False)
else:
logger.info("Folder conversion cancelled")

View File

@@ -189,9 +189,6 @@ class GuiMain(QMainWindow):
self.splitView.setVisible(False)
self.docEditor.closeSearch()
# Initialise the Project Tree
self.rebuildTrees()
# Assemble Main Window Elements
self.mainBox = QHBoxLayout()
self.mainBox.addWidget(self.sideBar)
@@ -222,6 +219,8 @@ class GuiMain(QMainWindow):
SHARED.projectStatusChanged.connect(self.mainStatus.updateProjectStatus)
SHARED.projectStatusMessage.connect(self.mainStatus.setStatusMessage)
SHARED.spellLanguageChanged.connect(self.mainStatus.setLanguage)
SHARED.statusLabelsChanged.connect(self.docViewerPanel.updateStatusLabels)
SHARED.statusLabelsChanged.connect(self.projView.refreshUserLabels)
self.mainMenu.requestDocAction.connect(self._passDocumentAction)
self.mainMenu.requestDocInsert.connect(self._passDocumentInsert)
@@ -461,7 +460,6 @@ class GuiMain(QMainWindow):
# Update GUI
self._updateWindowTitle(SHARED.project.data.name)
self.rebuildTrees()
self.docEditor.toggleSpellCheck(SHARED.project.data.spellCheck)
self.mainStatus.setRefTime(SHARED.project.projOpened)
self.projView.openProjectTasks()
@@ -526,8 +524,14 @@ class GuiMain(QMainWindow):
self.novelView.setActiveHandle(None)
return
def openDocument(self, tHandle: str | None, tLine: int | None = None,
changeFocus: bool = True, doScroll: bool = False) -> bool:
def openDocument(
self,
tHandle: str | None,
tLine: int | None = None,
sTitle: str | None = None,
changeFocus: bool = True,
doScroll: bool = False
) -> bool:
"""Open a specific document, optionally at a given line."""
if not SHARED.hasProject:
logger.error("No project open")
@@ -537,9 +541,12 @@ class GuiMain(QMainWindow):
logger.debug("Requested item '%s' is not a document", tHandle)
return False
if sTitle and tLine is None:
if hItem := SHARED.project.index.getItemHeading(tHandle, sTitle):
tLine = hItem.line
self._changeView(nwView.EDITOR)
cHandle = self.docEditor.docHandle
if cHandle == tHandle:
if tHandle == self.docEditor.docHandle:
self.docEditor.setCursorLine(tLine)
if changeFocus:
self.docEditor.setFocus()
@@ -711,7 +718,6 @@ class GuiMain(QMainWindow):
if SHARED.hasProject:
tHandle = None
sTitle = None
tLine = None
if self.projView.treeHasFocus():
tHandle = self.projView.getSelectedHandle()
elif self.novelView.treeHasFocus():
@@ -722,19 +728,11 @@ class GuiMain(QMainWindow):
logger.warning("No item selected")
return
if tHandle and sTitle:
if hItem := SHARED.project.index.getItemHeading(tHandle, sTitle):
tLine = hItem.line
if tHandle:
self.openDocument(tHandle, tLine=tLine, changeFocus=False, doScroll=False)
self.openDocument(tHandle, sTitle=sTitle, changeFocus=False, doScroll=False)
return
def rebuildTrees(self) -> None:
"""Rebuild the project tree."""
self.projView.populateTree()
return
def rebuildIndex(self, beQuiet: bool = False) -> None:
"""Rebuild the entire index."""
if SHARED.hasProject:
@@ -1093,15 +1091,13 @@ class GuiMain(QMainWindow):
return
@pyqtSlot(bool)
def _processProjectSettingsChanges(self, rebuildTrees: bool) -> None:
@pyqtSlot()
def _processProjectSettingsChanges(self) -> None:
"""Refresh data dependent on project settings."""
logger.debug("Applying new project settings")
SHARED.updateSpellCheckLanguage()
self.itemDetails.refreshDetails()
self._updateWindowTitle(SHARED.project.data.name)
if rebuildTrees:
self.rebuildTrees()
return
@pyqtSlot()
@@ -1118,7 +1114,7 @@ class GuiMain(QMainWindow):
tHandle, sTitle = self._getTagSource(tag)
if tHandle is not None:
if mode == nwDocMode.EDIT:
self.openDocument(tHandle)
self.openDocument(tHandle, sTitle=sTitle)
elif mode == nwDocMode.VIEW:
self.viewDocument(tHandle=tHandle, sTitle=sTitle)
return
@@ -1137,11 +1133,7 @@ class GuiMain(QMainWindow):
"""Handle an open document request."""
if tHandle is not None:
if mode == nwDocMode.EDIT:
tLine = None
hItem = SHARED.project.index.getItemHeading(tHandle, sTitle)
if hItem is not None:
tLine = hItem.line
self.openDocument(tHandle, tLine=tLine, changeFocus=setFocus)
self.openDocument(tHandle, sTitle=sTitle, changeFocus=setFocus)
elif mode == nwDocMode.VIEW:
self.viewDocument(tHandle=tHandle, sTitle=sTitle)
return

View File

@@ -64,6 +64,7 @@ class SharedData(QObject):
indexCleared = pyqtSignal()
indexAvailable = pyqtSignal()
mainClockTick = pyqtSignal()
statusLabelsChanged = pyqtSignal(str)
def __init__(self) -> None:
super().__init__()
@@ -309,6 +310,14 @@ class SharedData(QObject):
self.indexAvailable.emit()
return
def projectSingalProxy(self, data: dict) -> None:
"""Emit signals on project data change."""
event = data.get("event")
logger.debug("Received '%s' event from project data", event)
if event == "statusLabels":
self.statusLabelsChanged.emit(data.get("kind", ""))
return
##
# Alert Boxes
##

View File

@@ -823,6 +823,7 @@ class _PreviewWidget(QTextBrowser):
document.setDocumentMargin(CONFIG.getTextMargin())
self.setDocument(document)
self.setTabStopDistance(CONFIG.getTabWidth())
self._docTime = int(time())
self._updateBuildAge()

View File

@@ -753,8 +753,8 @@ def buildForLaunchpad(args: argparse.Namespace) -> None:
distLoop = [
("22.04", "jammy", True),
("23.10", "mantic", False),
("24.04", "noble", False),
("24.10", "oracular", False),
]
print("Building Ubuntu packages for:")

View File

@@ -16,6 +16,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: Implementation :: CPython",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Development Status :: 5 - Production/Stable",

View File

@@ -1,6 +1,6 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.5" hexVersion="0x020500f0" fileVersion="1.5" fileRevision="4" timeStamp="2024-07-10 16:23:02">
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="1950" autoCount="277" editTime="90880">
<novelWriterXML appVersion="2.5.3" hexVersion="0x020503f0" fileVersion="1.5" fileRevision="4" timeStamp="2024-11-26 22:14:35">
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="1958" autoCount="277" editTime="91015">
<name>Sample Project</name>
<author>Jane Smith</author>
</project>
@@ -118,7 +118,7 @@
<name status="sf12341" import="i2d7a54" active="yes">Earth</name>
</item>
<item handle="f1471bef9f2ae" parent="15c4492bd5107" root="15c4492bd5107" order="1" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="no" heading="H1" charCount="115" wordCount="24" paraCount="1" cursorPos="135" />
<meta expanded="no" heading="H1" charCount="115" wordCount="24" paraCount="1" cursorPos="0" />
<name status="sf12341" import="i4a1d39" active="yes">Space</name>
</item>
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="15c4492bd5107" order="2" type="FILE" class="WORLD" layout="NOTE">

View File

@@ -15,6 +15,7 @@ classifiers =
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12
Programming Language :: Python :: 3.13
Programming Language :: Python :: Implementation :: CPython
License :: OSI Approved :: GNU General Public License v3 (GPLv3)
Development Status :: 5 - Production/Stable

BIN
setup/signpath_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -3,7 +3,6 @@
<head>
<meta charset='utf-8'>
<title>Lorem Ipsum</title>
</head>
<style>
body {font-family: 'Arial'; font-size: 12pt; font-weight: 400; font-style: normal;}
p {text-align: justify; line-height: 150%; margin-top: 0.00em; margin-bottom: 0.76em;}
@@ -22,6 +21,7 @@ mark {background: rgb(255, 255, 166);}
.dialog {color: rgb(66, 113, 174);}
.altdialog {color: rgb(129, 55, 9);}
</style>
</head>
<body>
<article>
<h1 class='title' style='text-align: center;'>Lorem Ipsum</h1>

View File

@@ -32,12 +32,12 @@ from PyQt5.QtGui import QColor, QDesktopServices, QFontDatabase
from novelwriter.common import (
NWConfigParser, checkBool, checkFloat, checkInt, checkIntTuple, checkPath,
checkString, checkStringNone, checkUuid, cssCol, describeFont, elide,
formatFileFilter, formatInt, formatTime, formatTimeStamp, formatVersion,
fuzzyTime, getFileSize, hexToInt, isHandle, isItemClass, isItemLayout,
isItemType, isListInstance, isTitleTag, jsonEncode, makeFileNameSafe,
minmax, numberToRoman, openExternalPath, readTextFile, simplified,
transferCase, xmlIndent, yesNo
checkString, checkStringNone, checkUuid, compact, cssCol, describeFont,
elide, formatFileFilter, formatInt, formatTime, formatTimeStamp,
formatVersion, fuzzyTime, getFileSize, hexToInt, isHandle, isItemClass,
isItemLayout, isItemType, isListInstance, isTitleTag, jsonEncode,
makeFileNameSafe, minmax, numberToRoman, openExternalPath, readTextFile,
simplified, transferCase, uniqueCompact, xmlIndent, yesNo
)
from tests.mocked import causeOSError
@@ -346,6 +346,27 @@ def testBaseCommon_simplified():
assert simplified("\tHello\n\r\tWorld") == "Hello World"
@pytest.mark.base
def testBaseCommon_compact():
"""Test the compact function."""
assert compact("! ! !") == "!!!"
assert compact("1\t2\t3") == "123"
assert compact("1\n2\n3") == "123"
assert compact("1\r2\r3") == "123"
assert compact("1\u00a02\u00a03") == "123"
@pytest.mark.base
def testBaseCommon_uniqueCompact():
"""Test the uniqueCompact function."""
assert uniqueCompact("! ! !") == "!"
assert uniqueCompact("1\t2\t3") == "123"
assert uniqueCompact("1\n2\n3") == "123"
assert uniqueCompact("1\r2\r3") == "123"
assert uniqueCompact("1\u00a02\u00a03") == "123"
assert uniqueCompact("3 2 1") == "123"
@pytest.mark.base
def testBaseCommon_elide():
"""Test the elide function."""

View File

@@ -142,7 +142,9 @@ def testCoreStorage_InitProjectStorage(monkeypatch, mockGUI, fncPath, mockRnd):
storage.clear()
# Open twice, where second should fail due to lockfile
# Note that locking is only possible after a successful open
assert storage.initProjectStorage(fncPath) == NWStorageOpen.READY
storage.lockSession()
assert storage.initProjectStorage(fncPath) == NWStorageOpen.LOCKED
assert isinstance(storage.lockStatus, list)
assert len(storage.lockStatus) == 4

View File

@@ -628,10 +628,10 @@ def testCoreToHtml_Save(mockGUI, fncPath):
"<head>\n"
"<meta charset='utf-8'>\n"
"<title></title>\n"
"</head>\n"
"<style>\n"
"{htmlStyle:s}\n"
"</style>\n"
"</head>\n"
"<body>\n"
"<article>\n"
"{bodyText:s}\n"

View File

@@ -1100,6 +1100,7 @@ def testCoreToken_Dialogue(mockGUI):
project = NWProject()
tokens = BareTokenizer(project)
tokens.setDialogueHighlight(True)
tokens._isNovel = True
# Single quotes
tokens._text = "Text with \u2018dialogue one,\u2019 and \u2018dialogue two.\u2019\n"
@@ -1161,6 +1162,24 @@ def testCoreToken_Dialogue(mockGUI):
Tokenizer.A_NONE
)]
# Special Cases
# =============
# Dialogue + formatting on same index (Issue #2012)
tokens._text = "[i]\u201cDialogue text.\u201d[/i]\n"
tokens.tokenizeText()
assert tokens._tokens == [(
Tokenizer.T_TEXT, 0,
"\u201cDialogue text.\u201d",
[
(0, Tokenizer.FMT_I_B, ""),
(0, Tokenizer.FMT_DL_B, ""),
(16, Tokenizer.FMT_I_E, ""),
(16, Tokenizer.FMT_DL_E, ""),
],
Tokenizer.A_NONE
)]
@pytest.mark.core
def testCoreToken_SpecialFormat(mockGUI):

View File

@@ -379,7 +379,7 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, tstPaths):
assert CONFIG.doReplaceDQuote is False
assert CONFIG.doReplaceDash is False
assert CONFIG.doReplaceDots is False
assert CONFIG.fmtPadBefore == "!?:"
assert CONFIG.fmtPadBefore == "!:?"
assert CONFIG.fmtPadAfter == "¡¿"
assert CONFIG.fmtPadThin is True

View File

@@ -113,13 +113,13 @@ def testDlgProjSettings_SettingsPage(qtbot, monkeypatch, nwGUI, fncPath, projPat
assert settings.projAuthor.text() == "Jane Smith"
assert settings.projLang.currentData() == "en"
assert settings.spellLang.currentData() == "en"
assert settings.doBackup.isChecked() is False
assert settings.noBackup.isChecked() is False
settings.projName.setText("Project Name")
settings.projAuthor.setText("Jane Doe")
settings.projLang.setCurrentIndex(settings.projLang.findData("de"))
settings.spellLang.setCurrentIndex(settings.spellLang.findData("de"))
settings.doBackup.setChecked(True)
settings.noBackup.setChecked(True)
projSettings._doSave()
assert project.data.name == "Project Name"
@@ -128,7 +128,7 @@ def testDlgProjSettings_SettingsPage(qtbot, monkeypatch, nwGUI, fncPath, projPat
assert project.data.spellLang == "de"
assert project.data.doBackup is False
nwGUI._processProjectSettingsChanges(False)
nwGUI._processProjectSettingsChanges()
assert nwGUI.windowTitle() == "Project Name - novelWriter"
# qtbot.stop()
@@ -161,7 +161,7 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn
project.tree[hCharNote].setImport(C.iMajor) # type: ignore
project.tree[hWorldNote].setImport(C.iMain) # type: ignore
nwGUI.rebuildTrees()
nwGUI.projView.populateTree()
project.countStatus()
assert [e.count for _, e in project.data.itemStatus.iterItems()] == [2, 0, 2, 1]

View File

@@ -221,8 +221,14 @@ def testGuiViewerPanel_Tags(qtbot, monkeypatch, caplog, nwGUI, projPath, mockRnd
nwJohn = SHARED.project.tree[hJohn]
assert isinstance(nwJohn, NWItem)
nwJohn.setActive(False)
projTree.setTreeItemValues(hJohn)
projTree.setTreeItemValues(nwJohn)
projTree._alertTreeChange(hJohn, flush=False)
assert charTab.topLevelItemCount() == 1
# Update Labels
assert charTab.topLevelItem(0).text(charTab.C_IMPORT) == "New"
SHARED.project.data.itemImport.add(C.iNew, "Stuff", (100, 100, 100), "SQUARE", 0)
viewPanel.updateStatusLabels("i")
assert charTab.topLevelItem(0).text(charTab.C_IMPORT) == "Stuff"
# qtbot.stop()

View File

@@ -39,6 +39,7 @@ from novelwriter.gui.outline import GuiOutlineView
from novelwriter.gui.projtree import GuiProjectTree
from novelwriter.tools.welcome import GuiWelcome
from tests.mocked import causeOSError
from tests.tools import NWD_IGNORE, XML_IGNORE, C, buildTestProject, cmpFiles
KEY_DELAY = 1
@@ -651,7 +652,7 @@ def testGuiMain_Viewing(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
@pytest.mark.gui
def testGuiMain_Features(qtbot, nwGUI, projPath, mockRnd):
def testGuiMain_Features(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
"""Test various features of the main window."""
buildTestProject(nwGUI, projPath)
assert SHARED.focusMode is False
@@ -672,6 +673,7 @@ def testGuiMain_Features(qtbot, nwGUI, projPath, mockRnd):
# Enable focus mode
nwGUI.toggleFocusMode()
assert SHARED.focusMode is True
assert nwGUI.treePane.isVisible() is False
assert nwGUI.mainStatus.isVisible() is False
assert nwGUI.mainMenu.isVisible() is False
@@ -680,12 +682,19 @@ def testGuiMain_Features(qtbot, nwGUI, projPath, mockRnd):
# Disable focus mode
nwGUI.toggleFocusMode()
assert SHARED.focusMode is False
assert nwGUI.treePane.isVisible() is True
assert nwGUI.mainStatus.isVisible() is True
assert nwGUI.mainMenu.isVisible() is True
assert nwGUI.sideBar.isVisible() is True
assert nwGUI.splitView.isVisible() is True
# Closing editor disables focus mode
nwGUI.toggleFocusMode()
assert SHARED.focusMode is True
nwGUI.closeDocument()
assert SHARED.focusMode is False
# Full Screen Mode
# ================
@@ -702,6 +711,17 @@ def testGuiMain_Features(qtbot, nwGUI, projPath, mockRnd):
nwGUI.sideBar.mSettings.show()
nwGUI.sideBar.mSettings.hide()
# Document Open Errors
# ====================
# Cannot edit a folder
assert nwGUI.openDocument(C.hChapterDir) is False
# Handle I/O error
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
assert nwGUI.openDocument(C.hChapterDoc) is False
# qtbot.stop()

View File

@@ -186,6 +186,10 @@ def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, projPath, mockRn
# Adding an invalid item directly to the tree should also fail
assert projTree._addTreeItem(None) is None
# Setting values for a non-existing tree item should be handled
projTree.setTreeItemValues(None)
projTree.setTreeItemValues(C.hInvalid) # The function used to take handles
# Clean up
# qtbot.stop()
nwGUI.closeProject()

View File

@@ -197,7 +197,7 @@ def buildTestProject(obj: object, projPath: Path) -> None:
project._valid = True
if nwGUI is not None:
nwGUI.rebuildTrees()
nwGUI.projView.populateTree()
return