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