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