Skip to content

Commit 391f9bc

Browse files
authored
Merge 2.8.1 into main (#2629)
2 parents 3d475a2 + a636bd8 commit 391f9bc

File tree

10 files changed

+101
-50
lines changed

10 files changed

+101
-50
lines changed

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,28 @@
11
# novelWriter Changelog
22

3+
## Version 2.8.1 [2025-12-28]
4+
5+
### Release Notes
6+
7+
This is a patch release that fixes an issue where the application could crash due to a change in
8+
PyQt/Qt 6.10. The issue is related to certain input methods, and is at least triggered when using
9+
Spotlight on MacOS.
10+
11+
### Detailed Changelog
12+
13+
**Bugfixes**
14+
15+
* Fix an issue where the inputMethodQuery call to the Qt library returns an object that PyQt6
16+
cannot convert to a Python object. The fix just disables the conversion since the return value is
17+
passed back to the Qt library anyway. The problem affects Qt/PyQt 6.10 and is triggered in MacOS
18+
at least, but it is not known if it affects other platforms. Issue #2622. PR #2623.
19+
20+
**Documentation**
21+
22+
* Removed references to the old colour theme system in the documentation. Issue #2619. PR #2625.
23+
24+
----
25+
326
## Version 2.8 [2025-12-14]
427

528
### Release Notes

docs/source/conf.py

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,25 @@
1414
project = "novelWriter"
1515
copyright = f"{datetime.date.today().year}" # noqa: A001
1616

17-
tmp_authors = ["Veronica Berglyd Olsen"]
18-
if additional := os.environ.get("SPHINX_I18N_AUTHORS"):
19-
tmp_authors.extend(a.strip() for a in additional.split(","))
17+
if override_authors := os.environ.get("SPHINX_I18N_AUTHORS"):
18+
author = override_authors
19+
else:
20+
author = "Veronica Berglyd Olsen"
2021

21-
author = ", ".join(tmp_authors)
22-
23-
initFile = os.path.join(
24-
os.path.dirname(__file__), os.pardir, os.pardir,
25-
"novelwriter", "__init__.py"
26-
)
27-
with open(initFile, encoding="utf-8") as inFile:
28-
for aLine in inFile:
29-
if aLine.startswith("__version__"):
30-
release = aLine.split('"')[1].strip()
31-
break
32-
else:
33-
release = "unknown"
22+
if override_version := os.environ.get("SPHINX_I18N_VERSION"):
23+
release = override_version
24+
else:
25+
initFile = os.path.join(
26+
os.path.dirname(__file__), os.pardir, os.pardir,
27+
"novelwriter", "__init__.py"
28+
)
29+
with open(initFile, encoding="utf-8") as inFile:
30+
for aLine in inFile:
31+
if aLine.startswith("__version__"):
32+
release = aLine.split('"')[1].strip()
33+
break
34+
else:
35+
release = "unknown"
3436

3537
version = release.partition("-")[0]
3638

@@ -42,7 +44,7 @@
4244
needs_sphinx = "5.0"
4345
extensions = ["sphinx_design", "sphinx_copybutton"]
4446
templates_path = ["_templates"]
45-
source_suffix = ".rst"
47+
source_suffix = {".rst": "restructuredtext"}
4648
master_doc = "index"
4749
today_fmt = "%A, %d %B %Y at %H:%M"
4850
language = "en"
@@ -67,8 +69,8 @@
6769
"navigation_with_keys": True,
6870
"use_repository_button": True,
6971
"use_issues_button": True,
70-
"pygment_light_style": "tango",
71-
"pygment_dark_style": "dracula",
72+
"pygments_light_style": "tango",
73+
"pygments_dark_style": "dracula",
7274
}
7375
html_sidebars = {
7476
"**": ["navbar-logo", "sidebar-title", "sbt-sidebar-nav"],

docs/source/locales/authors_fr.conf

Lines changed: 0 additions & 2 deletions
This file was deleted.

docs/source/locales/config.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[fr]
2+
version = "2.7"
3+
authors = ["Jean-Michel Heras", "Veronica Berglyd Olsen"]

docs/source/user_interface/main_window.rst

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -186,17 +186,11 @@ See :ref:`docs_features_shortcuts` for more details.
186186
Colour Themes
187187
=============
188188

189-
By default, novelWriter uses a light colour theme. You can also choose between a standard dark
190-
theme that have neutral colours, or a series of other included themes, from **Preferences**.
189+
By default, novelWriter should pick a dark or light theme based on the colour mode of your
190+
operating system. You can select which colour mode to use from the application sidebar.
191+
192+
The default light and dark theme can be changed from **Preferences**. There are a number of themes
193+
to choose from. There are also some high contrast themes available.
191194

192195
If you wish, you *can* create your own colour themes, and even have them added to the application.
193196
See :ref:`docs_more_custom_theme` for more details.
194-
195-
Switching the GUI colour theme does not affect the colours of the editor and viewer. They have
196-
separate themes selectable from the "Document colour theme" setting in **Preferences**. They are
197-
separated because there are a lot more options to choose from for the editor and viewer.
198-
199-
.. note::
200-
201-
If you switch between light and dark mode on the GUI, you should also switch editor theme to
202-
match, otherwise icons may be hard to see in the editor and viewer.

novelwriter/gui/doceditor.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from enum import Enum, IntFlag
4040
from time import time
4141

42+
from PyQt6 import sip
4243
from PyQt6.QtCore import (
4344
QMimeData, QObject, QPoint, QRect, QRegularExpression, QRunnable, Qt,
4445
QTimer, QVariant, pyqtSignal, pyqtSlot
@@ -76,12 +77,13 @@
7677
from novelwriter.tools.lipsum import GuiLipsum
7778
from novelwriter.types import (
7879
QtAlignCenterTop, QtAlignJustify, QtAlignLeft, QtAlignLeftTop,
79-
QtAlignRight, QtBlack, QtImCursorRectangle, QtKeepAnchor, QtModCtrl,
80-
QtModNone, QtModShift, QtMouseLeft, QtMoveAnchor, QtMoveDown, QtMoveEnd,
81-
QtMoveEndOfLine, QtMoveEndOfWord, QtMoveLeft, QtMoveNextChar,
82-
QtMoveNextWord, QtMovePreviousWord, QtMoveRight, QtMoveStart,
83-
QtMoveStartOfLine, QtMoveUp, QtScrollAlwaysOff, QtScrollAsNeeded,
84-
QtSelectBlock, QtSelectDocument, QtSelectLine, QtSelectWord, QtTransparent
80+
QtAlignRight, QtBlack, QtImCurrentSelection, QtImCursorRectangle,
81+
QtKeepAnchor, QtModCtrl, QtModNone, QtModShift, QtMouseLeft, QtMoveAnchor,
82+
QtMoveDown, QtMoveEnd, QtMoveEndOfLine, QtMoveEndOfWord, QtMoveLeft,
83+
QtMoveNextChar, QtMoveNextWord, QtMovePreviousWord, QtMoveRight,
84+
QtMoveStart, QtMoveStartOfLine, QtMoveUp, QtScrollAlwaysOff,
85+
QtScrollAsNeeded, QtSelectBlock, QtSelectDocument, QtSelectLine,
86+
QtSelectWord, QtTransparent
8587
)
8688

8789
logger = logging.getLogger(__name__)
@@ -1070,6 +1072,12 @@ def inputMethodQuery(self, query: Qt.InputMethodQuery) -> QRect | QVariant:
10701072
rect = self.cursorRect()
10711073
rect.translate(vM.left(), vM.top())
10721074
return rect
1075+
elif query == QtImCurrentSelection:
1076+
# See issue #2622
1077+
ac = sip.enableautoconversion(QVariant, False) # type: ignore
1078+
variant = super().inputMethodQuery(query)
1079+
sip.enableautoconversion(QVariant, ac) # type: ignore
1080+
return variant
10731081
return super().inputMethodQuery(query)
10741082

10751083
def insertFromMimeData(self, source: QMimeData | None) -> None:

novelwriter/types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@
131131
QtSelectDocument = QTextCursor.SelectionType.Document
132132

133133
QtImCursorRectangle = Qt.InputMethodQuery.ImCursorRectangle
134+
QtImCurrentSelection = Qt.InputMethodQuery.ImCurrentSelection
134135

135136
# Size Policy
136137

tests/test_gui/test_gui_doceditor.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
import pytest
2626

27-
from PyQt6.QtCore import QEvent, QMimeData, QPointF, Qt, QThreadPool, QUrl
27+
from PyQt6.QtCore import QEvent, QMimeData, QPointF, QRect, Qt, QThreadPool, QUrl, QVariant
2828
from PyQt6.QtGui import (
2929
QAction, QClipboard, QDesktopServices, QDragEnterEvent, QDragMoveEvent,
3030
QDropEvent, QFont, QInputMethodEvent, QMouseEvent, QTextBlock, QTextCursor,
@@ -41,9 +41,9 @@
4141
from novelwriter.gui.dochighlight import TextBlockData
4242
from novelwriter.text.counting import standardCounter
4343
from novelwriter.types import (
44-
QtAlignJustify, QtAlignLeft, QtKeepAnchor, QtModCtrl, QtModNone,
45-
QtMouseLeft, QtMoveAnchor, QtMoveRight, QtScrollAlwaysOff,
46-
QtScrollAsNeeded, QtSelectDocument, QtSelectWord
44+
QtAlignJustify, QtAlignLeft, QtImCurrentSelection, QtImCursorRectangle,
45+
QtKeepAnchor, QtModCtrl, QtModNone, QtMouseLeft, QtMoveAnchor, QtMoveRight,
46+
QtScrollAlwaysOff, QtScrollAsNeeded, QtSelectDocument, QtSelectWord
4747
)
4848

4949
from tests.mocked import causeOSError
@@ -2553,6 +2553,23 @@ def prep(text: str) -> tuple[str, QTextCursor]:
25532553
assert doc.toRawText() == "Text «\u202f"
25542554

25552555

2556+
@pytest.mark.gui
2557+
def testGuiEditor_BigFixes(qtbot, nwGUI):
2558+
"""Test specific bug fixes in the editor."""
2559+
docEditor = nwGUI.docEditor
2560+
2561+
# Cursor rectangle should take into account viewport margins (Issues #2267 and #2517)
2562+
vM = docEditor.viewportMargins()
2563+
cR = docEditor.inputMethodQuery(QtImCursorRectangle)
2564+
assert isinstance(cR, QRect)
2565+
assert cR.left() >= vM.left()
2566+
assert cR.top() >= vM.top()
2567+
2568+
# This one should simply not crash, and return a plain QVariant (Issue #2622)
2569+
cV = docEditor.inputMethodQuery(QtImCurrentSelection)
2570+
assert isinstance(cV, QVariant)
2571+
2572+
25562573
@pytest.mark.gui
25572574
def testGuiEditor_Vim_EnableVimMode(qtbot, nwGUI, projPath, mockRnd):
25582575
"""Test that enabling CONFIG.vimMode activates vim behavior."""

utils/build_debian.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,9 @@ def makeDebianPackage(
8989
else:
9090
pkgVers = numVers
9191
if debianVersion < DEB_STABLE:
92-
pkgDist = "oldstable"
92+
pkgDist = "-oldstable"
9393
elif debianVersion > DEB_STABLE:
94-
pkgDist = "testing"
94+
pkgDist = "-testing"
9595
pkgVers = f"{pkgVers}+{buildName}" if buildName else pkgVers
9696

9797
# Set Up Folder

utils/docs.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import os
2525
import shutil
2626
import subprocess
27+
import tomllib
2728

2829
from utils.common import ROOT_DIR, systemCall
2930

@@ -74,6 +75,7 @@ def buildHtmlDocs(args: argparse.Namespace | None = None) -> None:
7475
locsDir = ROOT_DIR / "docs" / "source" / "locales"
7576
locsDir.mkdir(exist_ok=True)
7677
bldRoot.mkdir(exist_ok=True, parents=True)
78+
locConf = tomllib.loads((locsDir / "config.toml").read_text(encoding="utf-8"))
7779

7880
lang = args.lang if args else ["all"]
7981
build = []
@@ -87,9 +89,10 @@ def buildHtmlDocs(args: argparse.Namespace | None = None) -> None:
8789
env = os.environ.copy()
8890
cmd = "make clean html"
8991
if code != "en":
90-
data = (locsDir / f"authors_{code}.conf").read_text(encoding="utf-8")
91-
authors = [x for x in data.splitlines() if x and not x.startswith("#")]
92-
env["SPHINX_I18N_AUTHORS"] = ", ".join(authors)
92+
if code not in locConf:
93+
print(f"ERROR: No config for language code '{code}' in config.toml")
94+
env["SPHINX_I18N_VERSION"] = locConf[code].get("version", "")
95+
env["SPHINX_I18N_AUTHORS"] = ", ".join(locConf[code].get("authors", []))
9396
cmd += f" -e SPHINXOPTS=\"-D language='{code}'\""
9497

9598
if (ex := subprocess.call(cmd, cwd=docsDir, env=env, shell=True)) == 0:
@@ -114,6 +117,7 @@ def buildPdfDocAssets(args: argparse.Namespace | None = None) -> None:
114117
locsDir = ROOT_DIR / "docs" / "source" / "locales"
115118
pdfFile = ROOT_DIR / "docs" / "build" / "latex" / "manual.pdf"
116119
locsDir.mkdir(exist_ok=True)
120+
locConf = tomllib.loads((locsDir / "config.toml").read_text(encoding="utf-8"))
117121

118122
lang = args.lang if args else ["all"]
119123
build = []
@@ -127,9 +131,10 @@ def buildPdfDocAssets(args: argparse.Namespace | None = None) -> None:
127131
cmd = "make clean latexpdf"
128132
name = "manual.pdf"
129133
if code != "en":
130-
data = (locsDir / f"authors_{code}.conf").read_text(encoding="utf-8")
131-
authors = [x for x in data.splitlines() if x and not x.startswith("#")]
132-
env["SPHINX_I18N_AUTHORS"] = ", ".join(authors)
134+
if code not in locConf:
135+
print(f"ERROR: No config for language code '{code}' in config.toml")
136+
env["SPHINX_I18N_VERSION"] = locConf[code].get("version", "")
137+
env["SPHINX_I18N_AUTHORS"] = ", ".join(locConf[code].get("authors", []))
133138
cmd += f" -e SPHINXOPTS=\"-D language='{code}'\""
134139
name = f"manual_{code}.pdf"
135140

0 commit comments

Comments
 (0)