Switch to Qt6 (#2184)

This commit is contained in:
Veronica Berglyd Olsen
2025-01-12 19:40:34 +01:00
committed by GitHub
122 changed files with 934 additions and 894 deletions
+2 -2
View File
@@ -45,8 +45,8 @@ contributions are listed on the project's Members page.
The following libraries are dependencies of novelWriter: The following libraries are dependencies of novelWriter:
* **Qt5** by Qt Company * **Qt6** by Qt Company
* **PyQt5** by Riverbank Computing * **PyQt6** by Riverbank Computing
* **Enchant** by Dom Lachowicz * **Enchant** by Dom Lachowicz
* **PyEnchant** by Dimitri Merejkowsky * **PyEnchant** by Dimitri Merejkowsky
+1 -1
View File
@@ -40,7 +40,7 @@ documentation.
## Implementation ## 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, Linux, Windows and macOS. It can in principle run on any Operating System that also supports Qt,
PyQt and Python. PyQt and Python.
+3 -4
View File
@@ -36,12 +36,11 @@ Everything else is handled with standard Python libraries.
The following Python packages are needed to run all features of novelWriter: 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). * ``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`` If you want spell checking, you must install the ``PyEnchant`` package. The spell check library
package. The spell check library must be at least 3.0 to work with Windows. On Linux, 2.0 also must be at least 3.0 to work with Windows. On Linux, 2.0 also works fine.
works fine.
If you install from PyPi, these dependencies should be installed automatically. If you install from If you install from PyPi, these dependencies should be installed automatically. If you install from
source, dependencies can still be installed from PyPi with: source, dependencies can still be installed from PyPi with:
+4 -5
View File
@@ -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 * 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 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. 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 * 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 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: 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. translation work.
* The `project_XX.json` files. See [Project Localisation](#project-localisation) below. * 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. instance `en_GB` for British English.
## Qt5 GUI Localisation ## Qt GUI Localisation
You will need the translation tool Qt 5 Linguist on your system. 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` be `nw_fr_FR.ts`
Note: The `qtlupdate` command needs the `lupdate` tool provided by PyQt6, which uses the latest 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 TS file format. On Debian/Ubuntu it is provided by the package `pyqt6-dev-tools`.
by the package `pyqt6-dev-tools`.
### Edit the Translation File in Qt Linguist ### Edit the Translation File in Qt Linguist
+1 -1
View File
@@ -10,7 +10,7 @@ If a qtbase_xx.qm file already exists, do not add a translation for the
entries generated from this file. entries generated from this file.
""" """
from PyQt5.QtCore import QT_TRANSLATE_NOOP from PyQt6.QtCore import QT_TRANSLATE_NOOP
# QDialogButtonBox # QDialogButtonBox
# ================ # ================
+4 -4
View File
@@ -7,11 +7,11 @@ import os
import sys import sys
try: try:
import PyQt5.QtWidgets # noqa: F401 import PyQt6.QtCore # noqa: F401
import PyQt5.QtGui # noqa: F401 import PyQt6.QtGui # noqa: F401
import PyQt5.QtCore # noqa: F401 import PyQt6.QtWidgets # noqa: F401
except Exception: except Exception:
print("ERROR: Failed to load dependency PyQt5") print("ERROR: Failed to load dependency PyQt6")
sys.exit(1) sys.exit(1)
os.curdir = os.path.abspath(os.path.dirname(__file__)) os.curdir = os.path.abspath(os.path.dirname(__file__))
+21 -21
View File
@@ -29,7 +29,7 @@ import sys
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtWidgets import QApplication, QErrorMessage from PyQt6.QtWidgets import QApplication, QErrorMessage
from novelwriter.config import Config from novelwriter.config import Config
from novelwriter.error import exceptionHandler from novelwriter.error import exceptionHandler
@@ -97,7 +97,6 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
"style=", "style=",
"config=", "config=",
"data=", "data=",
"testmode",
"meminfo" "meminfo"
] ]
@@ -117,7 +116,7 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
" --debug Print debug output. Includes --info.\n" " --debug Print debug output. Includes --info.\n"
" --color Add ANSI colors to log output.\n" " --color Add ANSI colors to log output.\n"
" --meminfo Show memory usage information in the status bar.\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" " --config= Alternative config file.\n"
" --data= Alternative user data path.\n" " --data= Alternative user data path.\n"
) )
@@ -127,7 +126,6 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
fmtFlags = 0b00 fmtFlags = 0b00
confPath = None confPath = None
dataPath = None dataPath = None
testMode = False
qtStyle = "Fusion" qtStyle = "Fusion"
cmdOpen = None cmdOpen = None
@@ -163,8 +161,6 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
confPath = inArg confPath = inArg
elif inOpt == "--data": elif inOpt == "--data":
dataPath = inArg dataPath = inArg
elif inOpt == "--testmode":
testMode = True
elif inOpt == "--meminfo": elif inOpt == "--meminfo":
CONFIG.memInfo = True 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 "At least Python 3.10 is required, found %s" % CONFIG.verPyString
) )
errorCode |= 0x04 errorCode |= 0x04
if CONFIG.verQtValue < 0x050f00: if CONFIG.verQtValue < 0x060000:
errorData.append( 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 errorCode |= 0x08
if CONFIG.verPyQtValue < 0x050f00: if CONFIG.verPyQtValue < 0x060000:
errorData.append( 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 errorCode |= 0x10
@@ -254,19 +250,11 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
pass # Quietly ignore error pass # Quietly ignore error
# Import GUI (after dependency checks), and launch # Import GUI (after dependency checks), and launch
from novelwriter.gui.theme import GuiTheme
from novelwriter.guimain import GuiMain from novelwriter.guimain import GuiMain
if testMode: # Create App
# Only used for testing where the test framework creates the app app = _createApp(qtStyle)
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)
# Connect the exception handler before making the main GUI # Connect the exception handler before making the main GUI
sys.excepthook = exceptionHandler sys.excepthook = exceptionHandler
@@ -274,9 +262,21 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
# Run Config steps that require the QApplication # Run Config steps that require the QApplication
CONFIG.loadConfig() CONFIG.loadConfig()
CONFIG.initLocalisation(app) CONFIG.initLocalisation(app)
SHARED.initTheme(GuiTheme())
# Launch main GUI # Launch main GUI
nwGUI = GuiMain() nwGUI = GuiMain()
nwGUI.postLaunchTasks(cmdOpen) nwGUI.postLaunchTasks(cmdOpen)
sys.exit(app.exec()) 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
+2 -2
View File
@@ -54,8 +54,8 @@ more contributions are listed on the project's Members page.</p>
<p>The following libraries are dependencies of novelWriter:</p> <p>The following libraries are dependencies of novelWriter:</p>
<ul> <ul>
<li><b>Qt5</b> by Qt Company</li> <li><b>Qt6</b> by Qt Company</li>
<li><b>PyQt5</b> by Riverbank Computing</li> <li><b>PyQt6</b> by Riverbank Computing</li>
<li><b>Enchant</b> by Dom Lachowicz</li> <li><b>Enchant</b> by Dom Lachowicz</li>
<li><b>PyEnchant</b> by Dimitri Merejkowsky</li> <li><b>PyEnchant</b> by Dimitri Merejkowsky</li>
</ul> </ul>
+5 -6
View File
@@ -37,8 +37,8 @@ from typing import Any, Literal, TypeGuard, TypeVar
from urllib.parse import urljoin from urllib.parse import urljoin
from urllib.request import pathname2url from urllib.request import pathname2url
from PyQt5.QtCore import QCoreApplication, QMimeData, QUrl from PyQt6.QtCore import QCoreApplication, QMimeData, QUrl
from PyQt5.QtGui import QColor, QDesktopServices, QFont, QFontDatabase, QFontInfo from PyQt6.QtGui import QColor, QDesktopServices, QFont, QFontDatabase, QFontInfo
from novelwriter.constants import nwConst, nwLabels, nwUnicode, trConst from novelwriter.constants import nwConst, nwLabels, nwUnicode, trConst
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
@@ -434,17 +434,16 @@ def describeFont(font: QFont) -> str:
def fontMatcher(font: QFont) -> QFont: def fontMatcher(font: QFont) -> QFont:
"""Make sure the font is the correct family, if possible. This """Make sure the font is the correct family, if possible. This
ensures that Qt doesn't re-use another font under the hood. The 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. application fonts at runtime.
""" """
info = QFontInfo(font) info = QFontInfo(font)
if (famRequest := font.family()) != (famActual := info.family()): if (famRequest := font.family()) != (famActual := info.family()):
logger.warning("Font mismatch: Requested '%s', but got '%s'", famRequest, famActual) logger.warning("Font mismatch: Requested '%s', but got '%s'", famRequest, famActual)
db = QFontDatabase() if famRequest in QFontDatabase.families():
if famRequest in db.families():
styleRequest, sizeRequest = font.styleName(), font.pointSize() styleRequest, sizeRequest = font.styleName(), font.pointSize()
logger.info("Lookup: %s, %s, %d pt", famRequest, styleRequest, sizeRequest) 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 temp.setPointSize(sizeRequest) # Make sure it isn't changed
famFound, styleFound, sizeFound = temp.family(), temp.styleName(), temp.pointSize() famFound, styleFound, sizeFound = temp.family(), temp.styleName(), temp.pointSize()
if famFound == famRequest: if famFound == famRequest:
+24 -31
View File
@@ -33,12 +33,12 @@ from datetime import datetime
from pathlib import Path from pathlib import Path
from time import time from time import time
from PyQt5.QtCore import ( from PyQt6.QtCore import (
PYQT_VERSION, PYQT_VERSION_STR, QT_VERSION, QT_VERSION_STR, QLibraryInfo, PYQT_VERSION, PYQT_VERSION_STR, QT_VERSION, QT_VERSION_STR, QLibraryInfo,
QLocale, QStandardPaths, QSysInfo, QTranslator QLocale, QStandardPaths, QSysInfo, QTranslator
) )
from PyQt5.QtGui import QFont, QFontDatabase from PyQt6.QtGui import QFont, QFontDatabase
from PyQt5.QtWidgets import QApplication from PyQt6.QtWidgets import QApplication
from novelwriter.common import ( from novelwriter.common import (
NWConfigParser, checkInt, checkPath, describeFont, fontMatcher, NWConfigParser, checkInt, checkPath, describeFont, fontMatcher,
@@ -91,7 +91,7 @@ class Config:
# Localisation # Localisation
# Note that these paths must be strings # Note that these paths must be strings
self._nwLangPath = self._appPath / "assets" / "i18n" 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() hasLocale = (self._nwLangPath / f"nw_{QLocale.system().name()}.qm").exists()
self._qLocale = QLocale.system() if hasLocale else QLocale("en_GB") self._qLocale = QLocale.system() if hasLocale else QLocale("en_GB")
@@ -115,7 +115,6 @@ class Config:
self.guiTheme = "default" # GUI theme self.guiTheme = "default" # GUI theme
self.guiSyntax = "default_light" # Syntax theme self.guiSyntax = "default_light" # Syntax theme
self.guiFont = QFont() # Main GUI font 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.hideVScroll = False # Hide vertical scroll bars on main widgets
self.hideHScroll = False # Hide horizontal 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 self.lastNotes = "0x0" # The latest release notes that have been shown
@@ -217,7 +216,7 @@ class Config:
# System and App Information # System and App Information
# ========================== # ==========================
# Check Qt5 Versions # Check Qt Versions
self.verQtString = QT_VERSION_STR self.verQtString = QT_VERSION_STR
self.verQtValue = QT_VERSION self.verQtValue = QT_VERSION
self.verPyQtString = PYQT_VERSION_STR self.verPyQtString = PYQT_VERSION_STR
@@ -272,27 +271,27 @@ class Config:
@property @property
def mainWinSize(self) -> list[int]: def mainWinSize(self) -> list[int]:
return [int(x*self.guiScale) for x in self._mainWinSize] return self._mainWinSize
@property @property
def welcomeWinSize(self) -> list[int]: def welcomeWinSize(self) -> list[int]:
return [int(x*self.guiScale) for x in self._welcomeSize] return self._welcomeSize
@property @property
def preferencesWinSize(self) -> list[int]: def preferencesWinSize(self) -> list[int]:
return [int(x*self.guiScale) for x in self._prefsWinSize] return self._prefsWinSize
@property @property
def mainPanePos(self) -> list[int]: def mainPanePos(self) -> list[int]:
return [int(x*self.guiScale) for x in self._mainPanePos] return self._mainPanePos
@property @property
def viewPanePos(self) -> list[int]: def viewPanePos(self) -> list[int]:
return [int(x*self.guiScale) for x in self._viewPanePos] return self._viewPanePos
@property @property
def outlinePanePos(self) -> list[int]: def outlinePanePos(self) -> list[int]:
return [int(x*self.guiScale) for x in self._outlnPanePos] return self._outlnPanePos
## ##
# Getters # Getters
@@ -323,8 +322,6 @@ class Config:
adjust it a bit, and we don't want the main window to shrink or adjust it a bit, and we don't want the main window to shrink or
grow each time the app is opened. grow each time the app is opened.
""" """
width = int(width/self.guiScale)
height = int(height/self.guiScale)
if abs(self._mainWinSize[0] - width) > 5: if abs(self._mainWinSize[0] - width) > 5:
self._mainWinSize[0] = width self._mainWinSize[0] = width
if abs(self._mainWinSize[1] - height) > 5: if abs(self._mainWinSize[1] - height) > 5:
@@ -333,29 +330,27 @@ class Config:
def setWelcomeWinSize(self, width: int, height: int) -> None: def setWelcomeWinSize(self, width: int, height: int) -> None:
"""Set the size of the Preferences dialog window.""" """Set the size of the Preferences dialog window."""
self._welcomeSize[0] = int(width/self.guiScale) self._welcomeSize = [width, height]
self._welcomeSize[1] = int(height/self.guiScale)
return return
def setPreferencesWinSize(self, width: int, height: int) -> None: def setPreferencesWinSize(self, width: int, height: int) -> None:
"""Set the size of the Preferences dialog window.""" """Set the size of the Preferences dialog window."""
self._prefsWinSize[0] = int(width/self.guiScale) self._prefsWinSize = [width, height]
self._prefsWinSize[1] = int(height/self.guiScale)
return return
def setMainPanePos(self, pos: list[int]) -> None: def setMainPanePos(self, pos: list[int]) -> None:
"""Set the position of the main GUI splitter.""" """Set the position of the main GUI splitter."""
self._mainPanePos = [int(x/self.guiScale) for x in pos] self._mainPanePos = pos
return return
def setViewPanePos(self, pos: list[int]) -> None: def setViewPanePos(self, pos: list[int]) -> None:
"""Set the position of the viewer meta data splitter.""" """Set the position of the viewer meta data splitter."""
self._viewPanePos = [int(x/self.guiScale) for x in pos] self._viewPanePos = pos
return return
def setOutlinePanePos(self, pos: list[int]) -> None: def setOutlinePanePos(self, pos: list[int]) -> None:
"""Set the position of the outline details splitter.""" """Set the position of the outline details splitter."""
self._outlnPanePos = [int(x/self.guiScale) for x in pos] self._outlnPanePos = pos
return return
def setLastPath(self, key: str, path: str | Path) -> None: def setLastPath(self, key: str, path: str | Path) -> None:
@@ -385,13 +380,12 @@ class Config:
self.guiFont = fontMatcher(font) self.guiFont = fontMatcher(font)
else: else:
font = QFont() font = QFont()
fontDB = QFontDatabase() if self.osWindows and "Arial" in QFontDatabase.families():
if self.osWindows and "Arial" in fontDB.families():
# On Windows we default to Arial if possible # On Windows we default to Arial if possible
font.setFamily("Arial") font.setFamily("Arial")
font.setPointSize(10) font.setPointSize(10)
else: else:
font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont) font = QFontDatabase.systemFont(QFontDatabase.SystemFont.GeneralFont)
self.guiFont = fontMatcher(font) self.guiFont = fontMatcher(font)
logger.debug("GUI font set to: %s", describeFont(font)) logger.debug("GUI font set to: %s", describeFont(font))
QApplication.setFont(self.guiFont) QApplication.setFont(self.guiFont)
@@ -408,8 +402,7 @@ class Config:
font.fromString(value) font.fromString(value)
self.textFont = fontMatcher(font) self.textFont = fontMatcher(font)
else: else:
fontDB = QFontDatabase() fontFam = QFontDatabase.families()
fontFam = fontDB.families()
if self.osWindows and "Arial" in fontFam: if self.osWindows and "Arial" in fontFam:
font = QFont() font = QFont()
font.setFamily("Arial") font.setFamily("Arial")
@@ -419,7 +412,7 @@ class Config:
font.setFamily("Helvetica") font.setFamily("Helvetica")
font.setPointSize(12) font.setPointSize(12)
else: else:
font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont) font = QFontDatabase.systemFont(QFontDatabase.SystemFont.GeneralFont)
self.textFont = fontMatcher(font) self.textFont = fontMatcher(font)
logger.debug("Text font set to: %s", describeFont(self.textFont)) logger.debug("Text font set to: %s", describeFont(self.textFont))
return return
@@ -429,12 +422,12 @@ class Config:
## ##
def pxInt(self, value: int) -> int: def pxInt(self, value: int) -> int:
"""Scale fixed gui sizes by the screen scale factor.""" """Deprecated. Do not use."""
return int(value*self.guiScale) return value
def rpxInt(self, value: int) -> int: def rpxInt(self, value: int) -> int:
"""Un-scale fixed gui sizes by the screen scale factor.""" """Deprecated. Do not use."""
return int(value/self.guiScale) return value
def homePath(self) -> Path: def homePath(self) -> Path:
"""The user's home folder.""" """The user's home folder."""
+1 -1
View File
@@ -23,7 +23,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from __future__ import annotations from __future__ import annotations
from PyQt5.QtCore import QT_TRANSLATE_NOOP, QCoreApplication from PyQt6.QtCore import QT_TRANSLATE_NOOP, QCoreApplication
from novelwriter.enum import ( from novelwriter.enum import (
nwBuildFmt, nwComment, nwItemClass, nwItemLayout, nwOutline, nwStatusShape nwBuildFmt, nwComment, nwItemClass, nwItemLayout, nwOutline, nwStatusShape
+1 -1
View File
@@ -32,7 +32,7 @@ from collections.abc import Iterable
from enum import Enum from enum import Enum
from pathlib import Path 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 import CONFIG
from novelwriter.common import checkUuid, isHandle, jsonEncode from novelwriter.common import checkUuid, isHandle, jsonEncode
+1 -1
View File
@@ -35,7 +35,7 @@ from functools import partial
from pathlib import Path from pathlib import Path
from zipfile import ZipFile, is_zipfile from zipfile import ZipFile, is_zipfile
from PyQt5.QtCore import QCoreApplication from PyQt6.QtCore import QCoreApplication
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.common import isHandle, minmax, simplified from novelwriter.common import isHandle, minmax, simplified
+1 -1
View File
@@ -28,7 +28,7 @@ import logging
from collections.abc import Iterable from collections.abc import Iterable
from pathlib import Path from pathlib import Path
from PyQt5.QtGui import QFont from PyQt6.QtGui import QFont
from novelwriter import CONFIG from novelwriter import CONFIG
from novelwriter.constants import nwLabels from novelwriter.constants import nwLabels
+1 -1
View File
@@ -27,7 +27,7 @@ import logging
from typing import TYPE_CHECKING, Any 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 import CONFIG, SHARED
from novelwriter.common import ( from novelwriter.common import (
+2 -2
View File
@@ -28,8 +28,8 @@ import logging
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import QAbstractItemModel, QMimeData, QModelIndex, Qt from PyQt6.QtCore import QAbstractItemModel, QMimeData, QModelIndex, Qt
from PyQt5.QtGui import QFont, QIcon from PyQt6.QtGui import QFont, QIcon
from novelwriter.common import decodeMimeHandles, encodeMimeHandles, minmax from novelwriter.common import decodeMimeHandles, encodeMimeHandles, minmax
from novelwriter.constants import nwConst from novelwriter.constants import nwConst
+1 -1
View File
@@ -31,7 +31,7 @@ from functools import partial
from pathlib import Path from pathlib import Path
from time import time from time import time
from PyQt5.QtCore import QCoreApplication from PyQt6.QtCore import QCoreApplication
from novelwriter import CONFIG, SHARED, __hexversion__, __version__ from novelwriter import CONFIG, SHARED, __hexversion__, __version__
from novelwriter.common import ( from novelwriter.common import (
+1 -1
View File
@@ -31,7 +31,7 @@ from collections.abc import Iterator
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import QLocale from PyQt6.QtCore import QLocale
from novelwriter.constants import nwFiles from novelwriter.constants import nwFiles
from novelwriter.error import logException from novelwriter.error import logException
+2 -2
View File
@@ -31,8 +31,8 @@ import random
from collections.abc import Iterable from collections.abc import Iterable
from typing import Literal, TypeGuard from typing import Literal, TypeGuard
from PyQt5.QtCore import QPointF, Qt from PyQt6.QtCore import QPointF, Qt
from PyQt5.QtGui import QColor, QIcon, QPainter, QPainterPath, QPixmap, QPolygonF from PyQt6.QtGui import QColor, QIcon, QPainter, QPainterPath, QPixmap, QPolygonF
from novelwriter import SHARED from novelwriter import SHARED
from novelwriter.common import simplified from novelwriter.common import simplified
+1 -1
View File
@@ -31,7 +31,7 @@ from collections.abc import Iterable, Iterator
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING, Literal, overload from typing import TYPE_CHECKING, Literal, overload
from PyQt5.QtCore import QModelIndex from PyQt6.QtCore import QModelIndex
from novelwriter import SHARED from novelwriter import SHARED
from novelwriter.constants import nwFiles, nwLabels, trConst from novelwriter.constants import nwFiles, nwLabels, trConst
+2 -3
View File
@@ -25,8 +25,8 @@ from __future__ import annotations
import logging import logging
from PyQt5.QtGui import QCloseEvent, QColor from PyQt6.QtGui import QCloseEvent
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QDialogButtonBox, QHBoxLayout, QLabel, QTextBrowser, QVBoxLayout, QWidget QDialogButtonBox, QHBoxLayout, QLabel, QTextBrowser, QVBoxLayout, QWidget
) )
@@ -58,7 +58,6 @@ class GuiAbout(NDialog):
# Logo and Banner # Logo and Banner
self.nwImage = SHARED.theme.loadDecoration("nw-text", h=nwH) 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 = QLabel(self)
self.nwLogo.setPixmap(SHARED.theme.getPixmap("novelwriter", (nwPx, nwPx))) self.nwLogo.setPixmap(SHARED.theme.getPixmap("novelwriter", (nwPx, nwPx)))
+2 -2
View File
@@ -26,8 +26,8 @@ from __future__ import annotations
import logging import logging
from PyQt5.QtCore import Qt, pyqtSlot from PyQt6.QtCore import Qt, pyqtSlot
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QAbstractItemView, QDialogButtonBox, QGridLayout, QLabel, QListWidget, QAbstractItemView, QDialogButtonBox, QGridLayout, QLabel, QListWidget,
QListWidgetItem, QVBoxLayout, QWidget QListWidgetItem, QVBoxLayout, QWidget
) )
+2 -2
View File
@@ -26,8 +26,8 @@ from __future__ import annotations
import logging import logging
from PyQt5.QtCore import pyqtSlot from PyQt6.QtCore import pyqtSlot
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QAbstractItemView, QComboBox, QDialogButtonBox, QGridLayout, QLabel, QAbstractItemView, QComboBox, QDialogButtonBox, QGridLayout, QLabel,
QListWidget, QListWidgetItem, QVBoxLayout, QWidget QListWidget, QListWidgetItem, QVBoxLayout, QWidget
) )
+1 -1
View File
@@ -25,7 +25,7 @@ from __future__ import annotations
import logging 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 import CONFIG
from novelwriter.extensions.modified import NDialog from novelwriter.extensions.modified import NDialog
+3 -3
View File
@@ -26,9 +26,9 @@ from __future__ import annotations
import logging import logging
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QCloseEvent, QKeyEvent, QKeySequence from PyQt6.QtGui import QCloseEvent, QKeyEvent, QKeySequence
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QCompleter, QDialogButtonBox, QFileDialog, QHBoxLayout, QLineEdit, QCompleter, QDialogButtonBox, QFileDialog, QHBoxLayout, QLineEdit,
QPushButton, QVBoxLayout, QWidget QPushButton, QVBoxLayout, QWidget
) )
+3 -3
View File
@@ -29,9 +29,9 @@ import logging
from pathlib import Path from pathlib import Path
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QCloseEvent, QColor from PyQt6.QtGui import QCloseEvent, QColor
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QAbstractItemView, QApplication, QColorDialog, QDialogButtonBox, QAbstractItemView, QApplication, QColorDialog, QDialogButtonBox,
QFileDialog, QGridLayout, QHBoxLayout, QLineEdit, QMenu, QStackedWidget, QFileDialog, QGridLayout, QHBoxLayout, QLineEdit, QMenu, QStackedWidget,
QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
+3 -3
View File
@@ -25,9 +25,9 @@ from __future__ import annotations
import logging import logging
from PyQt5.QtCore import QSize, pyqtSlot from PyQt6.QtCore import QSize, pyqtSlot
from PyQt5.QtGui import QFontMetrics from PyQt6.QtGui import QFontMetrics
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QDialogButtonBox, QFrame, QHBoxLayout, QLabel, QListWidget, QDialogButtonBox, QFrame, QHBoxLayout, QLabel, QListWidget,
QListWidgetItem, QVBoxLayout, QWidget QListWidgetItem, QVBoxLayout, QWidget
) )
+3 -3
View File
@@ -27,9 +27,9 @@ import logging
from pathlib import Path from pathlib import Path
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QCloseEvent from PyQt6.QtGui import QCloseEvent
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QAbstractItemView, QApplication, QDialogButtonBox, QFileDialog, QAbstractItemView, QApplication, QDialogButtonBox, QFileDialog,
QHBoxLayout, QLineEdit, QListWidget, QVBoxLayout, QWidget QHBoxLayout, QLineEdit, QListWidget, QVBoxLayout, QWidget
) )
+5 -5
View File
@@ -29,9 +29,9 @@ import sys
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import Qt, pyqtSlot from PyQt6.QtCore import Qt, pyqtSlot
from PyQt5.QtGui import QFont, QFontDatabase from PyQt6.QtGui import QFont, QFontDatabase
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QApplication, QDialog, QDialogButtonBox, QGridLayout, QLabel, QApplication, QDialog, QDialogButtonBox, QGridLayout, QLabel,
QPlainTextEdit, QStyle, QWidget QPlainTextEdit, QStyle, QWidget
) )
@@ -118,7 +118,7 @@ class NWErrorMessage(QDialog):
""" """
from traceback import format_tb 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 import __version__
from novelwriter.constants import nwConst from novelwriter.constants import nwConst
@@ -174,7 +174,7 @@ def exceptionHandler(exType: type, exValue: BaseException, exTrace: TracebackTyp
"""Function to catch unhandled global exceptions.""" """Function to catch unhandled global exceptions."""
from traceback import print_tb from traceback import print_tb
from PyQt5.QtWidgets import QApplication from PyQt6.QtWidgets import QApplication
logger.critical("%s: %s", exType.__name__, str(exValue)) logger.critical("%s: %s", exType.__name__, str(exValue))
print_tb(exTrace) print_tb(exTrace)
+2 -2
View File
@@ -27,8 +27,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from __future__ import annotations from __future__ import annotations
from PyQt5.QtGui import QColor, QFont, QPalette, QPixmap from PyQt6.QtGui import QColor, QFont, QPalette, QPixmap
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QAbstractButton, QFrame, QHBoxLayout, QLabel, QLayout, QScrollArea, QAbstractButton, QFrame, QHBoxLayout, QLabel, QLayout, QScrollArea,
QVBoxLayout, QWidget QVBoxLayout, QWidget
) )
+3 -3
View File
@@ -24,9 +24,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from __future__ import annotations from __future__ import annotations
from PyQt5.QtCore import QEvent, QObject from PyQt6.QtCore import QEvent, QObject
from PyQt5.QtGui import QStatusTipEvent, QWheelEvent from PyQt6.QtGui import QStatusTipEvent, QWheelEvent
from PyQt5.QtWidgets import QWidget from PyQt6.QtWidgets import QWidget
class WheelEventFilter(QObject): class WheelEventFilter(QObject):
+3 -3
View File
@@ -30,9 +30,9 @@ from __future__ import annotations
from enum import Enum from enum import Enum
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import QSize, Qt, pyqtSignal, pyqtSlot from PyQt6.QtCore import QSize, Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QMouseEvent, QWheelEvent from PyQt6.QtGui import QMouseEvent, QWheelEvent
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QApplication, QComboBox, QDialog, QDoubleSpinBox, QLabel, QSpinBox, QApplication, QComboBox, QDialog, QDoubleSpinBox, QLabel, QSpinBox,
QToolButton, QWidget QToolButton, QWidget
) )
+2 -2
View File
@@ -25,8 +25,8 @@ from __future__ import annotations
import logging import logging
from PyQt5.QtCore import pyqtSignal, pyqtSlot from PyQt6.QtCore import pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QComboBox, QWidget from PyQt6.QtWidgets import QComboBox, QWidget
from novelwriter import SHARED from novelwriter import SHARED
from novelwriter.constants import nwLabels from novelwriter.constants import nwLabels
+5 -5
View File
@@ -25,11 +25,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from __future__ import annotations from __future__ import annotations
from PyQt5.QtCore import QPoint, QRectF, QSize, Qt, pyqtSignal, pyqtSlot from PyQt6.QtCore import QPoint, QRectF, QSize, Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QColor, QPainter, QPaintEvent, QPolygon from PyQt6.QtGui import QAction, QColor, QPainter, QPaintEvent, QPolygon
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QAbstractButton, QAction, QButtonGroup, QLabel, QStyle, QAbstractButton, QButtonGroup, QLabel, QStyle, QStyleOptionToolButton,
QStyleOptionToolButton, QToolBar, QToolButton, QWidget QToolBar, QToolButton, QWidget
) )
from novelwriter.types import ( from novelwriter.types import (
+3 -3
View File
@@ -26,9 +26,9 @@ from __future__ import annotations
from math import ceil from math import ceil
from PyQt5.QtCore import QRect from PyQt6.QtCore import QRect
from PyQt5.QtGui import QBrush, QColor, QPainter, QPaintEvent, QPen from PyQt6.QtGui import QBrush, QColor, QPainter, QPaintEvent, QPen
from PyQt5.QtWidgets import QProgressBar, QWidget from PyQt6.QtWidgets import QProgressBar, QWidget
from novelwriter.types import ( from novelwriter.types import (
QtAlignCenter, QtPaintAntiAlias, QtRoundCap, QtSizeFixed, QtSolidLine, QtAlignCenter, QtPaintAntiAlias, QtRoundCap, QtSizeFixed, QtSolidLine,
+2 -2
View File
@@ -25,8 +25,8 @@ from __future__ import annotations
import logging import logging
from PyQt5.QtGui import QColor, QPainter, QPaintEvent from PyQt6.QtGui import QColor, QPainter, QPaintEvent
from PyQt5.QtWidgets import QAbstractButton, QWidget from PyQt6.QtWidgets import QAbstractButton, QWidget
from novelwriter import CONFIG from novelwriter import CONFIG
from novelwriter.enum import nwTrinary from novelwriter.enum import nwTrinary
+9 -8
View File
@@ -23,9 +23,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from __future__ import annotations from __future__ import annotations
from PyQt5.QtCore import QEvent, QPropertyAnimation, Qt, pyqtProperty from PyQt6.QtCore import QPropertyAnimation, Qt, pyqtProperty
from PyQt5.QtGui import QMouseEvent, QPainter, QPaintEvent, QResizeEvent from PyQt6.QtGui import QEnterEvent, QMouseEvent, QPainter, QPaintEvent, QResizeEvent
from PyQt5.QtWidgets import QAbstractButton, QWidget from PyQt6.QtWidgets import QAbstractButton, QWidget
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.types import QtMouseLeft, QtNoPen, QtPaintAntiAlias, QtSizeFixed from novelwriter.types import QtMouseLeft, QtNoPen, QtPaintAntiAlias, QtSizeFixed
@@ -57,11 +57,11 @@ class NSwitch(QAbstractButton):
# Properties # Properties
## ##
@pyqtProperty(int) # type: ignore @pyqtProperty(int)
def offset(self) -> int: # type: ignore def offset(self) -> int: # type: ignore
return self._offset return self._offset
@offset.setter # type: ignore @offset.setter
def offset(self, offset: int) -> None: def offset(self, offset: int) -> None:
self._offset = offset self._offset = offset
self.update() self.update()
@@ -98,14 +98,14 @@ class NSwitch(QAbstractButton):
trackBrush = palette.highlight() trackBrush = palette.highlight()
thumbBrush = palette.highlightedText() thumbBrush = palette.highlightedText()
else: else:
trackBrush = palette.dark() trackBrush = palette.mid()
thumbBrush = palette.light() thumbBrush = palette.light()
if self.isEnabled(): if self.isEnabled():
trackOpacity = 1.0 trackOpacity = 1.0
else: else:
trackOpacity = 0.6 trackOpacity = 0.6
trackBrush = palette.shadow() trackBrush = palette.dark()
thumbBrush = palette.mid() thumbBrush = palette.mid()
painter.setBrush(trackBrush) painter.setBrush(trackBrush)
@@ -114,6 +114,7 @@ class NSwitch(QAbstractButton):
painter.setBrush(thumbBrush) painter.setBrush(thumbBrush)
painter.drawEllipse(self._offset - self._rR, self._rB, self._rH, self._rH) painter.drawEllipse(self._offset - self._rR, self._rB, self._rH, self._rH)
painter.end()
return return
@@ -128,7 +129,7 @@ class NSwitch(QAbstractButton):
anim.start() anim.start()
return return
def enterEvent(self, event: QEvent) -> None: def enterEvent(self, event: QEnterEvent) -> None:
"""Change the cursor when hovering the button.""" """Change the cursor when hovering the button."""
self.setCursor(Qt.CursorShape.PointingHandCursor) self.setCursor(Qt.CursorShape.PointingHandCursor)
super().enterEvent(event) super().enterEvent(event)
+3 -3
View File
@@ -23,9 +23,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from __future__ import annotations from __future__ import annotations
from PyQt5.QtCore import pyqtSignal from PyQt6.QtCore import pyqtSignal
from PyQt5.QtGui import QIcon from PyQt6.QtGui import QIcon
from PyQt5.QtWidgets import QGridLayout, QLabel, QScrollArea, QWidget from PyQt6.QtWidgets import QGridLayout, QLabel, QScrollArea, QWidget
from novelwriter.extensions.switch import NSwitch from novelwriter.extensions.switch import NSwitch
from novelwriter.types import ( from novelwriter.types import (
+3 -3
View File
@@ -31,9 +31,9 @@ from time import sleep
from urllib.error import HTTPError from urllib.error import HTTPError
from urllib.request import Request, urlopen from urllib.request import Request, urlopen
from PyQt5.QtCore import QObject, QRunnable, QUrl, pyqtSignal, pyqtSlot from PyQt6.QtCore import QObject, QRunnable, QUrl, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QDesktopServices from PyQt6.QtGui import QDesktopServices
from PyQt5.QtWidgets import QLabel, QVBoxLayout, QWidget from PyQt6.QtWidgets import QLabel, QVBoxLayout, QWidget
from novelwriter import CONFIG, SHARED, __date__, __domain__, __version__ from novelwriter import CONFIG, SHARED, __date__, __domain__, __version__
from novelwriter.common import formatVersion from novelwriter.common import formatVersion
+4 -1
View File
@@ -27,7 +27,9 @@ import re
from enum import Flag, IntEnum 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"\ ": ""} ESCAPES = {r"\*": "*", r"\~": "~", r"\_": "_", r"\[": "[", r"\]": "]", r"\ ": ""}
RX_ESC = re.compile("|".join([re.escape(k) for k in ESCAPES.keys()]), flags=re.DOTALL) 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 return text
@nwDataClass
class TextDocumentTheme: class TextDocumentTheme:
"""Default document theme.""" """Default document theme."""
+2 -2
View File
@@ -33,8 +33,8 @@ from pathlib import Path
from typing import NamedTuple from typing import NamedTuple
from zipfile import ZIP_DEFLATED, ZipFile from zipfile import ZIP_DEFLATED, ZipFile
from PyQt5.QtCore import QMargins, QSize from PyQt6.QtCore import QMargins, QSize
from PyQt5.QtGui import QColor from PyQt6.QtGui import QColor
from novelwriter import __version__ from novelwriter import __version__
from novelwriter.common import firstFloat, xmlElement, xmlSubElem from novelwriter.common import firstFloat, xmlElement, xmlSubElem
+2 -2
View File
@@ -31,8 +31,8 @@ from abc import ABC, abstractmethod
from pathlib import Path from pathlib import Path
from typing import NamedTuple from typing import NamedTuple
from PyQt5.QtCore import QLocale from PyQt6.QtCore import QLocale
from PyQt5.QtGui import QColor, QFont from PyQt6.QtGui import QColor, QFont
from novelwriter import CONFIG from novelwriter import CONFIG
from novelwriter.common import checkInt, fontMatcher, numberToRoman from novelwriter.common import checkInt, fontMatcher, numberToRoman
+1 -1
View File
@@ -35,7 +35,7 @@ from hashlib import sha256
from pathlib import Path from pathlib import Path
from zipfile import ZIP_DEFLATED, ZipFile from zipfile import ZIP_DEFLATED, ZipFile
from PyQt5.QtGui import QColor, QFont from PyQt6.QtGui import QColor, QFont
from novelwriter import __version__ from novelwriter import __version__
from novelwriter.common import xmlElement, xmlIndent, xmlSubElem from novelwriter.common import xmlElement, xmlIndent, xmlSubElem
+8 -10
View File
@@ -27,12 +27,12 @@ import logging
from pathlib import Path from pathlib import Path
from PyQt5.QtCore import QMarginsF, QSizeF from PyQt6.QtCore import QMarginsF, QSizeF
from PyQt5.QtGui import ( from PyQt6.QtGui import (
QColor, QFont, QFontDatabase, QPageSize, QTextBlockFormat, QTextCharFormat, QColor, QFont, QFontDatabase, QPageLayout, QPageSize, QTextBlockFormat,
QTextCursor, QTextDocument, QTextFrameFormat QTextCharFormat, QTextCursor, QTextDocument, QTextFrameFormat
) )
from PyQt5.QtPrintSupport import QPrinter from PyQt6.QtPrintSupport import QPrinter
from novelwriter import __version__ from novelwriter import __version__
from novelwriter.constants import nwStyles, nwUnicode from novelwriter.constants import nwStyles, nwUnicode
@@ -129,10 +129,9 @@ class ToQTextDocument(Tokenizer):
super().initDocument() super().initDocument()
if pdf: if pdf:
fontDB = QFontDatabase()
family = self._textFont.family() family = self._textFont.family()
style = self._textFont.styleName() 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.setUndoRedoEnabled(False)
self._document.blockSignals(True) self._document.blockSignals(True)
@@ -268,7 +267,6 @@ class ToQTextDocument(Tokenizer):
def saveDocument(self, path: Path) -> None: def saveDocument(self, path: Path) -> None:
"""Save the document as a PDF file.""" """Save the document as a PDF file."""
m = self._pageMargins
logger.info("Writing PDF at %d DPI", self._dpi) logger.info("Writing PDF at %d DPI", self._dpi)
printer = QPrinter(QPrinter.PrinterMode.HighResolution) printer = QPrinter(QPrinter.PrinterMode.HighResolution)
@@ -277,11 +275,11 @@ class ToQTextDocument(Tokenizer):
printer.setResolution(self._dpi) printer.setResolution(self._dpi)
printer.setOutputFormat(QPrinter.OutputFormat.PdfFormat) printer.setOutputFormat(QPrinter.OutputFormat.PdfFormat)
printer.setPageSize(self._pageSize) 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)) printer.setOutputFileName(str(path))
self._document.documentLayout().setPaintDevice(printer) 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) self._document.print(printer)
return return
+39 -31
View File
@@ -37,18 +37,18 @@ import logging
from enum import Enum from enum import Enum
from time import time from time import time
from PyQt5.QtCore import ( from PyQt6.QtCore import (
QObject, QPoint, QRegularExpression, QRunnable, Qt, QTimer, pyqtSignal, QObject, QPoint, QRegularExpression, QRunnable, Qt, QTimer, pyqtSignal,
pyqtSlot pyqtSlot
) )
from PyQt5.QtGui import ( from PyQt6.QtGui import (
QColor, QCursor, QDragEnterEvent, QDragMoveEvent, QDropEvent, QKeyEvent, QAction, QColor, QCursor, QDragEnterEvent, QDragMoveEvent, QDropEvent,
QKeySequence, QMouseEvent, QPalette, QPixmap, QResizeEvent, QTextBlock, QKeyEvent, QKeySequence, QMouseEvent, QPalette, QPixmap, QResizeEvent,
QTextCursor, QTextDocument, QTextOption QShortcut, QTextBlock, QTextCursor, QTextDocument, QTextOption
) )
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QAction, QApplication, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QApplication, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMenu,
QMenu, QPlainTextEdit, QShortcut, QToolBar, QVBoxLayout, QWidget QPlainTextEdit, QToolBar, QVBoxLayout, QWidget
) )
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
@@ -287,16 +287,18 @@ class GuiDocEditor(QPlainTextEdit):
def updateSyntaxColours(self) -> None: def updateSyntaxColours(self) -> None:
"""Update the syntax highlighting theme.""" """Update the syntax highlighting theme."""
mainPalette = self.palette() syntax = SHARED.theme.syntaxTheme
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)
docPalette = self.viewport().palette() palette = self.palette()
docPalette.setColor(QPalette.ColorRole.Base, SHARED.theme.colBack) palette.setColor(QPalette.ColorRole.Window, syntax.back)
docPalette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText) palette.setColor(QPalette.ColorRole.Base, syntax.back)
self.viewport().setPalette(docPalette) 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.docHeader.matchColours()
self.docFooter.matchColours() self.docFooter.matchColours()
@@ -2032,8 +2034,9 @@ class GuiDocEditor(QPlainTextEdit):
return cursor return cursor
def _makeSelection(self, mode: QTextCursor.SelectionType, def _makeSelection(
cursor: QTextCursor | None = None) -> None: self, mode: QTextCursor.SelectionType, cursor: QTextCursor | None = None
) -> None:
"""Select text based on selection mode.""" """Select text based on selection mode."""
if cursor is None: if cursor is None:
cursor = self.textCursor() cursor = self.textCursor()
@@ -2432,10 +2435,12 @@ class GuiDocToolBar(QWidget):
def updateTheme(self) -> None: def updateTheme(self) -> None:
"""Initialise GUI elements that depend on specific settings.""" """Initialise GUI elements that depend on specific settings."""
palette = QPalette() syntax = SHARED.theme.syntaxTheme
palette.setColor(QPalette.ColorRole.Window, SHARED.theme.colBack)
palette.setColor(QPalette.ColorRole.WindowText, SHARED.theme.colText) palette = self.palette()
palette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText) 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.setPalette(palette)
self.tbBoldMD.setThemeIcon("fmt_bold", "orange") 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 """Update the colours of the widget to match those of the syntax
theme rather than the main GUI. theme rather than the main GUI.
""" """
palette = QPalette() syntax = SHARED.theme.syntaxTheme
palette.setColor(QPalette.ColorRole.Window, SHARED.theme.colBack) palette = self.palette()
palette.setColor(QPalette.ColorRole.WindowText, SHARED.theme.colText) palette.setColor(QPalette.ColorRole.Window, syntax.back)
palette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText) palette.setColor(QPalette.ColorRole.WindowText, syntax.text)
palette.setColor(QPalette.ColorRole.Text, syntax.text)
self.setPalette(palette) self.setPalette(palette)
self.itemTitle.setTextColors( self.itemTitle.setTextColors(
color=palette.windowText().color(), faded=SHARED.theme.fadedText 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 """Update the colours of the widget to match those of the syntax
theme rather than the main GUI. theme rather than the main GUI.
""" """
palette = QPalette() syntax = SHARED.theme.syntaxTheme
palette.setColor(QPalette.ColorRole.Window, SHARED.theme.colBack)
palette.setColor(QPalette.ColorRole.WindowText, SHARED.theme.colText) palette = self.palette()
palette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText) 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.setPalette(palette)
self.statusText.setPalette(palette) self.statusText.setPalette(palette)
+31 -30
View File
@@ -29,8 +29,8 @@ import re
from time import time from time import time
from PyQt5.QtCore import Qt from PyQt6.QtCore import Qt
from PyQt5.QtGui import ( from PyQt6.QtGui import (
QBrush, QColor, QFont, QSyntaxHighlighter, QTextBlockUserData, QBrush, QColor, QFont, QSyntaxHighlighter, QTextBlockUserData,
QTextCharFormat, QTextDocument QTextCharFormat, QTextDocument
) )
@@ -91,44 +91,45 @@ class GuiDocHighlighter(QSyntaxHighlighter):
rules and building the RegExes. rules and building the RegExes.
""" """
logger.debug("Setting up highlighting rules") logger.debug("Setting up highlighting rules")
syntax = SHARED.theme.syntaxTheme
colEmph = SHARED.theme.colEmph if CONFIG.highlightEmph else None colEmph = syntax.emph if CONFIG.highlightEmph else None
colBreak = QColor(SHARED.theme.colEmph) colBreak = QColor(syntax.emph)
colBreak.setAlpha(64) colBreak.setAlpha(64)
# Create Character Formats # Create Character Formats
self._addCharFormat("text", SHARED.theme.colText) self._addCharFormat("text", syntax.text)
self._addCharFormat("header1", SHARED.theme.colHead, "b", nwStyles.H_SIZES[1]) self._addCharFormat("header1", syntax.head, "b", nwStyles.H_SIZES[1])
self._addCharFormat("header2", SHARED.theme.colHead, "b", nwStyles.H_SIZES[2]) self._addCharFormat("header2", syntax.head, "b", nwStyles.H_SIZES[2])
self._addCharFormat("header3", SHARED.theme.colHead, "b", nwStyles.H_SIZES[3]) self._addCharFormat("header3", syntax.head, "b", nwStyles.H_SIZES[3])
self._addCharFormat("header4", SHARED.theme.colHead, "b", nwStyles.H_SIZES[4]) self._addCharFormat("header4", syntax.head, "b", nwStyles.H_SIZES[4])
self._addCharFormat("head1h", SHARED.theme.colHeadH, "b", nwStyles.H_SIZES[1]) self._addCharFormat("head1h", syntax.headH, "b", nwStyles.H_SIZES[1])
self._addCharFormat("head2h", SHARED.theme.colHeadH, "b", nwStyles.H_SIZES[2]) self._addCharFormat("head2h", syntax.headH, "b", nwStyles.H_SIZES[2])
self._addCharFormat("head3h", SHARED.theme.colHeadH, "b", nwStyles.H_SIZES[3]) self._addCharFormat("head3h", syntax.headH, "b", nwStyles.H_SIZES[3])
self._addCharFormat("head4h", SHARED.theme.colHeadH, "b", nwStyles.H_SIZES[4]) self._addCharFormat("head4h", syntax.headH, "b", nwStyles.H_SIZES[4])
self._addCharFormat("bold", colEmph, "b") self._addCharFormat("bold", colEmph, "b")
self._addCharFormat("italic", colEmph, "i") self._addCharFormat("italic", colEmph, "i")
self._addCharFormat("strike", SHARED.theme.colHidden, "s") self._addCharFormat("strike", syntax.hidden, "s")
self._addCharFormat("mspaces", SHARED.theme.colError, "err") self._addCharFormat("mspaces", syntax.error, "err")
self._addCharFormat("nobreak", colBreak, "bg") self._addCharFormat("nobreak", colBreak, "bg")
self._addCharFormat("altdialog", SHARED.theme.colDialA) self._addCharFormat("altdialog", syntax.dialA)
self._addCharFormat("dialog", SHARED.theme.colDialN) self._addCharFormat("dialog", syntax.dialN)
self._addCharFormat("replace", SHARED.theme.colRepTag) self._addCharFormat("replace", syntax.repTag)
self._addCharFormat("hidden", SHARED.theme.colHidden) self._addCharFormat("hidden", syntax.hidden)
self._addCharFormat("markup", SHARED.theme.colHidden) self._addCharFormat("markup", syntax.hidden)
self._addCharFormat("link", SHARED.theme.colLink, "u") self._addCharFormat("link", syntax.link, "u")
self._addCharFormat("note", SHARED.theme.colNote) self._addCharFormat("note", syntax.note)
self._addCharFormat("code", SHARED.theme.colCode) self._addCharFormat("code", syntax.code)
self._addCharFormat("keyword", SHARED.theme.colKey) self._addCharFormat("keyword", syntax.key)
self._addCharFormat("tag", SHARED.theme.colTag, "u") self._addCharFormat("tag", syntax.tag, "u")
self._addCharFormat("modifier", SHARED.theme.colMod) self._addCharFormat("modifier", syntax.mod)
self._addCharFormat("value", SHARED.theme.colVal) self._addCharFormat("value", syntax.val)
self._addCharFormat("optional", SHARED.theme.colOpt) self._addCharFormat("optional", syntax.opt)
self._addCharFormat("invalid", None, "err") self._addCharFormat("invalid", None, "err")
# Cache Spell Error Format # Cache Spell Error Format
self._spellErr = QTextCharFormat() self._spellErr = QTextCharFormat()
self._spellErr.setUnderlineColor(SHARED.theme.colSpell) self._spellErr.setUnderlineColor(syntax.spell)
self._spellErr.setUnderlineStyle(QTextCharFormat.UnderlineStyle.SpellCheckUnderline) self._spellErr.setUnderlineStyle(QTextCharFormat.UnderlineStyle.SpellCheckUnderline)
self._txtRules.clear() self._txtRules.clear()
@@ -450,7 +451,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
if "s" in styles: if "s" in styles:
charFormat.setFontStrikeOut(True) charFormat.setFontStrikeOut(True)
if "err" in styles: if "err" in styles:
charFormat.setUnderlineColor(SHARED.theme.colError) charFormat.setUnderlineColor(SHARED.theme.syntaxTheme.error)
charFormat.setUnderlineStyle(QTextCharFormat.UnderlineStyle.SpellCheckUnderline) charFormat.setUnderlineStyle(QTextCharFormat.UnderlineStyle.SpellCheckUnderline)
if "bg" in styles and color is not None: if "bg" in styles and color is not None:
charFormat.setBackground(QBrush(color, Qt.BrushStyle.SolidPattern)) charFormat.setBackground(QBrush(color, Qt.BrushStyle.SolidPattern))
+41 -37
View File
@@ -30,14 +30,14 @@ import logging
from enum import Enum from enum import Enum
from PyQt5.QtCore import QPoint, Qt, QUrl, pyqtSignal, pyqtSlot from PyQt6.QtCore import QPoint, Qt, QUrl, pyqtSignal, pyqtSlot
from PyQt5.QtGui import ( from PyQt6.QtGui import (
QCursor, QDesktopServices, QDragEnterEvent, QDragMoveEvent, QDropEvent, QAction, QCursor, QDesktopServices, QDragEnterEvent, QDragMoveEvent,
QMouseEvent, QPalette, QResizeEvent, QTextCursor QDropEvent, QMouseEvent, QPalette, QResizeEvent, QTextCursor
) )
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QAction, QApplication, QFrame, QHBoxLayout, QMenu, QTextBrowser, QApplication, QFrame, QHBoxLayout, QMenu, QTextBrowser, QToolButton,
QToolButton, QWidget QWidget
) )
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
@@ -155,33 +155,35 @@ class GuiDocViewer(QTextBrowser):
self.docFooter.updateFont() self.docFooter.updateFont()
# Set the widget colours to match syntax theme # Set the widget colours to match syntax theme
mainPalette = self.palette() syntax = SHARED.theme.syntaxTheme
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)
docPalette = self.viewport().palette() palette = self.palette()
docPalette.setColor(QPalette.ColorRole.Base, SHARED.theme.colBack) palette.setColor(QPalette.ColorRole.Window, syntax.back)
docPalette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText) palette.setColor(QPalette.ColorRole.Base, syntax.back)
self.viewport().setPalette(docPalette) 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.docHeader.matchColours()
self.docFooter.matchColours() self.docFooter.matchColours()
# Update theme colours # Update theme colours
self._docTheme.text = SHARED.theme.colText self._docTheme.text = syntax.text
self._docTheme.highlight = SHARED.theme.colMark self._docTheme.highlight = syntax.mark
self._docTheme.head = SHARED.theme.colHead self._docTheme.head = syntax.head
self._docTheme.link = SHARED.theme.colLink self._docTheme.link = syntax.link
self._docTheme.comment = SHARED.theme.colHidden self._docTheme.comment = syntax.hidden
self._docTheme.note = SHARED.theme.colNote self._docTheme.note = syntax.note
self._docTheme.code = SHARED.theme.colCode self._docTheme.code = syntax.code
self._docTheme.modifier = SHARED.theme.colMod self._docTheme.modifier = syntax.mod
self._docTheme.keyword = SHARED.theme.colKey self._docTheme.keyword = syntax.key
self._docTheme.tag = SHARED.theme.colTag self._docTheme.tag = syntax.tag
self._docTheme.optional = SHARED.theme.colOpt self._docTheme.optional = syntax.opt
self._docTheme.dialog = SHARED.theme.colDialN self._docTheme.dialog = syntax.dialN
self._docTheme.altdialog = SHARED.theme.colDialA self._docTheme.altdialog = syntax.dialA
# Set default text margins # Set default text margins
self.document().setDocumentMargin(0) self.document().setDocumentMargin(0)
@@ -783,10 +785,11 @@ class GuiDocViewHeader(QWidget):
"""Update the colours of the widget to match those of the syntax """Update the colours of the widget to match those of the syntax
theme rather than the main GUI. theme rather than the main GUI.
""" """
palette = QPalette() syntax = SHARED.theme.syntaxTheme
palette.setColor(QPalette.ColorRole.Window, SHARED.theme.colBack) palette = self.palette()
palette.setColor(QPalette.ColorRole.WindowText, SHARED.theme.colText) palette.setColor(QPalette.ColorRole.Window, syntax.back)
palette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText) palette.setColor(QPalette.ColorRole.WindowText, syntax.text)
palette.setColor(QPalette.ColorRole.Text, syntax.text)
self.setPalette(palette) self.setPalette(palette)
self.itemTitle.setTextColors( self.itemTitle.setTextColors(
color=palette.windowText().color(), faded=SHARED.theme.fadedText 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 """Update the colours of the widget to match those of the syntax
theme rather than the main GUI. theme rather than the main GUI.
""" """
palette = QPalette() syntax = SHARED.theme.syntaxTheme
palette.setColor(QPalette.ColorRole.Window, SHARED.theme.colBack) palette = self.palette()
palette.setColor(QPalette.ColorRole.WindowText, SHARED.theme.colText) palette.setColor(QPalette.ColorRole.Window, syntax.back)
palette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText) palette.setColor(QPalette.ColorRole.WindowText, syntax.text)
palette.setColor(QPalette.ColorRole.Text, syntax.text)
self.setPalette(palette) self.setPalette(palette)
return return
+2 -2
View File
@@ -27,8 +27,8 @@ import logging
from enum import Enum from enum import Enum
from PyQt5.QtCore import QModelIndex, Qt, pyqtSignal, pyqtSlot from PyQt6.QtCore import QModelIndex, Qt, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QAbstractItemView, QFrame, QMenu, QTabWidget, QToolButton, QTreeWidget, QAbstractItemView, QFrame, QMenu, QTabWidget, QToolButton, QTreeWidget,
QTreeWidgetItem, QVBoxLayout, QWidget QTreeWidgetItem, QVBoxLayout, QWidget
) )
+3 -3
View File
@@ -28,9 +28,9 @@ import logging
from collections.abc import Iterable from collections.abc import Iterable
from time import time from time import time
from PyQt5.QtCore import QObject, pyqtSlot from PyQt6.QtCore import QObject, pyqtSlot
from PyQt5.QtGui import QTextBlock, QTextCursor, QTextDocument from PyQt6.QtGui import QTextBlock, QTextCursor, QTextDocument
from PyQt5.QtWidgets import QApplication, QPlainTextDocumentLayout from PyQt6.QtWidgets import QApplication, QPlainTextDocumentLayout
from novelwriter import SHARED from novelwriter import SHARED
from novelwriter.gui.dochighlight import GuiDocHighlighter, TextBlockData from novelwriter.gui.dochighlight import GuiDocHighlighter, TextBlockData
+2 -2
View File
@@ -27,8 +27,8 @@ import logging
from enum import Enum from enum import Enum
from PyQt5.QtCore import pyqtSlot from PyQt6.QtCore import pyqtSlot
from PyQt5.QtWidgets import QGridLayout, QLabel, QWidget from PyQt6.QtWidgets import QGridLayout, QLabel, QWidget
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.common import elide from novelwriter.common import elide
+5 -4
View File
@@ -28,8 +28,9 @@ import logging
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QAction, QMenuBar from PyQt6.QtGui import QAction
from PyQt6.QtWidgets import QMenuBar
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.common import openExternalPath, qtLambda from novelwriter.common import openExternalPath, qtLambda
@@ -1020,8 +1021,8 @@ class GuiMainMenu(QMenuBar):
self.aAboutNW.setMenuRole(QAction.MenuRole.AboutRole) self.aAboutNW.setMenuRole(QAction.MenuRole.AboutRole)
self.aAboutNW.triggered.connect(self.mainGui.showAboutNWDialog) self.aAboutNW.triggered.connect(self.mainGui.showAboutNWDialog)
# Help > About Qt5 # Help > About Qt
self.aAboutQt = self.helpMenu.addAction(self.tr("About Qt5")) self.aAboutQt = self.helpMenu.addAction(self.tr("About Qt"))
self.aAboutQt.setMenuRole(QAction.MenuRole.AboutQtRole) self.aAboutQt.setMenuRole(QAction.MenuRole.AboutQtRole)
self.aAboutQt.triggered.connect(self.mainGui.showAboutQtDialog) self.aAboutQt.triggered.connect(self.mainGui.showAboutQtDialog)
+5 -5
View File
@@ -30,11 +30,11 @@ import logging
from enum import Enum from enum import Enum
from time import time from time import time
from PyQt5.QtCore import QModelIndex, QPoint, Qt, pyqtSignal, pyqtSlot from PyQt6.QtCore import QModelIndex, QPoint, Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QFocusEvent, QFont, QMouseEvent, QPalette, QResizeEvent from PyQt6.QtGui import QActionGroup, QFocusEvent, QFont, QMouseEvent, QPalette, QResizeEvent
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QInputDialog, QMenu, QAbstractItemView, QFrame, QHBoxLayout, QInputDialog, QMenu, QToolTip,
QToolTip, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
) )
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
+4 -4
View File
@@ -33,10 +33,10 @@ import logging
from enum import Enum from enum import Enum
from time import time from time import time
from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt, pyqtSignal, pyqtSlot from PyQt6.QtCore import QT_TRANSLATE_NOOP, Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QIcon from PyQt6.QtGui import QAction, QIcon
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QAbstractItemView, QAction, QFileDialog, QFrame, QGridLayout, QGroupBox, QAbstractItemView, QFileDialog, QFrame, QGridLayout, QGroupBox,
QHBoxLayout, QLabel, QMenu, QScrollArea, QSplitter, QToolBar, QToolButton, QHBoxLayout, QLabel, QMenu, QScrollArea, QSplitter, QToolBar, QToolButton,
QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
) )
+4 -4
View File
@@ -32,10 +32,10 @@ import logging
from enum import Enum from enum import Enum
from PyQt5.QtCore import QModelIndex, QPoint, Qt, pyqtSignal, pyqtSlot from PyQt6.QtCore import QModelIndex, QPoint, Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QIcon, QMouseEvent, QPainter, QPalette from PyQt6.QtGui import QAction, QIcon, QMouseEvent, QPainter, QPalette, QShortcut
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QAbstractItemView, QAction, QFrame, QHBoxLayout, QLabel, QMenu, QShortcut, QAbstractItemView, QFrame, QHBoxLayout, QLabel, QMenu,
QStyleOptionViewItem, QTreeView, QVBoxLayout, QWidget QStyleOptionViewItem, QTreeView, QVBoxLayout, QWidget
) )
+3 -3
View File
@@ -27,9 +27,9 @@ import logging
from time import time from time import time
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QCursor, QKeyEvent from PyQt6.QtGui import QCursor, QKeyEvent
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QApplication, QFrame, QHBoxLayout, QLabel, QLineEdit, QToolBar, QApplication, QFrame, QHBoxLayout, QLabel, QLineEdit, QToolBar,
QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
) )
+3 -3
View File
@@ -27,9 +27,9 @@ import logging
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import QEvent, QPoint, QSize, pyqtSignal from PyQt6.QtCore import QEvent, QPoint, QSize, pyqtSignal
from PyQt5.QtGui import QPalette from PyQt6.QtGui import QPalette
from PyQt5.QtWidgets import QMenu, QVBoxLayout, QWidget from PyQt6.QtWidgets import QMenu, QVBoxLayout, QWidget
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.common import qtLambda from novelwriter.common import qtLambda
+2 -2
View File
@@ -28,8 +28,8 @@ import logging
from datetime import datetime from datetime import datetime
from time import time from time import time
from PyQt5.QtCore import QLocale, pyqtSlot from PyQt6.QtCore import QLocale, pyqtSlot
from PyQt5.QtWidgets import QApplication, QLabel, QStatusBar, QWidget from PyQt6.QtWidgets import QApplication, QLabel, QStatusBar, QWidget
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.common import formatTime from novelwriter.common import formatTime
+235 -175
View File
@@ -29,19 +29,19 @@ import logging
from math import ceil from math import ceil
from pathlib import Path from pathlib import Path
from PyQt5.QtCore import QSize, Qt from PyQt6.QtCore import QSize, Qt
from PyQt5.QtGui import ( from PyQt6.QtGui import (
QColor, QFont, QFontDatabase, QFontMetrics, QIcon, QPainter, QPainterPath, QColor, QFont, QFontDatabase, QFontMetrics, QIcon, QPainter, QPainterPath,
QPalette, QPixmap QPalette, QPixmap
) )
from PyQt5.QtWidgets import QApplication from PyQt6.QtWidgets import QApplication
from novelwriter import CONFIG from novelwriter import CONFIG
from novelwriter.common import NWConfigParser, cssCol, minmax from novelwriter.common import NWConfigParser, cssCol, minmax
from novelwriter.constants import nwLabels from novelwriter.constants import nwLabels
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
from novelwriter.error import logException from novelwriter.error import logException
from novelwriter.types import QtPaintAntiAlias, QtTransparent from novelwriter.types import QtPaintAntiAlias, QtTransparent, nwDataClass
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -50,30 +50,75 @@ STYLES_MIN_TOOLBUTTON = "minimalToolButton"
STYLES_BIG_TOOLBUTTON = "bigToolButton" 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: class GuiTheme:
"""Gui Theme Class """Gui Theme Class
Handles the look and feel of novelWriter. 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: def __init__(self) -> None:
self.iconCache = GuiIcons(self) 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.statNone = QColor(0, 0, 0)
self.statUnsaved = QColor(0, 0, 0) self.statUnsaved = QColor(0, 0, 0)
self.statSaved = QColor(0, 0, 0) self.statSaved = QColor(0, 0, 0)
@@ -81,42 +126,9 @@ class GuiTheme:
self.fadedText = QColor(0, 0, 0) self.fadedText = QColor(0, 0, 0)
self.errorText = QColor(255, 0, 0) self.errorText = QColor(255, 0, 0)
# Loaded Syntax Settings # Syntax Theme
# ====================== self.syntaxMeta = ThemeMeta()
self.syntaxTheme = SyntaxColors()
# 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
# ===========
# Load Themes # Load Themes
self._guiPalette = QPalette() self._guiPalette = QPalette()
@@ -143,13 +155,6 @@ class GuiTheme:
self.getHeaderDecoration = self.iconCache.getHeaderDecoration self.getHeaderDecoration = self.iconCache.getHeaderDecoration
self.getHeaderDecorationNarrow = self.iconCache.getHeaderDecorationNarrow 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 # Fonts
self.guiFont = QApplication.font() self.guiFont = QApplication.font()
self.guiFontB = QApplication.font() self.guiFontB = QApplication.font()
@@ -233,20 +238,21 @@ class GuiTheme:
return False return False
# Reset Palette # Reset Palette
self._guiPalette = QApplication.style().standardPalette() self._resetTheme()
self._resetGuiColors()
self.iconCache.clear()
# Main # Main
sec = "Main" sec = "Main"
meta = ThemeMeta()
if parser.has_section(sec): if parser.has_section(sec):
self.themeName = parser.rdStr(sec, "name", "") meta.name = parser.rdStr(sec, "name", "")
self.themeDescription = parser.rdStr(sec, "description", "N/A") meta.description = parser.rdStr(sec, "description", "N/A")
self.themeAuthor = parser.rdStr(sec, "author", "N/A") meta.author = parser.rdStr(sec, "author", "N/A")
self.themeCredit = parser.rdStr(sec, "credit", "N/A") meta.credit = parser.rdStr(sec, "credit", "N/A")
self.themeUrl = parser.rdStr(sec, "url", "") meta.url = parser.rdStr(sec, "url", "")
self.themeLicense = parser.rdStr(sec, "license", "N/A") meta.license = parser.rdStr(sec, "license", "N/A")
self.themeLicenseUrl = parser.rdStr(sec, "licenseurl", "") meta.licenseUrl = parser.rdStr(sec, "licenseurl", "")
self.themeMeta = meta
# Icons # Icons
sec = "Icons" sec = "Icons"
@@ -301,30 +307,60 @@ class GuiTheme:
self.statSaved = self._parseColour(parser, sec, "statussaved") self.statSaved = self._parseColour(parser, sec, "statussaved")
# Update Dependant Colours # Update Dependant Colours
backCol = self._guiPalette.window().color() # Based on: https://github.com/qt/qtbase/blob/dev/src/gui/kernel/qplatformtheme.cpp
textCol = self._guiPalette.windowText().color() text = self._guiPalette.text().color()
window = self._guiPalette.window().color()
highlight = self._guiPalette.highlight().color()
isDark = text.lightnessF() > window.lightnessF()
backLNess = backCol.lightnessF() QtColActive = QPalette.ColorGroup.Active
textLNess = textCol.lightnessF() QtColInactive = QPalette.ColorGroup.Inactive
self.isLightTheme = backLNess > textLNess QtColDisabled = QPalette.ColorGroup.Disabled
if self.helpText == QColor(0, 0, 0):
if self.isLightTheme: light = window.lighter(150)
helpLCol = textLNess + 0.35*(backLNess - textLNess) mid = window.darker(130)
else: midLight = mid.lighter(110)
helpLCol = backLNess + 0.65*(textLNess - backLNess) dark = window.darker(150)
self.helpText = QColor.fromHsl(0, 0, int(255*helpLCol)) shadow = dark.darker(135)
logger.debug( darkOff = dark.darker(150)
"Computed help text colour: rgb(%d, %d, %d)", shadowOff = shadow.darker(150)
self.helpText.red(), self.helpText.green(), self.helpText.blue()
) 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 # Load icons after theme is parsed
self.iconCache.loadTheme(CONFIG.iconTheme) self.iconCache.loadTheme(CONFIG.iconTheme)
# Apply styles # Finalise
self.isDarkTheme = isDark
QApplication.setPalette(self._guiPalette) QApplication.setPalette(self._guiPalette)
# Reset stylesheets so that they are regenerated
self._buildStyleSheets(self._guiPalette) self._buildStyleSheets(self._guiPalette)
return True return True
@@ -344,49 +380,54 @@ class GuiTheme:
logger.info("Loading syntax theme '%s'", guiSyntax) logger.info("Loading syntax theme '%s'", guiSyntax)
confParser = NWConfigParser() parser = NWConfigParser()
try: try:
with open(syntaxFile, mode="r", encoding="utf-8") as inFile: with open(syntaxFile, mode="r", encoding="utf-8") as inFile:
confParser.read_file(inFile) parser.read_file(inFile)
except Exception: except Exception:
logger.error("Could not load syntax colours from: %s", syntaxFile) logger.error("Could not load syntax colours from: %s", syntaxFile)
logException() logException()
return False return False
# Main # Main
cnfSec = "Main" sec = "Main"
if confParser.has_section(cnfSec): meta = ThemeMeta()
self.syntaxName = confParser.rdStr(cnfSec, "name", "") if parser.has_section(sec):
self.syntaxDescription = confParser.rdStr(cnfSec, "description", "N/A") meta.name = parser.rdStr(sec, "name", "")
self.syntaxAuthor = confParser.rdStr(cnfSec, "author", "N/A") meta.description = parser.rdStr(sec, "description", "N/A")
self.syntaxCredit = confParser.rdStr(cnfSec, "credit", "N/A") meta.author = parser.rdStr(sec, "author", "N/A")
self.syntaxUrl = confParser.rdStr(cnfSec, "url", "") meta.credit = parser.rdStr(sec, "credit", "N/A")
self.syntaxLicense = confParser.rdStr(cnfSec, "license", "N/A") meta.url = parser.rdStr(sec, "url", "")
self.syntaxLicenseUrl = confParser.rdStr(cnfSec, "licenseurl", "") meta.license = parser.rdStr(sec, "license", "N/A")
meta.licenseUrl = parser.rdStr(sec, "licenseurl", "")
# Syntax # Syntax
cnfSec = "Syntax" sec = "Syntax"
if confParser.has_section(cnfSec): syntax = SyntaxColors()
self.colBack = self._parseColour(confParser, cnfSec, "background") if parser.has_section(sec):
self.colText = self._parseColour(confParser, cnfSec, "text") syntax.back = self._parseColour(parser, sec, "background")
self.colLink = self._parseColour(confParser, cnfSec, "link") syntax.text = self._parseColour(parser, sec, "text")
self.colHead = self._parseColour(confParser, cnfSec, "headertext") syntax.link = self._parseColour(parser, sec, "link")
self.colHeadH = self._parseColour(confParser, cnfSec, "headertag") syntax.head = self._parseColour(parser, sec, "headertext")
self.colEmph = self._parseColour(confParser, cnfSec, "emphasis") syntax.headH = self._parseColour(parser, sec, "headertag")
self.colDialN = self._parseColour(confParser, cnfSec, "dialog") syntax.emph = self._parseColour(parser, sec, "emphasis")
self.colDialA = self._parseColour(confParser, cnfSec, "altdialog") syntax.dialN = self._parseColour(parser, sec, "dialog")
self.colHidden = self._parseColour(confParser, cnfSec, "hidden") syntax.dialA = self._parseColour(parser, sec, "altdialog")
self.colNote = self._parseColour(confParser, cnfSec, "note") syntax.hidden = self._parseColour(parser, sec, "hidden")
self.colCode = self._parseColour(confParser, cnfSec, "shortcode") syntax.note = self._parseColour(parser, sec, "note")
self.colKey = self._parseColour(confParser, cnfSec, "keyword") syntax.code = self._parseColour(parser, sec, "shortcode")
self.colTag = self._parseColour(confParser, cnfSec, "tag") syntax.key = self._parseColour(parser, sec, "keyword")
self.colVal = self._parseColour(confParser, cnfSec, "value") syntax.tag = self._parseColour(parser, sec, "tag")
self.colOpt = self._parseColour(confParser, cnfSec, "optional") syntax.val = self._parseColour(parser, sec, "value")
self.colSpell = self._parseColour(confParser, cnfSec, "spellcheckline") syntax.opt = self._parseColour(parser, sec, "optional")
self.colError = self._parseColour(confParser, cnfSec, "errorline") syntax.spell = self._parseColour(parser, sec, "spellcheckline")
self.colRepTag = self._parseColour(confParser, cnfSec, "replacetag") syntax.error = self._parseColour(parser, sec, "errorline")
self.colMod = self._parseColour(confParser, cnfSec, "modifier") syntax.repTag = self._parseColour(parser, sec, "replacetag")
self.colMark = self._parseColour(confParser, cnfSec, "texthighlight") syntax.mod = self._parseColour(parser, sec, "modifier")
syntax.mark = self._parseColour(parser, sec, "texthighlight")
self.syntaxMeta = meta
self.syntaxTheme = syntax
return True return True
@@ -395,12 +436,11 @@ class GuiTheme:
if self._themeList: if self._themeList:
return self._themeList return self._themeList
confParser = NWConfigParser() parser = NWConfigParser()
for themeKey, themePath in self._availThemes.items(): for key, path in self._availThemes.items():
logger.debug("Checking theme config for '%s'", themeKey) logger.debug("Checking theme config for '%s'", key)
themeName = _loadInternalName(confParser, themePath) if name := _loadInternalName(parser, path):
if themeName: self._themeList.append((key, name))
self._themeList.append((themeKey, themeName))
self._themeList = sorted(self._themeList, key=_sortTheme) self._themeList = sorted(self._themeList, key=_sortTheme)
@@ -411,12 +451,11 @@ class GuiTheme:
if self._syntaxList: if self._syntaxList:
return self._syntaxList return self._syntaxList
confParser = NWConfigParser() parser = NWConfigParser()
for syntaxKey, syntaxPath in self._availSyntax.items(): for key, path in self._availSyntax.items():
logger.debug("Checking theme syntax for '%s'", syntaxKey) logger.debug("Checking theme syntax for '%s'", key)
syntaxName = _loadInternalName(confParser, syntaxPath) if name := _loadInternalName(parser, path):
if syntaxName: self._syntaxList.append((key, name))
self._syntaxList.append((syntaxKey, syntaxName))
self._syntaxList = sorted(self._syntaxList, key=_sortTheme) self._syntaxList = sorted(self._syntaxList, key=_sortTheme)
@@ -430,14 +469,55 @@ class GuiTheme:
# Internal Functions # Internal Functions
## ##
def _resetGuiColors(self) -> None: def _resetTheme(self) -> None:
"""Reset GUI colours to default values.""" """Reset GUI colours to default values."""
self.statNone = QColor(120, 120, 120) palette = QPalette()
self.statUnsaved = QColor(200, 15, 39)
self.statSaved = QColor(2, 133, 37) text = palette.color(QPalette.ColorRole.Text)
self.helpText = QColor(0, 0, 0) window = palette.color(QPalette.ColorRole.Window)
self.fadedText = QColor(128, 128, 128) isDark = text.lightnessF() > window.lightnessF()
self.errorText = QColor(255, 0, 0)
# 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 return
def _listConf(self, targetDict: dict, checkDir: Path) -> bool: def _listConf(self, targetDict: dict, checkDir: Path) -> bool:
@@ -459,7 +539,7 @@ class GuiTheme:
self, parser: NWConfigParser, section: str, name: str, value: QPalette.ColorRole self, parser: NWConfigParser, section: str, name: str, value: QPalette.ColorRole
) -> None: ) -> None:
"""Set a palette colour value from a config string.""" """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 return
def _buildStyleSheets(self, palette: QPalette) -> None: def _buildStyleSheets(self, palette: QPalette) -> None:
@@ -507,6 +587,11 @@ class GuiIcons:
returned instead. returned instead.
""" """
__slots__ = (
"mainTheme", "themeMeta", "_svgData", "_svgColours", "_qIcons",
"_headerDec", "_headerDecNarrow", "_themeList", "_iconPath", "_noIcon",
)
TOGGLE_ICON_KEYS: dict[str, tuple[str, str]] = { TOGGLE_ICON_KEYS: dict[str, tuple[str, str]] = {
"bullet": ("bullet-on", "bullet-off"), "bullet": ("bullet-on", "bullet-off"),
"unfold": ("unfold-show", "unfold-hide"), "unfold": ("unfold-show", "unfold-hide"),
@@ -519,6 +604,7 @@ class GuiIcons:
def __init__(self, mainTheme: GuiTheme) -> None: def __init__(self, mainTheme: GuiTheme) -> None:
self.mainTheme = mainTheme self.mainTheme = mainTheme
self.themeMeta = ThemeMeta()
# Storage # Storage
self._svgData: dict[str, bytes] = {} self._svgData: dict[str, bytes] = {}
@@ -534,44 +620,16 @@ class GuiIcons:
# None Icon # None Icon
self._noIcon = QIcon(str(self._iconPath / "none.svg")) self._noIcon = QIcon(str(self._iconPath / "none.svg"))
# Icon Theme Meta
self.themeName = ""
self.themeAuthor = ""
self.themeLicense = ""
return return
def clear(self) -> None: def clear(self) -> None:
"""Clear the icon cache.""" """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._svgData = {}
self._svgColours = { 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._qIcons = {} self._qIcons = {}
self._headerDec = [] self._headerDec = []
self._headerDecNarrow = [] self._headerDecNarrow = []
self.themeName = "" self.themeMeta = ThemeMeta()
self.themeAuthor = ""
self.themeLicense = ""
return return
## ##
@@ -586,6 +644,7 @@ class GuiIcons:
logger.info("Loading icon theme '%s'", iconTheme) logger.info("Loading icon theme '%s'", iconTheme)
themePath = self._iconPath / f"{iconTheme}.icons" themePath = self._iconPath / f"{iconTheme}.icons"
try: try:
meta = ThemeMeta()
with open(themePath, mode="r", encoding="utf-8") as icons: with open(themePath, mode="r", encoding="utf-8") as icons:
for icon in icons: for icon in icons:
bits = icon.partition("=") bits = icon.partition("=")
@@ -595,11 +654,12 @@ class GuiIcons:
if key.startswith("icon:"): if key.startswith("icon:"):
self._svgData[key[5:]] = value.encode("utf-8") self._svgData[key[5:]] = value.encode("utf-8")
elif key == "meta:name": elif key == "meta:name":
self.themeName = value meta.name = value
elif key == "meta:author": elif key == "meta:author":
self.themeAuthor = value meta.author = value
elif key == "meta:license": elif key == "meta:license":
self.themeLicense = value meta.license = value
self.themeMeta = meta
except Exception: except Exception:
logger.error("Could not load icon theme from: %s", themePath) logger.error("Could not load icon theme from: %s", themePath)
logException() logException()
@@ -633,7 +693,7 @@ class GuiIcons:
map or the icon map. This function always returns a QPixmap. map or the icon map. This function always returns a QPixmap.
""" """
if name in self.IMAGE_MAP: 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] imgPath = CONFIG.assetPath("images") / self.IMAGE_MAP[name][idx]
else: else:
logger.error("Decoration with name '%s' does not exist", name) logger.error("Decoration with name '%s' does not exist", name)
+8 -8
View File
@@ -30,11 +30,11 @@ from datetime import datetime
from pathlib import Path from pathlib import Path
from time import time from time import time
from PyQt5.QtCore import Qt, QTimer, pyqtSlot from PyQt6.QtCore import Qt, QTimer, pyqtSlot
from PyQt5.QtGui import QCloseEvent, QCursor, QIcon from PyQt6.QtGui import QCloseEvent, QCursor, QIcon, QShortcut
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QApplication, QFileDialog, QHBoxLayout, QMainWindow, QMessageBox, QApplication, QFileDialog, QHBoxLayout, QMainWindow, QMessageBox,
QShortcut, QSplitter, QStackedWidget, QVBoxLayout, QWidget QSplitter, QStackedWidget, QVBoxLayout, QWidget
) )
from novelwriter import CONFIG, SHARED, __hexversion__, __version__ 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.search import GuiProjectSearch
from novelwriter.gui.sidebar import GuiSideBar from novelwriter.gui.sidebar import GuiSideBar
from novelwriter.gui.statusbar import GuiMainStatus from novelwriter.gui.statusbar import GuiMainStatus
from novelwriter.gui.theme import GuiTheme
from novelwriter.tools.dictionaries import GuiDictionaries from novelwriter.tools.dictionaries import GuiDictionaries
from novelwriter.tools.manuscript import GuiManuscript from novelwriter.tools.manuscript import GuiManuscript
from novelwriter.tools.noveldetails import GuiNovelDetails from novelwriter.tools.noveldetails import GuiNovelDetails
@@ -92,8 +91,8 @@ class GuiMain(QMainWindow):
logger.info("OS: %s", CONFIG.osType) logger.info("OS: %s", CONFIG.osType)
logger.info("Kernel: %s", CONFIG.kernelVer) logger.info("Kernel: %s", CONFIG.kernelVer)
logger.info("Host: %s", CONFIG.hostName) logger.info("Host: %s", CONFIG.hostName)
logger.info("Qt5: %s (0x%06x)", CONFIG.verQtString, CONFIG.verQtValue) logger.info("Qt: %s (0x%06x)", CONFIG.verQtString, CONFIG.verQtValue)
logger.info("PyQt5: %s (0x%06x)", CONFIG.verPyQtString, CONFIG.verPyQtValue) logger.info("PyQt: %s (0x%06x)", CONFIG.verPyQtString, CONFIG.verPyQtValue)
logger.info("Python: %s (0x%08x)", CONFIG.verPyString, sys.hexversion) logger.info("Python: %s (0x%08x)", CONFIG.verPyString, sys.hexversion)
logger.info("GUI Language: %s", CONFIG.guiLocale) logger.info("GUI Language: %s", CONFIG.guiLocale)
@@ -101,7 +100,7 @@ class GuiMain(QMainWindow):
# ============ # ============
# Initialise UserData Instance # Initialise UserData Instance
SHARED.initSharedData(self, GuiTheme()) SHARED.initSharedData(self)
# Prepare Main Window # Prepare Main Window
self.resize(*CONFIG.mainWinSize) self.resize(*CONFIG.mainWinSize)
@@ -1054,6 +1053,7 @@ class GuiMain(QMainWindow):
# We are doing this manually instead of connecting to # We are doing this manually instead of connecting to
# paletteChanged since the processing order matters # paletteChanged since the processing order matters
SHARED.theme.loadTheme() SHARED.theme.loadTheme()
self.setPalette(QApplication.palette())
self.docEditor.updateTheme() self.docEditor.updateTheme()
self.docViewer.updateTheme() self.docViewer.updateTheme()
self.docViewerPanel.updateTheme() self.docViewerPanel.updateTheme()
+18 -10
View File
@@ -31,9 +31,9 @@ from pathlib import Path
from time import time from time import time
from typing import TYPE_CHECKING, TypeVar from typing import TYPE_CHECKING, TypeVar
from PyQt5.QtCore import QObject, QRunnable, QThreadPool, QTimer, QUrl, pyqtSignal, pyqtSlot from PyQt6.QtCore import QObject, QRunnable, QThreadPool, QTimer, QUrl, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QDesktopServices, QFont from PyQt6.QtGui import QDesktopServices, QFont
from PyQt5.QtWidgets import QFileDialog, QFontDialog, QMessageBox, QWidget from PyQt6.QtWidgets import QFileDialog, QFontDialog, QMessageBox, QWidget
from novelwriter.common import formatFileFilter from novelwriter.common import formatFileFilter
from novelwriter.constants import nwFiles from novelwriter.constants import nwFiles
@@ -164,17 +164,24 @@ class SharedData(QObject):
# Methods # 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 """Initialise the SharedData instance. This must be called as
soon as the Main GUI is created to ensure the SHARED singleton soon as the Main GUI is created to ensure the SHARED singleton
has the properties needed for operation. has the properties needed for operation.
""" """
self._clock.start() self._clock.start()
self._gui = gui self._gui = gui
self._theme = theme
self._resetProject() self._resetProject()
logger.debug("Ready: SharedData") 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 return
def closeDocument(self, tHandle: str | None = None) -> None: def closeDocument(self, tHandle: str | None = None) -> None:
@@ -266,7 +273,8 @@ class SharedData(QObject):
def runInThreadPool(self, runnable: QRunnable, priority: int = 0) -> None: def runInThreadPool(self, runnable: QRunnable, priority: int = 0) -> None:
"""Queue a runnable in the application thread pool.""" """Queue a runnable in the application thread pool."""
QThreadPool.globalInstance().start(runnable, priority=priority) if pool := QThreadPool.globalInstance():
pool.start(runnable, priority=priority)
return return
def getProjectPath( def getProjectPath(
@@ -278,13 +286,13 @@ class SharedData(QObject):
label = (self.tr("novelWriter Project File or Zip File") label = (self.tr("novelWriter Project File or Zip File")
if allowZip else self.tr("novelWriter Project File")) if allowZip else self.tr("novelWriter Project File"))
ext = f"{nwFiles.PROJ_FILE} *.zip" if allowZip else nwFiles.PROJ_FILE ext = f"{nwFiles.PROJ_FILE} *.zip" if allowZip else nwFiles.PROJ_FILE
ffilter = formatFileFilter([(label, ext), "*"]) fFilter = formatFileFilter([(label, ext), "*"])
selected, _ = QFileDialog.getOpenFileName( 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 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.""" """Open the font dialog and select a font."""
kwargs = {} kwargs = {}
if not native: if not native:
+3 -3
View File
@@ -28,9 +28,9 @@ import logging
from pathlib import Path from pathlib import Path
from zipfile import ZipFile from zipfile import ZipFile
from PyQt5.QtCore import pyqtSlot from PyQt6.QtCore import pyqtSlot
from PyQt5.QtGui import QCloseEvent, QTextCursor from PyQt6.QtGui import QCloseEvent, QTextCursor
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QApplication, QDialogButtonBox, QFileDialog, QFrame, QHBoxLayout, QLabel, QApplication, QDialogButtonBox, QFileDialog, QFrame, QHBoxLayout, QLabel,
QLineEdit, QPlainTextEdit, QPushButton, QVBoxLayout, QWidget QLineEdit, QPlainTextEdit, QPushButton, QVBoxLayout, QWidget
) )
+2 -2
View File
@@ -26,8 +26,8 @@ from __future__ import annotations
import logging import logging
import random import random
from PyQt5.QtCore import pyqtSlot from PyQt6.QtCore import pyqtSlot
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QDialogButtonBox, QGridLayout, QHBoxLayout, QLabel, QSpinBox, QVBoxLayout, QDialogButtonBox, QGridLayout, QHBoxLayout, QLabel, QSpinBox, QVBoxLayout,
QWidget QWidget
) )
+3 -3
View File
@@ -27,9 +27,9 @@ import logging
from pathlib import Path from pathlib import Path
from PyQt5.QtCore import QTimer, pyqtSlot from PyQt6.QtCore import QTimer, pyqtSlot
from PyQt5.QtGui import QCloseEvent from PyQt6.QtGui import QCloseEvent
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QAbstractButton, QAbstractItemView, QDialogButtonBox, QFileDialog, QAbstractButton, QAbstractItemView, QDialogButtonBox, QFileDialog,
QGridLayout, QHBoxLayout, QLabel, QLineEdit, QListWidget, QListWidgetItem, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QListWidget, QListWidgetItem,
QPushButton, QSplitter, QVBoxLayout, QWidget QPushButton, QSplitter, QVBoxLayout, QWidget
+9 -9
View File
@@ -28,13 +28,13 @@ import logging
from time import time from time import time
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import Qt, QTimer, QUrl, pyqtSignal, pyqtSlot from PyQt6.QtCore import Qt, QTimer, QUrl, pyqtSignal, pyqtSlot
from PyQt5.QtGui import ( from PyQt6.QtGui import (
QCloseEvent, QColor, QCursor, QDesktopServices, QFont, QPalette, QCloseEvent, QColor, QCursor, QDesktopServices, QFont, QPageLayout,
QResizeEvent, QTextDocument QPalette, QResizeEvent, QTextDocument
) )
from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog from PyQt6.QtPrintSupport import QPrinter, QPrintPreviewDialog
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QAbstractItemView, QApplication, QFormLayout, QGridLayout, QHBoxLayout, QAbstractItemView, QApplication, QFormLayout, QGridLayout, QHBoxLayout,
QLabel, QListWidget, QListWidgetItem, QPushButton, QSplitter, QLabel, QListWidget, QListWidgetItem, QPushButton, QSplitter,
QStackedWidget, QTabWidget, QTextBrowser, QTreeWidget, QTreeWidgetItem, QStackedWidget, QTabWidget, QTextBrowser, QTreeWidget, QTreeWidgetItem,
@@ -42,7 +42,7 @@ from PyQt5.QtWidgets import (
) )
from novelwriter import CONFIG, SHARED 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.constants import nwLabels, nwStats, trConst
from novelwriter.core.buildsettings import BuildCollection, BuildSettings from novelwriter.core.buildsettings import BuildCollection, BuildSettings
from novelwriter.core.docbuild import NWBuildDocument from novelwriter.core.docbuild import NWBuildDocument
@@ -185,7 +185,7 @@ class GuiManuscript(NToolDialog):
self.btnBuild.clicked.connect(self._buildManuscript) self.btnBuild.clicked.connect(self._buildManuscript)
self.btnClose = QPushButton(self.tr("Close"), self) 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 = QGridLayout()
self.processBox.addWidget(self.btnPreview, 0, 0) self.processBox.addWidget(self.btnPreview, 0, 0)
@@ -884,7 +884,7 @@ class _PreviewWidget(QTextBrowser):
def printPreview(self, printer: QPrinter) -> None: def printPreview(self, printer: QPrinter) -> None:
"""Connect the print preview painter to the document viewer.""" """Connect the print preview painter to the document viewer."""
QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor)) QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
printer.setOrientation(QPrinter.Orientation.Portrait) printer.setPageOrientation(QPageLayout.Orientation.Portrait)
self.document().print(printer) self.document().print(printer)
QApplication.restoreOverrideCursor() QApplication.restoreOverrideCursor()
return return
+6 -5
View File
@@ -27,9 +27,9 @@ import logging
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import QEvent, pyqtSignal, pyqtSlot from PyQt6.QtCore import QEvent, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QFont, QIcon, QSyntaxHighlighter, QTextCharFormat, QTextDocument from PyQt6.QtGui import QFont, QIcon, QSyntaxHighlighter, QTextCharFormat, QTextDocument
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QAbstractButton, QAbstractItemView, QDialogButtonBox, QFrame, QGridLayout, QAbstractButton, QAbstractItemView, QDialogButtonBox, QFrame, QGridLayout,
QHBoxLayout, QLabel, QLineEdit, QMenu, QPlainTextEdit, QPushButton, QHBoxLayout, QLabel, QLineEdit, QMenu, QPlainTextEdit, QPushButton,
QSplitter, QStackedWidget, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QSplitter, QStackedWidget, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
@@ -906,10 +906,11 @@ class _HeadingSyntaxHighlighter(QSyntaxHighlighter):
def __init__(self, document: QTextDocument) -> None: def __init__(self, document: QTextDocument) -> None:
super().__init__(document) super().__init__(document)
syntax = SHARED.theme.syntaxTheme
self._fmtSymbol = QTextCharFormat() self._fmtSymbol = QTextCharFormat()
self._fmtSymbol.setForeground(SHARED.theme.colHead) self._fmtSymbol.setForeground(syntax.head)
self._fmtFormat = QTextCharFormat() self._fmtFormat = QTextCharFormat()
self._fmtFormat.setForeground(SHARED.theme.colEmph) self._fmtFormat.setForeground(syntax.emph)
return return
def highlightBlock(self, text: str) -> None: def highlightBlock(self, text: str) -> None:
+3 -3
View File
@@ -26,9 +26,9 @@ from __future__ import annotations
import logging import logging
import math import math
from PyQt5.QtCore import pyqtSlot from PyQt6.QtCore import pyqtSlot
from PyQt5.QtGui import QCloseEvent from PyQt6.QtGui import QCloseEvent
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QAbstractItemView, QDialogButtonBox, QFormLayout, QGridLayout, QHBoxLayout, QAbstractItemView, QDialogButtonBox, QFormLayout, QGridLayout, QHBoxLayout,
QLabel, QSpinBox, QStackedWidget, QTreeWidget, QTreeWidgetItem, QLabel, QSpinBox, QStackedWidget, QTreeWidget, QTreeWidgetItem,
QVBoxLayout, QWidget QVBoxLayout, QWidget
+12 -24
View File
@@ -28,20 +28,19 @@ import logging
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from PyQt5.QtCore import ( from PyQt6.QtCore import (
QAbstractListModel, QEvent, QModelIndex, QObject, QPoint, QSize, Qt, QAbstractListModel, QModelIndex, QObject, QPoint, QSize, Qt, pyqtSignal,
pyqtSignal, pyqtSlot pyqtSlot
) )
from PyQt5.QtGui import QCloseEvent, QColor, QFont, QPainter, QPaintEvent, QPen from PyQt6.QtGui import QAction, QCloseEvent, QColor, QFont, QPainter, QPaintEvent, QPen, QShortcut
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QAction, QApplication, QFileDialog, QFormLayout, QHBoxLayout, QLabel, QApplication, QFileDialog, QFormLayout, QHBoxLayout, QLabel, QLineEdit,
QLineEdit, QListView, QMenu, QPushButton, QScrollArea, QShortcut, QListView, QMenu, QPushButton, QScrollArea, QStackedWidget,
QStackedWidget, QStyledItemDelegate, QStyleOptionViewItem, QVBoxLayout, QStyledItemDelegate, QStyleOptionViewItem, QVBoxLayout, QWidget
QWidget
) )
from novelwriter import CONFIG, SHARED 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.constants import nwFiles
from novelwriter.core.coretools import ProjectBuilder from novelwriter.core.coretools import ProjectBuilder
from novelwriter.enum import nwItemClass from novelwriter.enum import nwItemClass
@@ -86,7 +85,7 @@ class GuiWelcome(NDialog):
self.bgImage = SHARED.theme.loadDecoration("welcome") self.bgImage = SHARED.theme.loadDecoration("welcome")
self.nwImage = SHARED.theme.loadDecoration("nw-text", h=hD) 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 = QLabel(self)
self.nwLogo.setPixmap(SHARED.theme.getPixmap("novelwriter", (hF, hF))) 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 = QPushButton(self.tr("Cancel"), self)
self.btnCancel.setIcon(SHARED.theme.getIcon("cancel", "red")) self.btnCancel.setIcon(SHARED.theme.getIcon("cancel", "red"))
self.btnCancel.setIconSize(btnIconSize) 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 = QPushButton(self.tr("Create"), self)
self.btnCreate.setIcon(SHARED.theme.getIcon("star", "yellow")) self.btnCreate.setIcon(SHARED.theme.getIcon("star", "yellow"))
@@ -591,7 +590,7 @@ class _NewProjectForm(QWidget):
self.browseFill = NIconToolButton(self, iSz, "document_add", "blue") 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 = self.fillMenu.addAction(self.tr("Create a fresh project"))
self.fillBlank.setIcon(SHARED.theme.getIcon("document")) self.fillBlank.setIcon(SHARED.theme.getIcon("document"))
@@ -803,14 +802,3 @@ class _NewProjectForm(QWidget):
self.extraWidget.setVisible(self._fillMode == self.FILL_BLANK) self.extraWidget.setVisible(self._fillMode == self.FILL_BLANK)
return 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)
+8 -10
View File
@@ -29,12 +29,11 @@ import logging
from datetime import datetime from datetime import datetime
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import Qt, pyqtSlot from PyQt6.QtCore import Qt, pyqtSlot
from PyQt5.QtGui import QCloseEvent, QCursor, QPixmap from PyQt6.QtGui import QAction, QCloseEvent, QCursor, QPixmap
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QAction, QApplication, QDialogButtonBox, QFileDialog, QGridLayout, QApplication, QDialogButtonBox, QFileDialog, QGridLayout, QGroupBox,
QGroupBox, QHBoxLayout, QLabel, QMenu, QSpinBox, QTreeWidget, QHBoxLayout, QLabel, QMenu, QSpinBox, QTreeWidget, QTreeWidgetItem
QTreeWidgetItem
) )
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
@@ -124,13 +123,12 @@ class GuiWritingStats(NToolDialog):
hHeader.setTextAlignment(self.C_IDLE, QtAlignRight) hHeader.setTextAlignment(self.C_IDLE, QtAlignRight)
hHeader.setTextAlignment(self.C_COUNT, QtAlignRight) hHeader.setTextAlignment(self.C_COUNT, QtAlignRight)
sDec = Qt.SortOrder.DescendingOrder
sAsc = Qt.SortOrder.AscendingOrder
sortCol = minmax(pOptions.getInt("GuiWritingStats", "sortCol", 0), 0, 2) sortCol = minmax(pOptions.getInt("GuiWritingStats", "sortCol", 0), 0, 2)
sortOrder = checkIntTuple( 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) self.listBox.setSortingEnabled(True)
# Word Bar # Word Bar
+33 -7
View File
@@ -23,12 +23,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from __future__ import annotations from __future__ import annotations
from PyQt5.QtCore import Qt from typing import Any, TypeVar
from PyQt5.QtGui import (
QColor, QFont, QPainter, QTextBlockFormat, QTextCharFormat, QTextCursor, from PyQt6.QtCore import Qt
QTextFormat from PyQt6.QtGui import QColor, QFont, QPainter, QTextCharFormat, QTextCursor, QTextFormat
) from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QHeaderView, QSizePolicy, QStyle
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QHeaderView, QSizePolicy, QStyle
# Qt Alignment Flags # Qt Alignment Flags
@@ -56,7 +55,7 @@ QtVAlignSuper = QTextCharFormat.VerticalAlignment.AlignSuperScript
QtPageBreakBefore = QTextFormat.PageBreakFlag.PageBreak_AlwaysBefore QtPageBreakBefore = QTextFormat.PageBreakFlag.PageBreak_AlwaysBefore
QtPageBreakAfter = QTextFormat.PageBreakFlag.PageBreak_AlwaysAfter QtPageBreakAfter = QTextFormat.PageBreakFlag.PageBreak_AlwaysAfter
QtPropLineHeight = QTextBlockFormat.LineHeightTypes.ProportionalHeight QtPropLineHeight = 1 # QTextBlockFormat.LineHeightTypes.ProportionalHeight
# Qt Painter Types # Qt Painter Types
@@ -149,3 +148,30 @@ FONT_STYLE: dict[QFont.Style, str] = {
QFont.Style.StyleItalic: "italic", QFont.Style.StyleItalic: "italic",
QFont.Style.StyleOblique: "oblique", 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)
+2 -2
View File
@@ -543,8 +543,8 @@ def buildTranslationAssets(args: argparse.Namespace | None = None) -> None:
try: try:
subprocess.call(["lrelease", "-verbose", *srcList]) subprocess.call(["lrelease", "-verbose", *srcList])
except Exception as exc: except Exception as exc:
print("Qt5 Linguist tools seem to be missing") print("Qt Linguist tools seem to be missing")
print("On Debian/Ubuntu, install: qttools5-dev-tools pyqt5-dev-tools") print("On Debian/Ubuntu, install: qttools5-dev-tools")
print(str(exc)) print(str(exc))
sys.exit(1) sys.exit(1)
+2 -2
View File
@@ -1,8 +1,8 @@
[pytest] [pytest]
log_level = DEBUG log_level = DEBUG
qt_api = pyqt5 qt_api = pyqt6
markers = markers =
base: Base classes tests base: Base classes tests
core: Core classes tests core: Core classes tests
gui: Qt5 GUI tests gui: GUI classes tests
serial serial
-1
View File
@@ -3,4 +3,3 @@ flake8-pep585
flake8-pyproject flake8-pyproject
flake8-annotations flake8-annotations
isort isort
pyqt5-stubs
+1 -1
View File
@@ -1,2 +1,2 @@
pyqt5>=5.15 pyqt6>=6.0
pyenchant>=3.0.0 pyenchant>=3.0.0
+1 -1
View File
@@ -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 project structure is stored in a single project XML file, and other meta data is primarily saved as
JSON files. 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 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. are met. It is regularly tested on Debian and Ubuntu Linux, Windows, and MacOS.
+2 -2
View File
@@ -18,12 +18,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
Dependencies Dependencies
Qt5 Qt6
Copyright: Qt Company Copyright: Qt Company
Website: <https://www.qt.io/> Website: <https://www.qt.io/>
License: LGPL v3 <https://www.gnu.org/licenses/lgpl-3.0.html> License: LGPL v3 <https://www.gnu.org/licenses/lgpl-3.0.html>
PyQt5 / PyQt5-sip PyQt6 / PyQt6-sip
Copyright: Riverbank Computing Copyright: Riverbank Computing
Website: <https://www.riverbankcomputing.com/software/pyqt/> Website: <https://www.riverbankcomputing.com/software/pyqt/>
License: GPL v3 <https://www.gnu.org/licenses/gpl-3.0.html> License: GPL v3 <https://www.gnu.org/licenses/gpl-3.0.html>
+7 -7
View File
@@ -165,13 +165,13 @@ rm -rf share/{gtk-,}doc
rm -rf lib/python3.1 rm -rf lib/python3.1
# Remove web engine # Remove web engine
rm lib/python3.*/site-packages/PyQt5/QtWebEngine* || true rm lib/python3.*/site-packages/PyQt6/QtWebEngine* || true
rm -r lib/python3.*/site-packages/PyQt5/Qt/translations/qtwebengine* || true rm -r lib/python3.*/site-packages/PyQt6/Qt/translations/qtwebengine* || true
rm lib/python3.*/site-packages/PyQt5/Qt/resources/qtwebengine* || true rm lib/python3.*/site-packages/PyQt6/Qt/resources/qtwebengine* || true
rm -r lib/python3.*/site-packages/PyQt5/Qt/qml/QtWebEngine* || true rm -r lib/python3.*/site-packages/PyQt6/Qt/qml/QtWebEngine* || true
rm -r lib/python3.*/site-packages/PyQt5/Qt/plugins/webview/libqtwebview* || true rm -r lib/python3.*/site-packages/PyQt6/Qt/plugins/webview/libqtwebview* || true
rm lib/python3.*/site-packages/PyQt5/Qt/libexec/QtWebEngineProcess* || true rm lib/python3.*/site-packages/PyQt6/Qt/libexec/QtWebEngineProcess* || true
rm lib/python3.*/site-packages/PyQt5/Qt/lib/libQt5WebEngine* || true rm lib/python3.*/site-packages/PyQt6/Qt/lib/libQt5WebEngine* || true
popd || exit 1 popd || exit 1
popd || exit 1 popd || exit 1
+10 -12
View File
@@ -28,12 +28,12 @@ from pathlib import Path
import pytest import pytest
from PyQt5.QtCore import QLocale from PyQt6.QtCore import QLocale
from PyQt5.QtWidgets import QMessageBox from PyQt6.QtWidgets import QMessageBox
sys.path.insert(1, str(Path(__file__).parent.parent.absolute())) 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.mocked import MockGuiMain, MockTheme # noqa: E402
from tests.tools import cleanProject # noqa: E402 from tests.tools import cleanProject # noqa: E402
@@ -160,11 +160,15 @@ def mockGUI(qtbot, monkeypatch):
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
def nwGUI(qtbot, monkeypatch, functionFixture): def nwGUI(qtbot, monkeypatch, functionFixture):
"""Create an instance of the novelWriter GUI.""" """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, "exec", lambda *a: None)
monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.Yes) monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.Yes)
nwGUI = main(["--testmode", f"--config={_TMP_CONF}", f"--data={_TMP_CONF}"]) CONFIG.loadConfig()
assert nwGUI is not None SHARED.initTheme(GuiTheme())
nwGUI = GuiMain()
qtbot.addWidget(nwGUI) qtbot.addWidget(nwGUI)
resetConfigVars() resetConfigVars()
nwGUI.docEditor.initEditor() nwGUI.docEditor.initEditor()
@@ -172,13 +176,7 @@ def nwGUI(qtbot, monkeypatch, functionFixture):
nwGUI.show() nwGUI.show()
qtbot.wait(20) qtbot.wait(20)
yield nwGUI return nwGUI
qtbot.wait(20)
nwGUI.closeMain()
qtbot.wait(20)
return
## ##
+2 -2
View File
@@ -22,8 +22,8 @@ from __future__ import annotations
from unittest.mock import MagicMock from unittest.mock import MagicMock
from PyQt5.QtGui import QFont, QIcon, QPixmap from PyQt6.QtGui import QFont, QIcon, QPixmap
from PyQt5.QtWidgets import QWidget from PyQt6.QtWidgets import QWidget
class MockGuiMain(QWidget): class MockGuiMain(QWidget):
+6 -8
View File
@@ -27,8 +27,8 @@ from xml.etree import ElementTree as ET
import pytest import pytest
from PyQt5.QtCore import QMimeData, QUrl from PyQt6.QtCore import QMimeData, QUrl
from PyQt5.QtGui import QColor, QDesktopServices, QFont, QFontDatabase, QFontInfo from PyQt6.QtGui import QColor, QDesktopServices, QFont, QFontDatabase, QFontInfo
from novelwriter.common import ( from novelwriter.common import (
NWConfigParser, checkBool, checkFloat, checkInt, checkIntTuple, checkPath, NWConfigParser, checkBool, checkFloat, checkInt, checkIntTuple, checkPath,
@@ -522,8 +522,7 @@ def testBaseCommon_cssCol():
@pytest.mark.base @pytest.mark.base
def testBaseCommon_describeFont(): def testBaseCommon_describeFont():
"""Test the describeFont function.""" """Test the describeFont function."""
fontDB = QFontDatabase() font = QFontDatabase.systemFont(QFontDatabase.SystemFont.GeneralFont)
font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont)
font.setPointSize(12) font.setPointSize(12)
assert describeFont(font).startswith("12 pt") assert describeFont(font).startswith("12 pt")
assert describeFont(None) == "Error" # type: ignore assert describeFont(None) == "Error" # type: ignore
@@ -537,10 +536,9 @@ def testBaseCommon_fontMatcher(monkeypatch):
assert fontMatcher(nonsense) is nonsense assert fontMatcher(nonsense) is nonsense
# General font # General font
fontDB = QFontDatabase() if len(QFontDatabase.families()) > 1:
if len(fontDB.families()) > 1: fontOne = QFont(QFontDatabase.families()[0])
fontOne = QFont(fontDB.families()[0]) fontTwo = QFont(QFontDatabase.families()[1])
fontTwo = QFont(fontDB.families()[1])
check = QFont(fontOne) check = QFont(fontOne)
check.setFamily(fontTwo.family()) check.setFamily(fontTwo.family())
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
-59
View File
@@ -245,35 +245,13 @@ def testBaseConfig_SettersGetters(fncPath):
tstConf = Config() tstConf = Config()
tstConf.initConfig(confPath=fncPath, dataPath=fncPath) 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 # Setter + Getter Combos
# ====================== # ======================
# Window Size # Window Size
tstConf.guiScale = 1.0
tstConf.setMainWinSize(1205, 655) tstConf.setMainWinSize(1205, 655)
assert tstConf.mainWinSize == [1200, 650] 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) tstConf.setMainWinSize(70, 70)
assert tstConf.mainWinSize == [70, 70] assert tstConf.mainWinSize == [70, 70]
assert tstConf._mainWinSize == [70, 70] assert tstConf._mainWinSize == [70, 70]
@@ -281,12 +259,6 @@ def testBaseConfig_SettersGetters(fncPath):
tstConf.setMainWinSize(1200, 650) tstConf.setMainWinSize(1200, 650)
# Welcome Window Size # 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) tstConf.setWelcomeWinSize(70, 70)
assert tstConf.welcomeWinSize == [70, 70] assert tstConf.welcomeWinSize == [70, 70]
assert tstConf._welcomeSize == [70, 70] assert tstConf._welcomeSize == [70, 70]
@@ -294,12 +266,6 @@ def testBaseConfig_SettersGetters(fncPath):
tstConf.setWelcomeWinSize(800, 500) tstConf.setWelcomeWinSize(800, 500)
# Preferences Size # 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) tstConf.setPreferencesWinSize(70, 70)
assert tstConf.preferencesWinSize == [70, 70] assert tstConf.preferencesWinSize == [70, 70]
assert tstConf._prefsWinSize == [70, 70] assert tstConf._prefsWinSize == [70, 70]
@@ -307,12 +273,6 @@ def testBaseConfig_SettersGetters(fncPath):
tstConf.setPreferencesWinSize(700, 615) tstConf.setPreferencesWinSize(700, 615)
# Main Pane Splitter # 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]) tstConf.setMainPanePos([200, 700])
assert tstConf.mainPanePos == [200, 700] assert tstConf.mainPanePos == [200, 700]
assert tstConf._mainPanePos == [200, 700] assert tstConf._mainPanePos == [200, 700]
@@ -320,12 +280,6 @@ def testBaseConfig_SettersGetters(fncPath):
tstConf.setMainPanePos([300, 800]) tstConf.setMainPanePos([300, 800])
# View Pane Splitter # 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]) tstConf.setViewPanePos([400, 250])
assert tstConf.viewPanePos == [400, 250] assert tstConf.viewPanePos == [400, 250]
assert tstConf._viewPanePos == [400, 250] assert tstConf._viewPanePos == [400, 250]
@@ -333,12 +287,6 @@ def testBaseConfig_SettersGetters(fncPath):
tstConf.setViewPanePos([500, 150]) tstConf.setViewPanePos([500, 150])
# Outline Pane Splitter # 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]) tstConf.setOutlinePanePos([400, 250])
assert tstConf.outlinePanePos == [400, 250] assert tstConf.outlinePanePos == [400, 250]
assert tstConf._outlnPanePos == [400, 250] assert tstConf._outlnPanePos == [400, 250]
@@ -348,18 +296,11 @@ def testBaseConfig_SettersGetters(fncPath):
# Getters Only # Getters Only
# ============ # ============
tstConf.guiScale = 1.0
assert tstConf.getTextWidth(False) == 700 assert tstConf.getTextWidth(False) == 700
assert tstConf.getTextWidth(True) == 800 assert tstConf.getTextWidth(True) == 800
assert tstConf.getTextMargin() == 40 assert tstConf.getTextMargin() == 40
assert tstConf.getTabWidth() == 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 @pytest.mark.base
def testBaseConfig_Internal(monkeypatch, fncPath): def testBaseConfig_Internal(monkeypatch, fncPath):
+8 -8
View File
@@ -42,7 +42,7 @@ def testBaseError_Dialog(qtbot, monkeypatch, nwGUI):
# Valid Error Message # Valid Error Message
with monkeypatch.context() as mp: 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 nwErr.setMessage(Exception, "Fine Error", None) # type: ignore
message = nwErr.msgBody.toPlainText() message = nwErr.msgBody.toPlainText()
assert message != "" assert message != ""
@@ -52,7 +52,7 @@ def testBaseError_Dialog(qtbot, monkeypatch, nwGUI):
# No kernel version retrieved # No kernel version retrieved
with monkeypatch.context() as mp: 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 nwErr.setMessage(Exception, "Almost Fine Error", None) # type: ignore
message = nwErr.msgBody.toPlainText() message = nwErr.msgBody.toPlainText()
assert message != "" assert message != ""
@@ -80,27 +80,27 @@ def testBaseError_Handler(qtbot, monkeypatch, nwGUI):
# Normal shutdown # Normal shutdown
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(NWErrorMessage, "exec", lambda *a: None) 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 exceptionHandler(Exception, "Error Message", None) # type: ignore
# Should not crash when no GUI is found # Should not crash when no GUI is found
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(NWErrorMessage, "exec", lambda *a: None) 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("PyQt5.QtWidgets.QApplication.topLevelWidgets", lambda: []) mp.setattr("PyQt6.QtWidgets.QApplication.topLevelWidgets", lambda: [])
exceptionHandler(Exception, "Error Message", None) # type: ignore exceptionHandler(Exception, "Error Message", None) # type: ignore
# Should handle QApplication failing # Should handle QApplication failing
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(NWErrorMessage, "exec", lambda *a: None) 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("PyQt5.QtWidgets.QApplication.topLevelWidgets", causeException) mp.setattr("PyQt6.QtWidgets.QApplication.topLevelWidgets", causeException)
exceptionHandler(Exception, "Error Message", None) # type: ignore exceptionHandler(Exception, "Error Message", None) # type: ignore
# Should handle failing to close main GUI # Should handle failing to close main GUI
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(NWErrorMessage, "exec", lambda *a: None) 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) mp.setattr("novelwriter.guimain.GuiMain.closeMain", causeException)
exceptionHandler(Exception, "Error Message", None) # type: ignore exceptionHandler(Exception, "Error Message", None) # type: ignore
+100 -97
View File
@@ -23,166 +23,169 @@ from __future__ import annotations
import logging import logging
import sys import sys
from unittest.mock import Mock
import pytest 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 @pytest.mark.base
def testBaseInit_Launch(caplog, monkeypatch, fncPath): def testBaseInit_Launch(caplog, monkeypatch, fncPath):
"""Check launching the main GUI.""" """Check launching the main GUI. This test """
monkeypatch.setattr("novelwriter.guimain.GuiMain", MockGuiMain) monkeypatch.setattr("novelwriter._createApp", lambda *a: Mock())
monkeypatch.setattr("novelwriter.guimain.GuiMain", Mock())
monkeypatch.setattr(sys, "exit", Mock())
# TestMode Launch # Default Launch
nwGUI = main(["--testmode", f"--config={fncPath}", f"--data={fncPath}"]) main([f"--config={fncPath}", f"--data={fncPath}"])
assert isinstance(nwGUI, MockGuiMain) assert CONFIG._confPath == fncPath
assert CONFIG._dataPath == fncPath
# Darwin Launch # Darwin Launch Error Handling
caplog.clear()
osDarwin = CONFIG.osDarwin
CONFIG.osDarwin = True
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setitem(sys.modules, "Foundation", None) mp.setitem(sys.modules, "Foundation", None)
nwGUI = main(["--testmode", f"--config={fncPath}", f"--data={fncPath}"]) main([f"--config={fncPath}", f"--data={fncPath}"])
assert isinstance(nwGUI, MockGuiMain)
CONFIG.osDarwin = osDarwin # Windows Launch Error Handling
# Windows Launch
caplog.clear()
osWindows = CONFIG.osWindows
CONFIG.osWindows = True
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setitem(sys.modules, "ctypes", None) mp.setitem(sys.modules, "ctypes", None)
nwGUI = main(["--testmode", f"--config={fncPath}", f"--data={fncPath}"]) main([f"--config={fncPath}", f"--data={fncPath}"])
assert isinstance(nwGUI, MockGuiMain)
CONFIG.osWindows = osWindows
# Normal Launch @pytest.mark.base
with monkeypatch.context() as mp: def testBaseInit_CreateApp(caplog, monkeypatch, fncPath):
mp.setattr("PyQt5.QtWidgets.QApplication.__init__", lambda *a: None) """Check creating the Qt app."""
mp.setattr("PyQt5.QtWidgets.QApplication.setApplicationName", lambda *a: None) monkeypatch.setattr("PyQt6.QtWidgets.QApplication.__init__", lambda *a: None)
mp.setattr("PyQt5.QtWidgets.QApplication.setApplicationVersion", lambda *a: None) monkeypatch.setattr("PyQt6.QtWidgets.QApplication.setApplicationName", lambda *a: None)
mp.setattr("PyQt5.QtWidgets.QApplication.setWindowIcon", lambda *a: None) monkeypatch.setattr("PyQt6.QtWidgets.QApplication.setApplicationVersion", lambda *a: None)
mp.setattr("PyQt5.QtWidgets.QApplication.setOrganizationDomain", lambda *a: None) monkeypatch.setattr("PyQt6.QtWidgets.QApplication.setWindowIcon", lambda *a: None)
mp.setattr("PyQt5.QtWidgets.QApplication.exec", lambda *a: 0) monkeypatch.setattr("PyQt6.QtWidgets.QApplication.setOrganizationDomain", lambda *a: None)
# mp.setattr("PyQt5.QtWidgets.QApplication.focusChange.connect", lambda *a: None) monkeypatch.setattr("PyQt6.QtWidgets.QApplication.exec", lambda *a: 0)
with pytest.raises(SystemExit) as ex:
main([f"--config={fncPath}", f"--data={fncPath}"]) app = _createApp("Fusion")
assert ex.value.code == 0 assert isinstance(app, QApplication)
@pytest.mark.base @pytest.mark.base
def testBaseInit_Options(monkeypatch, fncPath): def testBaseInit_Options(monkeypatch, fncPath):
"""Test command line options for logging level.""" """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", [ 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 # Defaults wo/Args
nwGUI = main() gui.reset_mock()
assert nwGUI is not None with pytest.raises(SystemExit) as ex:
main()
assert ex.value.code == 0
assert logger.getEffectiveLevel() == logging.WARNING assert logger.getEffectiveLevel() == logging.WARNING
assert nwGUI.closeMain() == "closeMain" gui.postLaunchTasks.assert_called_once()
gui.postLaunchTasks.assert_called_with(None)
# Defaults # Defaults
nwGUI = main( with pytest.raises(SystemExit) as ex:
["--testmode", f"--config={fncPath}", f"--data={fncPath}", "--style=Fusion"] main([f"--config={fncPath}", f"--data={fncPath}", "--style=Fusion", "--meminfo"])
) assert ex.value.code == 0
assert nwGUI is not None assert CONFIG.memInfo is True
assert logger.getEffectiveLevel() == logging.WARNING
assert nwGUI.closeMain() == "closeMain" 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 # Log Levels w/Color
nwGUI = main( clearLogHandlers()
["--testmode", "--info", "--color", f"--config={fncPath}", f"--data={fncPath}"] with pytest.raises(SystemExit) as ex:
) main(["--info", "--color", f"--config={fncPath}", f"--data={fncPath}"])
assert nwGUI is not None assert ex.value.code == 0
assert logger.getEffectiveLevel() == logging.INFO assert logger.getEffectiveLevel() == logging.INFO
assert nwGUI.closeMain() == "closeMain" assert getFormat() == f"{L_LVLC} {L_TEXT}"
nwGUI = main( clearLogHandlers()
["--testmode", "--debug", "--color", f"--config={fncPath}", f"--data={fncPath}"] with pytest.raises(SystemExit) as ex:
) main(["--debug", "--color", f"--config={fncPath}", f"--data={fncPath}"])
assert nwGUI is not None assert ex.value.code == 0
assert logger.getEffectiveLevel() == logging.DEBUG 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 # Log Levels wo/Color
nwGUI = main( clearLogHandlers()
["--testmode", "--info", f"--config={fncPath}", f"--data={fncPath}"] with pytest.raises(SystemExit) as ex:
) main(["--info", f"--config={fncPath}", f"--data={fncPath}"])
assert nwGUI is not None assert ex.value.code == 0
assert logger.getEffectiveLevel() == logging.INFO assert logger.getEffectiveLevel() == logging.INFO
assert nwGUI.closeMain() == "closeMain" assert getFormat() == f"{L_LVLP} {L_TEXT}"
nwGUI = main( clearLogHandlers()
["--testmode", "--debug", f"--config={fncPath}", f"--data={fncPath}"] with pytest.raises(SystemExit) as ex:
) main(["--debug", f"--config={fncPath}", f"--data={fncPath}"])
assert nwGUI is not None assert ex.value.code == 0
assert logger.getEffectiveLevel() == logging.DEBUG 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 # Help and Version
with pytest.raises(SystemExit) as ex: with pytest.raises(SystemExit) as ex:
nwGUI = main( main(["--help", f"--config={fncPath}", f"--data={fncPath}"])
["--testmode", "--help", f"--config={fncPath}", f"--data={fncPath}"]
)
assert nwGUI is not None
assert nwGUI.closeMain() == "closeMain"
assert ex.value.code == 0 assert ex.value.code == 0
with pytest.raises(SystemExit) as ex: with pytest.raises(SystemExit) as ex:
nwGUI = main( main(["--version", f"--config={fncPath}", f"--data={fncPath}"])
["--testmode", "--version", f"--config={fncPath}", f"--data={fncPath}"]
)
assert nwGUI is not None
assert nwGUI.closeMain() == "closeMain"
assert ex.value.code == 0 assert ex.value.code == 0
# Invalid options # Invalid options
with pytest.raises(SystemExit) as ex: with pytest.raises(SystemExit) as ex:
nwGUI = main( main(["--invalid", f"--config={fncPath}", f"--data={fncPath}"])
["--testmode", "--invalid", f"--config={fncPath}", f"--data={fncPath}"]
)
assert nwGUI is not None
assert nwGUI.closeMain() == "closeMain"
assert ex.value.code == 2 assert ex.value.code == 2
# Project Path # Project Path
nwGUI = main( gui.reset_mock()
["--testmode", f"--config={fncPath}", f"--data={fncPath}", "sample/"] with pytest.raises(SystemExit) as ex:
) main([f"--config={fncPath}", f"--data={fncPath}", "sample/"])
assert nwGUI is not None assert ex.value.code == 0
assert nwGUI.closeMain() == "closeMain" gui.postLaunchTasks.assert_called_once()
gui.postLaunchTasks.assert_called_with("sample/")
@pytest.mark.base @pytest.mark.base
def testBaseInit_Imports(caplog, monkeypatch, fncPath): def testBaseInit_Imports(caplog, monkeypatch, fncPath):
"""Check import error handling.""" """Check import error handling."""
monkeypatch.setattr("novelwriter.guimain.GuiMain", MockGuiMain) monkeypatch.setattr("novelwriter._createApp", lambda *a: Mock())
monkeypatch.setattr("PyQt5.QtWidgets.QApplication.__init__", lambda *a: None) monkeypatch.setattr("novelwriter.guimain.GuiMain", lambda *a: Mock())
monkeypatch.setattr("PyQt5.QtWidgets.QApplication.exec", lambda *a: 0)
monkeypatch.setattr("PyQt5.QtWidgets.QErrorMessage.__init__", lambda *a: None) monkeypatch.setattr("PyQt6.QtWidgets.QApplication.__init__", lambda *a: None)
monkeypatch.setattr("PyQt5.QtWidgets.QErrorMessage.resize", lambda *a: None) monkeypatch.setattr("PyQt6.QtWidgets.QApplication.exec", lambda *a: 0)
monkeypatch.setattr("PyQt5.QtWidgets.QErrorMessage.showMessage", lambda *a: None) 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("sys.hexversion", 0x0)
monkeypatch.setattr("novelwriter.CONFIG.verQtValue", 0x050000) monkeypatch.setattr("novelwriter.CONFIG.verQtValue", 0x050000)
monkeypatch.setattr("novelwriter.CONFIG.verPyQtValue", 0x050000) monkeypatch.setattr("novelwriter.CONFIG.verPyQtValue", 0x050000)
with pytest.raises(SystemExit) as ex: with pytest.raises(SystemExit) as ex:
_ = main( main([f"--config={fncPath}", f"--data={fncPath}"])
["--testmode", f"--config={fncPath}", f"--data={fncPath}"]
)
assert ex.value.code & 4 == 4 # Python version not satisfied # type: ignore 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 & 8 == 8 # Qt version not satisfied # type: ignore
assert ex.value.code & 16 == 16 # PyQt 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 Python" in caplog.messages[0]
assert "At least Qt5" in caplog.messages[1] assert "At least Qt6" in caplog.messages[1]
assert "At least PyQt5" in caplog.messages[2] assert "At least PyQt6" in caplog.messages[2]
+9 -6
View File
@@ -24,9 +24,9 @@ from unittest.mock import MagicMock
import pytest import pytest
from PyQt5.QtCore import QUrl from PyQt6.QtCore import QUrl
from PyQt5.QtGui import QDesktopServices from PyQt6.QtGui import QDesktopServices
from PyQt5.QtWidgets import QFileDialog, QMessageBox, QWidget from PyQt6.QtWidgets import QFileDialog, QMessageBox, QWidget
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
from novelwriter.shared import SharedData from novelwriter.shared import SharedData
@@ -56,7 +56,8 @@ def testBaseSharedData_Init():
assert mockGui is not mockTheme assert mockGui is not mockTheme
# Properly initialise the class # 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.mainGui is mockGui
assert shared.theme is mockTheme assert shared.theme is mockTheme
@@ -94,7 +95,8 @@ def testBaseSharedData_Projects(monkeypatch, caplog, fncPath):
# Initialise the instance, should create an empty project # Initialise the instance, should create an empty project
mockGui = MockGuiMain() mockGui = MockGuiMain()
mockTheme = MockTheme() mockTheme = MockTheme()
shared.initSharedData(mockGui, mockTheme) # type: ignore shared.initTheme(mockTheme) # type: ignore
shared.initSharedData(mockGui) # type: ignore
assert isinstance(shared.project, NWProject) assert isinstance(shared.project, NWProject)
assert shared.hasProject is False assert shared.hasProject is False
@@ -150,7 +152,8 @@ def testBaseSharedData_Alerts(qtbot, monkeypatch, caplog):
mockGui = MockGuiMain() mockGui = MockGuiMain()
mockTheme = MockTheme() mockTheme = MockTheme()
shared.initSharedData(mockGui, mockTheme) # type: ignore shared.initTheme(mockTheme) # type: ignore
shared.initSharedData(mockGui) # type: ignore
assert shared.lastAlert == "" assert shared.lastAlert == ""
+1 -1
View File
@@ -24,7 +24,7 @@ import copy
import pytest import pytest
from PyQt5.QtGui import QIcon from PyQt6.QtGui import QIcon
from novelwriter.core.item import NWItem from novelwriter.core.item import NWItem
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
+1 -1
View File
@@ -22,7 +22,7 @@ from __future__ import annotations
import pytest import pytest
from PyQt5.QtCore import QMimeData, QModelIndex, Qt from PyQt6.QtCore import QMimeData, QModelIndex, Qt
from novelwriter.common import decodeMimeHandles from novelwriter.common import decodeMimeHandles
from novelwriter.constants import nwConst from novelwriter.constants import nwConst
+1 -1
View File
@@ -25,7 +25,7 @@ from zipfile import ZipFile
import pytest import pytest
from PyQt5.QtWidgets import QMessageBox from PyQt6.QtWidgets import QMessageBox
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.constants import nwFiles from novelwriter.constants import nwFiles
+1 -1
View File
@@ -27,7 +27,7 @@ from shutil import copyfile
import pytest import pytest
from PyQt5.QtGui import QColor from PyQt6.QtGui import QColor
from novelwriter.constants import nwFiles from novelwriter.constants import nwFiles
from novelwriter.core.item import NWItem from novelwriter.core.item import NWItem
+1 -1
View File
@@ -22,7 +22,7 @@ from __future__ import annotations
import pytest import pytest
from PyQt5.QtGui import QColor, QIcon from PyQt6.QtGui import QColor, QIcon
from novelwriter.core.status import NWStatus, StatusEntry, _ShapeCache from novelwriter.core.status import NWStatus, StatusEntry, _ShapeCache
from novelwriter.enum import nwStatusShape from novelwriter.enum import nwStatusShape
+2 -1
View File
@@ -24,7 +24,8 @@ from pathlib import Path
import pytest import pytest
from PyQt5.QtWidgets import QAction, QMessageBox from PyQt6.QtGui import QAction
from PyQt6.QtWidgets import QMessageBox
from novelwriter import SHARED from novelwriter import SHARED
from novelwriter.dialogs.about import GuiAbout from novelwriter.dialogs.about import GuiAbout
+2 -2
View File
@@ -22,8 +22,8 @@ from __future__ import annotations
import pytest import pytest
from PyQt5.QtCore import QItemSelectionModel from PyQt6.QtCore import QItemSelectionModel
from PyQt5.QtWidgets import QListWidgetItem from PyQt6.QtWidgets import QListWidgetItem
from novelwriter.dialogs.editlabel import GuiEditLabel from novelwriter.dialogs.editlabel import GuiEditLabel
from novelwriter.dialogs.quotes import GuiQuoteSelect from novelwriter.dialogs.quotes import GuiQuoteSelect
+1 -1
View File
@@ -22,7 +22,7 @@ from __future__ import annotations
import pytest import pytest
from PyQt5.QtCore import Qt from PyQt6.QtCore import Qt
from novelwriter.dialogs.docmerge import GuiDocMerge from novelwriter.dialogs.docmerge import GuiDocMerge
from novelwriter.types import QtAccepted, QtRejected, QtUserRole from novelwriter.types import QtAccepted, QtRejected, QtUserRole
+3 -3
View File
@@ -22,9 +22,9 @@ from __future__ import annotations
import pytest import pytest
from PyQt5.QtCore import QEvent, Qt from PyQt6.QtCore import QEvent, Qt
from PyQt5.QtGui import QFont, QFontDatabase, QKeyEvent from PyQt6.QtGui import QAction, QFont, QFontDatabase, QKeyEvent
from PyQt5.QtWidgets import QAction, QFileDialog, QFontDialog from PyQt6.QtWidgets import QFileDialog, QFontDialog
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.constants import nwUnicode from novelwriter.constants import nwUnicode
@@ -22,8 +22,8 @@ from __future__ import annotations
import pytest import pytest
from PyQt5.QtGui import QColor from PyQt6.QtGui import QAction, QColor
from PyQt5.QtWidgets import QAction, QColorDialog, QFileDialog from PyQt6.QtWidgets import QColorDialog, QFileDialog
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.dialogs.editlabel import GuiEditLabel from novelwriter.dialogs.editlabel import GuiEditLabel
+5 -4
View File
@@ -22,8 +22,9 @@ from __future__ import annotations
import pytest import pytest
from PyQt5.QtCore import Qt from PyQt6.QtCore import Qt
from PyQt5.QtWidgets import QAction, QFileDialog from PyQt6.QtGui import QAction
from PyQt6.QtWidgets import QFileDialog
from novelwriter import SHARED from novelwriter import SHARED
from novelwriter.core.spellcheck import UserDictionary from novelwriter.core.spellcheck import UserDictionary
@@ -104,11 +105,11 @@ def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, fncPath, projPath):
wList._doAdd() wList._doAdd()
assert wList.listBox.item(0).text() == "delete_me" # type: ignore 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" assert delItem.text() == "delete_me"
delItem.setSelected(True) delItem.setSelected(True)
wList._doDelete() 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 assert wList.listBox.item(0).text() == "word_a" # type: ignore
# Import/Export # Import/Export
+5 -4
View File
@@ -22,9 +22,9 @@ from __future__ import annotations
import pytest import pytest
from PyQt5.QtCore import QEvent, QObject, QPoint, Qt from PyQt6.QtCore import QEvent, QObject, QPoint, QPointF, Qt
from PyQt5.QtGui import QKeyEvent, QWheelEvent from PyQt6.QtGui import QKeyEvent, QWheelEvent
from PyQt5.QtWidgets import QWidget from PyQt6.QtWidgets import QWidget
from novelwriter.extensions.eventfilters import WheelEventFilter from novelwriter.extensions.eventfilters import WheelEventFilter
from novelwriter.types import QtModNone, QtModShift from novelwriter.types import QtModNone, QtModShift
@@ -57,8 +57,9 @@ def testExtEventFilters_WheelEventFilter():
# Sending a mouse wheel event forwards it # Sending a mouse wheel event forwards it
pos = QPoint(0, 0) pos = QPoint(0, 0)
posF = QPointF(0.0, 0.0)
event = QWheelEvent( event = QWheelEvent(
pos, pos, pos, pos, posF, posF, pos, pos,
Qt.MouseButton.NoButton, QtModNone, Qt.MouseButton.NoButton, QtModNone,
Qt.ScrollPhase.NoScrollPhase, False, Qt.ScrollPhase.NoScrollPhase, False,
) )
+4 -4
View File
@@ -22,9 +22,9 @@ from __future__ import annotations
import pytest import pytest
from PyQt5.QtCore import QEvent, QPoint, QPointF, Qt from PyQt6.QtCore import QEvent, QPoint, QPointF, Qt
from PyQt5.QtGui import QKeyEvent, QMouseEvent, QWheelEvent from PyQt6.QtGui import QKeyEvent, QMouseEvent, QWheelEvent
from PyQt5.QtWidgets import QWidget from PyQt6.QtWidgets import QWidget
from novelwriter.extensions.modified import ( from novelwriter.extensions.modified import (
NClickableLabel, NComboBox, NDialog, NDoubleSpinBox, NSpinBox NClickableLabel, NComboBox, NDialog, NDoubleSpinBox, NSpinBox
@@ -150,7 +150,7 @@ def testExtModified_NClickableLabel(qtbot, monkeypatch):
dialog = SimpleDialog(widget) dialog = SimpleDialog(widget)
dialog.show() dialog.show()
position = widget.rect().center() position = QPointF(widget.rect().center())
event = QMouseEvent( event = QMouseEvent(
QEvent.Type.MouseButtonPress, position, QtMouseLeft, QtMouseLeft, QtModNone QEvent.Type.MouseButtonPress, position, QtMouseLeft, QtMouseLeft, QtModNone
) )
+1 -1
View File
@@ -24,7 +24,7 @@ from time import sleep
import pytest import pytest
from PyQt5.QtGui import QColor from PyQt6.QtGui import QColor
from novelwriter.extensions.progressbars import NProgressCircle, NProgressSimple from novelwriter.extensions.progressbars import NProgressCircle, NProgressSimple
+4 -4
View File
@@ -22,8 +22,8 @@ from __future__ import annotations
import pytest import pytest
from PyQt5.QtCore import QEvent, QPoint from PyQt6.QtCore import QEvent, QPointF
from PyQt5.QtGui import QMouseEvent from PyQt6.QtGui import QEnterEvent, QMouseEvent
from novelwriter.extensions.switch import NSwitch from novelwriter.extensions.switch import NSwitch
from novelwriter.types import QtModNone, QtMouseLeft from novelwriter.types import QtModNone, QtMouseLeft
@@ -65,10 +65,10 @@ def testExtSwitch_Main(qtbot):
button = QtMouseLeft button = QtMouseLeft
modifier = QtModNone modifier = QtModNone
event = QMouseEvent(QEvent.Type.MouseButtonRelease, QPoint(), button, button, modifier) event = QMouseEvent(QEvent.Type.MouseButtonRelease, QPointF(), button, button, modifier)
switch.mouseReleaseEvent(event) switch.mouseReleaseEvent(event)
event = QEvent(QEvent.Type.Enter) event = QEnterEvent(QPointF(), QPointF(), QPointF())
switch.enterEvent(event) switch.enterEvent(event)
# qtbot.stop() # qtbot.stop()
+1 -1
View File
@@ -24,7 +24,7 @@ from urllib.error import HTTPError
import pytest import pytest
from PyQt5.QtCore import QUrl from PyQt6.QtCore import QUrl
from novelwriter import SHARED from novelwriter import SHARED
from novelwriter.constants import nwConst from novelwriter.constants import nwConst
+1 -1
View File
@@ -22,7 +22,7 @@ from __future__ import annotations
import pytest import pytest
from PyQt5.QtGui import QFont from PyQt6.QtGui import QFont
from novelwriter import CONFIG from novelwriter import CONFIG
from novelwriter.constants import nwHeadFmt, nwStyles from novelwriter.constants import nwHeadFmt, nwStyles

Some files were not shown because too many files have changed in this diff Show More