diff --git a/CREDITS.md b/CREDITS.md
index 51c45f6c..1f51a83d 100644
--- a/CREDITS.md
+++ b/CREDITS.md
@@ -45,8 +45,8 @@ contributions are listed on the project's Members page.
The following libraries are dependencies of novelWriter:
-* **Qt5** by Qt Company
-* **PyQt5** by Riverbank Computing
+* **Qt6** by Qt Company
+* **PyQt6** by Riverbank Computing
* **Enchant** by Dom Lachowicz
* **PyEnchant** by Dimitri Merejkowsky
diff --git a/README.md b/README.md
index 3ac3c385..4f3e65f9 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@ documentation.
## Implementation
-novelWriter is written with Python 3 (3.10+) using Qt5 and PyQt5 (5.15 only), and is released on
+novelWriter is written with Python 3 (3.10+) using Qt6 and PyQt6 (6.0+), and is released on
Linux, Windows and macOS. It can in principle run on any Operating System that also supports Qt,
PyQt and Python.
diff --git a/docs/source/tech_source.rst b/docs/source/tech_source.rst
index 9b100ebd..ad3d07c9 100644
--- a/docs/source/tech_source.rst
+++ b/docs/source/tech_source.rst
@@ -36,12 +36,11 @@ Everything else is handled with standard Python libraries.
The following Python packages are needed to run all features of novelWriter:
-* ``PyQt5`` – needed for connecting with the Qt5 libraries.
+* ``PyQt6`` – needed for connecting with the Qt6 libraries.
* ``PyEnchant`` – needed for spell checking (optional).
-PyQt/Qt must be at least 5.15.0. If you want spell checking, you must install the ``PyEnchant``
-package. The spell check library must be at least 3.0 to work with Windows. On Linux, 2.0 also
-works fine.
+If you want spell checking, you must install the ``PyEnchant`` package. The spell check library
+must be at least 3.0 to work with Windows. On Linux, 2.0 also works fine.
If you install from PyPi, these dependencies should be installed automatically. If you install from
source, dependencies can still be installed from PyPi with:
diff --git a/i18n/README.md b/i18n/README.md
index 1c5303a1..b45439dd 100644
--- a/i18n/README.md
+++ b/i18n/README.md
@@ -18,7 +18,7 @@ When contributing translations, keep the following things in mind.
* For descriptive labels and dialog boxes, make sure you do _not_ change the meaning of the text
when you translate it from English. The user must receive the same instructions or information
- regardless of language. This is improtant, otherwise the documentation will be inconsistent with
+ regardless of language. This is important, otherwise the documentation will be inconsistent with
the user interface and it will become a lot more difficult to handle user issues and questions.
* If you think a label or description is misleading or incomplete, please file an issue report. The
correct way to handle such changes is to change the text in the code first, which will then be
@@ -35,7 +35,7 @@ Linguist, or updating the translations for an already existing supported languag
There are two areas relevant to localisation:
-* The Qt5 GUI translation files, which consists of `nw_XX.ts` files. This is the bulk of the
+* The Qt GUI translation files, which consists of `nw_XX.ts` files. This is the bulk of the
translation work.
* The `project_XX.json` files. See [Project Localisation](#project-localisation) below.
@@ -43,7 +43,7 @@ The `XX` in the file name corresponds to the language and country code for the t
instance `en_GB` for British English.
-## Qt5 GUI Localisation
+## Qt GUI Localisation
You will need the translation tool Qt 5 Linguist on your system.
@@ -89,8 +89,7 @@ For instance, the French translation uses the language code `fr_FR`, so its tran
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`.
+TS file format. On Debian/Ubuntu it is provided by the package `pyqt6-dev-tools`.
### Edit the Translation File in Qt Linguist
diff --git a/i18n/qtbase.py b/i18n/qtbase.py
index 90f6f9ae..3126bea3 100644
--- a/i18n/qtbase.py
+++ b/i18n/qtbase.py
@@ -10,7 +10,7 @@ If a qtbase_xx.qm file already exists, do not add a translation for the
entries generated from this file.
"""
-from PyQt5.QtCore import QT_TRANSLATE_NOOP
+from PyQt6.QtCore import QT_TRANSLATE_NOOP
# QDialogButtonBox
# ================
diff --git a/novelWriter.py b/novelWriter.py
index 33ce3768..285cb5dd 100755
--- a/novelWriter.py
+++ b/novelWriter.py
@@ -7,11 +7,11 @@ import os
import sys
try:
- import PyQt5.QtWidgets # noqa: F401
- import PyQt5.QtGui # noqa: F401
- import PyQt5.QtCore # noqa: F401
+ import PyQt6.QtCore # noqa: F401
+ import PyQt6.QtGui # noqa: F401
+ import PyQt6.QtWidgets # noqa: F401
except Exception:
- print("ERROR: Failed to load dependency PyQt5")
+ print("ERROR: Failed to load dependency PyQt6")
sys.exit(1)
os.curdir = os.path.abspath(os.path.dirname(__file__))
diff --git a/novelwriter/__init__.py b/novelwriter/__init__.py
index 7e9f74f1..ab5b3c7a 100644
--- a/novelwriter/__init__.py
+++ b/novelwriter/__init__.py
@@ -29,7 +29,7 @@ import sys
from typing import TYPE_CHECKING
-from PyQt5.QtWidgets import QApplication, QErrorMessage
+from PyQt6.QtWidgets import QApplication, QErrorMessage
from novelwriter.config import Config
from novelwriter.error import exceptionHandler
@@ -97,7 +97,6 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
"style=",
"config=",
"data=",
- "testmode",
"meminfo"
]
@@ -117,7 +116,7 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
" --debug Print debug output. Includes --info.\n"
" --color Add ANSI colors to log output.\n"
" --meminfo Show memory usage information in the status bar.\n"
- " --style= Sets Qt5 style flag. Defaults to 'Fusion'.\n"
+ " --style= Sets Qt style flag. Defaults to 'Fusion'.\n"
" --config= Alternative config file.\n"
" --data= Alternative user data path.\n"
)
@@ -127,7 +126,6 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
fmtFlags = 0b00
confPath = None
dataPath = None
- testMode = False
qtStyle = "Fusion"
cmdOpen = None
@@ -163,8 +161,6 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
confPath = inArg
elif inOpt == "--data":
dataPath = inArg
- elif inOpt == "--testmode":
- testMode = True
elif inOpt == "--meminfo":
CONFIG.memInfo = True
@@ -206,14 +202,14 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
"At least Python 3.10 is required, found %s" % CONFIG.verPyString
)
errorCode |= 0x04
- if CONFIG.verQtValue < 0x050f00:
+ if CONFIG.verQtValue < 0x060000:
errorData.append(
- "At least Qt5 version 5.15.0 is required, found %s" % CONFIG.verQtString
+ "At least Qt6 version 6.0 is required, found %s" % CONFIG.verQtString
)
errorCode |= 0x08
- if CONFIG.verPyQtValue < 0x050f00:
+ if CONFIG.verPyQtValue < 0x060000:
errorData.append(
- "At least PyQt5 version 5.15.0 is required, found %s" % CONFIG.verPyQtString
+ "At least PyQt6 version 6.0 is required, found %s" % CONFIG.verPyQtString
)
errorCode |= 0x10
@@ -254,19 +250,11 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
pass # Quietly ignore error
# Import GUI (after dependency checks), and launch
+ from novelwriter.gui.theme import GuiTheme
from novelwriter.guimain import GuiMain
- if testMode:
- # Only used for testing where the test framework creates the app
- CONFIG.loadConfig()
- return GuiMain()
-
- app = QApplication([CONFIG.appName, (f"-style={qtStyle}")])
- app.setApplicationName(CONFIG.appName)
- app.setApplicationVersion(__version__)
- app.setOrganizationDomain(__domain__)
- app.setOrganizationName(__domain__)
- app.setDesktopFileName(CONFIG.appName)
+ # Create App
+ app = _createApp(qtStyle)
# Connect the exception handler before making the main GUI
sys.excepthook = exceptionHandler
@@ -274,9 +262,21 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
# Run Config steps that require the QApplication
CONFIG.loadConfig()
CONFIG.initLocalisation(app)
+ SHARED.initTheme(GuiTheme())
# Launch main GUI
nwGUI = GuiMain()
nwGUI.postLaunchTasks(cmdOpen)
sys.exit(app.exec())
+
+
+def _createApp(style: str) -> QApplication:
+ """Create the app."""
+ app = QApplication([CONFIG.appName, (f"-style={style}")])
+ app.setApplicationName(CONFIG.appName)
+ app.setApplicationVersion(__version__)
+ app.setOrganizationDomain(__domain__)
+ app.setOrganizationName(__domain__)
+ app.setDesktopFileName(CONFIG.appName)
+ return app
diff --git a/novelwriter/assets/text/credits_en.htm b/novelwriter/assets/text/credits_en.htm
index 30e6b9e0..60f5c41f 100644
--- a/novelwriter/assets/text/credits_en.htm
+++ b/novelwriter/assets/text/credits_en.htm
@@ -54,8 +54,8 @@ more contributions are listed on the project's Members page.
The following libraries are dependencies of novelWriter:
- - Qt5 by Qt Company
- - PyQt5 by Riverbank Computing
+ - Qt6 by Qt Company
+ - PyQt6 by Riverbank Computing
- Enchant by Dom Lachowicz
- PyEnchant by Dimitri Merejkowsky
diff --git a/novelwriter/common.py b/novelwriter/common.py
index 17a465ee..193f7c81 100644
--- a/novelwriter/common.py
+++ b/novelwriter/common.py
@@ -37,8 +37,8 @@ from typing import Any, Literal, TypeGuard, TypeVar
from urllib.parse import urljoin
from urllib.request import pathname2url
-from PyQt5.QtCore import QCoreApplication, QMimeData, QUrl
-from PyQt5.QtGui import QColor, QDesktopServices, QFont, QFontDatabase, QFontInfo
+from PyQt6.QtCore import QCoreApplication, QMimeData, QUrl
+from PyQt6.QtGui import QColor, QDesktopServices, QFont, QFontDatabase, QFontInfo
from novelwriter.constants import nwConst, nwLabels, nwUnicode, trConst
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
@@ -434,17 +434,16 @@ def describeFont(font: QFont) -> str:
def fontMatcher(font: QFont) -> QFont:
"""Make sure the font is the correct family, if possible. This
ensures that Qt doesn't re-use another font under the hood. The
- default Qt5 font matching algorithm doesn't handle well changing
+ default Qt font matching algorithm doesn't handle well changing
application fonts at runtime.
"""
info = QFontInfo(font)
if (famRequest := font.family()) != (famActual := info.family()):
logger.warning("Font mismatch: Requested '%s', but got '%s'", famRequest, famActual)
- db = QFontDatabase()
- if famRequest in db.families():
+ if famRequest in QFontDatabase.families():
styleRequest, sizeRequest = font.styleName(), font.pointSize()
logger.info("Lookup: %s, %s, %d pt", famRequest, styleRequest, sizeRequest)
- temp = db.font(famRequest, styleRequest, sizeRequest)
+ temp = QFontDatabase.font(famRequest, styleRequest, sizeRequest)
temp.setPointSize(sizeRequest) # Make sure it isn't changed
famFound, styleFound, sizeFound = temp.family(), temp.styleName(), temp.pointSize()
if famFound == famRequest:
diff --git a/novelwriter/config.py b/novelwriter/config.py
index cd377199..f1389253 100644
--- a/novelwriter/config.py
+++ b/novelwriter/config.py
@@ -33,12 +33,12 @@ from datetime import datetime
from pathlib import Path
from time import time
-from PyQt5.QtCore import (
+from PyQt6.QtCore import (
PYQT_VERSION, PYQT_VERSION_STR, QT_VERSION, QT_VERSION_STR, QLibraryInfo,
QLocale, QStandardPaths, QSysInfo, QTranslator
)
-from PyQt5.QtGui import QFont, QFontDatabase
-from PyQt5.QtWidgets import QApplication
+from PyQt6.QtGui import QFont, QFontDatabase
+from PyQt6.QtWidgets import QApplication
from novelwriter.common import (
NWConfigParser, checkInt, checkPath, describeFont, fontMatcher,
@@ -91,7 +91,7 @@ class Config:
# Localisation
# Note that these paths must be strings
self._nwLangPath = self._appPath / "assets" / "i18n"
- self._qtLangPath = QLibraryInfo.location(QLibraryInfo.LibraryLocation.TranslationsPath)
+ self._qtLangPath = QLibraryInfo.path(QLibraryInfo.LibraryPath.TranslationsPath)
hasLocale = (self._nwLangPath / f"nw_{QLocale.system().name()}.qm").exists()
self._qLocale = QLocale.system() if hasLocale else QLocale("en_GB")
@@ -115,7 +115,6 @@ class Config:
self.guiTheme = "default" # GUI theme
self.guiSyntax = "default_light" # Syntax theme
self.guiFont = QFont() # Main GUI font
- self.guiScale = 1.0 # Set automatically by Theme class
self.hideVScroll = False # Hide vertical scroll bars on main widgets
self.hideHScroll = False # Hide horizontal scroll bars on main widgets
self.lastNotes = "0x0" # The latest release notes that have been shown
@@ -217,7 +216,7 @@ class Config:
# System and App Information
# ==========================
- # Check Qt5 Versions
+ # Check Qt Versions
self.verQtString = QT_VERSION_STR
self.verQtValue = QT_VERSION
self.verPyQtString = PYQT_VERSION_STR
@@ -272,27 +271,27 @@ class Config:
@property
def mainWinSize(self) -> list[int]:
- return [int(x*self.guiScale) for x in self._mainWinSize]
+ return self._mainWinSize
@property
def welcomeWinSize(self) -> list[int]:
- return [int(x*self.guiScale) for x in self._welcomeSize]
+ return self._welcomeSize
@property
def preferencesWinSize(self) -> list[int]:
- return [int(x*self.guiScale) for x in self._prefsWinSize]
+ return self._prefsWinSize
@property
def mainPanePos(self) -> list[int]:
- return [int(x*self.guiScale) for x in self._mainPanePos]
+ return self._mainPanePos
@property
def viewPanePos(self) -> list[int]:
- return [int(x*self.guiScale) for x in self._viewPanePos]
+ return self._viewPanePos
@property
def outlinePanePos(self) -> list[int]:
- return [int(x*self.guiScale) for x in self._outlnPanePos]
+ return self._outlnPanePos
##
# Getters
@@ -323,8 +322,6 @@ class Config:
adjust it a bit, and we don't want the main window to shrink or
grow each time the app is opened.
"""
- width = int(width/self.guiScale)
- height = int(height/self.guiScale)
if abs(self._mainWinSize[0] - width) > 5:
self._mainWinSize[0] = width
if abs(self._mainWinSize[1] - height) > 5:
@@ -333,29 +330,27 @@ class Config:
def setWelcomeWinSize(self, width: int, height: int) -> None:
"""Set the size of the Preferences dialog window."""
- self._welcomeSize[0] = int(width/self.guiScale)
- self._welcomeSize[1] = int(height/self.guiScale)
+ self._welcomeSize = [width, height]
return
def setPreferencesWinSize(self, width: int, height: int) -> None:
"""Set the size of the Preferences dialog window."""
- self._prefsWinSize[0] = int(width/self.guiScale)
- self._prefsWinSize[1] = int(height/self.guiScale)
+ self._prefsWinSize = [width, height]
return
def setMainPanePos(self, pos: list[int]) -> None:
"""Set the position of the main GUI splitter."""
- self._mainPanePos = [int(x/self.guiScale) for x in pos]
+ self._mainPanePos = pos
return
def setViewPanePos(self, pos: list[int]) -> None:
"""Set the position of the viewer meta data splitter."""
- self._viewPanePos = [int(x/self.guiScale) for x in pos]
+ self._viewPanePos = pos
return
def setOutlinePanePos(self, pos: list[int]) -> None:
"""Set the position of the outline details splitter."""
- self._outlnPanePos = [int(x/self.guiScale) for x in pos]
+ self._outlnPanePos = pos
return
def setLastPath(self, key: str, path: str | Path) -> None:
@@ -385,13 +380,12 @@ class Config:
self.guiFont = fontMatcher(font)
else:
font = QFont()
- fontDB = QFontDatabase()
- if self.osWindows and "Arial" in fontDB.families():
+ if self.osWindows and "Arial" in QFontDatabase.families():
# On Windows we default to Arial if possible
font.setFamily("Arial")
font.setPointSize(10)
else:
- font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont)
+ font = QFontDatabase.systemFont(QFontDatabase.SystemFont.GeneralFont)
self.guiFont = fontMatcher(font)
logger.debug("GUI font set to: %s", describeFont(font))
QApplication.setFont(self.guiFont)
@@ -408,8 +402,7 @@ class Config:
font.fromString(value)
self.textFont = fontMatcher(font)
else:
- fontDB = QFontDatabase()
- fontFam = fontDB.families()
+ fontFam = QFontDatabase.families()
if self.osWindows and "Arial" in fontFam:
font = QFont()
font.setFamily("Arial")
@@ -419,7 +412,7 @@ class Config:
font.setFamily("Helvetica")
font.setPointSize(12)
else:
- font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont)
+ font = QFontDatabase.systemFont(QFontDatabase.SystemFont.GeneralFont)
self.textFont = fontMatcher(font)
logger.debug("Text font set to: %s", describeFont(self.textFont))
return
@@ -429,12 +422,12 @@ class Config:
##
def pxInt(self, value: int) -> int:
- """Scale fixed gui sizes by the screen scale factor."""
- return int(value*self.guiScale)
+ """Deprecated. Do not use."""
+ return value
def rpxInt(self, value: int) -> int:
- """Un-scale fixed gui sizes by the screen scale factor."""
- return int(value/self.guiScale)
+ """Deprecated. Do not use."""
+ return value
def homePath(self) -> Path:
"""The user's home folder."""
diff --git a/novelwriter/constants.py b/novelwriter/constants.py
index 36099c38..daf4e9e8 100644
--- a/novelwriter/constants.py
+++ b/novelwriter/constants.py
@@ -23,7 +23,7 @@ along with this program. If not, see .
"""
from __future__ import annotations
-from PyQt5.QtCore import QT_TRANSLATE_NOOP, QCoreApplication
+from PyQt6.QtCore import QT_TRANSLATE_NOOP, QCoreApplication
from novelwriter.enum import (
nwBuildFmt, nwComment, nwItemClass, nwItemLayout, nwOutline, nwStatusShape
diff --git a/novelwriter/core/buildsettings.py b/novelwriter/core/buildsettings.py
index 0d2d3b44..7c62c5b9 100644
--- a/novelwriter/core/buildsettings.py
+++ b/novelwriter/core/buildsettings.py
@@ -32,7 +32,7 @@ from collections.abc import Iterable
from enum import Enum
from pathlib import Path
-from PyQt5.QtCore import QT_TRANSLATE_NOOP, QCoreApplication
+from PyQt6.QtCore import QT_TRANSLATE_NOOP, QCoreApplication
from novelwriter import CONFIG
from novelwriter.common import checkUuid, isHandle, jsonEncode
diff --git a/novelwriter/core/coretools.py b/novelwriter/core/coretools.py
index d8432449..f118ac7a 100644
--- a/novelwriter/core/coretools.py
+++ b/novelwriter/core/coretools.py
@@ -35,7 +35,7 @@ from functools import partial
from pathlib import Path
from zipfile import ZipFile, is_zipfile
-from PyQt5.QtCore import QCoreApplication
+from PyQt6.QtCore import QCoreApplication
from novelwriter import CONFIG, SHARED
from novelwriter.common import isHandle, minmax, simplified
diff --git a/novelwriter/core/docbuild.py b/novelwriter/core/docbuild.py
index b2431567..2f775fa5 100644
--- a/novelwriter/core/docbuild.py
+++ b/novelwriter/core/docbuild.py
@@ -28,7 +28,7 @@ import logging
from collections.abc import Iterable
from pathlib import Path
-from PyQt5.QtGui import QFont
+from PyQt6.QtGui import QFont
from novelwriter import CONFIG
from novelwriter.constants import nwLabels
diff --git a/novelwriter/core/item.py b/novelwriter/core/item.py
index 6cf0ec90..38a2dbcc 100644
--- a/novelwriter/core/item.py
+++ b/novelwriter/core/item.py
@@ -27,7 +27,7 @@ import logging
from typing import TYPE_CHECKING, Any
-from PyQt5.QtGui import QFont, QIcon
+from PyQt6.QtGui import QFont, QIcon
from novelwriter import CONFIG, SHARED
from novelwriter.common import (
diff --git a/novelwriter/core/itemmodel.py b/novelwriter/core/itemmodel.py
index 8910e2b4..82b16e3e 100644
--- a/novelwriter/core/itemmodel.py
+++ b/novelwriter/core/itemmodel.py
@@ -28,8 +28,8 @@ import logging
from typing import TYPE_CHECKING
-from PyQt5.QtCore import QAbstractItemModel, QMimeData, QModelIndex, Qt
-from PyQt5.QtGui import QFont, QIcon
+from PyQt6.QtCore import QAbstractItemModel, QMimeData, QModelIndex, Qt
+from PyQt6.QtGui import QFont, QIcon
from novelwriter.common import decodeMimeHandles, encodeMimeHandles, minmax
from novelwriter.constants import nwConst
diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py
index 50bf4a94..3999371c 100644
--- a/novelwriter/core/project.py
+++ b/novelwriter/core/project.py
@@ -31,7 +31,7 @@ from functools import partial
from pathlib import Path
from time import time
-from PyQt5.QtCore import QCoreApplication
+from PyQt6.QtCore import QCoreApplication
from novelwriter import CONFIG, SHARED, __hexversion__, __version__
from novelwriter.common import (
diff --git a/novelwriter/core/spellcheck.py b/novelwriter/core/spellcheck.py
index dea46048..87b701ba 100644
--- a/novelwriter/core/spellcheck.py
+++ b/novelwriter/core/spellcheck.py
@@ -31,7 +31,7 @@ from collections.abc import Iterator
from pathlib import Path
from typing import TYPE_CHECKING
-from PyQt5.QtCore import QLocale
+from PyQt6.QtCore import QLocale
from novelwriter.constants import nwFiles
from novelwriter.error import logException
diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py
index 24b57aa6..92c78d50 100644
--- a/novelwriter/core/status.py
+++ b/novelwriter/core/status.py
@@ -31,8 +31,8 @@ import random
from collections.abc import Iterable
from typing import Literal, TypeGuard
-from PyQt5.QtCore import QPointF, Qt
-from PyQt5.QtGui import QColor, QIcon, QPainter, QPainterPath, QPixmap, QPolygonF
+from PyQt6.QtCore import QPointF, Qt
+from PyQt6.QtGui import QColor, QIcon, QPainter, QPainterPath, QPixmap, QPolygonF
from novelwriter import SHARED
from novelwriter.common import simplified
diff --git a/novelwriter/core/tree.py b/novelwriter/core/tree.py
index c217f942..002ff811 100644
--- a/novelwriter/core/tree.py
+++ b/novelwriter/core/tree.py
@@ -31,7 +31,7 @@ from collections.abc import Iterable, Iterator
from pathlib import Path
from typing import TYPE_CHECKING, Literal, overload
-from PyQt5.QtCore import QModelIndex
+from PyQt6.QtCore import QModelIndex
from novelwriter import SHARED
from novelwriter.constants import nwFiles, nwLabels, trConst
diff --git a/novelwriter/dialogs/about.py b/novelwriter/dialogs/about.py
index a7d921da..efc61388 100644
--- a/novelwriter/dialogs/about.py
+++ b/novelwriter/dialogs/about.py
@@ -25,8 +25,8 @@ from __future__ import annotations
import logging
-from PyQt5.QtGui import QCloseEvent, QColor
-from PyQt5.QtWidgets import (
+from PyQt6.QtGui import QCloseEvent
+from PyQt6.QtWidgets import (
QDialogButtonBox, QHBoxLayout, QLabel, QTextBrowser, QVBoxLayout, QWidget
)
@@ -58,7 +58,6 @@ class GuiAbout(NDialog):
# Logo and Banner
self.nwImage = SHARED.theme.loadDecoration("nw-text", h=nwH)
- self.bgColor = QColor(255, 255, 255) if SHARED.theme.isLightTheme else QColor(54, 54, 54)
self.nwLogo = QLabel(self)
self.nwLogo.setPixmap(SHARED.theme.getPixmap("novelwriter", (nwPx, nwPx)))
diff --git a/novelwriter/dialogs/docmerge.py b/novelwriter/dialogs/docmerge.py
index 8082e8d9..4d1d529f 100644
--- a/novelwriter/dialogs/docmerge.py
+++ b/novelwriter/dialogs/docmerge.py
@@ -26,8 +26,8 @@ from __future__ import annotations
import logging
-from PyQt5.QtCore import Qt, pyqtSlot
-from PyQt5.QtWidgets import (
+from PyQt6.QtCore import Qt, pyqtSlot
+from PyQt6.QtWidgets import (
QAbstractItemView, QDialogButtonBox, QGridLayout, QLabel, QListWidget,
QListWidgetItem, QVBoxLayout, QWidget
)
diff --git a/novelwriter/dialogs/docsplit.py b/novelwriter/dialogs/docsplit.py
index 80fd6418..259125a6 100644
--- a/novelwriter/dialogs/docsplit.py
+++ b/novelwriter/dialogs/docsplit.py
@@ -26,8 +26,8 @@ from __future__ import annotations
import logging
-from PyQt5.QtCore import pyqtSlot
-from PyQt5.QtWidgets import (
+from PyQt6.QtCore import pyqtSlot
+from PyQt6.QtWidgets import (
QAbstractItemView, QComboBox, QDialogButtonBox, QGridLayout, QLabel,
QListWidget, QListWidgetItem, QVBoxLayout, QWidget
)
diff --git a/novelwriter/dialogs/editlabel.py b/novelwriter/dialogs/editlabel.py
index 529caf21..9bc4c2b4 100644
--- a/novelwriter/dialogs/editlabel.py
+++ b/novelwriter/dialogs/editlabel.py
@@ -25,7 +25,7 @@ from __future__ import annotations
import logging
-from PyQt5.QtWidgets import QDialogButtonBox, QHBoxLayout, QLabel, QLineEdit, QVBoxLayout, QWidget
+from PyQt6.QtWidgets import QDialogButtonBox, QHBoxLayout, QLabel, QLineEdit, QVBoxLayout, QWidget
from novelwriter import CONFIG
from novelwriter.extensions.modified import NDialog
diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py
index a272dab5..8e3d65f1 100644
--- a/novelwriter/dialogs/preferences.py
+++ b/novelwriter/dialogs/preferences.py
@@ -26,9 +26,9 @@ from __future__ import annotations
import logging
-from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
-from PyQt5.QtGui import QCloseEvent, QKeyEvent, QKeySequence
-from PyQt5.QtWidgets import (
+from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot
+from PyQt6.QtGui import QCloseEvent, QKeyEvent, QKeySequence
+from PyQt6.QtWidgets import (
QCompleter, QDialogButtonBox, QFileDialog, QHBoxLayout, QLineEdit,
QPushButton, QVBoxLayout, QWidget
)
diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py
index e9572157..222a8c77 100644
--- a/novelwriter/dialogs/projectsettings.py
+++ b/novelwriter/dialogs/projectsettings.py
@@ -29,9 +29,9 @@ import logging
from pathlib import Path
-from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
-from PyQt5.QtGui import QCloseEvent, QColor
-from PyQt5.QtWidgets import (
+from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot
+from PyQt6.QtGui import QCloseEvent, QColor
+from PyQt6.QtWidgets import (
QAbstractItemView, QApplication, QColorDialog, QDialogButtonBox,
QFileDialog, QGridLayout, QHBoxLayout, QLineEdit, QMenu, QStackedWidget,
QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
diff --git a/novelwriter/dialogs/quotes.py b/novelwriter/dialogs/quotes.py
index 2d5c489e..373f8ca5 100644
--- a/novelwriter/dialogs/quotes.py
+++ b/novelwriter/dialogs/quotes.py
@@ -25,9 +25,9 @@ from __future__ import annotations
import logging
-from PyQt5.QtCore import QSize, pyqtSlot
-from PyQt5.QtGui import QFontMetrics
-from PyQt5.QtWidgets import (
+from PyQt6.QtCore import QSize, pyqtSlot
+from PyQt6.QtGui import QFontMetrics
+from PyQt6.QtWidgets import (
QDialogButtonBox, QFrame, QHBoxLayout, QLabel, QListWidget,
QListWidgetItem, QVBoxLayout, QWidget
)
diff --git a/novelwriter/dialogs/wordlist.py b/novelwriter/dialogs/wordlist.py
index 5ecd8c1d..1988b79b 100644
--- a/novelwriter/dialogs/wordlist.py
+++ b/novelwriter/dialogs/wordlist.py
@@ -27,9 +27,9 @@ import logging
from pathlib import Path
-from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
-from PyQt5.QtGui import QCloseEvent
-from PyQt5.QtWidgets import (
+from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot
+from PyQt6.QtGui import QCloseEvent
+from PyQt6.QtWidgets import (
QAbstractItemView, QApplication, QDialogButtonBox, QFileDialog,
QHBoxLayout, QLineEdit, QListWidget, QVBoxLayout, QWidget
)
diff --git a/novelwriter/error.py b/novelwriter/error.py
index bdca704b..6f272c0b 100644
--- a/novelwriter/error.py
+++ b/novelwriter/error.py
@@ -29,9 +29,9 @@ import sys
from typing import TYPE_CHECKING
-from PyQt5.QtCore import Qt, pyqtSlot
-from PyQt5.QtGui import QFont, QFontDatabase
-from PyQt5.QtWidgets import (
+from PyQt6.QtCore import Qt, pyqtSlot
+from PyQt6.QtGui import QFont, QFontDatabase
+from PyQt6.QtWidgets import (
QApplication, QDialog, QDialogButtonBox, QGridLayout, QLabel,
QPlainTextEdit, QStyle, QWidget
)
@@ -118,7 +118,7 @@ class NWErrorMessage(QDialog):
"""
from traceback import format_tb
- from PyQt5.QtCore import PYQT_VERSION_STR, QT_VERSION_STR, QSysInfo
+ from PyQt6.QtCore import PYQT_VERSION_STR, QT_VERSION_STR, QSysInfo
from novelwriter import __version__
from novelwriter.constants import nwConst
@@ -174,7 +174,7 @@ def exceptionHandler(exType: type, exValue: BaseException, exTrace: TracebackTyp
"""Function to catch unhandled global exceptions."""
from traceback import print_tb
- from PyQt5.QtWidgets import QApplication
+ from PyQt6.QtWidgets import QApplication
logger.critical("%s: %s", exType.__name__, str(exValue))
print_tb(exTrace)
diff --git a/novelwriter/extensions/configlayout.py b/novelwriter/extensions/configlayout.py
index 6582612a..47b74175 100644
--- a/novelwriter/extensions/configlayout.py
+++ b/novelwriter/extensions/configlayout.py
@@ -27,8 +27,8 @@ along with this program. If not, see .
"""
from __future__ import annotations
-from PyQt5.QtGui import QColor, QFont, QPalette, QPixmap
-from PyQt5.QtWidgets import (
+from PyQt6.QtGui import QColor, QFont, QPalette, QPixmap
+from PyQt6.QtWidgets import (
QAbstractButton, QFrame, QHBoxLayout, QLabel, QLayout, QScrollArea,
QVBoxLayout, QWidget
)
diff --git a/novelwriter/extensions/eventfilters.py b/novelwriter/extensions/eventfilters.py
index 3c2e2aa8..f305db6c 100644
--- a/novelwriter/extensions/eventfilters.py
+++ b/novelwriter/extensions/eventfilters.py
@@ -24,9 +24,9 @@ along with this program. If not, see .
"""
from __future__ import annotations
-from PyQt5.QtCore import QEvent, QObject
-from PyQt5.QtGui import QStatusTipEvent, QWheelEvent
-from PyQt5.QtWidgets import QWidget
+from PyQt6.QtCore import QEvent, QObject
+from PyQt6.QtGui import QStatusTipEvent, QWheelEvent
+from PyQt6.QtWidgets import QWidget
class WheelEventFilter(QObject):
diff --git a/novelwriter/extensions/modified.py b/novelwriter/extensions/modified.py
index 5471da29..67aa7453 100644
--- a/novelwriter/extensions/modified.py
+++ b/novelwriter/extensions/modified.py
@@ -30,9 +30,9 @@ from __future__ import annotations
from enum import Enum
from typing import TYPE_CHECKING
-from PyQt5.QtCore import QSize, Qt, pyqtSignal, pyqtSlot
-from PyQt5.QtGui import QMouseEvent, QWheelEvent
-from PyQt5.QtWidgets import (
+from PyQt6.QtCore import QSize, Qt, pyqtSignal, pyqtSlot
+from PyQt6.QtGui import QMouseEvent, QWheelEvent
+from PyQt6.QtWidgets import (
QApplication, QComboBox, QDialog, QDoubleSpinBox, QLabel, QSpinBox,
QToolButton, QWidget
)
diff --git a/novelwriter/extensions/novelselector.py b/novelwriter/extensions/novelselector.py
index b604bd22..f568f4ea 100644
--- a/novelwriter/extensions/novelselector.py
+++ b/novelwriter/extensions/novelselector.py
@@ -25,8 +25,8 @@ from __future__ import annotations
import logging
-from PyQt5.QtCore import pyqtSignal, pyqtSlot
-from PyQt5.QtWidgets import QComboBox, QWidget
+from PyQt6.QtCore import pyqtSignal, pyqtSlot
+from PyQt6.QtWidgets import QComboBox, QWidget
from novelwriter import SHARED
from novelwriter.constants import nwLabels
diff --git a/novelwriter/extensions/pagedsidebar.py b/novelwriter/extensions/pagedsidebar.py
index 319cfaaf..6dd835f3 100644
--- a/novelwriter/extensions/pagedsidebar.py
+++ b/novelwriter/extensions/pagedsidebar.py
@@ -25,11 +25,11 @@ along with this program. If not, see .
"""
from __future__ import annotations
-from PyQt5.QtCore import QPoint, QRectF, QSize, Qt, pyqtSignal, pyqtSlot
-from PyQt5.QtGui import QColor, QPainter, QPaintEvent, QPolygon
-from PyQt5.QtWidgets import (
- QAbstractButton, QAction, QButtonGroup, QLabel, QStyle,
- QStyleOptionToolButton, QToolBar, QToolButton, QWidget
+from PyQt6.QtCore import QPoint, QRectF, QSize, Qt, pyqtSignal, pyqtSlot
+from PyQt6.QtGui import QAction, QColor, QPainter, QPaintEvent, QPolygon
+from PyQt6.QtWidgets import (
+ QAbstractButton, QButtonGroup, QLabel, QStyle, QStyleOptionToolButton,
+ QToolBar, QToolButton, QWidget
)
from novelwriter.types import (
diff --git a/novelwriter/extensions/progressbars.py b/novelwriter/extensions/progressbars.py
index 1c7684fb..358501ac 100644
--- a/novelwriter/extensions/progressbars.py
+++ b/novelwriter/extensions/progressbars.py
@@ -26,9 +26,9 @@ from __future__ import annotations
from math import ceil
-from PyQt5.QtCore import QRect
-from PyQt5.QtGui import QBrush, QColor, QPainter, QPaintEvent, QPen
-from PyQt5.QtWidgets import QProgressBar, QWidget
+from PyQt6.QtCore import QRect
+from PyQt6.QtGui import QBrush, QColor, QPainter, QPaintEvent, QPen
+from PyQt6.QtWidgets import QProgressBar, QWidget
from novelwriter.types import (
QtAlignCenter, QtPaintAntiAlias, QtRoundCap, QtSizeFixed, QtSolidLine,
diff --git a/novelwriter/extensions/statusled.py b/novelwriter/extensions/statusled.py
index e9a88104..f4377823 100644
--- a/novelwriter/extensions/statusled.py
+++ b/novelwriter/extensions/statusled.py
@@ -25,8 +25,8 @@ from __future__ import annotations
import logging
-from PyQt5.QtGui import QColor, QPainter, QPaintEvent
-from PyQt5.QtWidgets import QAbstractButton, QWidget
+from PyQt6.QtGui import QColor, QPainter, QPaintEvent
+from PyQt6.QtWidgets import QAbstractButton, QWidget
from novelwriter import CONFIG
from novelwriter.enum import nwTrinary
diff --git a/novelwriter/extensions/switch.py b/novelwriter/extensions/switch.py
index 43d60fc6..2b320506 100644
--- a/novelwriter/extensions/switch.py
+++ b/novelwriter/extensions/switch.py
@@ -23,9 +23,9 @@ along with this program. If not, see .
"""
from __future__ import annotations
-from PyQt5.QtCore import QEvent, QPropertyAnimation, Qt, pyqtProperty
-from PyQt5.QtGui import QMouseEvent, QPainter, QPaintEvent, QResizeEvent
-from PyQt5.QtWidgets import QAbstractButton, QWidget
+from PyQt6.QtCore import QPropertyAnimation, Qt, pyqtProperty
+from PyQt6.QtGui import QEnterEvent, QMouseEvent, QPainter, QPaintEvent, QResizeEvent
+from PyQt6.QtWidgets import QAbstractButton, QWidget
from novelwriter import CONFIG, SHARED
from novelwriter.types import QtMouseLeft, QtNoPen, QtPaintAntiAlias, QtSizeFixed
@@ -57,11 +57,11 @@ class NSwitch(QAbstractButton):
# Properties
##
- @pyqtProperty(int) # type: ignore
+ @pyqtProperty(int)
def offset(self) -> int: # type: ignore
return self._offset
- @offset.setter # type: ignore
+ @offset.setter
def offset(self, offset: int) -> None:
self._offset = offset
self.update()
@@ -98,14 +98,14 @@ class NSwitch(QAbstractButton):
trackBrush = palette.highlight()
thumbBrush = palette.highlightedText()
else:
- trackBrush = palette.dark()
+ trackBrush = palette.mid()
thumbBrush = palette.light()
if self.isEnabled():
trackOpacity = 1.0
else:
trackOpacity = 0.6
- trackBrush = palette.shadow()
+ trackBrush = palette.dark()
thumbBrush = palette.mid()
painter.setBrush(trackBrush)
@@ -114,6 +114,7 @@ class NSwitch(QAbstractButton):
painter.setBrush(thumbBrush)
painter.drawEllipse(self._offset - self._rR, self._rB, self._rH, self._rH)
+ painter.end()
return
@@ -128,7 +129,7 @@ class NSwitch(QAbstractButton):
anim.start()
return
- def enterEvent(self, event: QEvent) -> None:
+ def enterEvent(self, event: QEnterEvent) -> None:
"""Change the cursor when hovering the button."""
self.setCursor(Qt.CursorShape.PointingHandCursor)
super().enterEvent(event)
diff --git a/novelwriter/extensions/switchbox.py b/novelwriter/extensions/switchbox.py
index ac11d083..9acc0cc9 100644
--- a/novelwriter/extensions/switchbox.py
+++ b/novelwriter/extensions/switchbox.py
@@ -23,9 +23,9 @@ along with this program. If not, see .
"""
from __future__ import annotations
-from PyQt5.QtCore import pyqtSignal
-from PyQt5.QtGui import QIcon
-from PyQt5.QtWidgets import QGridLayout, QLabel, QScrollArea, QWidget
+from PyQt6.QtCore import pyqtSignal
+from PyQt6.QtGui import QIcon
+from PyQt6.QtWidgets import QGridLayout, QLabel, QScrollArea, QWidget
from novelwriter.extensions.switch import NSwitch
from novelwriter.types import (
diff --git a/novelwriter/extensions/versioninfo.py b/novelwriter/extensions/versioninfo.py
index 22ecd251..c44ce820 100644
--- a/novelwriter/extensions/versioninfo.py
+++ b/novelwriter/extensions/versioninfo.py
@@ -31,9 +31,9 @@ from time import sleep
from urllib.error import HTTPError
from urllib.request import Request, urlopen
-from PyQt5.QtCore import QObject, QRunnable, QUrl, pyqtSignal, pyqtSlot
-from PyQt5.QtGui import QDesktopServices
-from PyQt5.QtWidgets import QLabel, QVBoxLayout, QWidget
+from PyQt6.QtCore import QObject, QRunnable, QUrl, pyqtSignal, pyqtSlot
+from PyQt6.QtGui import QDesktopServices
+from PyQt6.QtWidgets import QLabel, QVBoxLayout, QWidget
from novelwriter import CONFIG, SHARED, __date__, __domain__, __version__
from novelwriter.common import formatVersion
diff --git a/novelwriter/formats/shared.py b/novelwriter/formats/shared.py
index b1c0f288..fecafea9 100644
--- a/novelwriter/formats/shared.py
+++ b/novelwriter/formats/shared.py
@@ -27,7 +27,9 @@ import re
from enum import Flag, IntEnum
-from PyQt5.QtGui import QColor
+from PyQt6.QtGui import QColor
+
+from novelwriter.types import nwDataClass
ESCAPES = {r"\*": "*", r"\~": "~", r"\_": "_", r"\[": "[", r"\]": "]", r"\ ": ""}
RX_ESC = re.compile("|".join([re.escape(k) for k in ESCAPES.keys()]), flags=re.DOTALL)
@@ -40,6 +42,7 @@ def stripEscape(text: str) -> str:
return text
+@nwDataClass
class TextDocumentTheme:
"""Default document theme."""
diff --git a/novelwriter/formats/todocx.py b/novelwriter/formats/todocx.py
index 694ffac0..f3df8a31 100644
--- a/novelwriter/formats/todocx.py
+++ b/novelwriter/formats/todocx.py
@@ -33,8 +33,8 @@ from pathlib import Path
from typing import NamedTuple
from zipfile import ZIP_DEFLATED, ZipFile
-from PyQt5.QtCore import QMargins, QSize
-from PyQt5.QtGui import QColor
+from PyQt6.QtCore import QMargins, QSize
+from PyQt6.QtGui import QColor
from novelwriter import __version__
from novelwriter.common import firstFloat, xmlElement, xmlSubElem
diff --git a/novelwriter/formats/tokenizer.py b/novelwriter/formats/tokenizer.py
index 70d1de0c..b254400b 100644
--- a/novelwriter/formats/tokenizer.py
+++ b/novelwriter/formats/tokenizer.py
@@ -31,8 +31,8 @@ from abc import ABC, abstractmethod
from pathlib import Path
from typing import NamedTuple
-from PyQt5.QtCore import QLocale
-from PyQt5.QtGui import QColor, QFont
+from PyQt6.QtCore import QLocale
+from PyQt6.QtGui import QColor, QFont
from novelwriter import CONFIG
from novelwriter.common import checkInt, fontMatcher, numberToRoman
diff --git a/novelwriter/formats/toodt.py b/novelwriter/formats/toodt.py
index 442b8284..347c1235 100644
--- a/novelwriter/formats/toodt.py
+++ b/novelwriter/formats/toodt.py
@@ -35,7 +35,7 @@ from hashlib import sha256
from pathlib import Path
from zipfile import ZIP_DEFLATED, ZipFile
-from PyQt5.QtGui import QColor, QFont
+from PyQt6.QtGui import QColor, QFont
from novelwriter import __version__
from novelwriter.common import xmlElement, xmlIndent, xmlSubElem
diff --git a/novelwriter/formats/toqdoc.py b/novelwriter/formats/toqdoc.py
index 8d9c8f6e..13647988 100644
--- a/novelwriter/formats/toqdoc.py
+++ b/novelwriter/formats/toqdoc.py
@@ -27,12 +27,12 @@ import logging
from pathlib import Path
-from PyQt5.QtCore import QMarginsF, QSizeF
-from PyQt5.QtGui import (
- QColor, QFont, QFontDatabase, QPageSize, QTextBlockFormat, QTextCharFormat,
- QTextCursor, QTextDocument, QTextFrameFormat
+from PyQt6.QtCore import QMarginsF, QSizeF
+from PyQt6.QtGui import (
+ QColor, QFont, QFontDatabase, QPageLayout, QPageSize, QTextBlockFormat,
+ QTextCharFormat, QTextCursor, QTextDocument, QTextFrameFormat
)
-from PyQt5.QtPrintSupport import QPrinter
+from PyQt6.QtPrintSupport import QPrinter
from novelwriter import __version__
from novelwriter.constants import nwStyles, nwUnicode
@@ -129,10 +129,9 @@ class ToQTextDocument(Tokenizer):
super().initDocument()
if pdf:
- fontDB = QFontDatabase()
family = self._textFont.family()
style = self._textFont.styleName()
- self._dpi = 1200 if fontDB.isScalable(family, style) else 72
+ self._dpi = 1200 if QFontDatabase.isScalable(family, style) else 72
self._document.setUndoRedoEnabled(False)
self._document.blockSignals(True)
@@ -268,7 +267,6 @@ class ToQTextDocument(Tokenizer):
def saveDocument(self, path: Path) -> None:
"""Save the document as a PDF file."""
- m = self._pageMargins
logger.info("Writing PDF at %d DPI", self._dpi)
printer = QPrinter(QPrinter.PrinterMode.HighResolution)
@@ -277,11 +275,11 @@ class ToQTextDocument(Tokenizer):
printer.setResolution(self._dpi)
printer.setOutputFormat(QPrinter.OutputFormat.PdfFormat)
printer.setPageSize(self._pageSize)
- printer.setPageMargins(m.left(), m.top(), m.right(), m.bottom(), QPrinter.Unit.Millimeter)
+ printer.setPageMargins(self._pageMargins, QPageLayout.Unit.Millimeter)
printer.setOutputFileName(str(path))
self._document.documentLayout().setPaintDevice(printer)
- self._document.setPageSize(QSizeF(printer.pageRect().size()))
+ self._document.setPageSize(printer.pageRect(QPrinter.Unit.Millimeter).size())
self._document.print(printer)
return
diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py
index e7abcdb2..1a4f7c4d 100644
--- a/novelwriter/gui/doceditor.py
+++ b/novelwriter/gui/doceditor.py
@@ -37,18 +37,18 @@ import logging
from enum import Enum
from time import time
-from PyQt5.QtCore import (
+from PyQt6.QtCore import (
QObject, QPoint, QRegularExpression, QRunnable, Qt, QTimer, pyqtSignal,
pyqtSlot
)
-from PyQt5.QtGui import (
- QColor, QCursor, QDragEnterEvent, QDragMoveEvent, QDropEvent, QKeyEvent,
- QKeySequence, QMouseEvent, QPalette, QPixmap, QResizeEvent, QTextBlock,
- QTextCursor, QTextDocument, QTextOption
+from PyQt6.QtGui import (
+ QAction, QColor, QCursor, QDragEnterEvent, QDragMoveEvent, QDropEvent,
+ QKeyEvent, QKeySequence, QMouseEvent, QPalette, QPixmap, QResizeEvent,
+ QShortcut, QTextBlock, QTextCursor, QTextDocument, QTextOption
)
-from PyQt5.QtWidgets import (
- QAction, QApplication, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit,
- QMenu, QPlainTextEdit, QShortcut, QToolBar, QVBoxLayout, QWidget
+from PyQt6.QtWidgets import (
+ QApplication, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMenu,
+ QPlainTextEdit, QToolBar, QVBoxLayout, QWidget
)
from novelwriter import CONFIG, SHARED
@@ -287,16 +287,18 @@ class GuiDocEditor(QPlainTextEdit):
def updateSyntaxColours(self) -> None:
"""Update the syntax highlighting theme."""
- mainPalette = self.palette()
- mainPalette.setColor(QPalette.ColorRole.Window, SHARED.theme.colBack)
- mainPalette.setColor(QPalette.ColorRole.Base, SHARED.theme.colBack)
- mainPalette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
- self.setPalette(mainPalette)
+ syntax = SHARED.theme.syntaxTheme
- docPalette = self.viewport().palette()
- docPalette.setColor(QPalette.ColorRole.Base, SHARED.theme.colBack)
- docPalette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
- self.viewport().setPalette(docPalette)
+ palette = self.palette()
+ palette.setColor(QPalette.ColorRole.Window, syntax.back)
+ palette.setColor(QPalette.ColorRole.Base, syntax.back)
+ palette.setColor(QPalette.ColorRole.Text, syntax.text)
+ self.setPalette(palette)
+
+ palette = self.viewport().palette()
+ palette.setColor(QPalette.ColorRole.Base, syntax.back)
+ palette.setColor(QPalette.ColorRole.Text, syntax.text)
+ self.viewport().setPalette(palette)
self.docHeader.matchColours()
self.docFooter.matchColours()
@@ -2032,8 +2034,9 @@ class GuiDocEditor(QPlainTextEdit):
return cursor
- def _makeSelection(self, mode: QTextCursor.SelectionType,
- cursor: QTextCursor | None = None) -> None:
+ def _makeSelection(
+ self, mode: QTextCursor.SelectionType, cursor: QTextCursor | None = None
+ ) -> None:
"""Select text based on selection mode."""
if cursor is None:
cursor = self.textCursor()
@@ -2432,10 +2435,12 @@ class GuiDocToolBar(QWidget):
def updateTheme(self) -> None:
"""Initialise GUI elements that depend on specific settings."""
- palette = QPalette()
- palette.setColor(QPalette.ColorRole.Window, SHARED.theme.colBack)
- palette.setColor(QPalette.ColorRole.WindowText, SHARED.theme.colText)
- palette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
+ syntax = SHARED.theme.syntaxTheme
+
+ palette = self.palette()
+ palette.setColor(QPalette.ColorRole.Window, syntax.back)
+ palette.setColor(QPalette.ColorRole.WindowText, syntax.text)
+ palette.setColor(QPalette.ColorRole.Text, syntax.text)
self.setPalette(palette)
self.tbBoldMD.setThemeIcon("fmt_bold", "orange")
@@ -2975,10 +2980,11 @@ class GuiDocEditHeader(QWidget):
"""Update the colours of the widget to match those of the syntax
theme rather than the main GUI.
"""
- palette = QPalette()
- palette.setColor(QPalette.ColorRole.Window, SHARED.theme.colBack)
- palette.setColor(QPalette.ColorRole.WindowText, SHARED.theme.colText)
- palette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
+ syntax = SHARED.theme.syntaxTheme
+ palette = self.palette()
+ palette.setColor(QPalette.ColorRole.Window, syntax.back)
+ palette.setColor(QPalette.ColorRole.WindowText, syntax.text)
+ palette.setColor(QPalette.ColorRole.Text, syntax.text)
self.setPalette(palette)
self.itemTitle.setTextColors(
color=palette.windowText().color(), faded=SHARED.theme.fadedText
@@ -3174,10 +3180,12 @@ class GuiDocEditFooter(QWidget):
"""Update the colours of the widget to match those of the syntax
theme rather than the main GUI.
"""
- palette = QPalette()
- palette.setColor(QPalette.ColorRole.Window, SHARED.theme.colBack)
- palette.setColor(QPalette.ColorRole.WindowText, SHARED.theme.colText)
- palette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
+ syntax = SHARED.theme.syntaxTheme
+
+ palette = self.palette()
+ palette.setColor(QPalette.ColorRole.Window, syntax.back)
+ palette.setColor(QPalette.ColorRole.WindowText, syntax.text)
+ palette.setColor(QPalette.ColorRole.Text, syntax.text)
self.setPalette(palette)
self.statusText.setPalette(palette)
diff --git a/novelwriter/gui/dochighlight.py b/novelwriter/gui/dochighlight.py
index b6f01928..de9d9f27 100644
--- a/novelwriter/gui/dochighlight.py
+++ b/novelwriter/gui/dochighlight.py
@@ -29,8 +29,8 @@ import re
from time import time
-from PyQt5.QtCore import Qt
-from PyQt5.QtGui import (
+from PyQt6.QtCore import Qt
+from PyQt6.QtGui import (
QBrush, QColor, QFont, QSyntaxHighlighter, QTextBlockUserData,
QTextCharFormat, QTextDocument
)
@@ -91,44 +91,45 @@ class GuiDocHighlighter(QSyntaxHighlighter):
rules and building the RegExes.
"""
logger.debug("Setting up highlighting rules")
+ syntax = SHARED.theme.syntaxTheme
- colEmph = SHARED.theme.colEmph if CONFIG.highlightEmph else None
- colBreak = QColor(SHARED.theme.colEmph)
+ colEmph = syntax.emph if CONFIG.highlightEmph else None
+ colBreak = QColor(syntax.emph)
colBreak.setAlpha(64)
# Create Character Formats
- self._addCharFormat("text", SHARED.theme.colText)
- self._addCharFormat("header1", SHARED.theme.colHead, "b", nwStyles.H_SIZES[1])
- self._addCharFormat("header2", SHARED.theme.colHead, "b", nwStyles.H_SIZES[2])
- self._addCharFormat("header3", SHARED.theme.colHead, "b", nwStyles.H_SIZES[3])
- self._addCharFormat("header4", SHARED.theme.colHead, "b", nwStyles.H_SIZES[4])
- self._addCharFormat("head1h", SHARED.theme.colHeadH, "b", nwStyles.H_SIZES[1])
- self._addCharFormat("head2h", SHARED.theme.colHeadH, "b", nwStyles.H_SIZES[2])
- self._addCharFormat("head3h", SHARED.theme.colHeadH, "b", nwStyles.H_SIZES[3])
- self._addCharFormat("head4h", SHARED.theme.colHeadH, "b", nwStyles.H_SIZES[4])
+ self._addCharFormat("text", syntax.text)
+ self._addCharFormat("header1", syntax.head, "b", nwStyles.H_SIZES[1])
+ self._addCharFormat("header2", syntax.head, "b", nwStyles.H_SIZES[2])
+ self._addCharFormat("header3", syntax.head, "b", nwStyles.H_SIZES[3])
+ self._addCharFormat("header4", syntax.head, "b", nwStyles.H_SIZES[4])
+ self._addCharFormat("head1h", syntax.headH, "b", nwStyles.H_SIZES[1])
+ self._addCharFormat("head2h", syntax.headH, "b", nwStyles.H_SIZES[2])
+ self._addCharFormat("head3h", syntax.headH, "b", nwStyles.H_SIZES[3])
+ self._addCharFormat("head4h", syntax.headH, "b", nwStyles.H_SIZES[4])
self._addCharFormat("bold", colEmph, "b")
self._addCharFormat("italic", colEmph, "i")
- self._addCharFormat("strike", SHARED.theme.colHidden, "s")
- self._addCharFormat("mspaces", SHARED.theme.colError, "err")
+ self._addCharFormat("strike", syntax.hidden, "s")
+ self._addCharFormat("mspaces", syntax.error, "err")
self._addCharFormat("nobreak", colBreak, "bg")
- self._addCharFormat("altdialog", SHARED.theme.colDialA)
- self._addCharFormat("dialog", SHARED.theme.colDialN)
- self._addCharFormat("replace", SHARED.theme.colRepTag)
- self._addCharFormat("hidden", SHARED.theme.colHidden)
- self._addCharFormat("markup", SHARED.theme.colHidden)
- self._addCharFormat("link", SHARED.theme.colLink, "u")
- self._addCharFormat("note", SHARED.theme.colNote)
- self._addCharFormat("code", SHARED.theme.colCode)
- self._addCharFormat("keyword", SHARED.theme.colKey)
- self._addCharFormat("tag", SHARED.theme.colTag, "u")
- self._addCharFormat("modifier", SHARED.theme.colMod)
- self._addCharFormat("value", SHARED.theme.colVal)
- self._addCharFormat("optional", SHARED.theme.colOpt)
+ self._addCharFormat("altdialog", syntax.dialA)
+ self._addCharFormat("dialog", syntax.dialN)
+ self._addCharFormat("replace", syntax.repTag)
+ self._addCharFormat("hidden", syntax.hidden)
+ self._addCharFormat("markup", syntax.hidden)
+ self._addCharFormat("link", syntax.link, "u")
+ self._addCharFormat("note", syntax.note)
+ self._addCharFormat("code", syntax.code)
+ self._addCharFormat("keyword", syntax.key)
+ self._addCharFormat("tag", syntax.tag, "u")
+ self._addCharFormat("modifier", syntax.mod)
+ self._addCharFormat("value", syntax.val)
+ self._addCharFormat("optional", syntax.opt)
self._addCharFormat("invalid", None, "err")
# Cache Spell Error Format
self._spellErr = QTextCharFormat()
- self._spellErr.setUnderlineColor(SHARED.theme.colSpell)
+ self._spellErr.setUnderlineColor(syntax.spell)
self._spellErr.setUnderlineStyle(QTextCharFormat.UnderlineStyle.SpellCheckUnderline)
self._txtRules.clear()
@@ -450,7 +451,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
if "s" in styles:
charFormat.setFontStrikeOut(True)
if "err" in styles:
- charFormat.setUnderlineColor(SHARED.theme.colError)
+ charFormat.setUnderlineColor(SHARED.theme.syntaxTheme.error)
charFormat.setUnderlineStyle(QTextCharFormat.UnderlineStyle.SpellCheckUnderline)
if "bg" in styles and color is not None:
charFormat.setBackground(QBrush(color, Qt.BrushStyle.SolidPattern))
diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py
index 6bda315a..bc545b6f 100644
--- a/novelwriter/gui/docviewer.py
+++ b/novelwriter/gui/docviewer.py
@@ -30,14 +30,14 @@ import logging
from enum import Enum
-from PyQt5.QtCore import QPoint, Qt, QUrl, pyqtSignal, pyqtSlot
-from PyQt5.QtGui import (
- QCursor, QDesktopServices, QDragEnterEvent, QDragMoveEvent, QDropEvent,
- QMouseEvent, QPalette, QResizeEvent, QTextCursor
+from PyQt6.QtCore import QPoint, Qt, QUrl, pyqtSignal, pyqtSlot
+from PyQt6.QtGui import (
+ QAction, QCursor, QDesktopServices, QDragEnterEvent, QDragMoveEvent,
+ QDropEvent, QMouseEvent, QPalette, QResizeEvent, QTextCursor
)
-from PyQt5.QtWidgets import (
- QAction, QApplication, QFrame, QHBoxLayout, QMenu, QTextBrowser,
- QToolButton, QWidget
+from PyQt6.QtWidgets import (
+ QApplication, QFrame, QHBoxLayout, QMenu, QTextBrowser, QToolButton,
+ QWidget
)
from novelwriter import CONFIG, SHARED
@@ -155,33 +155,35 @@ class GuiDocViewer(QTextBrowser):
self.docFooter.updateFont()
# Set the widget colours to match syntax theme
- mainPalette = self.palette()
- mainPalette.setColor(QPalette.ColorRole.Window, SHARED.theme.colBack)
- mainPalette.setColor(QPalette.ColorRole.Base, SHARED.theme.colBack)
- mainPalette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
- self.setPalette(mainPalette)
+ syntax = SHARED.theme.syntaxTheme
- docPalette = self.viewport().palette()
- docPalette.setColor(QPalette.ColorRole.Base, SHARED.theme.colBack)
- docPalette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
- self.viewport().setPalette(docPalette)
+ palette = self.palette()
+ palette.setColor(QPalette.ColorRole.Window, syntax.back)
+ palette.setColor(QPalette.ColorRole.Base, syntax.back)
+ palette.setColor(QPalette.ColorRole.Text, syntax.text)
+ self.setPalette(palette)
+
+ palette = self.viewport().palette()
+ palette.setColor(QPalette.ColorRole.Base, syntax.back)
+ palette.setColor(QPalette.ColorRole.Text, syntax.text)
+ self.viewport().setPalette(palette)
self.docHeader.matchColours()
self.docFooter.matchColours()
# Update theme colours
- self._docTheme.text = SHARED.theme.colText
- self._docTheme.highlight = SHARED.theme.colMark
- self._docTheme.head = SHARED.theme.colHead
- self._docTheme.link = SHARED.theme.colLink
- self._docTheme.comment = SHARED.theme.colHidden
- self._docTheme.note = SHARED.theme.colNote
- self._docTheme.code = SHARED.theme.colCode
- self._docTheme.modifier = SHARED.theme.colMod
- self._docTheme.keyword = SHARED.theme.colKey
- self._docTheme.tag = SHARED.theme.colTag
- self._docTheme.optional = SHARED.theme.colOpt
- self._docTheme.dialog = SHARED.theme.colDialN
- self._docTheme.altdialog = SHARED.theme.colDialA
+ self._docTheme.text = syntax.text
+ self._docTheme.highlight = syntax.mark
+ self._docTheme.head = syntax.head
+ self._docTheme.link = syntax.link
+ self._docTheme.comment = syntax.hidden
+ self._docTheme.note = syntax.note
+ self._docTheme.code = syntax.code
+ self._docTheme.modifier = syntax.mod
+ self._docTheme.keyword = syntax.key
+ self._docTheme.tag = syntax.tag
+ self._docTheme.optional = syntax.opt
+ self._docTheme.dialog = syntax.dialN
+ self._docTheme.altdialog = syntax.dialA
# Set default text margins
self.document().setDocumentMargin(0)
@@ -783,10 +785,11 @@ class GuiDocViewHeader(QWidget):
"""Update the colours of the widget to match those of the syntax
theme rather than the main GUI.
"""
- palette = QPalette()
- palette.setColor(QPalette.ColorRole.Window, SHARED.theme.colBack)
- palette.setColor(QPalette.ColorRole.WindowText, SHARED.theme.colText)
- palette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
+ syntax = SHARED.theme.syntaxTheme
+ palette = self.palette()
+ palette.setColor(QPalette.ColorRole.Window, syntax.back)
+ palette.setColor(QPalette.ColorRole.WindowText, syntax.text)
+ palette.setColor(QPalette.ColorRole.Text, syntax.text)
self.setPalette(palette)
self.itemTitle.setTextColors(
color=palette.windowText().color(), faded=SHARED.theme.fadedText
@@ -970,10 +973,11 @@ class GuiDocViewFooter(QWidget):
"""Update the colours of the widget to match those of the syntax
theme rather than the main GUI.
"""
- palette = QPalette()
- palette.setColor(QPalette.ColorRole.Window, SHARED.theme.colBack)
- palette.setColor(QPalette.ColorRole.WindowText, SHARED.theme.colText)
- palette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
+ syntax = SHARED.theme.syntaxTheme
+ palette = self.palette()
+ palette.setColor(QPalette.ColorRole.Window, syntax.back)
+ palette.setColor(QPalette.ColorRole.WindowText, syntax.text)
+ palette.setColor(QPalette.ColorRole.Text, syntax.text)
self.setPalette(palette)
return
diff --git a/novelwriter/gui/docviewerpanel.py b/novelwriter/gui/docviewerpanel.py
index 7bcb52b5..91723b91 100644
--- a/novelwriter/gui/docviewerpanel.py
+++ b/novelwriter/gui/docviewerpanel.py
@@ -27,8 +27,8 @@ import logging
from enum import Enum
-from PyQt5.QtCore import QModelIndex, Qt, pyqtSignal, pyqtSlot
-from PyQt5.QtWidgets import (
+from PyQt6.QtCore import QModelIndex, Qt, pyqtSignal, pyqtSlot
+from PyQt6.QtWidgets import (
QAbstractItemView, QFrame, QMenu, QTabWidget, QToolButton, QTreeWidget,
QTreeWidgetItem, QVBoxLayout, QWidget
)
diff --git a/novelwriter/gui/editordocument.py b/novelwriter/gui/editordocument.py
index e46c01b5..79754656 100644
--- a/novelwriter/gui/editordocument.py
+++ b/novelwriter/gui/editordocument.py
@@ -28,9 +28,9 @@ import logging
from collections.abc import Iterable
from time import time
-from PyQt5.QtCore import QObject, pyqtSlot
-from PyQt5.QtGui import QTextBlock, QTextCursor, QTextDocument
-from PyQt5.QtWidgets import QApplication, QPlainTextDocumentLayout
+from PyQt6.QtCore import QObject, pyqtSlot
+from PyQt6.QtGui import QTextBlock, QTextCursor, QTextDocument
+from PyQt6.QtWidgets import QApplication, QPlainTextDocumentLayout
from novelwriter import SHARED
from novelwriter.gui.dochighlight import GuiDocHighlighter, TextBlockData
diff --git a/novelwriter/gui/itemdetails.py b/novelwriter/gui/itemdetails.py
index d6435bdf..8cc264b7 100644
--- a/novelwriter/gui/itemdetails.py
+++ b/novelwriter/gui/itemdetails.py
@@ -27,8 +27,8 @@ import logging
from enum import Enum
-from PyQt5.QtCore import pyqtSlot
-from PyQt5.QtWidgets import QGridLayout, QLabel, QWidget
+from PyQt6.QtCore import pyqtSlot
+from PyQt6.QtWidgets import QGridLayout, QLabel, QWidget
from novelwriter import CONFIG, SHARED
from novelwriter.common import elide
diff --git a/novelwriter/gui/mainmenu.py b/novelwriter/gui/mainmenu.py
index 02527c2b..f477217e 100644
--- a/novelwriter/gui/mainmenu.py
+++ b/novelwriter/gui/mainmenu.py
@@ -28,8 +28,9 @@ import logging
from pathlib import Path
from typing import TYPE_CHECKING
-from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
-from PyQt5.QtWidgets import QAction, QMenuBar
+from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot
+from PyQt6.QtGui import QAction
+from PyQt6.QtWidgets import QMenuBar
from novelwriter import CONFIG, SHARED
from novelwriter.common import openExternalPath, qtLambda
@@ -1020,8 +1021,8 @@ class GuiMainMenu(QMenuBar):
self.aAboutNW.setMenuRole(QAction.MenuRole.AboutRole)
self.aAboutNW.triggered.connect(self.mainGui.showAboutNWDialog)
- # Help > About Qt5
- self.aAboutQt = self.helpMenu.addAction(self.tr("About Qt5"))
+ # Help > About Qt
+ self.aAboutQt = self.helpMenu.addAction(self.tr("About Qt"))
self.aAboutQt.setMenuRole(QAction.MenuRole.AboutQtRole)
self.aAboutQt.triggered.connect(self.mainGui.showAboutQtDialog)
diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py
index 190bcafd..e2e321c5 100644
--- a/novelwriter/gui/noveltree.py
+++ b/novelwriter/gui/noveltree.py
@@ -30,11 +30,11 @@ import logging
from enum import Enum
from time import time
-from PyQt5.QtCore import QModelIndex, QPoint, Qt, pyqtSignal, pyqtSlot
-from PyQt5.QtGui import QFocusEvent, QFont, QMouseEvent, QPalette, QResizeEvent
-from PyQt5.QtWidgets import (
- QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QInputDialog, QMenu,
- QToolTip, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
+from PyQt6.QtCore import QModelIndex, QPoint, Qt, pyqtSignal, pyqtSlot
+from PyQt6.QtGui import QActionGroup, QFocusEvent, QFont, QMouseEvent, QPalette, QResizeEvent
+from PyQt6.QtWidgets import (
+ QAbstractItemView, QFrame, QHBoxLayout, QInputDialog, QMenu, QToolTip,
+ QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
)
from novelwriter import CONFIG, SHARED
diff --git a/novelwriter/gui/outline.py b/novelwriter/gui/outline.py
index 9250f06c..a237e5fd 100644
--- a/novelwriter/gui/outline.py
+++ b/novelwriter/gui/outline.py
@@ -33,10 +33,10 @@ import logging
from enum import Enum
from time import time
-from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt, pyqtSignal, pyqtSlot
-from PyQt5.QtGui import QIcon
-from PyQt5.QtWidgets import (
- QAbstractItemView, QAction, QFileDialog, QFrame, QGridLayout, QGroupBox,
+from PyQt6.QtCore import QT_TRANSLATE_NOOP, Qt, pyqtSignal, pyqtSlot
+from PyQt6.QtGui import QAction, QIcon
+from PyQt6.QtWidgets import (
+ QAbstractItemView, QFileDialog, QFrame, QGridLayout, QGroupBox,
QHBoxLayout, QLabel, QMenu, QScrollArea, QSplitter, QToolBar, QToolButton,
QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
)
diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py
index b45f8f24..6acaa264 100644
--- a/novelwriter/gui/projtree.py
+++ b/novelwriter/gui/projtree.py
@@ -32,10 +32,10 @@ import logging
from enum import Enum
-from PyQt5.QtCore import QModelIndex, QPoint, Qt, pyqtSignal, pyqtSlot
-from PyQt5.QtGui import QIcon, QMouseEvent, QPainter, QPalette
-from PyQt5.QtWidgets import (
- QAbstractItemView, QAction, QFrame, QHBoxLayout, QLabel, QMenu, QShortcut,
+from PyQt6.QtCore import QModelIndex, QPoint, Qt, pyqtSignal, pyqtSlot
+from PyQt6.QtGui import QAction, QIcon, QMouseEvent, QPainter, QPalette, QShortcut
+from PyQt6.QtWidgets import (
+ QAbstractItemView, QFrame, QHBoxLayout, QLabel, QMenu,
QStyleOptionViewItem, QTreeView, QVBoxLayout, QWidget
)
diff --git a/novelwriter/gui/search.py b/novelwriter/gui/search.py
index 10184d29..78f06838 100644
--- a/novelwriter/gui/search.py
+++ b/novelwriter/gui/search.py
@@ -27,9 +27,9 @@ import logging
from time import time
-from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
-from PyQt5.QtGui import QCursor, QKeyEvent
-from PyQt5.QtWidgets import (
+from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot
+from PyQt6.QtGui import QCursor, QKeyEvent
+from PyQt6.QtWidgets import (
QApplication, QFrame, QHBoxLayout, QLabel, QLineEdit, QToolBar,
QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
)
diff --git a/novelwriter/gui/sidebar.py b/novelwriter/gui/sidebar.py
index 8baaaa48..cf9d26bf 100644
--- a/novelwriter/gui/sidebar.py
+++ b/novelwriter/gui/sidebar.py
@@ -27,9 +27,9 @@ import logging
from typing import TYPE_CHECKING
-from PyQt5.QtCore import QEvent, QPoint, QSize, pyqtSignal
-from PyQt5.QtGui import QPalette
-from PyQt5.QtWidgets import QMenu, QVBoxLayout, QWidget
+from PyQt6.QtCore import QEvent, QPoint, QSize, pyqtSignal
+from PyQt6.QtGui import QPalette
+from PyQt6.QtWidgets import QMenu, QVBoxLayout, QWidget
from novelwriter import CONFIG, SHARED
from novelwriter.common import qtLambda
diff --git a/novelwriter/gui/statusbar.py b/novelwriter/gui/statusbar.py
index a2fe5b9f..413b93df 100644
--- a/novelwriter/gui/statusbar.py
+++ b/novelwriter/gui/statusbar.py
@@ -28,8 +28,8 @@ import logging
from datetime import datetime
from time import time
-from PyQt5.QtCore import QLocale, pyqtSlot
-from PyQt5.QtWidgets import QApplication, QLabel, QStatusBar, QWidget
+from PyQt6.QtCore import QLocale, pyqtSlot
+from PyQt6.QtWidgets import QApplication, QLabel, QStatusBar, QWidget
from novelwriter import CONFIG, SHARED
from novelwriter.common import formatTime
diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py
index 6fd4f198..9c583a80 100644
--- a/novelwriter/gui/theme.py
+++ b/novelwriter/gui/theme.py
@@ -29,19 +29,19 @@ import logging
from math import ceil
from pathlib import Path
-from PyQt5.QtCore import QSize, Qt
-from PyQt5.QtGui import (
+from PyQt6.QtCore import QSize, Qt
+from PyQt6.QtGui import (
QColor, QFont, QFontDatabase, QFontMetrics, QIcon, QPainter, QPainterPath,
QPalette, QPixmap
)
-from PyQt5.QtWidgets import QApplication
+from PyQt6.QtWidgets import QApplication
from novelwriter import CONFIG
from novelwriter.common import NWConfigParser, cssCol, minmax
from novelwriter.constants import nwLabels
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
from novelwriter.error import logException
-from novelwriter.types import QtPaintAntiAlias, QtTransparent
+from novelwriter.types import QtPaintAntiAlias, QtTransparent, nwDataClass
logger = logging.getLogger(__name__)
@@ -50,30 +50,75 @@ STYLES_MIN_TOOLBUTTON = "minimalToolButton"
STYLES_BIG_TOOLBUTTON = "bigToolButton"
+@nwDataClass
+class ThemeMeta:
+
+ name: str = ""
+ description: str = ""
+ author: str = ""
+ credit: str = ""
+ url: str = ""
+ license: str = ""
+ licenseUrl: str = ""
+
+
+@nwDataClass
+class SyntaxColors:
+
+ back: QColor = QColor(255, 255, 255)
+ text: QColor = QColor(0, 0, 0)
+ link: QColor = QColor(0, 0, 0)
+ head: QColor = QColor(0, 0, 0)
+ headH: QColor = QColor(0, 0, 0)
+ emph: QColor = QColor(0, 0, 0)
+ dialN: QColor = QColor(0, 0, 0)
+ dialA: QColor = QColor(0, 0, 0)
+ hidden: QColor = QColor(0, 0, 0)
+ note: QColor = QColor(0, 0, 0)
+ code: QColor = QColor(0, 0, 0)
+ key: QColor = QColor(0, 0, 0)
+ tag: QColor = QColor(0, 0, 0)
+ val: QColor = QColor(0, 0, 0)
+ opt: QColor = QColor(0, 0, 0)
+ spell: QColor = QColor(0, 0, 0)
+ error: QColor = QColor(0, 0, 0)
+ repTag: QColor = QColor(0, 0, 0)
+ mod: QColor = QColor(0, 0, 0)
+ mark: QColor = QColor(255, 255, 255, 128)
+
+
class GuiTheme:
"""Gui Theme Class
Handles the look and feel of novelWriter.
"""
+ __slots__ = (
+ # Attributes
+ "iconCache", "themeMeta", "isDarkTheme", "statNone", "statUnsaved",
+ "statSaved", "helpText", "fadedText", "errorText", "syntaxMeta",
+ "syntaxTheme", "guiFont", "guiFontB", "guiFontBU", "guiFontSmall",
+ "fontPointSize", "fontPixelSize", "baseIconHeight", "baseButtonHeight",
+ "textNHeight", "textNWidth", "baseIconSize", "buttonIconSize",
+ "guiFontFixed",
+
+ # Functions
+ "getIcon", "getPixmap", "getItemIcon", "getToggleIcon",
+ "loadDecoration", "getHeaderDecoration", "getHeaderDecorationNarrow",
+
+ # Internal
+ "_guiPalette", "_themeList", "_syntaxList", "_availThemes",
+ "_availSyntax", "_styleSheets",
+ )
+
def __init__(self) -> None:
self.iconCache = GuiIcons(self)
- # Loaded Theme Settings
- # =====================
+ # GUI Theme
+ self.themeMeta = ThemeMeta()
+ self.isDarkTheme = False
- # Theme
- self.themeName = ""
- self.themeDescription = ""
- self.themeAuthor = ""
- self.themeCredit = ""
- self.themeUrl = ""
- self.themeLicense = ""
- self.themeLicenseUrl = ""
- self.isLightTheme = True
-
- # GUI
self.statNone = QColor(0, 0, 0)
self.statUnsaved = QColor(0, 0, 0)
self.statSaved = QColor(0, 0, 0)
@@ -81,42 +126,9 @@ class GuiTheme:
self.fadedText = QColor(0, 0, 0)
self.errorText = QColor(255, 0, 0)
- # Loaded Syntax Settings
- # ======================
-
- # Main
- self.syntaxName = ""
- self.syntaxDescription = ""
- self.syntaxAuthor = ""
- self.syntaxCredit = ""
- self.syntaxUrl = ""
- self.syntaxLicense = ""
- self.syntaxLicenseUrl = ""
-
- # Colours
- self.colBack = QColor(255, 255, 255)
- self.colText = QColor(0, 0, 0)
- self.colLink = QColor(0, 0, 0)
- self.colHead = QColor(0, 0, 0)
- self.colHeadH = QColor(0, 0, 0)
- self.colEmph = QColor(0, 0, 0)
- self.colDialN = QColor(0, 0, 0)
- self.colDialA = QColor(0, 0, 0)
- self.colHidden = QColor(0, 0, 0)
- self.colNote = QColor(0, 0, 0)
- self.colCode = QColor(0, 0, 0)
- self.colKey = QColor(0, 0, 0)
- self.colTag = QColor(0, 0, 0)
- self.colVal = QColor(0, 0, 0)
- self.colOpt = QColor(0, 0, 0)
- self.colSpell = QColor(0, 0, 0)
- self.colError = QColor(0, 0, 0)
- self.colRepTag = QColor(0, 0, 0)
- self.colMod = QColor(0, 0, 0)
- self.colMark = QColor(255, 255, 255, 128)
-
- # Class Setup
- # ===========
+ # Syntax Theme
+ self.syntaxMeta = ThemeMeta()
+ self.syntaxTheme = SyntaxColors()
# Load Themes
self._guiPalette = QPalette()
@@ -143,13 +155,6 @@ class GuiTheme:
self.getHeaderDecoration = self.iconCache.getHeaderDecoration
self.getHeaderDecorationNarrow = self.iconCache.getHeaderDecorationNarrow
- # Extract Other Info
- self.guiDPI = QApplication.primaryScreen().logicalDotsPerInchX()
- self.guiScale = QApplication.primaryScreen().logicalDotsPerInchX()/96.0
- CONFIG.guiScale = self.guiScale
- logger.debug("GUI DPI: %.1f", self.guiDPI)
- logger.debug("GUI Scale: %.2f", self.guiScale)
-
# Fonts
self.guiFont = QApplication.font()
self.guiFontB = QApplication.font()
@@ -233,20 +238,21 @@ class GuiTheme:
return False
# Reset Palette
- self._guiPalette = QApplication.style().standardPalette()
- self._resetGuiColors()
- self.iconCache.clear()
+ self._resetTheme()
# Main
sec = "Main"
+ meta = ThemeMeta()
if parser.has_section(sec):
- self.themeName = parser.rdStr(sec, "name", "")
- self.themeDescription = parser.rdStr(sec, "description", "N/A")
- self.themeAuthor = parser.rdStr(sec, "author", "N/A")
- self.themeCredit = parser.rdStr(sec, "credit", "N/A")
- self.themeUrl = parser.rdStr(sec, "url", "")
- self.themeLicense = parser.rdStr(sec, "license", "N/A")
- self.themeLicenseUrl = parser.rdStr(sec, "licenseurl", "")
+ meta.name = parser.rdStr(sec, "name", "")
+ meta.description = parser.rdStr(sec, "description", "N/A")
+ meta.author = parser.rdStr(sec, "author", "N/A")
+ meta.credit = parser.rdStr(sec, "credit", "N/A")
+ meta.url = parser.rdStr(sec, "url", "")
+ meta.license = parser.rdStr(sec, "license", "N/A")
+ meta.licenseUrl = parser.rdStr(sec, "licenseurl", "")
+
+ self.themeMeta = meta
# Icons
sec = "Icons"
@@ -301,30 +307,60 @@ class GuiTheme:
self.statSaved = self._parseColour(parser, sec, "statussaved")
# Update Dependant Colours
- backCol = self._guiPalette.window().color()
- textCol = self._guiPalette.windowText().color()
+ # Based on: https://github.com/qt/qtbase/blob/dev/src/gui/kernel/qplatformtheme.cpp
+ text = self._guiPalette.text().color()
+ window = self._guiPalette.window().color()
+ highlight = self._guiPalette.highlight().color()
+ isDark = text.lightnessF() > window.lightnessF()
- backLNess = backCol.lightnessF()
- textLNess = textCol.lightnessF()
- self.isLightTheme = backLNess > textLNess
- if self.helpText == QColor(0, 0, 0):
- if self.isLightTheme:
- helpLCol = textLNess + 0.35*(backLNess - textLNess)
- else:
- helpLCol = backLNess + 0.65*(textLNess - backLNess)
- self.helpText = QColor.fromHsl(0, 0, int(255*helpLCol))
- logger.debug(
- "Computed help text colour: rgb(%d, %d, %d)",
- self.helpText.red(), self.helpText.green(), self.helpText.blue()
- )
+ QtColActive = QPalette.ColorGroup.Active
+ QtColInactive = QPalette.ColorGroup.Inactive
+ QtColDisabled = QPalette.ColorGroup.Disabled
+
+ light = window.lighter(150)
+ mid = window.darker(130)
+ midLight = mid.lighter(110)
+ dark = window.darker(150)
+ shadow = dark.darker(135)
+ darkOff = dark.darker(150)
+ shadowOff = shadow.darker(150)
+
+ grey = QColor(120, 120, 120) if isDark else QColor(140, 140, 140)
+ dimmed = QColor(130, 130, 130) if isDark else QColor(190, 190, 190)
+
+ placeholder = text
+ placeholder.setAlpha(128)
+
+ self._guiPalette.setBrush(QPalette.ColorRole.Light, light)
+ self._guiPalette.setBrush(QPalette.ColorRole.Mid, mid)
+ self._guiPalette.setBrush(QPalette.ColorRole.Midlight, midLight)
+ self._guiPalette.setBrush(QPalette.ColorRole.Dark, dark)
+ self._guiPalette.setBrush(QPalette.ColorRole.Shadow, shadow)
+
+ self._guiPalette.setBrush(QtColDisabled, QPalette.ColorRole.Text, dimmed)
+ self._guiPalette.setBrush(QtColDisabled, QPalette.ColorRole.WindowText, dimmed)
+ self._guiPalette.setBrush(QtColDisabled, QPalette.ColorRole.ButtonText, dimmed)
+ self._guiPalette.setBrush(QtColDisabled, QPalette.ColorRole.Base, window)
+ self._guiPalette.setBrush(QtColDisabled, QPalette.ColorRole.Dark, darkOff)
+ self._guiPalette.setBrush(QtColDisabled, QPalette.ColorRole.Shadow, shadowOff)
+
+ self._guiPalette.setBrush(QPalette.ColorRole.PlaceholderText, placeholder)
+
+ self._guiPalette.setBrush(QtColActive, QPalette.ColorRole.Highlight, highlight)
+ self._guiPalette.setBrush(QtColInactive, QPalette.ColorRole.Highlight, highlight)
+ self._guiPalette.setBrush(QtColDisabled, QPalette.ColorRole.Highlight, grey)
+
+ if CONFIG.verQtValue >= 0x060600:
+ self._guiPalette.setBrush(QtColActive, QPalette.ColorRole.Accent, highlight)
+ self._guiPalette.setBrush(QtColInactive, QPalette.ColorRole.Accent, highlight)
+ self._guiPalette.setBrush(QtColDisabled, QPalette.ColorRole.Accent, grey)
# Load icons after theme is parsed
self.iconCache.loadTheme(CONFIG.iconTheme)
- # Apply styles
+ # Finalise
+ self.isDarkTheme = isDark
QApplication.setPalette(self._guiPalette)
-
- # Reset stylesheets so that they are regenerated
self._buildStyleSheets(self._guiPalette)
return True
@@ -344,49 +380,54 @@ class GuiTheme:
logger.info("Loading syntax theme '%s'", guiSyntax)
- confParser = NWConfigParser()
+ parser = NWConfigParser()
try:
with open(syntaxFile, mode="r", encoding="utf-8") as inFile:
- confParser.read_file(inFile)
+ parser.read_file(inFile)
except Exception:
logger.error("Could not load syntax colours from: %s", syntaxFile)
logException()
return False
# Main
- cnfSec = "Main"
- if confParser.has_section(cnfSec):
- self.syntaxName = confParser.rdStr(cnfSec, "name", "")
- self.syntaxDescription = confParser.rdStr(cnfSec, "description", "N/A")
- self.syntaxAuthor = confParser.rdStr(cnfSec, "author", "N/A")
- self.syntaxCredit = confParser.rdStr(cnfSec, "credit", "N/A")
- self.syntaxUrl = confParser.rdStr(cnfSec, "url", "")
- self.syntaxLicense = confParser.rdStr(cnfSec, "license", "N/A")
- self.syntaxLicenseUrl = confParser.rdStr(cnfSec, "licenseurl", "")
+ sec = "Main"
+ meta = ThemeMeta()
+ if parser.has_section(sec):
+ meta.name = parser.rdStr(sec, "name", "")
+ meta.description = parser.rdStr(sec, "description", "N/A")
+ meta.author = parser.rdStr(sec, "author", "N/A")
+ meta.credit = parser.rdStr(sec, "credit", "N/A")
+ meta.url = parser.rdStr(sec, "url", "")
+ meta.license = parser.rdStr(sec, "license", "N/A")
+ meta.licenseUrl = parser.rdStr(sec, "licenseurl", "")
# Syntax
- cnfSec = "Syntax"
- if confParser.has_section(cnfSec):
- self.colBack = self._parseColour(confParser, cnfSec, "background")
- self.colText = self._parseColour(confParser, cnfSec, "text")
- self.colLink = self._parseColour(confParser, cnfSec, "link")
- self.colHead = self._parseColour(confParser, cnfSec, "headertext")
- self.colHeadH = self._parseColour(confParser, cnfSec, "headertag")
- self.colEmph = self._parseColour(confParser, cnfSec, "emphasis")
- self.colDialN = self._parseColour(confParser, cnfSec, "dialog")
- self.colDialA = self._parseColour(confParser, cnfSec, "altdialog")
- self.colHidden = self._parseColour(confParser, cnfSec, "hidden")
- self.colNote = self._parseColour(confParser, cnfSec, "note")
- self.colCode = self._parseColour(confParser, cnfSec, "shortcode")
- self.colKey = self._parseColour(confParser, cnfSec, "keyword")
- self.colTag = self._parseColour(confParser, cnfSec, "tag")
- self.colVal = self._parseColour(confParser, cnfSec, "value")
- self.colOpt = self._parseColour(confParser, cnfSec, "optional")
- self.colSpell = self._parseColour(confParser, cnfSec, "spellcheckline")
- self.colError = self._parseColour(confParser, cnfSec, "errorline")
- self.colRepTag = self._parseColour(confParser, cnfSec, "replacetag")
- self.colMod = self._parseColour(confParser, cnfSec, "modifier")
- self.colMark = self._parseColour(confParser, cnfSec, "texthighlight")
+ sec = "Syntax"
+ syntax = SyntaxColors()
+ if parser.has_section(sec):
+ syntax.back = self._parseColour(parser, sec, "background")
+ syntax.text = self._parseColour(parser, sec, "text")
+ syntax.link = self._parseColour(parser, sec, "link")
+ syntax.head = self._parseColour(parser, sec, "headertext")
+ syntax.headH = self._parseColour(parser, sec, "headertag")
+ syntax.emph = self._parseColour(parser, sec, "emphasis")
+ syntax.dialN = self._parseColour(parser, sec, "dialog")
+ syntax.dialA = self._parseColour(parser, sec, "altdialog")
+ syntax.hidden = self._parseColour(parser, sec, "hidden")
+ syntax.note = self._parseColour(parser, sec, "note")
+ syntax.code = self._parseColour(parser, sec, "shortcode")
+ syntax.key = self._parseColour(parser, sec, "keyword")
+ syntax.tag = self._parseColour(parser, sec, "tag")
+ syntax.val = self._parseColour(parser, sec, "value")
+ syntax.opt = self._parseColour(parser, sec, "optional")
+ syntax.spell = self._parseColour(parser, sec, "spellcheckline")
+ syntax.error = self._parseColour(parser, sec, "errorline")
+ syntax.repTag = self._parseColour(parser, sec, "replacetag")
+ syntax.mod = self._parseColour(parser, sec, "modifier")
+ syntax.mark = self._parseColour(parser, sec, "texthighlight")
+
+ self.syntaxMeta = meta
+ self.syntaxTheme = syntax
return True
@@ -395,12 +436,11 @@ class GuiTheme:
if self._themeList:
return self._themeList
- confParser = NWConfigParser()
- for themeKey, themePath in self._availThemes.items():
- logger.debug("Checking theme config for '%s'", themeKey)
- themeName = _loadInternalName(confParser, themePath)
- if themeName:
- self._themeList.append((themeKey, themeName))
+ parser = NWConfigParser()
+ for key, path in self._availThemes.items():
+ logger.debug("Checking theme config for '%s'", key)
+ if name := _loadInternalName(parser, path):
+ self._themeList.append((key, name))
self._themeList = sorted(self._themeList, key=_sortTheme)
@@ -411,12 +451,11 @@ class GuiTheme:
if self._syntaxList:
return self._syntaxList
- confParser = NWConfigParser()
- for syntaxKey, syntaxPath in self._availSyntax.items():
- logger.debug("Checking theme syntax for '%s'", syntaxKey)
- syntaxName = _loadInternalName(confParser, syntaxPath)
- if syntaxName:
- self._syntaxList.append((syntaxKey, syntaxName))
+ parser = NWConfigParser()
+ for key, path in self._availSyntax.items():
+ logger.debug("Checking theme syntax for '%s'", key)
+ if name := _loadInternalName(parser, path):
+ self._syntaxList.append((key, name))
self._syntaxList = sorted(self._syntaxList, key=_sortTheme)
@@ -430,14 +469,55 @@ class GuiTheme:
# Internal Functions
##
- def _resetGuiColors(self) -> None:
+ def _resetTheme(self) -> None:
"""Reset GUI colours to default values."""
- self.statNone = QColor(120, 120, 120)
- self.statUnsaved = QColor(200, 15, 39)
- self.statSaved = QColor(2, 133, 37)
- self.helpText = QColor(0, 0, 0)
- self.fadedText = QColor(128, 128, 128)
- self.errorText = QColor(255, 0, 0)
+ palette = QPalette()
+
+ text = palette.color(QPalette.ColorRole.Text)
+ window = palette.color(QPalette.ColorRole.Window)
+ isDark = text.lightnessF() > window.lightnessF()
+
+ # Reset GUI Palette
+ faded = QColor(128, 128, 128)
+ dimmed = QColor(130, 130, 130) if isDark else QColor(190, 190, 190)
+ grey = QColor(120, 120, 120) if isDark else QColor(140, 140, 140)
+ red = QColor(242, 119, 122) if isDark else QColor(240, 40, 41)
+ orange = QColor(249, 145, 57) if isDark else QColor(245, 135, 31)
+ yellow = QColor(255, 204, 102) if isDark else QColor(234, 183, 0)
+ green = QColor(153, 204, 153) if isDark else QColor(113, 140, 0)
+ aqua = QColor(102, 204, 204) if isDark else QColor(62, 153, 159)
+ blue = QColor(102, 153, 204) if isDark else QColor(66, 113, 174)
+ purple = QColor(204, 153, 204) if isDark else QColor(137, 89, 168)
+
+ self.statNone = grey
+ self.statUnsaved = red
+ self.statSaved = green
+ self.helpText = dimmed
+ self.fadedText = faded
+ self.errorText = red
+
+ self._guiPalette = palette
+
+ # Reset Icons
+ icons = self.iconCache
+ icons.clear()
+ icons.setIconColor("default", text)
+ icons.setIconColor("faded", faded)
+ icons.setIconColor("red", red)
+ icons.setIconColor("orange", orange)
+ icons.setIconColor("yellow", yellow)
+ icons.setIconColor("green", green)
+ icons.setIconColor("aqua", aqua)
+ icons.setIconColor("blue", blue)
+ icons.setIconColor("purple", purple)
+ icons.setIconColor("root", blue)
+ icons.setIconColor("folder", yellow)
+ icons.setIconColor("file", text)
+ icons.setIconColor("title", green)
+ icons.setIconColor("chapter", red)
+ icons.setIconColor("scene", blue)
+ icons.setIconColor("note", yellow)
+
return
def _listConf(self, targetDict: dict, checkDir: Path) -> bool:
@@ -459,7 +539,7 @@ class GuiTheme:
self, parser: NWConfigParser, section: str, name: str, value: QPalette.ColorRole
) -> None:
"""Set a palette colour value from a config string."""
- self._guiPalette.setColor(value, self._parseColour(parser, section, name))
+ self._guiPalette.setBrush(value, self._parseColour(parser, section, name))
return
def _buildStyleSheets(self, palette: QPalette) -> None:
@@ -507,6 +587,11 @@ class GuiIcons:
returned instead.
"""
+ __slots__ = (
+ "mainTheme", "themeMeta", "_svgData", "_svgColours", "_qIcons",
+ "_headerDec", "_headerDecNarrow", "_themeList", "_iconPath", "_noIcon",
+ )
+
TOGGLE_ICON_KEYS: dict[str, tuple[str, str]] = {
"bullet": ("bullet-on", "bullet-off"),
"unfold": ("unfold-show", "unfold-hide"),
@@ -519,6 +604,7 @@ class GuiIcons:
def __init__(self, mainTheme: GuiTheme) -> None:
self.mainTheme = mainTheme
+ self.themeMeta = ThemeMeta()
# Storage
self._svgData: dict[str, bytes] = {}
@@ -534,44 +620,16 @@ class GuiIcons:
# None Icon
self._noIcon = QIcon(str(self._iconPath / "none.svg"))
- # Icon Theme Meta
- self.themeName = ""
- self.themeAuthor = ""
- self.themeLicense = ""
-
return
def clear(self) -> None:
"""Clear the icon cache."""
- text = QApplication.palette().windowText().color()
- default = text.name(QColor.NameFormat.HexRgb).encode("utf-8")
- faded = self.mainTheme.fadedText.name(QColor.NameFormat.HexRgb).encode("utf-8")
-
self._svgData = {}
- self._svgColours = {
- "default": default,
- "faded": faded,
- "red": b"#ff0000",
- "orange": b"#ff7f00",
- "yellow": b"#ffff00",
- "green": b"#00ff00",
- "aqua": b"#00ffff",
- "blue": b"#0000ff",
- "purple": b"#ff00ff",
- "root": b"#0000ff",
- "folder": b"#ffff00",
- "file": default,
- "title": b"#00ff00",
- "chapter": b"#ff0000",
- "scene": b"#0000ff",
- "note": b"#ffff00",
- }
+ self._svgColours = {}
self._qIcons = {}
self._headerDec = []
self._headerDecNarrow = []
- self.themeName = ""
- self.themeAuthor = ""
- self.themeLicense = ""
+ self.themeMeta = ThemeMeta()
return
##
@@ -586,6 +644,7 @@ class GuiIcons:
logger.info("Loading icon theme '%s'", iconTheme)
themePath = self._iconPath / f"{iconTheme}.icons"
try:
+ meta = ThemeMeta()
with open(themePath, mode="r", encoding="utf-8") as icons:
for icon in icons:
bits = icon.partition("=")
@@ -595,11 +654,12 @@ class GuiIcons:
if key.startswith("icon:"):
self._svgData[key[5:]] = value.encode("utf-8")
elif key == "meta:name":
- self.themeName = value
+ meta.name = value
elif key == "meta:author":
- self.themeAuthor = value
+ meta.author = value
elif key == "meta:license":
- self.themeLicense = value
+ meta.license = value
+ self.themeMeta = meta
except Exception:
logger.error("Could not load icon theme from: %s", themePath)
logException()
@@ -633,7 +693,7 @@ class GuiIcons:
map or the icon map. This function always returns a QPixmap.
"""
if name in self.IMAGE_MAP:
- idx = 0 if self.mainTheme.isLightTheme else 1
+ idx = int(self.mainTheme.isDarkTheme)
imgPath = CONFIG.assetPath("images") / self.IMAGE_MAP[name][idx]
else:
logger.error("Decoration with name '%s' does not exist", name)
diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py
index f7aef529..56e17517 100644
--- a/novelwriter/guimain.py
+++ b/novelwriter/guimain.py
@@ -30,11 +30,11 @@ from datetime import datetime
from pathlib import Path
from time import time
-from PyQt5.QtCore import Qt, QTimer, pyqtSlot
-from PyQt5.QtGui import QCloseEvent, QCursor, QIcon
-from PyQt5.QtWidgets import (
+from PyQt6.QtCore import Qt, QTimer, pyqtSlot
+from PyQt6.QtGui import QCloseEvent, QCursor, QIcon, QShortcut
+from PyQt6.QtWidgets import (
QApplication, QFileDialog, QHBoxLayout, QMainWindow, QMessageBox,
- QShortcut, QSplitter, QStackedWidget, QVBoxLayout, QWidget
+ QSplitter, QStackedWidget, QVBoxLayout, QWidget
)
from novelwriter import CONFIG, SHARED, __hexversion__, __version__
@@ -56,7 +56,6 @@ from novelwriter.gui.projtree import GuiProjectView
from novelwriter.gui.search import GuiProjectSearch
from novelwriter.gui.sidebar import GuiSideBar
from novelwriter.gui.statusbar import GuiMainStatus
-from novelwriter.gui.theme import GuiTheme
from novelwriter.tools.dictionaries import GuiDictionaries
from novelwriter.tools.manuscript import GuiManuscript
from novelwriter.tools.noveldetails import GuiNovelDetails
@@ -92,8 +91,8 @@ class GuiMain(QMainWindow):
logger.info("OS: %s", CONFIG.osType)
logger.info("Kernel: %s", CONFIG.kernelVer)
logger.info("Host: %s", CONFIG.hostName)
- logger.info("Qt5: %s (0x%06x)", CONFIG.verQtString, CONFIG.verQtValue)
- logger.info("PyQt5: %s (0x%06x)", CONFIG.verPyQtString, CONFIG.verPyQtValue)
+ logger.info("Qt: %s (0x%06x)", CONFIG.verQtString, CONFIG.verQtValue)
+ logger.info("PyQt: %s (0x%06x)", CONFIG.verPyQtString, CONFIG.verPyQtValue)
logger.info("Python: %s (0x%08x)", CONFIG.verPyString, sys.hexversion)
logger.info("GUI Language: %s", CONFIG.guiLocale)
@@ -101,7 +100,7 @@ class GuiMain(QMainWindow):
# ============
# Initialise UserData Instance
- SHARED.initSharedData(self, GuiTheme())
+ SHARED.initSharedData(self)
# Prepare Main Window
self.resize(*CONFIG.mainWinSize)
@@ -1054,6 +1053,7 @@ class GuiMain(QMainWindow):
# We are doing this manually instead of connecting to
# paletteChanged since the processing order matters
SHARED.theme.loadTheme()
+ self.setPalette(QApplication.palette())
self.docEditor.updateTheme()
self.docViewer.updateTheme()
self.docViewerPanel.updateTheme()
diff --git a/novelwriter/shared.py b/novelwriter/shared.py
index 86ef6eca..588252d9 100644
--- a/novelwriter/shared.py
+++ b/novelwriter/shared.py
@@ -31,9 +31,9 @@ from pathlib import Path
from time import time
from typing import TYPE_CHECKING, TypeVar
-from PyQt5.QtCore import QObject, QRunnable, QThreadPool, QTimer, QUrl, pyqtSignal, pyqtSlot
-from PyQt5.QtGui import QDesktopServices, QFont
-from PyQt5.QtWidgets import QFileDialog, QFontDialog, QMessageBox, QWidget
+from PyQt6.QtCore import QObject, QRunnable, QThreadPool, QTimer, QUrl, pyqtSignal, pyqtSlot
+from PyQt6.QtGui import QDesktopServices, QFont
+from PyQt6.QtWidgets import QFileDialog, QFontDialog, QMessageBox, QWidget
from novelwriter.common import formatFileFilter
from novelwriter.constants import nwFiles
@@ -164,17 +164,24 @@ class SharedData(QObject):
# Methods
##
- def initSharedData(self, gui: GuiMain, theme: GuiTheme) -> None:
+ def initTheme(self, theme: GuiTheme) -> None:
+ """Initialise the GUI theme. This must be called before the GUI
+ is created.
+ """
+ self._theme = theme
+ return
+
+ def initSharedData(self, gui: GuiMain) -> None:
"""Initialise the SharedData instance. This must be called as
soon as the Main GUI is created to ensure the SHARED singleton
has the properties needed for operation.
"""
self._clock.start()
self._gui = gui
- self._theme = theme
self._resetProject()
logger.debug("Ready: SharedData")
- logger.debug("Thread Pool Max Count: %d", QThreadPool.globalInstance().maxThreadCount())
+ if pool := QThreadPool.globalInstance():
+ logger.debug("Thread Pool Max Count: %d", pool.maxThreadCount())
return
def closeDocument(self, tHandle: str | None = None) -> None:
@@ -266,7 +273,8 @@ class SharedData(QObject):
def runInThreadPool(self, runnable: QRunnable, priority: int = 0) -> None:
"""Queue a runnable in the application thread pool."""
- QThreadPool.globalInstance().start(runnable, priority=priority)
+ if pool := QThreadPool.globalInstance():
+ pool.start(runnable, priority=priority)
return
def getProjectPath(
@@ -278,13 +286,13 @@ class SharedData(QObject):
label = (self.tr("novelWriter Project File or Zip File")
if allowZip else self.tr("novelWriter Project File"))
ext = f"{nwFiles.PROJ_FILE} *.zip" if allowZip else nwFiles.PROJ_FILE
- ffilter = formatFileFilter([(label, ext), "*"])
+ fFilter = formatFileFilter([(label, ext), "*"])
selected, _ = QFileDialog.getOpenFileName(
- parent, self.tr("Open Project"), str(path or ""), filter=ffilter
+ parent, self.tr("Open Project"), str(path or ""), filter=fFilter
)
return Path(selected) if selected else None
- def getFont(self, current: QFont, native: bool) -> tuple[QFont, bool]:
+ def getFont(self, current: QFont, native: bool) -> tuple[QFont, bool | None]:
"""Open the font dialog and select a font."""
kwargs = {}
if not native:
diff --git a/novelwriter/tools/dictionaries.py b/novelwriter/tools/dictionaries.py
index 057637af..ddb371e1 100644
--- a/novelwriter/tools/dictionaries.py
+++ b/novelwriter/tools/dictionaries.py
@@ -28,9 +28,9 @@ import logging
from pathlib import Path
from zipfile import ZipFile
-from PyQt5.QtCore import pyqtSlot
-from PyQt5.QtGui import QCloseEvent, QTextCursor
-from PyQt5.QtWidgets import (
+from PyQt6.QtCore import pyqtSlot
+from PyQt6.QtGui import QCloseEvent, QTextCursor
+from PyQt6.QtWidgets import (
QApplication, QDialogButtonBox, QFileDialog, QFrame, QHBoxLayout, QLabel,
QLineEdit, QPlainTextEdit, QPushButton, QVBoxLayout, QWidget
)
diff --git a/novelwriter/tools/lipsum.py b/novelwriter/tools/lipsum.py
index df02d82d..f9144d49 100644
--- a/novelwriter/tools/lipsum.py
+++ b/novelwriter/tools/lipsum.py
@@ -26,8 +26,8 @@ from __future__ import annotations
import logging
import random
-from PyQt5.QtCore import pyqtSlot
-from PyQt5.QtWidgets import (
+from PyQt6.QtCore import pyqtSlot
+from PyQt6.QtWidgets import (
QDialogButtonBox, QGridLayout, QHBoxLayout, QLabel, QSpinBox, QVBoxLayout,
QWidget
)
diff --git a/novelwriter/tools/manusbuild.py b/novelwriter/tools/manusbuild.py
index b636ac3c..40e6eefc 100644
--- a/novelwriter/tools/manusbuild.py
+++ b/novelwriter/tools/manusbuild.py
@@ -27,9 +27,9 @@ import logging
from pathlib import Path
-from PyQt5.QtCore import QTimer, pyqtSlot
-from PyQt5.QtGui import QCloseEvent
-from PyQt5.QtWidgets import (
+from PyQt6.QtCore import QTimer, pyqtSlot
+from PyQt6.QtGui import QCloseEvent
+from PyQt6.QtWidgets import (
QAbstractButton, QAbstractItemView, QDialogButtonBox, QFileDialog,
QGridLayout, QHBoxLayout, QLabel, QLineEdit, QListWidget, QListWidgetItem,
QPushButton, QSplitter, QVBoxLayout, QWidget
diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py
index 07501af1..7238ea84 100644
--- a/novelwriter/tools/manuscript.py
+++ b/novelwriter/tools/manuscript.py
@@ -28,13 +28,13 @@ import logging
from time import time
from typing import TYPE_CHECKING
-from PyQt5.QtCore import Qt, QTimer, QUrl, pyqtSignal, pyqtSlot
-from PyQt5.QtGui import (
- QCloseEvent, QColor, QCursor, QDesktopServices, QFont, QPalette,
- QResizeEvent, QTextDocument
+from PyQt6.QtCore import Qt, QTimer, QUrl, pyqtSignal, pyqtSlot
+from PyQt6.QtGui import (
+ QCloseEvent, QColor, QCursor, QDesktopServices, QFont, QPageLayout,
+ QPalette, QResizeEvent, QTextDocument
)
-from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog
-from PyQt5.QtWidgets import (
+from PyQt6.QtPrintSupport import QPrinter, QPrintPreviewDialog
+from PyQt6.QtWidgets import (
QAbstractItemView, QApplication, QFormLayout, QGridLayout, QHBoxLayout,
QLabel, QListWidget, QListWidgetItem, QPushButton, QSplitter,
QStackedWidget, QTabWidget, QTextBrowser, QTreeWidget, QTreeWidgetItem,
@@ -42,7 +42,7 @@ from PyQt5.QtWidgets import (
)
from novelwriter import CONFIG, SHARED
-from novelwriter.common import fuzzyTime
+from novelwriter.common import fuzzyTime, qtLambda
from novelwriter.constants import nwLabels, nwStats, trConst
from novelwriter.core.buildsettings import BuildCollection, BuildSettings
from novelwriter.core.docbuild import NWBuildDocument
@@ -185,7 +185,7 @@ class GuiManuscript(NToolDialog):
self.btnBuild.clicked.connect(self._buildManuscript)
self.btnClose = QPushButton(self.tr("Close"), self)
- self.btnClose.clicked.connect(self.close)
+ self.btnClose.clicked.connect(qtLambda(self.close))
self.processBox = QGridLayout()
self.processBox.addWidget(self.btnPreview, 0, 0)
@@ -884,7 +884,7 @@ class _PreviewWidget(QTextBrowser):
def printPreview(self, printer: QPrinter) -> None:
"""Connect the print preview painter to the document viewer."""
QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
- printer.setOrientation(QPrinter.Orientation.Portrait)
+ printer.setPageOrientation(QPageLayout.Orientation.Portrait)
self.document().print(printer)
QApplication.restoreOverrideCursor()
return
diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py
index c0202e2f..549d39d1 100644
--- a/novelwriter/tools/manussettings.py
+++ b/novelwriter/tools/manussettings.py
@@ -27,9 +27,9 @@ import logging
from typing import TYPE_CHECKING
-from PyQt5.QtCore import QEvent, pyqtSignal, pyqtSlot
-from PyQt5.QtGui import QFont, QIcon, QSyntaxHighlighter, QTextCharFormat, QTextDocument
-from PyQt5.QtWidgets import (
+from PyQt6.QtCore import QEvent, pyqtSignal, pyqtSlot
+from PyQt6.QtGui import QFont, QIcon, QSyntaxHighlighter, QTextCharFormat, QTextDocument
+from PyQt6.QtWidgets import (
QAbstractButton, QAbstractItemView, QDialogButtonBox, QFrame, QGridLayout,
QHBoxLayout, QLabel, QLineEdit, QMenu, QPlainTextEdit, QPushButton,
QSplitter, QStackedWidget, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
@@ -906,10 +906,11 @@ class _HeadingSyntaxHighlighter(QSyntaxHighlighter):
def __init__(self, document: QTextDocument) -> None:
super().__init__(document)
+ syntax = SHARED.theme.syntaxTheme
self._fmtSymbol = QTextCharFormat()
- self._fmtSymbol.setForeground(SHARED.theme.colHead)
+ self._fmtSymbol.setForeground(syntax.head)
self._fmtFormat = QTextCharFormat()
- self._fmtFormat.setForeground(SHARED.theme.colEmph)
+ self._fmtFormat.setForeground(syntax.emph)
return
def highlightBlock(self, text: str) -> None:
diff --git a/novelwriter/tools/noveldetails.py b/novelwriter/tools/noveldetails.py
index 175462ab..a71539e5 100644
--- a/novelwriter/tools/noveldetails.py
+++ b/novelwriter/tools/noveldetails.py
@@ -26,9 +26,9 @@ from __future__ import annotations
import logging
import math
-from PyQt5.QtCore import pyqtSlot
-from PyQt5.QtGui import QCloseEvent
-from PyQt5.QtWidgets import (
+from PyQt6.QtCore import pyqtSlot
+from PyQt6.QtGui import QCloseEvent
+from PyQt6.QtWidgets import (
QAbstractItemView, QDialogButtonBox, QFormLayout, QGridLayout, QHBoxLayout,
QLabel, QSpinBox, QStackedWidget, QTreeWidget, QTreeWidgetItem,
QVBoxLayout, QWidget
diff --git a/novelwriter/tools/welcome.py b/novelwriter/tools/welcome.py
index 85482cfe..88126797 100644
--- a/novelwriter/tools/welcome.py
+++ b/novelwriter/tools/welcome.py
@@ -28,20 +28,19 @@ import logging
from datetime import datetime
from pathlib import Path
-from PyQt5.QtCore import (
- QAbstractListModel, QEvent, QModelIndex, QObject, QPoint, QSize, Qt,
- pyqtSignal, pyqtSlot
+from PyQt6.QtCore import (
+ QAbstractListModel, QModelIndex, QObject, QPoint, QSize, Qt, pyqtSignal,
+ pyqtSlot
)
-from PyQt5.QtGui import QCloseEvent, QColor, QFont, QPainter, QPaintEvent, QPen
-from PyQt5.QtWidgets import (
- QAction, QApplication, QFileDialog, QFormLayout, QHBoxLayout, QLabel,
- QLineEdit, QListView, QMenu, QPushButton, QScrollArea, QShortcut,
- QStackedWidget, QStyledItemDelegate, QStyleOptionViewItem, QVBoxLayout,
- QWidget
+from PyQt6.QtGui import QAction, QCloseEvent, QColor, QFont, QPainter, QPaintEvent, QPen, QShortcut
+from PyQt6.QtWidgets import (
+ QApplication, QFileDialog, QFormLayout, QHBoxLayout, QLabel, QLineEdit,
+ QListView, QMenu, QPushButton, QScrollArea, QStackedWidget,
+ QStyledItemDelegate, QStyleOptionViewItem, QVBoxLayout, QWidget
)
from novelwriter import CONFIG, SHARED
-from novelwriter.common import cssCol, formatInt, makeFileNameSafe
+from novelwriter.common import cssCol, formatInt, makeFileNameSafe, qtLambda
from novelwriter.constants import nwFiles
from novelwriter.core.coretools import ProjectBuilder
from novelwriter.enum import nwItemClass
@@ -86,7 +85,7 @@ class GuiWelcome(NDialog):
self.bgImage = SHARED.theme.loadDecoration("welcome")
self.nwImage = SHARED.theme.loadDecoration("nw-text", h=hD)
- self.bgColor = QColor(255, 255, 255) if SHARED.theme.isLightTheme else QColor(54, 54, 54)
+ self.bgColor = QColor(54, 54, 54) if SHARED.theme.isDarkTheme else QColor(255, 255, 255)
self.nwLogo = QLabel(self)
self.nwLogo.setPixmap(SHARED.theme.getPixmap("novelwriter", (hF, hF)))
@@ -127,7 +126,7 @@ class GuiWelcome(NDialog):
self.btnCancel = QPushButton(self.tr("Cancel"), self)
self.btnCancel.setIcon(SHARED.theme.getIcon("cancel", "red"))
self.btnCancel.setIconSize(btnIconSize)
- self.btnCancel.clicked.connect(self.close)
+ self.btnCancel.clicked.connect(qtLambda(self.close))
self.btnCreate = QPushButton(self.tr("Create"), self)
self.btnCreate.setIcon(SHARED.theme.getIcon("star", "yellow"))
@@ -591,7 +590,7 @@ class _NewProjectForm(QWidget):
self.browseFill = NIconToolButton(self, iSz, "document_add", "blue")
- self.fillMenu = _PopLeftDirectionMenu(self.browseFill)
+ self.fillMenu = QMenu(self.browseFill)
self.fillBlank = self.fillMenu.addAction(self.tr("Create a fresh project"))
self.fillBlank.setIcon(SHARED.theme.getIcon("document"))
@@ -803,14 +802,3 @@ class _NewProjectForm(QWidget):
self.extraWidget.setVisible(self._fillMode == self.FILL_BLANK)
return
-
-
-class _PopLeftDirectionMenu(QMenu):
-
- def event(self, event: QEvent) -> bool:
- """Overload the show event and move the menu popup location."""
- if event.type() == QEvent.Type.Show:
- if isinstance(parent := self.parent(), QWidget):
- offset = QPoint(parent.width() - self.width(), parent.height())
- self.move(parent.mapToGlobal(offset))
- return super(_PopLeftDirectionMenu, self).event(event)
diff --git a/novelwriter/tools/writingstats.py b/novelwriter/tools/writingstats.py
index 69fae080..bc59ab62 100644
--- a/novelwriter/tools/writingstats.py
+++ b/novelwriter/tools/writingstats.py
@@ -29,12 +29,11 @@ import logging
from datetime import datetime
from typing import TYPE_CHECKING
-from PyQt5.QtCore import Qt, pyqtSlot
-from PyQt5.QtGui import QCloseEvent, QCursor, QPixmap
-from PyQt5.QtWidgets import (
- QAction, QApplication, QDialogButtonBox, QFileDialog, QGridLayout,
- QGroupBox, QHBoxLayout, QLabel, QMenu, QSpinBox, QTreeWidget,
- QTreeWidgetItem
+from PyQt6.QtCore import Qt, pyqtSlot
+from PyQt6.QtGui import QAction, QCloseEvent, QCursor, QPixmap
+from PyQt6.QtWidgets import (
+ QApplication, QDialogButtonBox, QFileDialog, QGridLayout, QGroupBox,
+ QHBoxLayout, QLabel, QMenu, QSpinBox, QTreeWidget, QTreeWidgetItem
)
from novelwriter import CONFIG, SHARED
@@ -124,13 +123,12 @@ class GuiWritingStats(NToolDialog):
hHeader.setTextAlignment(self.C_IDLE, QtAlignRight)
hHeader.setTextAlignment(self.C_COUNT, QtAlignRight)
- sDec = Qt.SortOrder.DescendingOrder
- sAsc = Qt.SortOrder.AscendingOrder
sortCol = minmax(pOptions.getInt("GuiWritingStats", "sortCol", 0), 0, 2)
sortOrder = checkIntTuple(
- pOptions.getInt("GuiWritingStats", "sortOrder", sDec), (sAsc, sDec), sDec
+ pOptions.getInt("GuiWritingStats", "sortOrder", 1), (0, 1), 1
)
- self.listBox.sortByColumn(sortCol, sortOrder) # type: ignore
+ sortOrders = (Qt.SortOrder.AscendingOrder, Qt.SortOrder.DescendingOrder)
+ self.listBox.sortByColumn(sortCol, sortOrders[sortOrder])
self.listBox.setSortingEnabled(True)
# Word Bar
diff --git a/novelwriter/types.py b/novelwriter/types.py
index 0732f5a8..e06177b7 100644
--- a/novelwriter/types.py
+++ b/novelwriter/types.py
@@ -23,12 +23,11 @@ along with this program. If not, see .
"""
from __future__ import annotations
-from PyQt5.QtCore import Qt
-from PyQt5.QtGui import (
- QColor, QFont, QPainter, QTextBlockFormat, QTextCharFormat, QTextCursor,
- QTextFormat
-)
-from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QHeaderView, QSizePolicy, QStyle
+from typing import Any, TypeVar
+
+from PyQt6.QtCore import Qt
+from PyQt6.QtGui import QColor, QFont, QPainter, QTextCharFormat, QTextCursor, QTextFormat
+from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QHeaderView, QSizePolicy, QStyle
# Qt Alignment Flags
@@ -56,7 +55,7 @@ QtVAlignSuper = QTextCharFormat.VerticalAlignment.AlignSuperScript
QtPageBreakBefore = QTextFormat.PageBreakFlag.PageBreak_AlwaysBefore
QtPageBreakAfter = QTextFormat.PageBreakFlag.PageBreak_AlwaysAfter
-QtPropLineHeight = QTextBlockFormat.LineHeightTypes.ProportionalHeight
+QtPropLineHeight = 1 # QTextBlockFormat.LineHeightTypes.ProportionalHeight
# Qt Painter Types
@@ -149,3 +148,30 @@ FONT_STYLE: dict[QFont.Style, str] = {
QFont.Style.StyleItalic: "italic",
QFont.Style.StyleOblique: "oblique",
}
+
+##
+# Decorators and MetaClasses
+##
+
+T_ = TypeVar("T_", bound=object)
+
+
+def nwDataClass(cls: T_) -> T_:
+ """A simple data class decorator that generates slots automatically
+ and creates an init function to match.
+ """
+
+ def wrap(cls: T_) -> T_:
+ fields = tuple(a for a in dir(cls) if not a.startswith("__"))
+ values = {a: getattr(cls, a) for a in fields}
+
+ def init(self: Any) -> None:
+ nonlocal values
+ for a, v in values.items():
+ self.__setattr__(a, v)
+
+ return type(cls.__class__.__name__, (object,), { # type: ignore
+ "__slots__": fields, "__init__": init
+ })
+
+ return wrap(cls)
diff --git a/pkgutils.py b/pkgutils.py
index 8e0d5cbf..100db556 100755
--- a/pkgutils.py
+++ b/pkgutils.py
@@ -543,8 +543,8 @@ def buildTranslationAssets(args: argparse.Namespace | None = None) -> None:
try:
subprocess.call(["lrelease", "-verbose", *srcList])
except Exception as exc:
- print("Qt5 Linguist tools seem to be missing")
- print("On Debian/Ubuntu, install: qttools5-dev-tools pyqt5-dev-tools")
+ print("Qt Linguist tools seem to be missing")
+ print("On Debian/Ubuntu, install: qttools5-dev-tools")
print(str(exc))
sys.exit(1)
diff --git a/pytest.ini b/pytest.ini
index 641984a8..dbc7a006 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -1,8 +1,8 @@
[pytest]
log_level = DEBUG
-qt_api = pyqt5
+qt_api = pyqt6
markers =
base: Base classes tests
core: Core classes tests
- gui: Qt5 GUI tests
+ gui: GUI classes tests
serial
diff --git a/requirements-dev.txt b/requirements-dev.txt
index e2e3b1d0..8499c6e8 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -3,4 +3,3 @@ flake8-pep585
flake8-pyproject
flake8-annotations
isort
-pyqt5-stubs
diff --git a/requirements.txt b/requirements.txt
index b6d30310..7b7c4d39 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,2 @@
-pyqt5>=5.15
+pyqt6>=6.0
pyenchant>=3.0.0
diff --git a/setup/description_pypi.md b/setup/description_pypi.md
index 336a2e1c..0221db8d 100644
--- a/setup/description_pypi.md
+++ b/setup/description_pypi.md
@@ -10,7 +10,7 @@ synchronisation tools. All text is saved as plain text files with a meta data he
project structure is stored in a single project XML file, and other meta data is primarily saved as
JSON files.
-The application is written with Python 3 (3.10+) using Qt5 and PyQt5 (5.10+). It is developed on
+The application is written with Python 3 (3.10+) using Qt6 and PyQt6 (5.10+). It is developed on
Linux, but should in principle work fine on other operating systems as well as long as dependencies
are met. It is regularly tested on Debian and Ubuntu Linux, Windows, and MacOS.
diff --git a/setup/iss_license.txt b/setup/iss_license.txt
index 068dbc70..fe023cb4 100644
--- a/setup/iss_license.txt
+++ b/setup/iss_license.txt
@@ -18,12 +18,12 @@ along with this program. If not, see .
Dependencies
-Qt5
+Qt6
Copyright: Qt Company
Website:
License: LGPL v3
-PyQt5 / PyQt5-sip
+PyQt6 / PyQt6-sip
Copyright: Riverbank Computing
Website:
License: GPL v3
diff --git a/setup/macos/build.sh b/setup/macos/build.sh
index 8cd47ce9..dc23601d 100755
--- a/setup/macos/build.sh
+++ b/setup/macos/build.sh
@@ -165,13 +165,13 @@ rm -rf share/{gtk-,}doc
rm -rf lib/python3.1
# Remove web engine
-rm lib/python3.*/site-packages/PyQt5/QtWebEngine* || true
-rm -r lib/python3.*/site-packages/PyQt5/Qt/translations/qtwebengine* || true
-rm lib/python3.*/site-packages/PyQt5/Qt/resources/qtwebengine* || true
-rm -r lib/python3.*/site-packages/PyQt5/Qt/qml/QtWebEngine* || true
-rm -r lib/python3.*/site-packages/PyQt5/Qt/plugins/webview/libqtwebview* || true
-rm lib/python3.*/site-packages/PyQt5/Qt/libexec/QtWebEngineProcess* || true
-rm lib/python3.*/site-packages/PyQt5/Qt/lib/libQt5WebEngine* || true
+rm lib/python3.*/site-packages/PyQt6/QtWebEngine* || true
+rm -r lib/python3.*/site-packages/PyQt6/Qt/translations/qtwebengine* || true
+rm lib/python3.*/site-packages/PyQt6/Qt/resources/qtwebengine* || true
+rm -r lib/python3.*/site-packages/PyQt6/Qt/qml/QtWebEngine* || true
+rm -r lib/python3.*/site-packages/PyQt6/Qt/plugins/webview/libqtwebview* || true
+rm lib/python3.*/site-packages/PyQt6/Qt/libexec/QtWebEngineProcess* || true
+rm lib/python3.*/site-packages/PyQt6/Qt/lib/libQt5WebEngine* || true
popd || exit 1
popd || exit 1
diff --git a/tests/conftest.py b/tests/conftest.py
index 8209f8b6..0c7541d2 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -28,12 +28,12 @@ from pathlib import Path
import pytest
-from PyQt5.QtCore import QLocale
-from PyQt5.QtWidgets import QMessageBox
+from PyQt6.QtCore import QLocale
+from PyQt6.QtWidgets import QMessageBox
sys.path.insert(1, str(Path(__file__).parent.parent.absolute()))
-from novelwriter import CONFIG, SHARED, main # noqa: E402
+from novelwriter import CONFIG, SHARED # noqa: E402
from tests.mocked import MockGuiMain, MockTheme # noqa: E402
from tests.tools import cleanProject # noqa: E402
@@ -160,11 +160,15 @@ def mockGUI(qtbot, monkeypatch):
@pytest.fixture(scope="function")
def nwGUI(qtbot, monkeypatch, functionFixture):
"""Create an instance of the novelWriter GUI."""
+ from novelwriter.gui.theme import GuiTheme
+ from novelwriter.guimain import GuiMain
+
monkeypatch.setattr(QMessageBox, "exec", lambda *a: None)
monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.Yes)
- nwGUI = main(["--testmode", f"--config={_TMP_CONF}", f"--data={_TMP_CONF}"])
- assert nwGUI is not None
+ CONFIG.loadConfig()
+ SHARED.initTheme(GuiTheme())
+ nwGUI = GuiMain()
qtbot.addWidget(nwGUI)
resetConfigVars()
nwGUI.docEditor.initEditor()
@@ -172,13 +176,7 @@ def nwGUI(qtbot, monkeypatch, functionFixture):
nwGUI.show()
qtbot.wait(20)
- yield nwGUI
-
- qtbot.wait(20)
- nwGUI.closeMain()
- qtbot.wait(20)
-
- return
+ return nwGUI
##
diff --git a/tests/mocked.py b/tests/mocked.py
index 8778f0cb..b7edeaf9 100644
--- a/tests/mocked.py
+++ b/tests/mocked.py
@@ -22,8 +22,8 @@ from __future__ import annotations
from unittest.mock import MagicMock
-from PyQt5.QtGui import QFont, QIcon, QPixmap
-from PyQt5.QtWidgets import QWidget
+from PyQt6.QtGui import QFont, QIcon, QPixmap
+from PyQt6.QtWidgets import QWidget
class MockGuiMain(QWidget):
diff --git a/tests/test_base/test_base_common.py b/tests/test_base/test_base_common.py
index fea36070..5bfe2723 100644
--- a/tests/test_base/test_base_common.py
+++ b/tests/test_base/test_base_common.py
@@ -27,8 +27,8 @@ from xml.etree import ElementTree as ET
import pytest
-from PyQt5.QtCore import QMimeData, QUrl
-from PyQt5.QtGui import QColor, QDesktopServices, QFont, QFontDatabase, QFontInfo
+from PyQt6.QtCore import QMimeData, QUrl
+from PyQt6.QtGui import QColor, QDesktopServices, QFont, QFontDatabase, QFontInfo
from novelwriter.common import (
NWConfigParser, checkBool, checkFloat, checkInt, checkIntTuple, checkPath,
@@ -522,8 +522,7 @@ def testBaseCommon_cssCol():
@pytest.mark.base
def testBaseCommon_describeFont():
"""Test the describeFont function."""
- fontDB = QFontDatabase()
- font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont)
+ font = QFontDatabase.systemFont(QFontDatabase.SystemFont.GeneralFont)
font.setPointSize(12)
assert describeFont(font).startswith("12 pt")
assert describeFont(None) == "Error" # type: ignore
@@ -537,10 +536,9 @@ def testBaseCommon_fontMatcher(monkeypatch):
assert fontMatcher(nonsense) is nonsense
# General font
- fontDB = QFontDatabase()
- if len(fontDB.families()) > 1:
- fontOne = QFont(fontDB.families()[0])
- fontTwo = QFont(fontDB.families()[1])
+ if len(QFontDatabase.families()) > 1:
+ fontOne = QFont(QFontDatabase.families()[0])
+ fontTwo = QFont(QFontDatabase.families()[1])
check = QFont(fontOne)
check.setFamily(fontTwo.family())
with monkeypatch.context() as mp:
diff --git a/tests/test_base/test_base_config.py b/tests/test_base/test_base_config.py
index 864f9910..34bac678 100644
--- a/tests/test_base/test_base_config.py
+++ b/tests/test_base/test_base_config.py
@@ -245,35 +245,13 @@ def testBaseConfig_SettersGetters(fncPath):
tstConf = Config()
tstConf.initConfig(confPath=fncPath, dataPath=fncPath)
- # GUI Scaling
- # ===========
-
- tstConf.guiScale = 1.0
- assert tstConf.pxInt(10) == 10
- assert tstConf.pxInt(13) == 13
- assert tstConf.rpxInt(10) == 10
- assert tstConf.rpxInt(13) == 13
-
- tstConf.guiScale = 2.0
- assert tstConf.pxInt(10) == 20
- assert tstConf.pxInt(13) == 26
- assert tstConf.rpxInt(10) == 5
- assert tstConf.rpxInt(13) == 6
-
# Setter + Getter Combos
# ======================
# Window Size
- tstConf.guiScale = 1.0
tstConf.setMainWinSize(1205, 655)
assert tstConf.mainWinSize == [1200, 650]
- tstConf.guiScale = 2.0
- tstConf.setMainWinSize(70, 70)
- assert tstConf.mainWinSize == [70, 70]
- assert tstConf._mainWinSize == [35, 35]
-
- tstConf.guiScale = 1.0
tstConf.setMainWinSize(70, 70)
assert tstConf.mainWinSize == [70, 70]
assert tstConf._mainWinSize == [70, 70]
@@ -281,12 +259,6 @@ def testBaseConfig_SettersGetters(fncPath):
tstConf.setMainWinSize(1200, 650)
# Welcome Window Size
- tstConf.guiScale = 2.0
- tstConf.setWelcomeWinSize(70, 70)
- assert tstConf.welcomeWinSize == [70, 70]
- assert tstConf._welcomeSize == [35, 35]
-
- tstConf.guiScale = 1.0
tstConf.setWelcomeWinSize(70, 70)
assert tstConf.welcomeWinSize == [70, 70]
assert tstConf._welcomeSize == [70, 70]
@@ -294,12 +266,6 @@ def testBaseConfig_SettersGetters(fncPath):
tstConf.setWelcomeWinSize(800, 500)
# Preferences Size
- tstConf.guiScale = 2.0
- tstConf.setPreferencesWinSize(70, 70)
- assert tstConf.preferencesWinSize == [70, 70]
- assert tstConf._prefsWinSize == [35, 35]
-
- tstConf.guiScale = 1.0
tstConf.setPreferencesWinSize(70, 70)
assert tstConf.preferencesWinSize == [70, 70]
assert tstConf._prefsWinSize == [70, 70]
@@ -307,12 +273,6 @@ def testBaseConfig_SettersGetters(fncPath):
tstConf.setPreferencesWinSize(700, 615)
# Main Pane Splitter
- tstConf.guiScale = 2.0
- tstConf.setMainPanePos([200, 700])
- assert tstConf.mainPanePos == [200, 700]
- assert tstConf._mainPanePos == [100, 350]
-
- tstConf.guiScale = 1.0
tstConf.setMainPanePos([200, 700])
assert tstConf.mainPanePos == [200, 700]
assert tstConf._mainPanePos == [200, 700]
@@ -320,12 +280,6 @@ def testBaseConfig_SettersGetters(fncPath):
tstConf.setMainPanePos([300, 800])
# View Pane Splitter
- tstConf.guiScale = 2.0
- tstConf.setViewPanePos([400, 250])
- assert tstConf.viewPanePos == [400, 250]
- assert tstConf._viewPanePos == [200, 125]
-
- tstConf.guiScale = 1.0
tstConf.setViewPanePos([400, 250])
assert tstConf.viewPanePos == [400, 250]
assert tstConf._viewPanePos == [400, 250]
@@ -333,12 +287,6 @@ def testBaseConfig_SettersGetters(fncPath):
tstConf.setViewPanePos([500, 150])
# Outline Pane Splitter
- tstConf.guiScale = 2.0
- tstConf.setOutlinePanePos([400, 250])
- assert tstConf.outlinePanePos == [400, 250]
- assert tstConf._outlnPanePos == [200, 125]
-
- tstConf.guiScale = 1.0
tstConf.setOutlinePanePos([400, 250])
assert tstConf.outlinePanePos == [400, 250]
assert tstConf._outlnPanePos == [400, 250]
@@ -348,18 +296,11 @@ def testBaseConfig_SettersGetters(fncPath):
# Getters Only
# ============
- tstConf.guiScale = 1.0
assert tstConf.getTextWidth(False) == 700
assert tstConf.getTextWidth(True) == 800
assert tstConf.getTextMargin() == 40
assert tstConf.getTabWidth() == 40
- tstConf.guiScale = 2.0
- assert tstConf.getTextWidth(False) == 1400
- assert tstConf.getTextWidth(True) == 1600
- assert tstConf.getTextMargin() == 80
- assert tstConf.getTabWidth() == 80
-
@pytest.mark.base
def testBaseConfig_Internal(monkeypatch, fncPath):
diff --git a/tests/test_base/test_base_error.py b/tests/test_base/test_base_error.py
index 97e4f714..65499666 100644
--- a/tests/test_base/test_base_error.py
+++ b/tests/test_base/test_base_error.py
@@ -42,7 +42,7 @@ def testBaseError_Dialog(qtbot, monkeypatch, nwGUI):
# Valid Error Message
with monkeypatch.context() as mp:
- mp.setattr("PyQt5.QtCore.QSysInfo.kernelVersion", lambda: "1.2.3")
+ mp.setattr("PyQt6.QtCore.QSysInfo.kernelVersion", lambda: "1.2.3")
nwErr.setMessage(Exception, "Fine Error", None) # type: ignore
message = nwErr.msgBody.toPlainText()
assert message != ""
@@ -52,7 +52,7 @@ def testBaseError_Dialog(qtbot, monkeypatch, nwGUI):
# No kernel version retrieved
with monkeypatch.context() as mp:
- mp.setattr("PyQt5.QtCore.QSysInfo.kernelVersion", causeException)
+ mp.setattr("PyQt6.QtCore.QSysInfo.kernelVersion", causeException)
nwErr.setMessage(Exception, "Almost Fine Error", None) # type: ignore
message = nwErr.msgBody.toPlainText()
assert message != ""
@@ -80,27 +80,27 @@ def testBaseError_Handler(qtbot, monkeypatch, nwGUI):
# Normal shutdown
with monkeypatch.context() as mp:
mp.setattr(NWErrorMessage, "exec", lambda *a: None)
- mp.setattr("PyQt5.QtWidgets.QApplication.exit", lambda *a: None)
+ mp.setattr("PyQt6.QtWidgets.QApplication.exit", lambda *a: None)
exceptionHandler(Exception, "Error Message", None) # type: ignore
# Should not crash when no GUI is found
with monkeypatch.context() as mp:
mp.setattr(NWErrorMessage, "exec", lambda *a: None)
- mp.setattr("PyQt5.QtWidgets.QApplication.exit", lambda *a: None)
- mp.setattr("PyQt5.QtWidgets.QApplication.topLevelWidgets", lambda: [])
+ mp.setattr("PyQt6.QtWidgets.QApplication.exit", lambda *a: None)
+ mp.setattr("PyQt6.QtWidgets.QApplication.topLevelWidgets", lambda: [])
exceptionHandler(Exception, "Error Message", None) # type: ignore
# Should handle QApplication failing
with monkeypatch.context() as mp:
mp.setattr(NWErrorMessage, "exec", lambda *a: None)
- mp.setattr("PyQt5.QtWidgets.QApplication.exit", lambda *a: None)
- mp.setattr("PyQt5.QtWidgets.QApplication.topLevelWidgets", causeException)
+ mp.setattr("PyQt6.QtWidgets.QApplication.exit", lambda *a: None)
+ mp.setattr("PyQt6.QtWidgets.QApplication.topLevelWidgets", causeException)
exceptionHandler(Exception, "Error Message", None) # type: ignore
# Should handle failing to close main GUI
with monkeypatch.context() as mp:
mp.setattr(NWErrorMessage, "exec", lambda *a: None)
- mp.setattr("PyQt5.QtWidgets.QApplication.exit", lambda *a: None)
+ mp.setattr("PyQt6.QtWidgets.QApplication.exit", lambda *a: None)
mp.setattr("novelwriter.guimain.GuiMain.closeMain", causeException)
exceptionHandler(Exception, "Error Message", None) # type: ignore
diff --git a/tests/test_base/test_base_init.py b/tests/test_base/test_base_init.py
index 868c0278..664007eb 100644
--- a/tests/test_base/test_base_init.py
+++ b/tests/test_base/test_base_init.py
@@ -23,166 +23,169 @@ from __future__ import annotations
import logging
import sys
+from unittest.mock import Mock
+
import pytest
-from novelwriter import CONFIG, logger, main
+from PyQt6.QtWidgets import QApplication
-from tests.mocked import MockGuiMain
+from novelwriter import (
+ C_BLUE, C_END, C_WHITE, CONFIG, L_FILE, L_LINE, L_LVLC, L_LVLP, L_TEXT,
+ L_TIME, _createApp, logger, main
+)
+
+from tests.tools import clearLogHandlers
@pytest.mark.base
def testBaseInit_Launch(caplog, monkeypatch, fncPath):
- """Check launching the main GUI."""
- monkeypatch.setattr("novelwriter.guimain.GuiMain", MockGuiMain)
+ """Check launching the main GUI. This test """
+ monkeypatch.setattr("novelwriter._createApp", lambda *a: Mock())
+ monkeypatch.setattr("novelwriter.guimain.GuiMain", Mock())
+ monkeypatch.setattr(sys, "exit", Mock())
- # TestMode Launch
- nwGUI = main(["--testmode", f"--config={fncPath}", f"--data={fncPath}"])
- assert isinstance(nwGUI, MockGuiMain)
+ # Default Launch
+ main([f"--config={fncPath}", f"--data={fncPath}"])
+ assert CONFIG._confPath == fncPath
+ assert CONFIG._dataPath == fncPath
- # Darwin Launch
- caplog.clear()
- osDarwin = CONFIG.osDarwin
- CONFIG.osDarwin = True
+ # Darwin Launch Error Handling
with monkeypatch.context() as mp:
mp.setitem(sys.modules, "Foundation", None)
- nwGUI = main(["--testmode", f"--config={fncPath}", f"--data={fncPath}"])
- assert isinstance(nwGUI, MockGuiMain)
+ main([f"--config={fncPath}", f"--data={fncPath}"])
- CONFIG.osDarwin = osDarwin
-
- # Windows Launch
- caplog.clear()
- osWindows = CONFIG.osWindows
- CONFIG.osWindows = True
+ # Windows Launch Error Handling
with monkeypatch.context() as mp:
mp.setitem(sys.modules, "ctypes", None)
- nwGUI = main(["--testmode", f"--config={fncPath}", f"--data={fncPath}"])
- assert isinstance(nwGUI, MockGuiMain)
+ main([f"--config={fncPath}", f"--data={fncPath}"])
- CONFIG.osWindows = osWindows
- # Normal Launch
- with monkeypatch.context() as mp:
- mp.setattr("PyQt5.QtWidgets.QApplication.__init__", lambda *a: None)
- mp.setattr("PyQt5.QtWidgets.QApplication.setApplicationName", lambda *a: None)
- mp.setattr("PyQt5.QtWidgets.QApplication.setApplicationVersion", lambda *a: None)
- mp.setattr("PyQt5.QtWidgets.QApplication.setWindowIcon", lambda *a: None)
- mp.setattr("PyQt5.QtWidgets.QApplication.setOrganizationDomain", lambda *a: None)
- mp.setattr("PyQt5.QtWidgets.QApplication.exec", lambda *a: 0)
- # mp.setattr("PyQt5.QtWidgets.QApplication.focusChange.connect", lambda *a: None)
- with pytest.raises(SystemExit) as ex:
- main([f"--config={fncPath}", f"--data={fncPath}"])
- assert ex.value.code == 0
+@pytest.mark.base
+def testBaseInit_CreateApp(caplog, monkeypatch, fncPath):
+ """Check creating the Qt app."""
+ monkeypatch.setattr("PyQt6.QtWidgets.QApplication.__init__", lambda *a: None)
+ monkeypatch.setattr("PyQt6.QtWidgets.QApplication.setApplicationName", lambda *a: None)
+ monkeypatch.setattr("PyQt6.QtWidgets.QApplication.setApplicationVersion", lambda *a: None)
+ monkeypatch.setattr("PyQt6.QtWidgets.QApplication.setWindowIcon", lambda *a: None)
+ monkeypatch.setattr("PyQt6.QtWidgets.QApplication.setOrganizationDomain", lambda *a: None)
+ monkeypatch.setattr("PyQt6.QtWidgets.QApplication.exec", lambda *a: 0)
+
+ app = _createApp("Fusion")
+ assert isinstance(app, QApplication)
@pytest.mark.base
def testBaseInit_Options(monkeypatch, fncPath):
"""Test command line options for logging level."""
- monkeypatch.setattr("novelwriter.guimain.GuiMain", MockGuiMain)
+ gui = Mock()
+ app = Mock()
+ app.exec = Mock(return_value=0)
+
+ monkeypatch.setattr("novelwriter._createApp", lambda *a: app)
+ monkeypatch.setattr("novelwriter.guimain.GuiMain", lambda *a: gui)
monkeypatch.setattr(sys, "argv", [
- "novelWriter.py", "--testmode", "--meminfo", f"--config={fncPath}", f"--data={fncPath}"
+ "novelWriter.py", f"--config={fncPath}", f"--data={fncPath}"
])
- # Defaults w/None Args
- nwGUI = main()
- assert nwGUI is not None
+ # Defaults wo/Args
+ gui.reset_mock()
+ with pytest.raises(SystemExit) as ex:
+ main()
+ assert ex.value.code == 0
assert logger.getEffectiveLevel() == logging.WARNING
- assert nwGUI.closeMain() == "closeMain"
+ gui.postLaunchTasks.assert_called_once()
+ gui.postLaunchTasks.assert_called_with(None)
# Defaults
- nwGUI = main(
- ["--testmode", f"--config={fncPath}", f"--data={fncPath}", "--style=Fusion"]
- )
- assert nwGUI is not None
- assert logger.getEffectiveLevel() == logging.WARNING
- assert nwGUI.closeMain() == "closeMain"
+ with pytest.raises(SystemExit) as ex:
+ main([f"--config={fncPath}", f"--data={fncPath}", "--style=Fusion", "--meminfo"])
+ assert ex.value.code == 0
+ assert CONFIG.memInfo is True
+
+ def getFormat() -> str:
+ formatter = logger.handlers[0].formatter
+ assert formatter is not None
+ fmt = formatter._fmt
+ assert fmt is not None
+ return fmt
# Log Levels w/Color
- nwGUI = main(
- ["--testmode", "--info", "--color", f"--config={fncPath}", f"--data={fncPath}"]
- )
- assert nwGUI is not None
+ clearLogHandlers()
+ with pytest.raises(SystemExit) as ex:
+ main(["--info", "--color", f"--config={fncPath}", f"--data={fncPath}"])
+ assert ex.value.code == 0
assert logger.getEffectiveLevel() == logging.INFO
- assert nwGUI.closeMain() == "closeMain"
+ assert getFormat() == f"{L_LVLC} {L_TEXT}"
- nwGUI = main(
- ["--testmode", "--debug", "--color", f"--config={fncPath}", f"--data={fncPath}"]
- )
- assert nwGUI is not None
+ clearLogHandlers()
+ with pytest.raises(SystemExit) as ex:
+ main(["--debug", "--color", f"--config={fncPath}", f"--data={fncPath}"])
+ assert ex.value.code == 0
assert logger.getEffectiveLevel() == logging.DEBUG
- assert nwGUI.closeMain() == "closeMain"
+ assert getFormat() == (
+ f"{L_TIME} {C_BLUE}{L_FILE}{C_END}:{C_WHITE}{L_LINE}{C_END} {L_LVLC} {L_TEXT}"
+ )
# Log Levels wo/Color
- nwGUI = main(
- ["--testmode", "--info", f"--config={fncPath}", f"--data={fncPath}"]
- )
- assert nwGUI is not None
+ clearLogHandlers()
+ with pytest.raises(SystemExit) as ex:
+ main(["--info", f"--config={fncPath}", f"--data={fncPath}"])
+ assert ex.value.code == 0
assert logger.getEffectiveLevel() == logging.INFO
- assert nwGUI.closeMain() == "closeMain"
+ assert getFormat() == f"{L_LVLP} {L_TEXT}"
- nwGUI = main(
- ["--testmode", "--debug", f"--config={fncPath}", f"--data={fncPath}"]
- )
- assert nwGUI is not None
+ clearLogHandlers()
+ with pytest.raises(SystemExit) as ex:
+ main(["--debug", f"--config={fncPath}", f"--data={fncPath}"])
+ assert ex.value.code == 0
assert logger.getEffectiveLevel() == logging.DEBUG
- assert nwGUI.closeMain() == "closeMain"
+ assert getFormat() == f"{L_TIME} {L_FILE}:{L_LINE} {L_LVLP} {L_TEXT}"
# Help and Version
with pytest.raises(SystemExit) as ex:
- nwGUI = main(
- ["--testmode", "--help", f"--config={fncPath}", f"--data={fncPath}"]
- )
- assert nwGUI is not None
- assert nwGUI.closeMain() == "closeMain"
+ main(["--help", f"--config={fncPath}", f"--data={fncPath}"])
assert ex.value.code == 0
with pytest.raises(SystemExit) as ex:
- nwGUI = main(
- ["--testmode", "--version", f"--config={fncPath}", f"--data={fncPath}"]
- )
- assert nwGUI is not None
- assert nwGUI.closeMain() == "closeMain"
+ main(["--version", f"--config={fncPath}", f"--data={fncPath}"])
assert ex.value.code == 0
# Invalid options
with pytest.raises(SystemExit) as ex:
- nwGUI = main(
- ["--testmode", "--invalid", f"--config={fncPath}", f"--data={fncPath}"]
- )
- assert nwGUI is not None
- assert nwGUI.closeMain() == "closeMain"
+ main(["--invalid", f"--config={fncPath}", f"--data={fncPath}"])
assert ex.value.code == 2
# Project Path
- nwGUI = main(
- ["--testmode", f"--config={fncPath}", f"--data={fncPath}", "sample/"]
- )
- assert nwGUI is not None
- assert nwGUI.closeMain() == "closeMain"
+ gui.reset_mock()
+ with pytest.raises(SystemExit) as ex:
+ main([f"--config={fncPath}", f"--data={fncPath}", "sample/"])
+ assert ex.value.code == 0
+ gui.postLaunchTasks.assert_called_once()
+ gui.postLaunchTasks.assert_called_with("sample/")
@pytest.mark.base
def testBaseInit_Imports(caplog, monkeypatch, fncPath):
"""Check import error handling."""
- monkeypatch.setattr("novelwriter.guimain.GuiMain", MockGuiMain)
- monkeypatch.setattr("PyQt5.QtWidgets.QApplication.__init__", lambda *a: None)
- monkeypatch.setattr("PyQt5.QtWidgets.QApplication.exec", lambda *a: 0)
- monkeypatch.setattr("PyQt5.QtWidgets.QErrorMessage.__init__", lambda *a: None)
- monkeypatch.setattr("PyQt5.QtWidgets.QErrorMessage.resize", lambda *a: None)
- monkeypatch.setattr("PyQt5.QtWidgets.QErrorMessage.showMessage", lambda *a: None)
+ monkeypatch.setattr("novelwriter._createApp", lambda *a: Mock())
+ monkeypatch.setattr("novelwriter.guimain.GuiMain", lambda *a: Mock())
+
+ monkeypatch.setattr("PyQt6.QtWidgets.QApplication.__init__", lambda *a: None)
+ monkeypatch.setattr("PyQt6.QtWidgets.QApplication.exec", lambda *a: 0)
+ monkeypatch.setattr("PyQt6.QtWidgets.QErrorMessage.__init__", lambda *a: None)
+ monkeypatch.setattr("PyQt6.QtWidgets.QErrorMessage.resize", lambda *a: None)
+ monkeypatch.setattr("PyQt6.QtWidgets.QErrorMessage.showMessage", lambda *a: None)
monkeypatch.setattr("sys.hexversion", 0x0)
monkeypatch.setattr("novelwriter.CONFIG.verQtValue", 0x050000)
monkeypatch.setattr("novelwriter.CONFIG.verPyQtValue", 0x050000)
with pytest.raises(SystemExit) as ex:
- _ = main(
- ["--testmode", f"--config={fncPath}", f"--data={fncPath}"]
- )
+ main([f"--config={fncPath}", f"--data={fncPath}"])
assert ex.value.code & 4 == 4 # Python version not satisfied # type: ignore
assert ex.value.code & 8 == 8 # Qt version not satisfied # type: ignore
assert ex.value.code & 16 == 16 # PyQt version not satisfied # type: ignore
assert "At least Python" in caplog.messages[0]
- assert "At least Qt5" in caplog.messages[1]
- assert "At least PyQt5" in caplog.messages[2]
+ assert "At least Qt6" in caplog.messages[1]
+ assert "At least PyQt6" in caplog.messages[2]
diff --git a/tests/test_base/test_base_shared.py b/tests/test_base/test_base_shared.py
index 81713b1d..4f147b2b 100644
--- a/tests/test_base/test_base_shared.py
+++ b/tests/test_base/test_base_shared.py
@@ -24,9 +24,9 @@ from unittest.mock import MagicMock
import pytest
-from PyQt5.QtCore import QUrl
-from PyQt5.QtGui import QDesktopServices
-from PyQt5.QtWidgets import QFileDialog, QMessageBox, QWidget
+from PyQt6.QtCore import QUrl
+from PyQt6.QtGui import QDesktopServices
+from PyQt6.QtWidgets import QFileDialog, QMessageBox, QWidget
from novelwriter.core.project import NWProject
from novelwriter.shared import SharedData
@@ -56,7 +56,8 @@ def testBaseSharedData_Init():
assert mockGui is not mockTheme
# Properly initialise the class
- shared.initSharedData(mockGui, mockTheme) # type: ignore
+ shared.initTheme(mockTheme) # type: ignore
+ shared.initSharedData(mockGui) # type: ignore
assert shared.mainGui is mockGui
assert shared.theme is mockTheme
@@ -94,7 +95,8 @@ def testBaseSharedData_Projects(monkeypatch, caplog, fncPath):
# Initialise the instance, should create an empty project
mockGui = MockGuiMain()
mockTheme = MockTheme()
- shared.initSharedData(mockGui, mockTheme) # type: ignore
+ shared.initTheme(mockTheme) # type: ignore
+ shared.initSharedData(mockGui) # type: ignore
assert isinstance(shared.project, NWProject)
assert shared.hasProject is False
@@ -150,7 +152,8 @@ def testBaseSharedData_Alerts(qtbot, monkeypatch, caplog):
mockGui = MockGuiMain()
mockTheme = MockTheme()
- shared.initSharedData(mockGui, mockTheme) # type: ignore
+ shared.initTheme(mockTheme) # type: ignore
+ shared.initSharedData(mockGui) # type: ignore
assert shared.lastAlert == ""
diff --git a/tests/test_core/test_core_item.py b/tests/test_core/test_core_item.py
index b6946896..395db6da 100644
--- a/tests/test_core/test_core_item.py
+++ b/tests/test_core/test_core_item.py
@@ -24,7 +24,7 @@ import copy
import pytest
-from PyQt5.QtGui import QIcon
+from PyQt6.QtGui import QIcon
from novelwriter.core.item import NWItem
from novelwriter.core.project import NWProject
diff --git a/tests/test_core/test_core_itemmodel.py b/tests/test_core/test_core_itemmodel.py
index 6c60d9ed..e2033f5e 100644
--- a/tests/test_core/test_core_itemmodel.py
+++ b/tests/test_core/test_core_itemmodel.py
@@ -22,7 +22,7 @@ from __future__ import annotations
import pytest
-from PyQt5.QtCore import QMimeData, QModelIndex, Qt
+from PyQt6.QtCore import QMimeData, QModelIndex, Qt
from novelwriter.common import decodeMimeHandles
from novelwriter.constants import nwConst
diff --git a/tests/test_core/test_core_project.py b/tests/test_core/test_core_project.py
index 89b18b75..4c8618f3 100644
--- a/tests/test_core/test_core_project.py
+++ b/tests/test_core/test_core_project.py
@@ -25,7 +25,7 @@ from zipfile import ZipFile
import pytest
-from PyQt5.QtWidgets import QMessageBox
+from PyQt6.QtWidgets import QMessageBox
from novelwriter import CONFIG, SHARED
from novelwriter.constants import nwFiles
diff --git a/tests/test_core/test_core_projectxml.py b/tests/test_core/test_core_projectxml.py
index 746a14a1..20f6965d 100644
--- a/tests/test_core/test_core_projectxml.py
+++ b/tests/test_core/test_core_projectxml.py
@@ -27,7 +27,7 @@ from shutil import copyfile
import pytest
-from PyQt5.QtGui import QColor
+from PyQt6.QtGui import QColor
from novelwriter.constants import nwFiles
from novelwriter.core.item import NWItem
diff --git a/tests/test_core/test_core_status.py b/tests/test_core/test_core_status.py
index afc05ba4..dd6b18a5 100644
--- a/tests/test_core/test_core_status.py
+++ b/tests/test_core/test_core_status.py
@@ -22,7 +22,7 @@ from __future__ import annotations
import pytest
-from PyQt5.QtGui import QColor, QIcon
+from PyQt6.QtGui import QColor, QIcon
from novelwriter.core.status import NWStatus, StatusEntry, _ShapeCache
from novelwriter.enum import nwStatusShape
diff --git a/tests/test_dialogs/test_dlg_about.py b/tests/test_dialogs/test_dlg_about.py
index 2cfcb065..0f659035 100644
--- a/tests/test_dialogs/test_dlg_about.py
+++ b/tests/test_dialogs/test_dlg_about.py
@@ -24,7 +24,8 @@ from pathlib import Path
import pytest
-from PyQt5.QtWidgets import QAction, QMessageBox
+from PyQt6.QtGui import QAction
+from PyQt6.QtWidgets import QMessageBox
from novelwriter import SHARED
from novelwriter.dialogs.about import GuiAbout
diff --git a/tests/test_dialogs/test_dlg_dialogs.py b/tests/test_dialogs/test_dlg_dialogs.py
index b8a1d8df..ce3d6bc3 100644
--- a/tests/test_dialogs/test_dlg_dialogs.py
+++ b/tests/test_dialogs/test_dlg_dialogs.py
@@ -22,8 +22,8 @@ from __future__ import annotations
import pytest
-from PyQt5.QtCore import QItemSelectionModel
-from PyQt5.QtWidgets import QListWidgetItem
+from PyQt6.QtCore import QItemSelectionModel
+from PyQt6.QtWidgets import QListWidgetItem
from novelwriter.dialogs.editlabel import GuiEditLabel
from novelwriter.dialogs.quotes import GuiQuoteSelect
diff --git a/tests/test_dialogs/test_dlg_docmerge.py b/tests/test_dialogs/test_dlg_docmerge.py
index 2c1f5e11..7431b3d7 100644
--- a/tests/test_dialogs/test_dlg_docmerge.py
+++ b/tests/test_dialogs/test_dlg_docmerge.py
@@ -22,7 +22,7 @@ from __future__ import annotations
import pytest
-from PyQt5.QtCore import Qt
+from PyQt6.QtCore import Qt
from novelwriter.dialogs.docmerge import GuiDocMerge
from novelwriter.types import QtAccepted, QtRejected, QtUserRole
diff --git a/tests/test_dialogs/test_dlg_preferences.py b/tests/test_dialogs/test_dlg_preferences.py
index df0a0ff8..bdc4db27 100644
--- a/tests/test_dialogs/test_dlg_preferences.py
+++ b/tests/test_dialogs/test_dlg_preferences.py
@@ -22,9 +22,9 @@ from __future__ import annotations
import pytest
-from PyQt5.QtCore import QEvent, Qt
-from PyQt5.QtGui import QFont, QFontDatabase, QKeyEvent
-from PyQt5.QtWidgets import QAction, QFileDialog, QFontDialog
+from PyQt6.QtCore import QEvent, Qt
+from PyQt6.QtGui import QAction, QFont, QFontDatabase, QKeyEvent
+from PyQt6.QtWidgets import QFileDialog, QFontDialog
from novelwriter import CONFIG, SHARED
from novelwriter.constants import nwUnicode
diff --git a/tests/test_dialogs/test_dlg_projectsettings.py b/tests/test_dialogs/test_dlg_projectsettings.py
index 53a8db7c..54b5cd1d 100644
--- a/tests/test_dialogs/test_dlg_projectsettings.py
+++ b/tests/test_dialogs/test_dlg_projectsettings.py
@@ -22,8 +22,8 @@ from __future__ import annotations
import pytest
-from PyQt5.QtGui import QColor
-from PyQt5.QtWidgets import QAction, QColorDialog, QFileDialog
+from PyQt6.QtGui import QAction, QColor
+from PyQt6.QtWidgets import QColorDialog, QFileDialog
from novelwriter import CONFIG, SHARED
from novelwriter.dialogs.editlabel import GuiEditLabel
diff --git a/tests/test_dialogs/test_dlg_wordlist.py b/tests/test_dialogs/test_dlg_wordlist.py
index 84fb3500..9b42d5a7 100644
--- a/tests/test_dialogs/test_dlg_wordlist.py
+++ b/tests/test_dialogs/test_dlg_wordlist.py
@@ -22,8 +22,9 @@ from __future__ import annotations
import pytest
-from PyQt5.QtCore import Qt
-from PyQt5.QtWidgets import QAction, QFileDialog
+from PyQt6.QtCore import Qt
+from PyQt6.QtGui import QAction
+from PyQt6.QtWidgets import QFileDialog
from novelwriter import SHARED
from novelwriter.core.spellcheck import UserDictionary
@@ -104,11 +105,11 @@ def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, fncPath, projPath):
wList._doAdd()
assert wList.listBox.item(0).text() == "delete_me" # type: ignore
- delItem = wList.listBox.findItems("delete_me", Qt.MatchExactly)[0]
+ delItem = wList.listBox.findItems("delete_me", Qt.MatchFlag.MatchExactly)[0]
assert delItem.text() == "delete_me"
delItem.setSelected(True)
wList._doDelete()
- assert wList.listBox.findItems("delete_me", Qt.MatchExactly) == []
+ assert wList.listBox.findItems("delete_me", Qt.MatchFlag.MatchExactly) == []
assert wList.listBox.item(0).text() == "word_a" # type: ignore
# Import/Export
diff --git a/tests/test_ext/test_ext_eventfilters.py b/tests/test_ext/test_ext_eventfilters.py
index afc92ebc..628932f1 100644
--- a/tests/test_ext/test_ext_eventfilters.py
+++ b/tests/test_ext/test_ext_eventfilters.py
@@ -22,9 +22,9 @@ from __future__ import annotations
import pytest
-from PyQt5.QtCore import QEvent, QObject, QPoint, Qt
-from PyQt5.QtGui import QKeyEvent, QWheelEvent
-from PyQt5.QtWidgets import QWidget
+from PyQt6.QtCore import QEvent, QObject, QPoint, QPointF, Qt
+from PyQt6.QtGui import QKeyEvent, QWheelEvent
+from PyQt6.QtWidgets import QWidget
from novelwriter.extensions.eventfilters import WheelEventFilter
from novelwriter.types import QtModNone, QtModShift
@@ -57,8 +57,9 @@ def testExtEventFilters_WheelEventFilter():
# Sending a mouse wheel event forwards it
pos = QPoint(0, 0)
+ posF = QPointF(0.0, 0.0)
event = QWheelEvent(
- pos, pos, pos, pos,
+ posF, posF, pos, pos,
Qt.MouseButton.NoButton, QtModNone,
Qt.ScrollPhase.NoScrollPhase, False,
)
diff --git a/tests/test_ext/test_ext_modified.py b/tests/test_ext/test_ext_modified.py
index 69d4d717..161531da 100644
--- a/tests/test_ext/test_ext_modified.py
+++ b/tests/test_ext/test_ext_modified.py
@@ -22,9 +22,9 @@ from __future__ import annotations
import pytest
-from PyQt5.QtCore import QEvent, QPoint, QPointF, Qt
-from PyQt5.QtGui import QKeyEvent, QMouseEvent, QWheelEvent
-from PyQt5.QtWidgets import QWidget
+from PyQt6.QtCore import QEvent, QPoint, QPointF, Qt
+from PyQt6.QtGui import QKeyEvent, QMouseEvent, QWheelEvent
+from PyQt6.QtWidgets import QWidget
from novelwriter.extensions.modified import (
NClickableLabel, NComboBox, NDialog, NDoubleSpinBox, NSpinBox
@@ -150,7 +150,7 @@ def testExtModified_NClickableLabel(qtbot, monkeypatch):
dialog = SimpleDialog(widget)
dialog.show()
- position = widget.rect().center()
+ position = QPointF(widget.rect().center())
event = QMouseEvent(
QEvent.Type.MouseButtonPress, position, QtMouseLeft, QtMouseLeft, QtModNone
)
diff --git a/tests/test_ext/test_ext_progressbars.py b/tests/test_ext/test_ext_progressbars.py
index bbe08161..f3872735 100644
--- a/tests/test_ext/test_ext_progressbars.py
+++ b/tests/test_ext/test_ext_progressbars.py
@@ -24,7 +24,7 @@ from time import sleep
import pytest
-from PyQt5.QtGui import QColor
+from PyQt6.QtGui import QColor
from novelwriter.extensions.progressbars import NProgressCircle, NProgressSimple
diff --git a/tests/test_ext/test_ext_switch.py b/tests/test_ext/test_ext_switch.py
index 95dbf2bd..734a2257 100644
--- a/tests/test_ext/test_ext_switch.py
+++ b/tests/test_ext/test_ext_switch.py
@@ -22,8 +22,8 @@ from __future__ import annotations
import pytest
-from PyQt5.QtCore import QEvent, QPoint
-from PyQt5.QtGui import QMouseEvent
+from PyQt6.QtCore import QEvent, QPointF
+from PyQt6.QtGui import QEnterEvent, QMouseEvent
from novelwriter.extensions.switch import NSwitch
from novelwriter.types import QtModNone, QtMouseLeft
@@ -65,10 +65,10 @@ def testExtSwitch_Main(qtbot):
button = QtMouseLeft
modifier = QtModNone
- event = QMouseEvent(QEvent.Type.MouseButtonRelease, QPoint(), button, button, modifier)
+ event = QMouseEvent(QEvent.Type.MouseButtonRelease, QPointF(), button, button, modifier)
switch.mouseReleaseEvent(event)
- event = QEvent(QEvent.Type.Enter)
+ event = QEnterEvent(QPointF(), QPointF(), QPointF())
switch.enterEvent(event)
# qtbot.stop()
diff --git a/tests/test_ext/test_ext_versioninfo.py b/tests/test_ext/test_ext_versioninfo.py
index a5566db2..cbf1428c 100644
--- a/tests/test_ext/test_ext_versioninfo.py
+++ b/tests/test_ext/test_ext_versioninfo.py
@@ -24,7 +24,7 @@ from urllib.error import HTTPError
import pytest
-from PyQt5.QtCore import QUrl
+from PyQt6.QtCore import QUrl
from novelwriter import SHARED
from novelwriter.constants import nwConst
diff --git a/tests/test_formats/test_fmt_tokenizer.py b/tests/test_formats/test_fmt_tokenizer.py
index 706bcdd6..3716657c 100644
--- a/tests/test_formats/test_fmt_tokenizer.py
+++ b/tests/test_formats/test_fmt_tokenizer.py
@@ -22,7 +22,7 @@ from __future__ import annotations
import pytest
-from PyQt5.QtGui import QFont
+from PyQt6.QtGui import QFont
from novelwriter import CONFIG
from novelwriter.constants import nwHeadFmt, nwStyles
diff --git a/tests/test_formats/test_fmt_toodt.py b/tests/test_formats/test_fmt_toodt.py
index e2796c3d..a6936598 100644
--- a/tests/test_formats/test_fmt_toodt.py
+++ b/tests/test_formats/test_fmt_toodt.py
@@ -27,7 +27,7 @@ from shutil import copyfile
import pytest
-from PyQt5.QtGui import QColor
+from PyQt6.QtGui import QColor
from novelwriter.common import xmlIndent
from novelwriter.constants import nwHeadFmt
diff --git a/tests/test_formats/test_fmt_toqdoc.py b/tests/test_formats/test_fmt_toqdoc.py
index c53a34ba..97a16d9a 100644
--- a/tests/test_formats/test_fmt_toqdoc.py
+++ b/tests/test_formats/test_fmt_toqdoc.py
@@ -22,7 +22,7 @@ from __future__ import annotations
import pytest
-from PyQt5.QtGui import QFont, QTextBlock, QTextCharFormat, QTextCursor
+from PyQt6.QtGui import QFont, QTextBlock, QTextCharFormat, QTextCursor
from novelwriter import CONFIG
from novelwriter.constants import nwUnicode
diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py
index 611f0145..32198bec 100644
--- a/tests/test_gui/test_gui_doceditor.py
+++ b/tests/test_gui/test_gui_doceditor.py
@@ -24,12 +24,12 @@ from unittest.mock import MagicMock
import pytest
-from PyQt5.QtCore import QEvent, QMimeData, Qt, QThreadPool, QUrl
-from PyQt5.QtGui import (
- QClipboard, QDesktopServices, QDragEnterEvent, QDragMoveEvent, QDropEvent,
- QFont, QMouseEvent, QTextBlock, QTextCursor, QTextOption
+from PyQt6.QtCore import QEvent, QMimeData, QPointF, Qt, QThreadPool, QUrl
+from PyQt6.QtGui import (
+ QAction, QClipboard, QDesktopServices, QDragEnterEvent, QDragMoveEvent,
+ QDropEvent, QFont, QMouseEvent, QTextBlock, QTextCursor, QTextOption
)
-from PyQt5.QtWidgets import QAction, QApplication, QMenu, QPlainTextEdit
+from PyQt6.QtWidgets import QApplication, QMenu, QPlainTextEdit
from novelwriter import CONFIG, SHARED
from novelwriter.common import decodeMimeHandles
@@ -102,8 +102,8 @@ def testGuiEditor_Init(qtbot, nwGUI, projPath, ipsumText, mockRnd):
qDoc = docEditor.document()
assert CONFIG.textFont == qDoc.defaultFont()
assert qDoc.defaultTextOption().alignment() == QtAlignJustify
- assert qDoc.defaultTextOption().flags() & QTextOption.ShowTabsAndSpaces
- assert qDoc.defaultTextOption().flags() & QTextOption.ShowLineAndParagraphSeparators
+ assert qDoc.defaultTextOption().flags() & QTextOption.Flag.ShowTabsAndSpaces
+ assert qDoc.defaultTextOption().flags() & QTextOption.Flag.ShowLineAndParagraphSeparators
assert docEditor.verticalScrollBarPolicy() == QtScrollAlwaysOff
assert docEditor.horizontalScrollBarPolicy() == QtScrollAlwaysOff
assert docEditor._autoReplace._padChar == nwUnicode.U_THNBSP
@@ -262,6 +262,7 @@ def testGuiEditor_DragAndDrop(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
assert mockMove.call_count == 1
# Drop
+ middle = QPointF(docEditor.viewport().rect().center())
mockDrop = MagicMock()
docEvent = QDropEvent(middle, action, docMime, mouse, QtModNone)
noneEvent = QDropEvent(middle, action, noneMime, mouse, QtModNone)
@@ -1708,7 +1709,7 @@ def testGuiEditor_Tags(qtbot, nwGUI, projPath, ipsumText, mockRnd):
# On Known Tag, Follow
docEditor.setCursorPosition(22)
- position = docEditor.cursorRect().center()
+ position = QPointF(docEditor.cursorRect().center())
event = QMouseEvent(
QEvent.Type.MouseButtonPress, position, QtMouseLeft, QtMouseLeft, QtModCtrl
)
@@ -1750,7 +1751,7 @@ def testGuiEditor_Links(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd)
docEditor.replaceText("### Scene\n\nFoo http://www.example.com bar.\n\n")
docEditor.setCursorPosition(20)
- position = docEditor.cursorRect().center()
+ position = QPointF(docEditor.cursorRect().center())
event = QMouseEvent(
QEvent.Type.MouseButtonPress, position, QtMouseLeft, QtMouseLeft, QtModCtrl
)
@@ -1988,7 +1989,7 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum):
# Select the Word "est"
docEditor.setCursorPosition(645)
- docEditor._makeSelection(QTextCursor.WordUnderCursor)
+ docEditor._makeSelection(QTextCursor.SelectionType.WordUnderCursor)
cursor = docEditor.textCursor()
assert cursor.selectedText() == "est"
@@ -2118,7 +2119,7 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum):
# Close search and select "est" again
docSearch.cancelSearch.activate(QAction.ActionEvent.Trigger)
docEditor.setCursorPosition(645)
- docEditor._makeSelection(QTextCursor.WordUnderCursor)
+ docEditor._makeSelection(QTextCursor.SelectionType.WordUnderCursor)
cursor = docEditor.textCursor()
assert cursor.selectedText() == "est"
diff --git a/tests/test_gui/test_gui_docviewer.py b/tests/test_gui/test_gui_docviewer.py
index b2e3d26f..2eb8cb59 100644
--- a/tests/test_gui/test_gui_docviewer.py
+++ b/tests/test_gui/test_gui_docviewer.py
@@ -24,18 +24,18 @@ from unittest.mock import MagicMock
import pytest
-from PyQt5.QtCore import QEvent, QMimeData, QPoint, Qt, QUrl
-from PyQt5.QtGui import (
- QDesktopServices, QDragEnterEvent, QDragMoveEvent, QDropEvent, QMouseEvent,
- QTextCursor
+from PyQt6.QtCore import QEvent, QMimeData, QPointF, Qt, QUrl
+from PyQt6.QtGui import (
+ QAction, QDesktopServices, QDragEnterEvent, QDragMoveEvent, QDropEvent,
+ QMouseEvent, QTextCursor
)
-from PyQt5.QtWidgets import QAction, QApplication, QMenu, QTextBrowser
+from PyQt6.QtWidgets import QApplication, QMenu, QTextBrowser
from novelwriter import CONFIG, SHARED
from novelwriter.common import decodeMimeHandles
from novelwriter.enum import nwChange, nwDocAction
from novelwriter.formats.toqdoc import ToQTextDocument
-from novelwriter.types import QtModNone, QtMouseLeft
+from novelwriter.types import QtModNone, QtMouseLeft, QtMouseMiddle
from tests.mocked import causeException
from tests.tools import C, buildTestProject
@@ -59,7 +59,7 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum):
# Middle-click the selected item
index = SHARED.project.tree.model.indexFromHandle("88243afbe5ed8")
rect = nwGUI.projView.projTree.visualRect(index)
- qtbot.mouseClick(nwGUI.projView.projTree.viewport(), Qt.MidButton, pos=rect.center())
+ qtbot.mouseClick(nwGUI.projView.projTree.viewport(), QtMouseMiddle, pos=rect.center())
assert docViewer.docHandle == "88243afbe5ed8"
# Clear selection
@@ -69,7 +69,7 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum):
# Re-select via header click
button = QtMouseLeft
modifier = QtModNone
- event = QMouseEvent(QEvent.Type.MouseButtonPress, QPoint(), button, button, modifier)
+ event = QMouseEvent(QEvent.Type.MouseButtonPress, QPointF(), button, button, modifier)
docViewer.docHeader.mousePressEvent(event)
assert nwGUI.projView.projTree.getSelectedHandle() == "88243afbe5ed8"
@@ -89,7 +89,7 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum):
cursor = docViewer.textCursor()
cursor.setPosition(100)
docViewer.setTextCursor(cursor)
- docViewer._makeSelection(QTextCursor.WordUnderCursor)
+ docViewer._makeSelection(QTextCursor.SelectionType.WordUnderCursor)
qClip = QApplication.clipboard()
qClip.clear()
@@ -158,7 +158,7 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum):
cursor = docViewer.textCursor()
cursor.setPosition(27)
docViewer.setTextCursor(cursor)
- docViewer._makeSelection(QTextCursor.WordUnderCursor)
+ docViewer._makeSelection(QTextCursor.SelectionType.WordUnderCursor)
with monkeypatch.context() as mp:
mp.setattr(QMenu, "exec", mockExec)
docViewer._openContextMenu(docViewer.cursorRect().center())
@@ -168,7 +168,7 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum):
cursor = docViewer.textCursor()
cursor.setPosition(27)
docViewer.setTextCursor(cursor)
- docViewer._makeSelection(QTextCursor.WordUnderCursor)
+ docViewer._makeSelection(QTextCursor.SelectionType.WordUnderCursor)
rect = docViewer.cursorRect()
docViewer._linkClicked(QUrl("#tag_bod"))
assert docViewer.docHandle == "4c4f28287af27"
@@ -187,11 +187,12 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum):
assert openUrl.call_args[0][0] == QUrl("http://www.example.com")
# Click mouse nav buttons
- qtbot.mouseClick(docViewer.viewport(), Qt.BackButton, pos=rect.center(), delay=100)
+ viewport = docViewer.viewport()
+ qtbot.mouseClick(viewport, Qt.MouseButton.BackButton, pos=rect.center(), delay=100)
assert docViewer.docHandle == "88243afbe5ed8"
- qtbot.mouseClick(docViewer.viewport(), Qt.ForwardButton, pos=rect.center(), delay=100)
+ qtbot.mouseClick(viewport, Qt.MouseButton.ForwardButton, pos=rect.center(), delay=100)
assert docViewer.docHandle == "4c4f28287af27"
- qtbot.mouseClick(docViewer.viewport(), QtMouseLeft, pos=rect.center(), delay=100)
+ qtbot.mouseClick(viewport, QtMouseLeft, pos=rect.center(), delay=100)
assert docViewer.docHandle == "4c4f28287af27"
# Scroll bar default on empty document
@@ -299,6 +300,7 @@ def testGuiViewer_DragAndDrop(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
# Drop
mockDrop = MagicMock()
+ middle = QPointF(docViewer.viewport().rect().center())
docEvent = QDropEvent(middle, action, docMime, mouse, QtModNone)
noneEvent = QDropEvent(middle, action, noneMime, mouse, QtModNone)
with monkeypatch.context() as mp:
diff --git a/tests/test_gui/test_gui_docviewerpanel.py b/tests/test_gui/test_gui_docviewerpanel.py
index 0daaaad0..b0d02493 100644
--- a/tests/test_gui/test_gui_docviewerpanel.py
+++ b/tests/test_gui/test_gui_docviewerpanel.py
@@ -22,7 +22,7 @@ from __future__ import annotations
import pytest
-from PyQt5.QtGui import QIcon
+from PyQt6.QtGui import QIcon
from novelwriter import SHARED
from novelwriter.constants import nwLists
diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py
index ff472363..42cf1cb1 100644
--- a/tests/test_gui/test_gui_guimain.py
+++ b/tests/test_gui/test_gui_guimain.py
@@ -28,9 +28,9 @@ from shutil import copyfile
import pytest
-from PyQt5.QtCore import Qt
-from PyQt5.QtGui import QPalette
-from PyQt5.QtWidgets import QInputDialog, QMenu, QMessageBox
+from PyQt6.QtCore import Qt
+from PyQt6.QtGui import QPalette
+from PyQt6.QtWidgets import QInputDialog, QMenu, QMessageBox
from novelwriter import CONFIG, SHARED
from novelwriter.constants import nwFiles
@@ -186,12 +186,12 @@ def testGuiMain_UpdateTheme(qtbot, nwGUI):
mainTheme.loadSyntax()
nwGUI._processConfigChanges(True, True, True, True)
- syntaxBack = SHARED.theme.colBack
+ syntax = SHARED.theme.syntaxTheme
- assert nwGUI.docEditor.palette().color(QPalette.ColorRole.Window) == syntaxBack
- assert nwGUI.docEditor.docHeader.palette().color(QPalette.ColorRole.Window) == syntaxBack
- assert nwGUI.docViewer.palette().color(QPalette.ColorRole.Window) == syntaxBack
- assert nwGUI.docViewer.docHeader.palette().color(QPalette.ColorRole.Window) == syntaxBack
+ assert nwGUI.docEditor.palette().color(QPalette.ColorRole.Window) == syntax.back
+ assert nwGUI.docEditor.docHeader.palette().color(QPalette.ColorRole.Window) == syntax.back
+ assert nwGUI.docViewer.palette().color(QPalette.ColorRole.Window) == syntax.back
+ assert nwGUI.docViewer.docHeader.palette().color(QPalette.ColorRole.Window) == syntax.back
# qtbot.stop()
@@ -782,11 +782,12 @@ def testGuiMain_Features(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
# Full Screen Mode
# ================
- assert nwGUI.windowState() & Qt.WindowFullScreen != Qt.WindowFullScreen
+ fullScreen = Qt.WindowState.WindowFullScreen
+ assert nwGUI.windowState() & fullScreen != fullScreen
nwGUI.toggleFullScreenMode()
- assert nwGUI.windowState() & Qt.WindowFullScreen == Qt.WindowFullScreen
+ assert nwGUI.windowState() & fullScreen == fullScreen
nwGUI.toggleFullScreenMode()
- assert nwGUI.windowState() & Qt.WindowFullScreen != Qt.WindowFullScreen
+ assert nwGUI.windowState() & fullScreen != fullScreen
# SideBar Menu
# ============
diff --git a/tests/test_gui/test_gui_i18n.py b/tests/test_gui/test_gui_i18n.py
index e9caa806..aab521ce 100644
--- a/tests/test_gui/test_gui_i18n.py
+++ b/tests/test_gui/test_gui_i18n.py
@@ -24,7 +24,7 @@ import sys
import pytest
-from PyQt5.QtWidgets import QApplication, QDialog, QMessageBox
+from PyQt6.QtWidgets import QApplication, QDialog, QMessageBox
from novelwriter import CONFIG, SHARED
from novelwriter.dialogs.about import GuiAbout
diff --git a/tests/test_gui/test_gui_mainmenu.py b/tests/test_gui/test_gui_mainmenu.py
index 66b1d94e..b2bc3fd9 100644
--- a/tests/test_gui/test_gui_mainmenu.py
+++ b/tests/test_gui/test_gui_mainmenu.py
@@ -24,8 +24,8 @@ from unittest.mock import MagicMock
import pytest
-from PyQt5.QtGui import QDesktopServices, QTextBlock, QTextCursor
-from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox
+from PyQt6.QtGui import QAction, QDesktopServices, QTextBlock, QTextCursor
+from PyQt6.QtWidgets import QFileDialog, QMessageBox
from novelwriter import CONFIG, SHARED
from novelwriter.constants import nwKeyWords, nwShortcode, nwStats, nwUnicode
@@ -187,7 +187,7 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum):
# Cut, Copy and Paste
docEditor.setCursorPosition(54)
- docEditor._makeSelection(QTextCursor.WordUnderCursor)
+ docEditor._makeSelection(QTextCursor.SelectionType.WordUnderCursor)
mainMenu.aEditCut.activate(QAction.ActionEvent.Trigger)
assert docEditor.getText()[54:104] == (
@@ -200,7 +200,7 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum):
)
docEditor.setCursorPosition(54)
- docEditor._makeSelection(QTextCursor.WordUnderCursor)
+ docEditor._makeSelection(QTextCursor.SelectionType.WordUnderCursor)
mainMenu.aEditCopy.activate(QAction.ActionEvent.Trigger)
assert docEditor.getText()[54:104] == (
diff --git a/tests/test_gui/test_gui_noveltree.py b/tests/test_gui/test_gui_noveltree.py
index 3974d432..9ec5f352 100644
--- a/tests/test_gui/test_gui_noveltree.py
+++ b/tests/test_gui/test_gui_noveltree.py
@@ -24,9 +24,9 @@ from pathlib import Path
import pytest
-from PyQt5.QtCore import QEvent, QPoint, Qt
-from PyQt5.QtGui import QFocusEvent
-from PyQt5.QtWidgets import QInputDialog, QToolTip
+from PyQt6.QtCore import QEvent, QPoint, Qt
+from PyQt6.QtGui import QFocusEvent
+from PyQt6.QtWidgets import QInputDialog, QToolTip
from novelwriter import CONFIG, SHARED
from novelwriter.dialogs.editlabel import GuiEditLabel
@@ -225,7 +225,7 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
scItem = novelTree.topLevelItem(2)
scItem.setSelected(True)
assert scItem.isSelected()
- novelTree.focusOutEvent(QFocusEvent(QEvent.Type.None_, Qt.MouseFocusReason))
+ novelTree.focusOutEvent(QFocusEvent(QEvent.Type.None_, Qt.FocusReason.MouseFocusReason))
assert not scItem.isSelected()
# Close
diff --git a/tests/test_gui/test_gui_outline.py b/tests/test_gui/test_gui_outline.py
index 9ddc87b8..8ee650df 100644
--- a/tests/test_gui/test_gui_outline.py
+++ b/tests/test_gui/test_gui_outline.py
@@ -26,7 +26,8 @@ from shutil import copyfile
import pytest
-from PyQt5.QtWidgets import QAction, QFileDialog, QWidget
+from PyQt6.QtGui import QAction
+from PyQt6.QtWidgets import QFileDialog, QWidget
from novelwriter import CONFIG, SHARED
from novelwriter.constants import nwKeyWords
diff --git a/tests/test_gui/test_gui_projtree.py b/tests/test_gui/test_gui_projtree.py
index 3642ef4b..de3163d2 100644
--- a/tests/test_gui/test_gui_projtree.py
+++ b/tests/test_gui/test_gui_projtree.py
@@ -24,9 +24,9 @@ from unittest.mock import MagicMock
import pytest
-from PyQt5.QtCore import QEvent, QItemSelectionModel, QModelIndex, QPoint
-from PyQt5.QtGui import QMouseEvent
-from PyQt5.QtWidgets import QMenu, QMessageBox
+from PyQt6.QtCore import QEvent, QItemSelectionModel, QModelIndex, QPointF
+from PyQt6.QtGui import QMouseEvent
+from PyQt6.QtWidgets import QMenu, QMessageBox
from novelwriter import CONFIG, SHARED
from novelwriter.dialogs.docmerge import GuiDocMerge
@@ -516,14 +516,14 @@ def testGuiProjTree_MouseClicks(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
modifier = QtModNone
# Trigger the viewer
- pos = projTree.visualRect(model.indexFromHandle(C.hChapterDoc)).center()
+ pos = QPointF(projTree.visualRect(model.indexFromHandle(C.hChapterDoc)).center())
button = QtMouseMiddle
event = QMouseEvent(eType, pos, button, button, modifier)
projTree.mousePressEvent(event)
assert nwGUI.docViewer.docHandle == C.hChapterDoc
# Trigger the left click clear
- pos = QPoint(5000, 5000)
+ pos = QPointF(5000.0, 5000.0)
button = QtMouseLeft
event = QMouseEvent(eType, pos, button, button, modifier)
diff --git a/tests/test_gui/test_gui_search.py b/tests/test_gui/test_gui_search.py
index 426591fd..843c1ae9 100644
--- a/tests/test_gui/test_gui_search.py
+++ b/tests/test_gui/test_gui_search.py
@@ -24,8 +24,8 @@ from time import time
import pytest
-from PyQt5.QtCore import Qt
-from PyQt5.QtWidgets import QAction
+from PyQt6.QtCore import Qt
+from PyQt6.QtGui import QAction
from novelwriter.enum import nwView
from novelwriter.gui.search import GuiProjectSearch
diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py
index e8cc42ee..9c368f11 100644
--- a/tests/test_gui/test_gui_theme.py
+++ b/tests/test_gui/test_gui_theme.py
@@ -24,8 +24,7 @@ from pathlib import Path
import pytest
-from PyQt5.QtGui import QColor, QIcon, QPalette, QPixmap
-from PyQt5.QtWidgets import QApplication
+from PyQt6.QtGui import QColor, QIcon, QPalette, QPixmap
from novelwriter import CONFIG, SHARED
from novelwriter.common import NWConfigParser
@@ -152,7 +151,7 @@ def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI, tstPaths):
assert mainTheme.loadTheme() is True
# This should load a standard palette
- wCol = QApplication.style().standardPalette().color(QPalette.ColorRole.Window).getRgb()
+ wCol = QPalette().color(QPalette.ColorRole.Window).getRgb()
assert mainTheme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == wCol
# Mock Dark Theme
@@ -166,16 +165,13 @@ def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI, tstPaths):
"[Palette]\n"
"window = 0, 0, 0\n"
"windowtext = 255, 255, 255\n"
- "\n"
- "[GUI]\n"
- "helptext = 0, 0, 0\n"
)
mainTheme._availThemes["test"] = mockTheme
CONFIG.guiTheme = "test"
assert mainTheme.loadTheme() is True
- assert mainTheme.isLightTheme is False
- assert mainTheme.helpText.getRgb() == (165, 165, 165, 255)
+ assert mainTheme.isDarkTheme is False
+ assert mainTheme.helpText.getRgb() == (190, 190, 190, 255)
# Load Default Light Theme
# ========================
@@ -254,10 +250,10 @@ def testGuiTheme_Syntax(qtbot, monkeypatch, nwGUI):
assert mainTheme.loadSyntax() is True
# Check some values
- assert mainTheme.syntaxName == "Default Light"
- assert mainTheme.colBack == QColor(255, 255, 255)
- assert mainTheme.colText == QColor(0, 0, 0)
- assert mainTheme.colLink == QColor(0, 0, 200)
+ assert mainTheme.syntaxMeta.name == "Default Light"
+ assert mainTheme.syntaxTheme.back == QColor(255, 255, 255)
+ assert mainTheme.syntaxTheme.text == QColor(0, 0, 0)
+ assert mainTheme.syntaxTheme.link == QColor(0, 0, 200)
# Load Default Dark Theme
# =======================
@@ -267,10 +263,10 @@ def testGuiTheme_Syntax(qtbot, monkeypatch, nwGUI):
assert mainTheme.loadSyntax() is True
# Check some values
- assert mainTheme.syntaxName == "Default Dark"
- assert mainTheme.colBack == QColor(42, 42, 42)
- assert mainTheme.colText == QColor(204, 204, 204)
- assert mainTheme.colLink == QColor(102, 153, 204)
+ assert mainTheme.syntaxMeta.name == "Default Dark"
+ assert mainTheme.syntaxTheme.back == QColor(42, 42, 42)
+ assert mainTheme.syntaxTheme.text == QColor(204, 204, 204)
+ assert mainTheme.syntaxTheme.link == QColor(102, 153, 204)
# qtbot.stop()
@@ -293,7 +289,7 @@ def testGuiTheme_IconThemes(qtbot, caplog, monkeypatch, nwGUI, tstPaths):
# Load working theme file
assert iconCache.loadTheme("material_rounded_normal") is True
- assert iconCache.themeName == "Material Symbols - Rounded Medium"
+ assert iconCache.themeMeta.name == "Material Symbols - Rounded Medium"
# Load with project colour override
purple = iconCache._svgColours["purple"]
diff --git a/tests/test_tools/test_tools_dictionaries.py b/tests/test_tools/test_tools_dictionaries.py
index e225f973..e8a23c7b 100644
--- a/tests/test_tools/test_tools_dictionaries.py
+++ b/tests/test_tools/test_tools_dictionaries.py
@@ -25,8 +25,8 @@ from zipfile import ZipFile
import enchant
import pytest
-from PyQt5.QtGui import QDesktopServices
-from PyQt5.QtWidgets import QFileDialog
+from PyQt6.QtGui import QDesktopServices
+from PyQt6.QtWidgets import QFileDialog
from novelwriter import SHARED
from novelwriter.tools.dictionaries import GuiDictionaries
diff --git a/tests/test_tools/test_tools_lipsum.py b/tests/test_tools/test_tools_lipsum.py
index 889c47bd..27098b5c 100644
--- a/tests/test_tools/test_tools_lipsum.py
+++ b/tests/test_tools/test_tools_lipsum.py
@@ -22,7 +22,7 @@ from __future__ import annotations
import pytest
-from PyQt5.QtWidgets import QAction
+from PyQt6.QtGui import QAction
from novelwriter import SHARED
from novelwriter.enum import nwDocInsert
diff --git a/tests/test_tools/test_tools_manusbuild.py b/tests/test_tools/test_tools_manusbuild.py
index 898f120c..07b0c1c5 100644
--- a/tests/test_tools/test_tools_manusbuild.py
+++ b/tests/test_tools/test_tools_manusbuild.py
@@ -24,9 +24,9 @@ from pathlib import Path
import pytest
-from PyQt5.QtCore import QUrl
-from PyQt5.QtGui import QDesktopServices
-from PyQt5.QtWidgets import QFileDialog, QListWidgetItem, QMessageBox
+from PyQt6.QtCore import QUrl
+from PyQt6.QtGui import QDesktopServices
+from PyQt6.QtWidgets import QFileDialog, QListWidgetItem, QMessageBox
from pytestqt.qtbot import QtBot
from novelwriter.constants import nwLabels
diff --git a/tests/test_tools/test_tools_manuscript.py b/tests/test_tools/test_tools_manuscript.py
index 5db4f933..bdd5b900 100644
--- a/tests/test_tools/test_tools_manuscript.py
+++ b/tests/test_tools/test_tools_manuscript.py
@@ -26,10 +26,10 @@ from unittest.mock import MagicMock
import pytest
-from PyQt5.QtCore import QUrl, pyqtSlot
-from PyQt5.QtGui import QDesktopServices
-from PyQt5.QtPrintSupport import QPrintPreviewDialog
-from PyQt5.QtWidgets import QAction, QListWidgetItem
+from PyQt6.QtCore import QUrl, pyqtSlot
+from PyQt6.QtGui import QAction, QDesktopServices
+from PyQt6.QtPrintSupport import QPrintPreviewDialog
+from PyQt6.QtWidgets import QListWidgetItem
from novelwriter import SHARED
from novelwriter.constants import nwHeadFmt
diff --git a/tests/test_tools/test_tools_manussettings.py b/tests/test_tools/test_tools_manussettings.py
index b41d50a8..256feee7 100644
--- a/tests/test_tools/test_tools_manussettings.py
+++ b/tests/test_tools/test_tools_manussettings.py
@@ -22,9 +22,9 @@ from __future__ import annotations
import pytest
-from PyQt5.QtCore import pyqtSlot
-from PyQt5.QtGui import QFont
-from PyQt5.QtWidgets import QFontDialog
+from PyQt6.QtCore import pyqtSlot
+from PyQt6.QtGui import QFont
+from PyQt6.QtWidgets import QFontDialog
from novelwriter import SHARED
from novelwriter.common import describeFont
diff --git a/tests/test_tools/test_tools_noveldetails.py b/tests/test_tools/test_tools_noveldetails.py
index 73bb6063..067cbc08 100644
--- a/tests/test_tools/test_tools_noveldetails.py
+++ b/tests/test_tools/test_tools_noveldetails.py
@@ -22,7 +22,7 @@ from __future__ import annotations
import pytest
-from PyQt5.QtWidgets import QAction
+from PyQt6.QtGui import QAction
from novelwriter import SHARED
from novelwriter.enum import nwItemClass
diff --git a/tests/test_tools/test_tools_welcome.py b/tests/test_tools/test_tools_welcome.py
index eb30fcb7..228b162f 100644
--- a/tests/test_tools/test_tools_welcome.py
+++ b/tests/test_tools/test_tools_welcome.py
@@ -25,9 +25,9 @@ from pathlib import Path
import pytest
-from PyQt5.QtCore import QPoint
-from PyQt5.QtWidgets import QAction, QFileDialog, QMenu
-from pytestqt.qtbot import QtBot
+from PyQt6.QtCore import QPoint
+from PyQt6.QtGui import QAction
+from PyQt6.QtWidgets import QFileDialog, QMenu
from novelwriter import CONFIG, SHARED
from novelwriter.constants import nwFiles
@@ -37,7 +37,7 @@ from novelwriter.types import QtMouseLeft
@pytest.mark.gui
-def testToolWelcome_Main(qtbot: QtBot, monkeypatch, nwGUI, fncPath):
+def testToolWelcome_Main(qtbot, monkeypatch, nwGUI, fncPath):
"""Test the main Welcome window."""
welcome = GuiWelcome(nwGUI)
with qtbot.waitExposed(welcome):
@@ -67,7 +67,7 @@ def testToolWelcome_Main(qtbot: QtBot, monkeypatch, nwGUI, fncPath):
@pytest.mark.gui
-def testToolWelcome_Open(qtbot: QtBot, monkeypatch, nwGUI, fncPath):
+def testToolWelcome_Open(qtbot, monkeypatch, nwGUI, fncPath):
"""Test the open tab in the Welcome window."""
monkeypatch.setattr(QMenu, "exec", lambda *a: None)
@@ -162,8 +162,9 @@ def testToolWelcome_Open(qtbot: QtBot, monkeypatch, nwGUI, fncPath):
welcome.close()
+# @pytest.mark.skip
@pytest.mark.gui
-def testToolWelcome_New(qtbot: QtBot, caplog, monkeypatch, nwGUI, fncPath):
+def testToolWelcome_New(qtbot, caplog, monkeypatch, nwGUI, fncPath):
"""Test the new project tab in the Welcome window."""
welcome = GuiWelcome(nwGUI)
with qtbot.waitExposed(welcome):
@@ -203,10 +204,7 @@ def testToolWelcome_New(qtbot: QtBot, caplog, monkeypatch, nwGUI, fncPath):
assert newForm.extraWidget.isVisible() is False
# Change back to fill blank using the menu
- newForm.browseFill.click()
- assert newForm.fillMenu.isVisible() is True
newForm.fillMenu.actions()[0].activate(QAction.ActionEvent.Trigger)
- newForm.fillMenu.close()
assert newForm._fillMode == newForm.FILL_BLANK
assert newForm.projFill.text() == "Fresh Project"
assert newForm.extraWidget.isVisible() is True
diff --git a/tests/test_tools/test_tools_writingstats.py b/tests/test_tools/test_tools_writingstats.py
index c56f9731..4902c3fd 100644
--- a/tests/test_tools/test_tools_writingstats.py
+++ b/tests/test_tools/test_tools_writingstats.py
@@ -24,7 +24,9 @@ import json
import pytest
-from PyQt5.QtWidgets import QAction, QFileDialog
+from PyQt6.QtCore import Qt
+from PyQt6.QtGui import QAction
+from PyQt6.QtWidgets import QFileDialog
from novelwriter import SHARED
from novelwriter.constants import nwFiles
@@ -117,7 +119,7 @@ def testToolWritingStats_Export(qtbot, monkeypatch, nwGUI, projPath, tstPaths):
assert not sessLog._saveData(None) # type: ignore
# Sort by time
- sessLog.listBox.sortByColumn(sessLog.C_TIME, 0) # type: ignore
+ sessLog.listBox.sortByColumn(sessLog.C_TIME, Qt.SortOrder.AscendingOrder)
assert sessLog.novelWords.text() == "{:n}".format(600)
assert sessLog.notesWords.text() == "{:n}".format(275)
@@ -201,7 +203,7 @@ def testToolWritingStats_Filters(qtbot, monkeypatch, nwGUI, projPath, tstPaths):
]
sessFile.write_text("".join(data), encoding="utf-8")
sessLog.populateGUI()
- sessLog.listBox.sortByColumn(sessLog.C_TIME, 0) # type: ignore
+ sessLog.listBox.sortByColumn(sessLog.C_TIME, Qt.SortOrder.AscendingOrder)
assert sessLog.listBox.topLevelItem(0).text(sessLog.C_COUNT) == "{:n}".format(1)
assert sessLog.listBox.topLevelItem(1).text(sessLog.C_COUNT) == "{:n}".format(-200)
diff --git a/tests/tools.py b/tests/tools.py
index 28f4acce..a21ac432 100644
--- a/tests/tools.py
+++ b/tests/tools.py
@@ -26,7 +26,9 @@ import xml.etree.ElementTree as ET
from datetime import datetime
from pathlib import Path
-from PyQt5.QtWidgets import QDialog, QVBoxLayout, QWidget
+from PyQt6.QtWidgets import QDialog, QVBoxLayout, QWidget
+
+from novelwriter import logger
XML_IGNORE = (" None:
"""Build a standard test project in projPath using the project
object as the parent.