From 0728b35735362b5b60301c36781b6a030aa161fb Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 12 Jan 2025 22:23:20 +0100 Subject: [PATCH 1/8] Drop the custom data class decorator for now, since it is not performance relevant --- novelwriter/formats/shared.py | 3 --- novelwriter/gui/theme.py | 4 +--- novelwriter/types.py | 29 ----------------------------- 3 files changed, 1 insertion(+), 35 deletions(-) diff --git a/novelwriter/formats/shared.py b/novelwriter/formats/shared.py index fecafea9..549f6f19 100644 --- a/novelwriter/formats/shared.py +++ b/novelwriter/formats/shared.py @@ -29,8 +29,6 @@ from enum import Flag, IntEnum from PyQt6.QtGui import QColor -from novelwriter.types import nwDataClass - ESCAPES = {r"\*": "*", r"\~": "~", r"\_": "_", r"\[": "[", r"\]": "]", r"\ ": ""} RX_ESC = re.compile("|".join([re.escape(k) for k in ESCAPES.keys()]), flags=re.DOTALL) @@ -42,7 +40,6 @@ def stripEscape(text: str) -> str: return text -@nwDataClass class TextDocumentTheme: """Default document theme.""" diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index 9c583a80..3f85e2c1 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -41,7 +41,7 @@ from novelwriter.common import NWConfigParser, cssCol, minmax from novelwriter.constants import nwLabels from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType from novelwriter.error import logException -from novelwriter.types import QtPaintAntiAlias, QtTransparent, nwDataClass +from novelwriter.types import QtPaintAntiAlias, QtTransparent logger = logging.getLogger(__name__) @@ -50,7 +50,6 @@ STYLES_MIN_TOOLBUTTON = "minimalToolButton" STYLES_BIG_TOOLBUTTON = "bigToolButton" -@nwDataClass class ThemeMeta: name: str = "" @@ -62,7 +61,6 @@ class ThemeMeta: licenseUrl: str = "" -@nwDataClass class SyntaxColors: back: QColor = QColor(255, 255, 255) diff --git a/novelwriter/types.py b/novelwriter/types.py index e06177b7..e1b05ead 100644 --- a/novelwriter/types.py +++ b/novelwriter/types.py @@ -23,8 +23,6 @@ along with this program. If not, see . """ from __future__ import annotations -from typing import Any, TypeVar - from PyQt6.QtCore import Qt from PyQt6.QtGui import QColor, QFont, QPainter, QTextCharFormat, QTextCursor, QTextFormat from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QHeaderView, QSizePolicy, QStyle @@ -148,30 +146,3 @@ FONT_STYLE: dict[QFont.Style, str] = { QFont.Style.StyleItalic: "italic", QFont.Style.StyleOblique: "oblique", } - -## -# Decorators and MetaClasses -## - -T_ = TypeVar("T_", bound=object) - - -def nwDataClass(cls: T_) -> T_: - """A simple data class decorator that generates slots automatically - and creates an init function to match. - """ - - def wrap(cls: T_) -> T_: - fields = tuple(a for a in dir(cls) if not a.startswith("__")) - values = {a: getattr(cls, a) for a in fields} - - def init(self: Any) -> None: - nonlocal values - for a, v in values.items(): - self.__setattr__(a, v) - - return type(cls.__class__.__name__, (object,), { # type: ignore - "__slots__": fields, "__init__": init - }) - - return wrap(cls) From 717ade24e30c922920e68cb1cd249c48b09ec2b0 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 12 Jan 2025 23:30:27 +0100 Subject: [PATCH 2/8] Remove scaling and add slots to main config class --- novelwriter/config.py | 124 +++++++----------- novelwriter/dialogs/preferences.py | 2 +- novelwriter/gui/doceditor.py | 4 +- novelwriter/gui/docviewer.py | 6 +- novelwriter/guimain.py | 6 +- novelwriter/tools/manuscript.py | 8 +- tests/test_base/test_base_config.py | 31 +---- tests/test_dialogs/test_dlg_preferences.py | 26 ++-- .../test_dialogs/test_dlg_projectsettings.py | 7 +- 9 files changed, 87 insertions(+), 127 deletions(-) diff --git a/novelwriter/config.py b/novelwriter/config.py index f1389253..b5d09e2d 100644 --- a/novelwriter/config.py +++ b/novelwriter/config.py @@ -52,6 +52,31 @@ logger = logging.getLogger(__name__) class Config: + __slots__ = ( + "_confPath", "_dataPath", "_homePath", "_backPath", "_appPath", "_appRoot", "_hasError", + "_errData", "_nwLangPath", "_qtLangPath", "_qLocale", "_dLocale", "_dShortDate", + "_dShortDateTime", "_qtTrans", "_recentProjects", "_recentPaths", "_backupPath", + + "appName", "appHandle", "pdfDocs", "guiLocale", "guiTheme", "guiSyntax", "guiFont", + "hideVScroll", "hideHScroll", "lastNotes", "nativeFont", "iconTheme", "iconColTree", + "iconColDocs", "mainWinSize", "welcomeWinSize", "prefsWinSize", "mainPanePos", + "viewPanePos", "outlinePanePos", "autoSaveProj", "autoSaveDoc", "emphLabels", + "backupOnClose", "askBeforeBackup", "textFont", "textWidth", "textMargin", "tabWidth", + "focusWidth", "hideFocusFooter", "showFullPath", "autoSelect", "doJustify", + "showTabsNSpaces", "showLineEndings", "showMultiSpaces", "doReplace", "doReplaceSQuote", + "doReplaceDQuote", "doReplaceDash", "doReplaceDots", "autoScroll", "autoScrollPos", + "scrollPastEnd", "dialogStyle", "allowOpenDial", "dialogLine", "narratorBreak", + "narratorDialog", "altDialogOpen", "altDialogClose", "highlightEmph", "stopWhenIdle", + "userIdleTime", "incNotesWCount", "fmtApostrophe", "fmtSQuoteOpen", "fmtSQuoteClose", + "fmtDQuoteOpen", "fmtDQuoteClose", "fmtPadBefore", "fmtPadAfter", "fmtPadThin", + "spellLanguage", "showViewerPanel", "showEditToolBar", "showSessionTime", "viewComments", + "viewSynopsis", "searchCase", "searchWord", "searchRegEx", "searchLoop", "searchNextFile", + "searchMatchCap", "searchProjCase", "searchProjWord", "searchProjRegEx", "verQtString", + "verQtValue", "verPyQtString", "verPyQtValue", "verPyString", "osType", "osLinux", + "osWindows", "osDarwin", "osUnknown", "hostName", "kernelVer", "isDebug", "memInfo", + "hasEnchant", + ) + LANG_NW = 1 LANG_PROJ = 2 @@ -126,12 +151,12 @@ class Config: self.iconColDocs = False # Keep theme colours on documents # Size Settings - self._mainWinSize = [1200, 650] # Last size of the main GUI window - self._welcomeSize = [800, 550] # Last size of the welcome window - self._prefsWinSize = [700, 615] # Last size of the Preferences dialog - self._mainPanePos = [300, 800] # Last position of the main window splitter - self._viewPanePos = [500, 150] # Last position of the document viewer splitter - self._outlnPanePos = [500, 150] # Last position of the outline panel splitter + self.mainWinSize = [1200, 650] # Last size of the main GUI window + self.welcomeWinSize = [800, 550] # Last size of the welcome window + self.prefsWinSize = [700, 615] # Last size of the Preferences dialog + self.mainPanePos = [300, 800] # Last position of the main window splitter + self.viewPanePos = [500, 150] # Last position of the document viewer splitter + self.outlinePanePos = [500, 150] # Last position of the outline panel splitter # Project Settings self.autoSaveProj = 60 # Interval for auto-saving project, in seconds @@ -269,30 +294,6 @@ class Config: def recentProjects(self) -> RecentProjects: return self._recentProjects - @property - def mainWinSize(self) -> list[int]: - return self._mainWinSize - - @property - def welcomeWinSize(self) -> list[int]: - return self._welcomeSize - - @property - def preferencesWinSize(self) -> list[int]: - return self._prefsWinSize - - @property - def mainPanePos(self) -> list[int]: - return self._mainPanePos - - @property - def viewPanePos(self) -> list[int]: - return self._viewPanePos - - @property - def outlinePanePos(self) -> list[int]: - return self._outlnPanePos - ## # Getters ## @@ -300,17 +301,9 @@ class Config: def getTextWidth(self, focusMode: bool = False) -> int: """Get the text with for the correct editor mode.""" if focusMode: - return self.pxInt(max(self.focusWidth, 200)) + return max(self.focusWidth, 200) else: - return self.pxInt(max(self.textWidth, 200)) - - def getTextMargin(self) -> int: - """Get the scaled text margin.""" - return self.pxInt(max(self.textMargin, 0)) - - def getTabWidth(self) -> int: - """Get the scaled tab width.""" - return self.pxInt(max(self.tabWidth, 0)) + return max(self.textWidth, 200) ## # Setters @@ -322,35 +315,20 @@ class Config: adjust it a bit, and we don't want the main window to shrink or grow each time the app is opened. """ - if abs(self._mainWinSize[0] - width) > 5: - self._mainWinSize[0] = width - if abs(self._mainWinSize[1] - height) > 5: - self._mainWinSize[1] = height + if abs(self.mainWinSize[0] - width) > 5: + self.mainWinSize[0] = width + if abs(self.mainWinSize[1] - height) > 5: + self.mainWinSize[1] = height return def setWelcomeWinSize(self, width: int, height: int) -> None: """Set the size of the Preferences dialog window.""" - self._welcomeSize = [width, height] + self.welcomeWinSize = [width, height] return def setPreferencesWinSize(self, width: int, height: int) -> None: """Set the size of the Preferences dialog window.""" - self._prefsWinSize = [width, height] - return - - def setMainPanePos(self, pos: list[int]) -> None: - """Set the position of the main GUI splitter.""" - self._mainPanePos = pos - return - - def setViewPanePos(self, pos: list[int]) -> None: - """Set the position of the viewer meta data splitter.""" - self._viewPanePos = pos - return - - def setOutlinePanePos(self, pos: list[int]) -> None: - """Set the position of the outline details splitter.""" - self._outlnPanePos = pos + self.prefsWinSize = [width, height] return def setLastPath(self, key: str, path: str | Path) -> None: @@ -614,12 +592,12 @@ class Config: # Sizes sec = "Sizes" - self._mainWinSize = conf.rdIntList(sec, "mainwindow", self._mainWinSize) - self._welcomeSize = conf.rdIntList(sec, "welcome", self._welcomeSize) - self._prefsWinSize = conf.rdIntList(sec, "preferences", self._prefsWinSize) - self._mainPanePos = conf.rdIntList(sec, "mainpane", self._mainPanePos) - self._viewPanePos = conf.rdIntList(sec, "viewpane", self._viewPanePos) - self._outlnPanePos = conf.rdIntList(sec, "outlinepane", self._outlnPanePos) + self.mainWinSize = conf.rdIntList(sec, "mainwindow", self.mainWinSize) + self.welcomeWinSize = conf.rdIntList(sec, "welcome", self.welcomeWinSize) + self.prefsWinSize = conf.rdIntList(sec, "preferences", self.prefsWinSize) + self.mainPanePos = conf.rdIntList(sec, "mainpane", self.mainPanePos) + self.viewPanePos = conf.rdIntList(sec, "viewpane", self.viewPanePos) + self.outlinePanePos = conf.rdIntList(sec, "outlinepane", self.outlinePanePos) # Project sec = "Project" @@ -728,12 +706,12 @@ class Config: } conf["Sizes"] = { - "mainwindow": self._packList(self._mainWinSize), - "welcome": self._packList(self._welcomeSize), - "preferences": self._packList(self._prefsWinSize), - "mainpane": self._packList(self._mainPanePos), - "viewpane": self._packList(self._viewPanePos), - "outlinepane": self._packList(self._outlnPanePos), + "mainwindow": self._packList(self.mainWinSize), + "welcome": self._packList(self.welcomeWinSize), + "preferences": self._packList(self.prefsWinSize), + "mainpane": self._packList(self.mainPanePos), + "viewpane": self._packList(self.viewPanePos), + "outlinepane": self._packList(self.outlinePanePos), } conf["Project"] = { diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index 8e3d65f1..8a433273 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -59,7 +59,7 @@ class GuiPreferences(NDialog): self.setObjectName("GuiPreferences") self.setWindowTitle(self.tr("Preferences")) self.setMinimumSize(CONFIG.pxInt(600), CONFIG.pxInt(500)) - self.resize(*CONFIG.preferencesWinSize) + self.resize(*CONFIG.prefsWinSize) # Title self.titleLabel = NColourLabel( diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 1a4f7c4d..d67f514c 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -331,7 +331,7 @@ class GuiDocEditor(QPlainTextEdit): # Due to cursor visibility, a part of the margin must be # allocated to the document itself. See issue #1112. self._qDocument.setDocumentMargin(4) - self._vpMargin = max(CONFIG.getTextMargin() - 4, 0) + self._vpMargin = max(CONFIG.textMargin - 4, 0) self.setViewportMargins(self._vpMargin, self._vpMargin, self._vpMargin, self._vpMargin) # Also set the document text options for the document text flow @@ -359,7 +359,7 @@ class GuiDocEditor(QPlainTextEdit): self.setHorizontalScrollBarPolicy(QtScrollAsNeeded) # Refresh the tab stops - self.setTabStopDistance(CONFIG.getTabWidth()) + self.setTabStopDistance(CONFIG.tabWidth) # If we have a document open, we should refresh it in case the # font changed, otherwise we just clear the editor entirely, diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py index bc545b6f..e813a5c4 100644 --- a/novelwriter/gui/docviewer.py +++ b/novelwriter/gui/docviewer.py @@ -200,7 +200,7 @@ class GuiDocViewer(QTextBrowser): self.setHorizontalScrollBarPolicy(QtScrollAsNeeded) # Refresh the tab stops - self.setTabStopDistance(CONFIG.getTabWidth()) + self.setTabStopDistance(CONFIG.tabWidth) # If we have a document open, we should reload it in case the font changed self.reloadText() @@ -250,7 +250,7 @@ class GuiDocViewer(QTextBrowser): self.setDocumentTitle(tHandle) self.setDocument(qDoc.document) - self.setTabStopDistance(CONFIG.getTabWidth()) + self.setTabStopDistance(CONFIG.tabWidth) if self._docHandle == tHandle: # This is a refresh, so we set the scrollbar back to where it was @@ -308,7 +308,7 @@ class GuiDocViewer(QTextBrowser): """Automatically adjust the margins so the text is centred.""" wW = self.width() wH = self.height() - cM = CONFIG.getTextMargin() + cM = CONFIG.textMargin vBar = self.verticalScrollBar() sW = vBar.width() if vBar.isVisible() else 0 diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 56e17517..3769090d 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -860,10 +860,10 @@ class GuiMain(QMainWindow): logger.info("Exiting novelWriter") if not SHARED.focusMode: - CONFIG.setMainPanePos(self.splitMain.sizes()) - CONFIG.setOutlinePanePos(self.outlineView.splitSizes()) + CONFIG.mainPanePos = self.splitMain.sizes() + CONFIG.outlinePanePos = self.outlineView.splitSizes() if self.docViewerPanel.isVisible(): - CONFIG.setViewPanePos(self.splitView.sizes()) + CONFIG.viewPanePos = self.splitView.sizes() CONFIG.showViewerPanel = self.docViewerPanel.isVisible() wFull = Qt.WindowState.WindowFullScreen diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py index 7238ea84..448b8db2 100644 --- a/novelwriter/tools/manuscript.py +++ b/novelwriter/tools/manuscript.py @@ -750,11 +750,11 @@ class _PreviewWidget(QTextBrowser): self.setPalette(dPalette) self.setMinimumWidth(40*SHARED.theme.textNWidth) - self.setTabStopDistance(CONFIG.getTabWidth()) + self.setTabStopDistance(CONFIG.tabWidth) self.setOpenExternalLinks(False) self.setOpenLinks(False) - self.document().setDocumentMargin(CONFIG.getTextMargin()) + self.document().setDocumentMargin(CONFIG.textMargin) self.setPlaceholderText(self.tr( "Press the \"Preview\" button to generate ..." )) @@ -852,9 +852,9 @@ class _PreviewWidget(QTextBrowser): self.buildProgress.setCentreText(self.tr("Processing ...")) QApplication.processEvents() - document.setDocumentMargin(CONFIG.getTextMargin()) + document.setDocumentMargin(CONFIG.textMargin) self.setDocument(document) - self.setTabStopDistance(CONFIG.getTabWidth()) + self.setTabStopDistance(CONFIG.tabWidth) self._docTime = int(time()) self._updateBuildAge() diff --git a/tests/test_base/test_base_config.py b/tests/test_base/test_base_config.py index 34bac678..6d968292 100644 --- a/tests/test_base/test_base_config.py +++ b/tests/test_base/test_base_config.py @@ -254,52 +254,29 @@ def testBaseConfig_SettersGetters(fncPath): tstConf.setMainWinSize(70, 70) assert tstConf.mainWinSize == [70, 70] - assert tstConf._mainWinSize == [70, 70] + assert tstConf.mainWinSize == [70, 70] tstConf.setMainWinSize(1200, 650) # Welcome Window Size tstConf.setWelcomeWinSize(70, 70) assert tstConf.welcomeWinSize == [70, 70] - assert tstConf._welcomeSize == [70, 70] + assert tstConf.welcomeWinSize == [70, 70] tstConf.setWelcomeWinSize(800, 500) # Preferences Size tstConf.setPreferencesWinSize(70, 70) - assert tstConf.preferencesWinSize == [70, 70] - assert tstConf._prefsWinSize == [70, 70] + assert tstConf.prefsWinSize == [70, 70] + assert tstConf.prefsWinSize == [70, 70] tstConf.setPreferencesWinSize(700, 615) - # Main Pane Splitter - tstConf.setMainPanePos([200, 700]) - assert tstConf.mainPanePos == [200, 700] - assert tstConf._mainPanePos == [200, 700] - - tstConf.setMainPanePos([300, 800]) - - # View Pane Splitter - tstConf.setViewPanePos([400, 250]) - assert tstConf.viewPanePos == [400, 250] - assert tstConf._viewPanePos == [400, 250] - - tstConf.setViewPanePos([500, 150]) - - # Outline Pane Splitter - tstConf.setOutlinePanePos([400, 250]) - assert tstConf.outlinePanePos == [400, 250] - assert tstConf._outlnPanePos == [400, 250] - - tstConf.setOutlinePanePos([500, 150]) - # Getters Only # ============ assert tstConf.getTextWidth(False) == 700 assert tstConf.getTextWidth(True) == 800 - assert tstConf.getTextMargin() == 40 - assert tstConf.getTabWidth() == 40 @pytest.mark.base diff --git a/tests/test_dialogs/test_dlg_preferences.py b/tests/test_dialogs/test_dlg_preferences.py index bdc4db27..e5bed49a 100644 --- a/tests/test_dialogs/test_dlg_preferences.py +++ b/tests/test_dialogs/test_dlg_preferences.py @@ -72,17 +72,16 @@ def testDlgPreferences_Main(qtbot, monkeypatch, nwGUI, tstPaths): prefs.close() # Check Fallback Values - with monkeypatch.context() as mp: - mp.setattr(CONFIG, "hasEnchant", False) - prefs = GuiPreferences(nwGUI) - prefs.show() + CONFIG.hasEnchant = False + prefs = GuiPreferences(nwGUI) + prefs.show() - # Check Spell Checking - spelling = [prefs.spellLanguage.itemData(i) for i in range(prefs.spellLanguage.count())] - assert len(spelling) == 1 - assert spelling == [""] + # Check Spell Checking + spelling = [prefs.spellLanguage.itemData(i) for i in range(prefs.spellLanguage.count())] + assert len(spelling) == 1 + assert spelling == [""] - prefs.close() + prefs.close() # qtbot.stop() @@ -138,13 +137,14 @@ def testDlgPreferences_Actions(qtbot, monkeypatch, nwGUI): @pytest.mark.gui -def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, tstPaths): +def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths): """Test the preferences dialog settings.""" spelling = [("en", "English [en]"), ("de", "Deutch [de]")] - languages = [("en_GB", "British English"), ("en_US", "US English")] - monkeypatch.setattr(SHARED._spelling, "listDictionaries", lambda: spelling) - monkeypatch.setattr(CONFIG, "listLanguages", lambda *a: languages) + + (fncPath / "nw_en_US.qm").touch() + (fncPath / "project_en_US.json").touch() + CONFIG._nwLangPath = fncPath prefs = GuiPreferences(nwGUI) with qtbot.waitExposed(prefs): diff --git a/tests/test_dialogs/test_dlg_projectsettings.py b/tests/test_dialogs/test_dlg_projectsettings.py index 54b5cd1d..d28322d4 100644 --- a/tests/test_dialogs/test_dlg_projectsettings.py +++ b/tests/test_dialogs/test_dlg_projectsettings.py @@ -88,7 +88,12 @@ def testDlgProjSettings_SettingsPage(qtbot, monkeypatch, nwGUI, fncPath, projPat """Test the settings page of the dialog.""" languages = [("en", "English"), ("de", "German")] monkeypatch.setattr(SHARED._spelling, "listDictionaries", lambda *a: languages) - monkeypatch.setattr(CONFIG, "listLanguages", lambda *a: languages) + + (fncPath / "nw_en.qm").touch() + (fncPath / "nw_de.qm").touch() + (fncPath / "project_en.json").touch() + (fncPath / "project_de.json").touch() + CONFIG._nwLangPath = fncPath # Create new project buildTestProject(nwGUI, projPath) From ca2943775de211fe43609e7ace5558c03dd0c624 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 12 Jan 2025 23:59:09 +0100 Subject: [PATCH 3/8] Clean up warnings and deprecations in base and dialogs --- novelwriter/dialogs/about.py | 22 ++++------ novelwriter/dialogs/docmerge.py | 20 ++++----- novelwriter/dialogs/docsplit.py | 21 ++++------ novelwriter/dialogs/editlabel.py | 10 ++--- novelwriter/dialogs/preferences.py | 42 +++++++++---------- novelwriter/dialogs/projectsettings.py | 57 ++++++++++++-------------- novelwriter/dialogs/quotes.py | 15 +++---- novelwriter/dialogs/wordlist.py | 23 ++++------- novelwriter/error.py | 10 ++--- novelwriter/guimain.py | 12 ++---- 10 files changed, 96 insertions(+), 136 deletions(-) diff --git a/novelwriter/dialogs/about.py b/novelwriter/dialogs/about.py index efc61388..0d5ca304 100644 --- a/novelwriter/dialogs/about.py +++ b/novelwriter/dialogs/about.py @@ -49,18 +49,13 @@ class GuiAbout(NDialog): self.setObjectName("GuiAbout") self.setWindowTitle(self.tr("About novelWriter")) - self.resize(CONFIG.pxInt(700), CONFIG.pxInt(500)) - - hA = CONFIG.pxInt(8) - hB = CONFIG.pxInt(16) - nwH = CONFIG.pxInt(36) - nwPx = CONFIG.pxInt(128) + self.resize(700, 500) # Logo and Banner - self.nwImage = SHARED.theme.loadDecoration("nw-text", h=nwH) + self.nwImage = SHARED.theme.loadDecoration("nw-text", h=36) self.nwLogo = QLabel(self) - self.nwLogo.setPixmap(SHARED.theme.getPixmap("novelwriter", (nwPx, nwPx))) + self.nwLogo.setPixmap(SHARED.theme.getPixmap("novelwriter", (128, 128))) self.nwLabel = QLabel(self) self.nwLabel.setPixmap(self.nwImage) @@ -79,8 +74,7 @@ class GuiAbout(NDialog): self.txtCredits = QTextBrowser(self) self.txtCredits.setOpenExternalLinks(True) - self.txtCredits.document().setDocumentMargin(0) - self.txtCredits.setViewportMargins(0, hA, hA, 0) + self.txtCredits.setViewportMargins(0, 8, 8, 0) # Buttons self.btnBox = QDialogButtonBox(QtDialogClose, self) @@ -88,15 +82,15 @@ class GuiAbout(NDialog): # Assemble self.innerBox = QVBoxLayout() - self.innerBox.addSpacing(hB) + self.innerBox.addSpacing(16) self.innerBox.addWidget(self.nwLabel) self.innerBox.addWidget(self.nwInfo) - self.innerBox.addSpacing(hA) + self.innerBox.addSpacing(8) self.innerBox.addWidget(self.nwLicence) - self.innerBox.addSpacing(hA) + self.innerBox.addSpacing(8) self.innerBox.addWidget(self.lblCredits) self.innerBox.addWidget(self.txtCredits) - self.innerBox.addSpacing(hB) + self.innerBox.addSpacing(16) self.innerBox.addWidget(self.btnBox) self.outerBox = QHBoxLayout() diff --git a/novelwriter/dialogs/docmerge.py b/novelwriter/dialogs/docmerge.py index 4d1d529f..fe77dc60 100644 --- a/novelwriter/dialogs/docmerge.py +++ b/novelwriter/dialogs/docmerge.py @@ -32,7 +32,7 @@ from PyQt6.QtWidgets import ( QListWidgetItem, QVBoxLayout, QWidget ) -from novelwriter import CONFIG, SHARED +from novelwriter import SHARED from novelwriter.extensions.configlayout import NColourLabel from novelwriter.extensions.modified import NDialog from novelwriter.extensions.switch import NSwitch @@ -63,14 +63,11 @@ class GuiDocMerge(NDialog): iPx = SHARED.theme.baseIconHeight iSz = SHARED.theme.baseIconSize - hSp = CONFIG.pxInt(12) - vSp = CONFIG.pxInt(8) - bSp = CONFIG.pxInt(12) self.listBox = QListWidget(self) self.listBox.setIconSize(iSz) - self.listBox.setMinimumWidth(CONFIG.pxInt(400)) - self.listBox.setMinimumHeight(CONFIG.pxInt(180)) + self.listBox.setMinimumWidth(400) + self.listBox.setMinimumHeight(180) self.listBox.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) self.listBox.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) self.listBox.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove) @@ -82,7 +79,7 @@ class GuiDocMerge(NDialog): self.optBox = QGridLayout() self.optBox.addWidget(self.trashLabel, 0, 0) self.optBox.addWidget(self.trashSwitch, 0, 1) - self.optBox.setHorizontalSpacing(hSp) + self.optBox.setHorizontalSpacing(12) self.optBox.setColumnStretch(2, 1) # Buttons @@ -91,18 +88,19 @@ class GuiDocMerge(NDialog): self.buttonBox.rejected.connect(self.reject) self.resetButton = self.buttonBox.addButton(QtDialogReset) - self.resetButton.clicked.connect(self._resetList) + if self.resetButton is not None: + self.resetButton.clicked.connect(self._resetList) # Assemble self.outerBox = QVBoxLayout() self.outerBox.setSpacing(0) self.outerBox.addWidget(self.headLabel) self.outerBox.addWidget(self.helpLabel) - self.outerBox.addSpacing(vSp) + self.outerBox.addSpacing(8) self.outerBox.addWidget(self.listBox) - self.outerBox.addSpacing(vSp) + self.outerBox.addSpacing(8) self.outerBox.addLayout(self.optBox) - self.outerBox.addSpacing(bSp) + self.outerBox.addSpacing(12) self.outerBox.addWidget(self.buttonBox) self.setLayout(self.outerBox) diff --git a/novelwriter/dialogs/docsplit.py b/novelwriter/dialogs/docsplit.py index 259125a6..4707eb51 100644 --- a/novelwriter/dialogs/docsplit.py +++ b/novelwriter/dialogs/docsplit.py @@ -32,7 +32,7 @@ from PyQt6.QtWidgets import ( QListWidget, QListWidgetItem, QVBoxLayout, QWidget ) -from novelwriter import CONFIG, SHARED +from novelwriter import SHARED from novelwriter.extensions.configlayout import NColourLabel from novelwriter.extensions.modified import NDialog from novelwriter.extensions.switch import NSwitch @@ -67,9 +67,6 @@ class GuiDocSplit(NDialog): # Values iPx = SHARED.theme.baseIconHeight - hSp = CONFIG.pxInt(12) - vSp = CONFIG.pxInt(8) - bSp = CONFIG.pxInt(12) pOptions = SHARED.project.options spLevel = pOptions.getInt("GuiDocSplit", "spLevel", 3) @@ -79,11 +76,11 @@ class GuiDocSplit(NDialog): # Heading Selection self.listBox = QListWidget(self) self.listBox.setDragDropMode(QAbstractItemView.DragDropMode.NoDragDrop) - self.listBox.setMinimumWidth(CONFIG.pxInt(400)) - self.listBox.setMinimumHeight(CONFIG.pxInt(180)) + self.listBox.setMinimumWidth(400) + self.listBox.setMinimumHeight(180) self.splitLevel = QComboBox(self) - self.splitLevel.addItem(self.tr("Split on Heading Level 1 (Partition)"), 1) + self.splitLevel.addItem(self.tr("Split on Heading Level 1 (Partition)"), 1) self.splitLevel.addItem(self.tr("Split up to Heading Level 2 (Chapter)"), 2) self.splitLevel.addItem(self.tr("Split up to Heading Level 3 (Scene)"), 3) self.splitLevel.addItem(self.tr("Split up to Heading Level 4 (Section)"), 4) @@ -111,8 +108,8 @@ class GuiDocSplit(NDialog): self.optBox.addWidget(self.hierarchySwitch, 1, 1) self.optBox.addWidget(self.trashLabel, 2, 0) self.optBox.addWidget(self.trashSwitch, 2, 1) - self.optBox.setVerticalSpacing(vSp) - self.optBox.setHorizontalSpacing(hSp) + self.optBox.setVerticalSpacing(8) + self.optBox.setHorizontalSpacing(12) self.optBox.setColumnStretch(3, 1) # Buttons @@ -125,12 +122,12 @@ class GuiDocSplit(NDialog): self.outerBox.setSpacing(0) self.outerBox.addWidget(self.headLabel) self.outerBox.addWidget(self.helpLabel) - self.outerBox.addSpacing(vSp) + self.outerBox.addSpacing(8) self.outerBox.addWidget(self.listBox) self.outerBox.addWidget(self.splitLevel) - self.outerBox.addSpacing(vSp) + self.outerBox.addSpacing(8) self.outerBox.addLayout(self.optBox) - self.outerBox.addSpacing(bSp) + self.outerBox.addSpacing(12) self.outerBox.addWidget(self.buttonBox) self.setLayout(self.outerBox) diff --git a/novelwriter/dialogs/editlabel.py b/novelwriter/dialogs/editlabel.py index 9bc4c2b4..caf97335 100644 --- a/novelwriter/dialogs/editlabel.py +++ b/novelwriter/dialogs/editlabel.py @@ -27,7 +27,6 @@ import logging from PyQt6.QtWidgets import QDialogButtonBox, QHBoxLayout, QLabel, QLineEdit, QVBoxLayout, QWidget -from novelwriter import CONFIG from novelwriter.extensions.modified import NDialog from novelwriter.types import QtAccepted, QtDialogCancel, QtDialogOk @@ -43,12 +42,9 @@ class GuiEditLabel(NDialog): self.setObjectName("GuiEditLabel") self.setWindowTitle(self.tr("Item Label")) - mVd = CONFIG.pxInt(220) - mSp = CONFIG.pxInt(12) - # Item Label self.labelValue = QLineEdit(self) - self.labelValue.setMinimumWidth(mVd) + self.labelValue.setMinimumWidth(220) self.labelValue.setMaxLength(200) self.labelValue.setText(text) self.labelValue.selectAll() @@ -62,10 +58,10 @@ class GuiEditLabel(NDialog): self.innerBox = QHBoxLayout() self.innerBox.addWidget(QLabel(self.tr("Label"), self), 0) self.innerBox.addWidget(self.labelValue, 1) - self.innerBox.setSpacing(mSp) + self.innerBox.setSpacing(12) self.outerBox = QVBoxLayout() - self.outerBox.setSpacing(mSp) + self.outerBox.setSpacing(12) self.outerBox.addLayout(self.innerBox, 1) self.outerBox.addWidget(self.buttonBox, 0) diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index 8a433273..f746fb2b 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -27,7 +27,7 @@ from __future__ import annotations import logging from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot -from PyQt6.QtGui import QCloseEvent, QKeyEvent, QKeySequence +from PyQt6.QtGui import QAction, QCloseEvent, QKeyEvent, QKeySequence from PyQt6.QtWidgets import ( QCompleter, QDialogButtonBox, QFileDialog, QHBoxLayout, QLineEdit, QPushButton, QVBoxLayout, QWidget @@ -58,23 +58,23 @@ class GuiPreferences(NDialog): logger.debug("Create: GuiPreferences") self.setObjectName("GuiPreferences") self.setWindowTitle(self.tr("Preferences")) - self.setMinimumSize(CONFIG.pxInt(600), CONFIG.pxInt(500)) + self.setMinimumSize(600, 500) self.resize(*CONFIG.prefsWinSize) # Title self.titleLabel = NColourLabel( self.tr("Preferences"), self, color=SHARED.theme.helpText, - scale=NColourLabel.HEADER_SCALE, indent=CONFIG.pxInt(4) + scale=NColourLabel.HEADER_SCALE, indent=4, ) # Search Box + self.searchAction = QAction(SHARED.theme.getIcon("search"), "") + self.searchAction.triggered.connect(self._gotoSearch) + self.searchText = QLineEdit(self) self.searchText.setPlaceholderText(self.tr("Search")) - self.searchText.setMinimumWidth(CONFIG.pxInt(200)) - self.searchAction = self.searchText.addAction( - SHARED.theme.getIcon("search"), QLineEdit.ActionPosition.TrailingPosition - ) - self.searchAction.triggered.connect(self._gotoSearch) + self.searchText.setMinimumWidth(200) + self.searchText.addAction(self.searchAction, QLineEdit.ActionPosition.TrailingPosition) # SideBar self.sidebar = NPagedSideBar(self) @@ -105,7 +105,7 @@ class GuiPreferences(NDialog): self.outerBox.addLayout(self.searchBox) self.outerBox.addLayout(self.mainBox) self.outerBox.addWidget(self.buttonBox) - self.outerBox.setSpacing(CONFIG.pxInt(8)) + self.outerBox.setSpacing(8) self.setLayout(self.outerBox) self.setSizeGripEnabled(True) @@ -134,8 +134,6 @@ class GuiPreferences(NDialog): section = 0 iSz = SHARED.theme.baseIconSize boxFixed = 6*SHARED.theme.textNWidth - minWidth = CONFIG.pxInt(200) - fontWidth = CONFIG.pxInt(162) # Temporary Variables self._guiFont = CONFIG.guiFont @@ -154,7 +152,7 @@ class GuiPreferences(NDialog): # Display Language self.guiLocale = NComboBox(self) - self.guiLocale.setMinimumWidth(minWidth) + self.guiLocale.setMinimumWidth(200) for lang, name in CONFIG.listLanguages(CONFIG.LANG_NW): self.guiLocale.addItem(name, lang) self.guiLocale.setCurrentData(CONFIG.guiLocale, "en_GB") @@ -166,7 +164,7 @@ class GuiPreferences(NDialog): # Colour Theme self.guiTheme = NComboBox(self) - self.guiTheme.setMinimumWidth(minWidth) + self.guiTheme.setMinimumWidth(200) for theme, name in SHARED.theme.listThemes(): self.guiTheme.addItem(name, theme) self.guiTheme.setCurrentData(CONFIG.guiTheme, "default") @@ -178,7 +176,7 @@ class GuiPreferences(NDialog): # Icon Theme self.iconTheme = NComboBox(self) - self.iconTheme.setMinimumWidth(minWidth) + self.iconTheme.setMinimumWidth(200) for theme, name in SHARED.theme.iconCache.listThemes(): self.iconTheme.addItem(name, theme) self.iconTheme.setCurrentData(CONFIG.iconTheme, "material_rounded_bold") @@ -190,7 +188,7 @@ class GuiPreferences(NDialog): # Tree Icon Colours self.iconColTree = NComboBox(self) - self.iconColTree.setMinimumWidth(minWidth) + self.iconColTree.setMinimumWidth(200) for key, label in nwLabels.THEME_COLORS.items(): self.iconColTree.addItem(trConst(label), key) self.iconColTree.setCurrentData(CONFIG.iconColTree, "theme") @@ -211,7 +209,7 @@ class GuiPreferences(NDialog): # Application Font Family self.guiFont = QLineEdit(self) self.guiFont.setReadOnly(True) - self.guiFont.setMinimumWidth(fontWidth) + self.guiFont.setMinimumWidth(162) self.guiFont.setText(describeFont(self._guiFont)) self.guiFont.setCursorPosition(0) self.guiFontButton = NIconToolButton(self, iSz, "font") @@ -256,7 +254,7 @@ class GuiPreferences(NDialog): # Document Colour Theme self.guiSyntax = NComboBox(self) - self.guiSyntax.setMinimumWidth(CONFIG.pxInt(200)) + self.guiSyntax.setMinimumWidth(200) for syntax, name in SHARED.theme.listSyntax(): self.guiSyntax.addItem(name, syntax) self.guiSyntax.setCurrentData(CONFIG.guiSyntax, "default_light") @@ -269,7 +267,7 @@ class GuiPreferences(NDialog): # Document Font Family self.textFont = QLineEdit(self) self.textFont.setReadOnly(True) - self.textFont.setMinimumWidth(fontWidth) + self.textFont.setMinimumWidth(162) self.textFont.setText(describeFont(CONFIG.textFont)) self.textFont.setCursorPosition(0) self.textFontButton = NIconToolButton(self, iSz, "font") @@ -482,7 +480,7 @@ class GuiPreferences(NDialog): # Spell Checking self.spellLanguage = NComboBox(self) - self.spellLanguage.setMinimumWidth(minWidth) + self.spellLanguage.setMinimumWidth(200) if CONFIG.hasEnchant: for tag, language in SHARED.spelling.listDictionaries(): @@ -651,8 +649,6 @@ class GuiPreferences(NDialog): self.sidebar.addButton(title, section) self.mainForm.addGroupLabel(title, section) - boxWidth = CONFIG.pxInt(150) - # Auto-Replace as You Type Main Switch self.doReplace = NSwitch(self) self.doReplace.setChecked(CONFIG.doReplace) @@ -701,7 +697,7 @@ class GuiPreferences(NDialog): # Pad Before self.fmtPadBefore = QLineEdit(self) self.fmtPadBefore.setMaxLength(32) - self.fmtPadBefore.setMinimumWidth(boxWidth) + self.fmtPadBefore.setMinimumWidth(150) self.fmtPadBefore.setText(CONFIG.fmtPadBefore) self.mainForm.addRow( self.tr("Insert non-breaking space before"), self.fmtPadBefore, @@ -711,7 +707,7 @@ class GuiPreferences(NDialog): # Pad After self.fmtPadAfter = QLineEdit(self) self.fmtPadAfter.setMaxLength(32) - self.fmtPadAfter.setMinimumWidth(boxWidth) + self.fmtPadAfter.setMinimumWidth(150) self.fmtPadAfter.setText(CONFIG.fmtPadAfter) self.mainForm.addRow( self.tr("Insert non-breaking space after"), self.fmtPadAfter, diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py index 222a8c77..1c95d242 100644 --- a/novelwriter/dialogs/projectsettings.py +++ b/novelwriter/dialogs/projectsettings.py @@ -30,7 +30,7 @@ import logging from pathlib import Path from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot -from PyQt6.QtGui import QCloseEvent, QColor +from PyQt6.QtGui import QAction, QCloseEvent, QColor from PyQt6.QtWidgets import ( QAbstractItemView, QApplication, QColorDialog, QDialogButtonBox, QFileDialog, QGridLayout, QHBoxLayout, QLineEdit, QMenu, QStackedWidget, @@ -71,16 +71,16 @@ class GuiProjectSettings(NDialog): self.setWindowTitle(self.tr("Project Settings")) options = SHARED.project.options - self.setMinimumSize(CONFIG.pxInt(500), CONFIG.pxInt(400)) + self.setMinimumSize(500, 400) self.resize( - CONFIG.pxInt(options.getInt("GuiProjectSettings", "winWidth", CONFIG.pxInt(650))), - CONFIG.pxInt(options.getInt("GuiProjectSettings", "winHeight", CONFIG.pxInt(500))) + options.getInt("GuiProjectSettings", "winWidth", 650), + options.getInt("GuiProjectSettings", "winHeight", 500), ) # Title self.titleLabel = NColourLabel( self.tr("Project Settings"), self, color=SHARED.theme.helpText, - scale=NColourLabel.HEADER_SCALE, indent=CONFIG.pxInt(4) + scale=NColourLabel.HEADER_SCALE, indent=4, ) # SideBar @@ -125,7 +125,7 @@ class GuiProjectSettings(NDialog): self.outerBox.addLayout(self.topBox) self.outerBox.addLayout(self.mainBox) self.outerBox.addWidget(self.buttonBox) - self.outerBox.setSpacing(CONFIG.pxInt(8)) + self.outerBox.setSpacing(8) self.setLayout(self.outerBox) self.setSizeGripEnabled(True) @@ -210,11 +210,11 @@ class GuiProjectSettings(NDialog): def _saveSettings(self) -> None: """Save GUI settings.""" - winWidth = CONFIG.rpxInt(self.width()) - winHeight = CONFIG.rpxInt(self.height()) - statusColW = CONFIG.rpxInt(self.statusPage.columnWidth()) - importColW = CONFIG.rpxInt(self.importPage.columnWidth()) - replaceColW = CONFIG.rpxInt(self.replacePage.columnWidth()) + winWidth = self.width() + winHeight = self.height() + statusColW = self.statusPage.columnWidth() + importColW = self.importPage.columnWidth() + replaceColW = self.replacePage.columnWidth() logger.debug("Saving State: GuiProjectSettings") options = SHARED.project.options @@ -232,7 +232,6 @@ class _SettingsPage(NScrollableForm): def __init__(self, parent: QWidget) -> None: super().__init__(parent=parent) - xW = CONFIG.pxInt(200) data = SHARED.project.data self.setHelpTextStyle(SHARED.theme.helpText) self.setRowIndent(0) @@ -240,7 +239,7 @@ class _SettingsPage(NScrollableForm): # Project Name self.projName = QLineEdit(self) self.projName.setMaxLength(200) - self.projName.setMinimumWidth(xW) + self.projName.setMinimumWidth(200) self.projName.setText(data.name) self.addRow( self.tr("Project name"), self.projName, @@ -251,7 +250,7 @@ class _SettingsPage(NScrollableForm): # Project Author self.projAuthor = QLineEdit(self) self.projAuthor.setMaxLength(200) - self.projAuthor.setMinimumWidth(xW) + self.projAuthor.setMinimumWidth(200) self.projAuthor.setText(data.author) self.addRow( self.tr("Author(s)"), self.projAuthor, @@ -262,7 +261,7 @@ class _SettingsPage(NScrollableForm): # Project Language projLang = data.language or CONFIG.guiLocale self.projLang = NComboBox(self) - self.projLang.setMinimumWidth(xW) + self.projLang.setMinimumWidth(200) for tag, language in CONFIG.listLanguages(CONFIG.LANG_PROJ): self.projLang.addItem(language, tag) self.projLang.setCurrentData(projLang, projLang) @@ -274,7 +273,7 @@ class _SettingsPage(NScrollableForm): # Spell Check Language self.spellLang = NComboBox(self) - self.spellLang.setMinimumWidth(xW) + self.spellLang.setMinimumWidth(200) self.spellLang.addItem(self.tr("Default"), "None") if CONFIG.hasEnchant: for tag, language in SHARED.spelling.listDictionaries(): @@ -323,9 +322,7 @@ class _StatusPage(NFixedPage): pageLabel = self.tr("Project Note Importance Levels") colSetting = "importColW" - wCol0 = CONFIG.pxInt( - SHARED.project.options.getInt("GuiProjectSettings", colSetting, 130) - ) + wCol0 = SHARED.project.options.getInt("GuiProjectSettings", colSetting, 130) self._changed = False self._color = QColor(100, 100, 100) @@ -334,7 +331,6 @@ class _StatusPage(NFixedPage): self._iPx = SHARED.theme.baseIconHeight iSz = SHARED.theme.baseIconSize - bPd = CONFIG.pxInt(4) iColor = self.palette().text().color() @@ -395,7 +391,7 @@ class _StatusPage(NFixedPage): self.labelText.textEdited.connect(self._onNameEdit) buttonStyle = ( - f"QToolButton {{padding: 0 {bPd}px;}} " + f"QToolButton {{padding: 0 {4}px;}} " "QToolButton::menu-indicator {image: none;}" ) @@ -406,12 +402,14 @@ class _StatusPage(NFixedPage): self.colorButton.setEnabled(False) self.colorButton.clicked.connect(self._onColourSelect) - def buildMenu(menu: QMenu, items: dict[nwStatusShape, str]) -> None: - for shape, label in items.items(): - icon = NWStatus.createIcon(self._iPx, iColor, shape) - action = menu.addAction(icon, trConst(label)) - action.triggered.connect(qtLambda(self._selectShape, shape)) - self._icons[shape] = icon + def buildMenu(menu: QMenu | None, items: dict[nwStatusShape, str]) -> None: + if menu is not None: + for shape, label in items.items(): + icon = NWStatus.createIcon(self._iPx, iColor, shape) + action = QAction(icon, trConst(label)) + action.triggered.connect(qtLambda(self._selectShape, shape)) + menu.addAction(action) + self._icons[shape] = icon self.shapeMenu = QMenu(self) buildMenu(self.shapeMenu, nwLabels.SHAPES_PLAIN) @@ -676,10 +674,7 @@ class _ReplacePage(NFixedPage): self._changed = False iSz = SHARED.theme.baseIconSize - - wCol0 = CONFIG.pxInt( - SHARED.project.options.getInt("GuiProjectSettings", "replaceColW", 130) - ) + wCol0 = SHARED.project.options.getInt("GuiProjectSettings", "replaceColW", 130) # Title self.pageTitle = NColourLabel( diff --git a/novelwriter/dialogs/quotes.py b/novelwriter/dialogs/quotes.py index 373f8ca5..20c57801 100644 --- a/novelwriter/dialogs/quotes.py +++ b/novelwriter/dialogs/quotes.py @@ -32,7 +32,6 @@ from PyQt6.QtWidgets import ( QListWidgetItem, QVBoxLayout, QWidget ) -from novelwriter import CONFIG from novelwriter.constants import nwQuotes, trConst from novelwriter.extensions.modified import NDialog from novelwriter.types import ( @@ -62,18 +61,14 @@ class GuiQuoteSelect(NDialog): self._selected = current - qMetrics = QFontMetrics(self.font()) - pxW = 7*qMetrics.boundingRectChar("M").width() - pxH = 7*qMetrics.boundingRectChar("M").height() - pxH = 7*qMetrics.boundingRectChar("M").height() - lblFont = self.font() lblFont.setPointSizeF(4*lblFont.pointSizeF()) + metrics = QFontMetrics(self.font()) # Preview Label self.previewLabel = QLabel(current, self) self.previewLabel.setFont(lblFont) - self.previewLabel.setFixedSize(QSize(pxW, pxH)) + self.previewLabel.setFixedSize(QSize(4*metrics.maxWidth(), 5*metrics.height())) self.previewLabel.setAlignment(QtAlignCenter) self.previewLabel.setFrameStyle(QFrame.Shape.Box | QFrame.Shadow.Plain) @@ -84,15 +79,15 @@ class GuiQuoteSelect(NDialog): minSize = 100 for sKey, sLabel in nwQuotes.SYMBOLS.items(): text = "[ %s ] %s" % (sKey, trConst(sLabel)) - minSize = max(minSize, qMetrics.boundingRect(text).width()) + minSize = max(minSize, metrics.boundingRect(text).width()) qtItem = QListWidgetItem(text) qtItem.setData(self.D_KEY, sKey) self.listBox.addItem(qtItem) if sKey == current: self.listBox.setCurrentItem(qtItem) - self.listBox.setMinimumWidth(minSize + CONFIG.pxInt(40)) - self.listBox.setMinimumHeight(CONFIG.pxInt(150)) + self.listBox.setMinimumWidth(minSize + 40) + self.listBox.setMinimumHeight(150) # Buttons self.buttonBox = QDialogButtonBox(QtDialogOk | QtDialogCancel, self) diff --git a/novelwriter/dialogs/wordlist.py b/novelwriter/dialogs/wordlist.py index 1988b79b..cf91b491 100644 --- a/novelwriter/dialogs/wordlist.py +++ b/novelwriter/dialogs/wordlist.py @@ -56,15 +56,12 @@ class GuiWordList(NDialog): self.setWindowTitle(self.tr("Project Word List")) iSz = SHARED.theme.baseIconSize - mS = CONFIG.pxInt(250) - wW = CONFIG.pxInt(320) - wH = CONFIG.pxInt(340) - self.setMinimumWidth(mS) - self.setMinimumHeight(mS) + self.setMinimumWidth(250) + self.setMinimumHeight(250) self.resize( - CONFIG.pxInt(SHARED.project.options.getInt("GuiWordList", "winWidth", wW)), - CONFIG.pxInt(SHARED.project.options.getInt("GuiWordList", "winHeight", wH)) + SHARED.project.options.getInt("GuiWordList", "winWidth", 320), + SHARED.project.options.getInt("GuiWordList", "winHeight", 340), ) # Header @@ -118,9 +115,9 @@ class GuiWordList(NDialog): self.outerBox.addLayout(self.headerBox, 0) self.outerBox.addWidget(self.listBox, 1) self.outerBox.addLayout(self.editBox, 0) - self.outerBox.addSpacing(CONFIG.pxInt(12)) + self.outerBox.addSpacing(12) self.outerBox.addWidget(self.buttonBox, 0) - self.outerBox.setSpacing(CONFIG.pxInt(4)) + self.outerBox.setSpacing(4) self.setLayout(self.outerBox) @@ -230,14 +227,10 @@ class GuiWordList(NDialog): def _saveGuiSettings(self) -> None: """Save GUI settings.""" - winWidth = CONFIG.rpxInt(self.width()) - winHeight = CONFIG.rpxInt(self.height()) - logger.debug("Saving State: GuiWordList") pOptions = SHARED.project.options - pOptions.setValue("GuiWordList", "winWidth", winWidth) - pOptions.setValue("GuiWordList", "winHeight", winHeight) - + pOptions.setValue("GuiWordList", "winWidth", self.width()) + pOptions.setValue("GuiWordList", "winHeight", self.height()) return def _addWord(self, word: str) -> None: diff --git a/novelwriter/error.py b/novelwriter/error.py index 6f272c0b..7410cc95 100644 --- a/novelwriter/error.py +++ b/novelwriter/error.py @@ -65,11 +65,11 @@ class NWErrorMessage(QDialog): # Widgets self.msgIcon = QLabel() - self.msgIcon.setPixmap( - QApplication.style().standardIcon( - QStyle.StandardPixmap.SP_MessageBoxCritical - ).pixmap(64, 64) - ) + if style := QApplication.style(): + self.msgIcon.setPixmap( + style.standardIcon(QStyle.StandardPixmap.SP_MessageBoxCritical).pixmap(64, 64) + ) + self.msgHead = QLabel() self.msgHead.setOpenExternalLinks(True) self.msgHead.setWordWrap(True) diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 3769090d..47d85a4a 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -114,10 +114,6 @@ class GuiMain(QMainWindow): # Build the GUI # ============= - # Sizes - mPx = CONFIG.pxInt(4) - hWd = CONFIG.pxInt(4) - # Main GUI Elements self.mainStatus = GuiMainStatus(self) self.projView = GuiProjectView(self) @@ -142,7 +138,7 @@ class GuiMain(QMainWindow): self.treePane = QWidget(self) self.treeBox = QVBoxLayout() self.treeBox.setContentsMargins(0, 0, 0, 0) - self.treeBox.setSpacing(mPx) + self.treeBox.setSpacing(4) self.treeBox.addWidget(self.projStack) self.treeBox.addWidget(self.itemDetails) self.treePane.setLayout(self.treeBox) @@ -151,7 +147,7 @@ class GuiMain(QMainWindow): self.splitView = QSplitter(Qt.Orientation.Vertical, self) self.splitView.addWidget(self.docViewer) self.splitView.addWidget(self.docViewerPanel) - self.splitView.setHandleWidth(hWd) + self.splitView.setHandleWidth(4) self.splitView.setOpaqueResize(False) self.splitView.setSizes(CONFIG.viewPanePos) self.splitView.setCollapsible(0, False) @@ -162,7 +158,7 @@ class GuiMain(QMainWindow): self.splitDocs.addWidget(self.docEditor) self.splitDocs.addWidget(self.splitView) self.splitDocs.setOpaqueResize(False) - self.splitDocs.setHandleWidth(hWd) + self.splitDocs.setHandleWidth(4) self.splitDocs.setCollapsible(0, False) self.splitDocs.setCollapsible(1, False) @@ -172,7 +168,7 @@ class GuiMain(QMainWindow): self.splitMain.addWidget(self.treePane) self.splitMain.addWidget(self.splitDocs) self.splitMain.setOpaqueResize(False) - self.splitMain.setHandleWidth(hWd) + self.splitMain.setHandleWidth(4) self.splitMain.setSizes(CONFIG.mainPanePos) self.splitMain.setCollapsible(0, False) self.splitMain.setCollapsible(0, False) From a60e0b3c8740032730d251fc32cb0dd2fe5b169c Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Mon, 13 Jan 2025 00:38:50 +0100 Subject: [PATCH 4/8] Clean up warnings and deprecations in extensions and formats --- novelwriter/extensions/configlayout.py | 26 ++++---- novelwriter/extensions/pagedsidebar.py | 82 +++++++++++--------------- novelwriter/extensions/statusled.py | 15 ++--- novelwriter/extensions/switch.py | 15 +++-- novelwriter/extensions/versioninfo.py | 2 +- novelwriter/formats/toqdoc.py | 9 ++- 6 files changed, 67 insertions(+), 82 deletions(-) diff --git a/novelwriter/extensions/configlayout.py b/novelwriter/extensions/configlayout.py index 47b74175..f9a0d940 100644 --- a/novelwriter/extensions/configlayout.py +++ b/novelwriter/extensions/configlayout.py @@ -33,7 +33,6 @@ from PyQt6.QtWidgets import ( QVBoxLayout, QWidget ) -from novelwriter import CONFIG from novelwriter.types import QtScrollAsNeeded DEFAULT_SCALE = 0.9 @@ -99,14 +98,14 @@ class NScrollableForm(QScrollArea): self._helpCol = QColor(0, 0, 0) self._fontScale = DEFAULT_SCALE self._first = True - self._indent = CONFIG.pxInt(12) + self._indent = 12 self._sections: dict[int, QLabel] = {} self._editable: dict[str, NColourLabel] = {} self._index: dict[str, QWidget] = {} self._layout = QVBoxLayout() - self._layout.setSpacing(CONFIG.pxInt(12)) + self._layout.setSpacing(12) self._widget = QWidget(self) self._widget.setLayout(self._layout) @@ -156,24 +155,25 @@ class NScrollableForm(QScrollArea): def scrollToSection(self, identifier: int) -> None: """Scroll to the requested section identifier.""" if identifier in self._sections: - yPos = self._sections[identifier].pos().y() - CONFIG.pxInt(8) - self.verticalScrollBar().setValue(yPos) + yPos = self._sections[identifier].pos().y() - 8 + if vBar := self.verticalScrollBar(): + vBar.setValue(yPos) return def scrollToLabel(self, label: str) -> None: """Scroll to the requested label.""" if label in self._index: - yPos = self._index[label].pos().y() - CONFIG.pxInt(8) - self.verticalScrollBar().setValue(yPos) + yPos = self._index[label].pos().y() - 8 + if vBar := self.verticalScrollBar(): + vBar.setValue(yPos) return def addGroupLabel(self, label: str, identifier: int | None = None) -> None: """Add a text label to separate groups of settings.""" - hM = CONFIG.pxInt(4) qLabel = QLabel(f"{label}", self) - qLabel.setContentsMargins(0, hM, 0, hM) + qLabel.setContentsMargins(0, 4, 0, 4) if not self._first: - self._layout.addSpacing(5*hM) + self._layout.addSpacing(20) self._layout.addWidget(qLabel) self._first = False if identifier is not None: @@ -192,7 +192,7 @@ class NScrollableForm(QScrollArea): ) -> None: """Add a label and a widget as a new row of the form.""" row = QHBoxLayout() - row.setSpacing(CONFIG.pxInt(12)) + row.setSpacing(12) if isinstance(widget, list): wBox = QHBoxLayout() @@ -205,7 +205,7 @@ class NScrollableForm(QScrollArea): icon.setPixmap(item) wBox.addWidget(icon) elif isinstance(item, int): - wBox.addSpacing(CONFIG.pxInt(item)) + wBox.addSpacing(item) qWidget = QWidget(self) qWidget.setLayout(wBox) else: @@ -252,7 +252,7 @@ class NScrollableForm(QScrollArea): def finalise(self) -> None: """Finalise the layout when the form is built.""" - self._layout.addSpacing(CONFIG.pxInt(20)) + self._layout.addSpacing(20) self._layout.addStretch(1) return diff --git a/novelwriter/extensions/pagedsidebar.py b/novelwriter/extensions/pagedsidebar.py index 6dd835f3..b58de63a 100644 --- a/novelwriter/extensions/pagedsidebar.py +++ b/novelwriter/extensions/pagedsidebar.py @@ -26,10 +26,10 @@ along with this program. If not, see . from __future__ import annotations from PyQt6.QtCore import QPoint, QRectF, QSize, Qt, pyqtSignal, pyqtSlot -from PyQt6.QtGui import QAction, QColor, QPainter, QPaintEvent, QPolygon +from PyQt6.QtGui import QColor, QPainter, QPaintEvent, QPolygon from PyQt6.QtWidgets import ( - QAbstractButton, QButtonGroup, QLabel, QStyle, QStyleOptionToolButton, - QToolBar, QToolButton, QWidget + QAbstractButton, QButtonGroup, QLabel, QStyleOptionToolButton, QToolBar, + QToolButton, QWidget ) from novelwriter.types import ( @@ -83,21 +83,19 @@ class NPagedSideBar(QToolBar): self.insertWidget(self._stretchAction, label) return - def addButton(self, text: str, buttonId: int = -1) -> QAction: + def addButton(self, text: str, buttonId: int = -1) -> None: """Add a new button to the toolbar.""" button = _PagedToolButton(self) button.setText(text) - - action = self.insertWidget(self._stretchAction, button) + self.insertWidget(self._stretchAction, button) self._group.addButton(button, id=buttonId) - self._buttons[buttonId] = button - - return action + return def setSelected(self, buttonId: int) -> None: """Set the selected button.""" - self._group.button(buttonId).setChecked(True) + if button := self._group.button(buttonId): + button.setChecked(True) return ## @@ -115,7 +113,7 @@ class NPagedSideBar(QToolBar): class _PagedToolButton(QToolButton): - __slots__ = ("_bH", "_tM", "_lM", "_cR", "_aH") + __slots__ = ("_bH", "_tM", "_aH") def __init__(self, parent: QWidget) -> None: super().__init__(parent=parent) @@ -126,8 +124,6 @@ class _PagedToolButton(QToolButton): fH = self.fontMetrics().height() self._bH = round(fH * 1.7) self._tM = (self._bH - fH)//2 - self._lM = 3*self.style().pixelMetric(QStyle.PixelMetric.PM_ButtonMargin)//2 - self._cR = self._lM//2 self._aH = 2*fH//7 self.setFixedHeight(self._bH) @@ -145,53 +141,51 @@ class _PagedToolButton(QToolButton): opt = QStyleOptionToolButton() opt.initFrom(self) - paint = QPainter(self) - paint.setRenderHint(QtPaintAntiAlias, True) - paint.setPen(QtNoPen) - paint.setBrush(QtNoBrush) + painter = QPainter(self) + painter.setRenderHint(QtPaintAntiAlias, True) + painter.setPen(QtNoPen) + painter.setBrush(QtNoBrush) width = self.width() height = self.height() palette = self.palette() if opt.state & QtMouseOver == QtMouseOver: # pragma: no cover - backCol = palette.base() - paint.setBrush(backCol) - paint.setOpacity(0.75) - paint.drawRoundedRect(0, 0, width, height, self._cR, self._cR) + painter.setBrush(palette.light()) + painter.drawRoundedRect(0, 0, width, height, 4, 4) if self.isChecked(): - backCol = palette.highlight() - paint.setBrush(backCol) - paint.setOpacity(0.35) - paint.drawRoundedRect(0, 0, width, height, self._cR, self._cR) + painter.setBrush(palette.highlight()) + painter.setOpacity(0.35) + painter.drawRoundedRect(0, 0, width, height, 4, 4) textCol = palette.highlightedText().color() else: textCol = palette.text().color() - tW = width - 2*self._lM + tW = width - 24 tH = height - 2*self._tM - paint.setPen(textCol) - paint.setOpacity(1.0) - paint.drawText(QRectF(self._lM, self._tM, tW, tH), QtAlignLeft, self.text()) + painter.setPen(textCol) + painter.setOpacity(1.0) + painter.drawText(QRectF(12, self._tM, tW, tH), QtAlignLeft, self.text()) tC = self.height()//2 - tW = self.width() - self._aH - self._lM + tW = self.width() - self._aH - 12 if self.isChecked(): - paint.setBrush(textCol) - paint.drawPolygon(QPolygon([ + painter.setBrush(textCol) + painter.drawPolygon(QPolygon([ QPoint(tW, tC - self._aH), QPoint(tW + self._aH, tC), QPoint(tW, tC + self._aH), ])) + painter.end() return class _NPagedToolLabel(QLabel): - __slots__ = ("_bH", "_tM", "_lM", "_textCol") + __slots__ = ("_bH", "_tM", "_textCol") def __init__(self, parent: QWidget, textColor: QColor | None = None) -> None: super().__init__(parent=parent) @@ -201,7 +195,6 @@ class _NPagedToolLabel(QLabel): fH = self.fontMetrics().height() self._bH = round(fH * 1.7) self._tM = (self._bH - fH)//2 - self._lM = self.style().pixelMetric(QStyle.PixelMetric.PM_ButtonMargin)//2 self.setFixedHeight(self._bH) self._textCol = textColor or self.palette().text().color() @@ -212,18 +205,15 @@ class _NPagedToolLabel(QLabel): """Overload the paint event to draw a simple, left aligned text label that matches the button style. """ - paint = QPainter(self) - paint.setRenderHint(QtPaintAntiAlias, True) - paint.setPen(QtNoPen) + tW = self.width() - 8 + tH = self.height() - 2*self._tM - width = self.width() - height = self.height() - - tW = width - 2*self._lM - tH = height - 2*self._tM - - paint.setPen(self._textCol) - paint.setOpacity(1.0) - paint.drawText(QRectF(self._lM, self._tM, tW, tH), QtAlignLeft, self.text()) + painter = QPainter(self) + painter.setRenderHint(QtPaintAntiAlias, True) + painter.setPen(QtNoPen) + painter.setPen(self._textCol) + painter.setOpacity(1.0) + painter.drawText(QRectF(4, self._tM, tW, tH), QtAlignLeft, self.text()) + painter.end() return diff --git a/novelwriter/extensions/statusled.py b/novelwriter/extensions/statusled.py index f4377823..a027a112 100644 --- a/novelwriter/extensions/statusled.py +++ b/novelwriter/extensions/statusled.py @@ -28,7 +28,6 @@ import logging from PyQt6.QtGui import QColor, QPainter, QPaintEvent from PyQt6.QtWidgets import QAbstractButton, QWidget -from novelwriter import CONFIG from novelwriter.enum import nwTrinary from novelwriter.types import QtBlack, QtPaintAntiAlias @@ -37,9 +36,7 @@ logger = logging.getLogger(__name__) class StatusLED(QAbstractButton): - __slots__ = ( - "_neutral", "_postitve", "_negative", "_color", "_state", "_bPx" - ) + __slots__ = ("_neutral", "_postitve", "_negative", "_color", "_state") def __init__(self, sW: int, sH: int, parent: QWidget | None = None) -> None: super().__init__(parent=parent) @@ -48,7 +45,6 @@ class StatusLED(QAbstractButton): self._negative = QtBlack self._color = QtBlack self._state = nwTrinary.NEUTRAL - self._bPx = CONFIG.pxInt(1) self.setFixedWidth(sW) self.setFixedHeight(sH) return @@ -82,12 +78,9 @@ class StatusLED(QAbstractButton): """Draw the LED.""" painter = QPainter(self) painter.setRenderHint(QtPaintAntiAlias, True) - painter.setPen(self.palette().windowText().color()) + painter.setPen(self.palette().text().color()) painter.setBrush(self._color) painter.setOpacity(1.0) - painter.drawEllipse( - self._bPx, self._bPx, - self.width() - 2*self._bPx, - self.height() - 2*self._bPx - ) + painter.drawEllipse(1, 1, self.width() - 2, self.height() - 2) + painter.end() return diff --git a/novelwriter/extensions/switch.py b/novelwriter/extensions/switch.py index 2b320506..ef936553 100644 --- a/novelwriter/extensions/switch.py +++ b/novelwriter/extensions/switch.py @@ -27,13 +27,13 @@ from PyQt6.QtCore import QPropertyAnimation, Qt, pyqtProperty from PyQt6.QtGui import QEnterEvent, QMouseEvent, QPainter, QPaintEvent, QResizeEvent from PyQt6.QtWidgets import QAbstractButton, QWidget -from novelwriter import CONFIG, SHARED +from novelwriter import SHARED from novelwriter.types import QtMouseLeft, QtNoPen, QtPaintAntiAlias, QtSizeFixed class NSwitch(QAbstractButton): - __slots__ = ("_xW", "_xH", "_xR", "_rB", "_rH", "_rR", "_offset") + __slots__ = ("_xW", "_xH", "_xR", "_rH", "_rR", "_offset") def __init__(self, parent: QWidget, height: int = 0) -> None: super().__init__(parent=parent) @@ -41,9 +41,8 @@ class NSwitch(QAbstractButton): self._xH = height or SHARED.theme.baseButtonHeight self._xW = 2*self._xH self._xR = int(self._xH*0.5) - self._rB = CONFIG.pxInt(2) - self._rH = self._xH - 2*self._rB - self._rR = self._xR - self._rB + self._rH = self._xH - 4 + self._rR = self._xR - 2 self.setCheckable(True) self.setSizePolicy(QtSizeFixed, QtSizeFixed) @@ -98,14 +97,14 @@ class NSwitch(QAbstractButton): trackBrush = palette.highlight() thumbBrush = palette.highlightedText() else: - trackBrush = palette.mid() + trackBrush = palette.midlight() thumbBrush = palette.light() if self.isEnabled(): trackOpacity = 1.0 else: trackOpacity = 0.6 - trackBrush = palette.dark() + trackBrush = palette.mid() thumbBrush = palette.mid() painter.setBrush(trackBrush) @@ -113,7 +112,7 @@ class NSwitch(QAbstractButton): painter.drawRoundedRect(0, 0, self._xW, self._xH, self._xR, self._xR) painter.setBrush(thumbBrush) - painter.drawEllipse(self._offset - self._rR, self._rB, self._rH, self._rH) + painter.drawEllipse(self._offset - self._rR, 2, self._rH, self._rH) painter.end() return diff --git a/novelwriter/extensions/versioninfo.py b/novelwriter/extensions/versioninfo.py index c44ce820..2f13afaa 100644 --- a/novelwriter/extensions/versioninfo.py +++ b/novelwriter/extensions/versioninfo.py @@ -70,7 +70,7 @@ class VersionInfoWidget(QWidget): self._layout = QVBoxLayout() self._layout.addWidget(self._lblInfo) self._layout.addWidget(self._lblRelease) - self._layout.setSpacing(CONFIG.pxInt(2)) + self._layout.setSpacing(2) self._layout.setContentsMargins(0, 0, 0, 0) self.setLayout(self._layout) diff --git a/novelwriter/formats/toqdoc.py b/novelwriter/formats/toqdoc.py index 13647988..731990f5 100644 --- a/novelwriter/formats/toqdoc.py +++ b/novelwriter/formats/toqdoc.py @@ -278,8 +278,10 @@ class ToQTextDocument(Tokenizer): printer.setPageMargins(self._pageMargins, QPageLayout.Unit.Millimeter) printer.setOutputFileName(str(path)) - self._document.documentLayout().setPaintDevice(printer) - self._document.setPageSize(printer.pageRect(QPrinter.Unit.Millimeter).size()) + if layout := self._document.documentLayout(): + layout.setPaintDevice(printer) + + self._document.setPageSize(printer.pageRect(QPrinter.Unit.DevicePixel).size()) self._document.print(printer) return @@ -458,7 +460,8 @@ class ToQTextDocument(Tokenizer): cursor.insertFrame(fFmt) cursor.setBlockFormat(bFmt) cursor.insertText(self._project.localLookup("New Page"), cFmt) - cursor.swap(self._document.rootFrame().lastCursorPosition()) + if root := self._document.rootFrame(): + cursor.swap(root.lastCursorPosition()) return From 5027b8df1990d1968cad0a53c6120541fd11f0f4 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Mon, 13 Jan 2025 02:21:56 +0100 Subject: [PATCH 5/8] Clean up warnings and deprecations in gui components --- novelwriter/common.py | 17 +- novelwriter/gui/doceditor.py | 175 ++++++++++--------- novelwriter/gui/dochighlight.py | 16 +- novelwriter/gui/docviewer.py | 95 +++++------ novelwriter/gui/docviewerpanel.py | 60 ++++--- novelwriter/gui/itemdetails.py | 11 +- novelwriter/gui/mainmenu.py | 272 +++++++++++++++--------------- novelwriter/gui/noveltree.py | 36 ++-- novelwriter/gui/outline.py | 69 ++++---- novelwriter/gui/projtree.py | 116 +++++++------ novelwriter/gui/search.py | 40 ++--- novelwriter/gui/sidebar.py | 4 +- novelwriter/gui/statusbar.py | 10 +- novelwriter/gui/theme.py | 11 +- 14 files changed, 458 insertions(+), 474 deletions(-) diff --git a/novelwriter/common.py b/novelwriter/common.py index 193f7c81..50ae5dfd 100644 --- a/novelwriter/common.py +++ b/novelwriter/common.py @@ -38,7 +38,8 @@ from urllib.parse import urljoin from urllib.request import pathname2url from PyQt6.QtCore import QCoreApplication, QMimeData, QUrl -from PyQt6.QtGui import QColor, QDesktopServices, QFont, QFontDatabase, QFontInfo +from PyQt6.QtGui import QAction, QColor, QDesktopServices, QFont, QFontDatabase, QFontInfo +from PyQt6.QtWidgets import QMenu, QMenuBar, QWidget from novelwriter.constants import nwConst, nwLabels, nwUnicode, trConst from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType @@ -461,6 +462,20 @@ def qtLambda(func: Callable, *args: Any, **kwargs: Any) -> Callable: return wrapper +def qtAddAction(parent: QWidget, label: str) -> QAction: + """Helper to add action to widget and always return the action.""" + action = QAction(label, parent) + parent.addAction(action) + return action + + +def qtAddMenu(parent: QMenuBar | QMenu, label: str) -> QMenu: + """Helper to add menu to menu and always return the menu.""" + menu = QMenu(label, parent) + parent.addMenu(menu) + return menu + + def encodeMimeHandles(mimeData: QMimeData, handles: list[str]) -> None: """Encode handles into a mime data object.""" mimeData.setData(nwConst.MIME_HANDLE, b"|".join(h.encode() for h in handles)) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index d67f514c..101859b8 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -52,7 +52,9 @@ from PyQt6.QtWidgets import ( ) from novelwriter import CONFIG, SHARED -from novelwriter.common import decodeMimeHandles, fontMatcher, minmax, qtLambda, transferCase +from novelwriter.common import ( + decodeMimeHandles, fontMatcher, minmax, qtAddAction, qtLambda, transferCase +) from novelwriter.constants import nwConst, nwKeyWords, nwShortcode, nwUnicode from novelwriter.core.document import NWDocument from novelwriter.enum import ( @@ -168,7 +170,7 @@ class GuiDocEditor(QPlainTextEdit): self.customContextMenuRequested.connect(self._openContextMenu) # Editor Settings - self.setMinimumWidth(CONFIG.pxInt(300)) + self.setMinimumWidth(300) self.setAutoFillBackground(True) self.setFrameStyle(QFrame.Shape.NoFrame) self.setAcceptDrops(True) @@ -295,10 +297,11 @@ class GuiDocEditor(QPlainTextEdit): 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) + if viewport := self.viewport(): + palette = viewport.palette() + palette.setColor(QPalette.ColorRole.Base, syntax.back) + palette.setColor(QPalette.ColorRole.Text, syntax.text) + viewport.setPalette(palette) self.docHeader.matchColours() self.docFooter.matchColours() @@ -513,29 +516,27 @@ class GuiDocEditor(QPlainTextEdit): def cursorIsVisible(self) -> bool: """Check if the cursor is visible in the editor.""" - return ( - 0 < self.cursorRect().top() - and self.cursorRect().bottom() < self.viewport().height() - ) + viewport = self.viewport() + height = viewport.height() if viewport else 0 + return 0 < self.cursorRect().top() and self.cursorRect().bottom() < height def ensureCursorVisibleNoCentre(self) -> None: """Ensure cursor is visible, but don't force it to centre.""" - cT = self.cursorRect().top() - cB = self.cursorRect().bottom() - vH = self.viewport().height() - if cT < 0: - count = 0 - vBar = self.verticalScrollBar() - while self.cursorRect().top() < 0 and count < 100000: - vBar.setValue(vBar.value() - 1) - count += 1 - elif cB > vH: - count = 0 - vBar = self.verticalScrollBar() - while self.cursorRect().bottom() > vH and count < 100000: - vBar.setValue(vBar.value() + 1) - count += 1 - QApplication.processEvents() + if (viewport := self.viewport()) and (vBar := self.verticalScrollBar()): + cT = self.cursorRect().top() + cB = self.cursorRect().bottom() + vH = viewport.height() + if cT < 0: + count = 0 + while self.cursorRect().top() < 0 and count < 100000: + vBar.setValue(vBar.value() - 1) + count += 1 + elif cB > vH: + count = 0 + while self.cursorRect().bottom() > vH and count < 100000: + vBar.setValue(vBar.value() + 1) + count += 1 + QApplication.processEvents() return def updateDocMargins(self) -> None: @@ -547,10 +548,10 @@ class GuiDocEditor(QPlainTextEdit): wH = self.height() vBar = self.verticalScrollBar() - sW = vBar.width() if vBar.isVisible() else 0 + sW = vBar.width() if vBar and vBar.isVisible() else 0 hBar = self.horizontalScrollBar() - sH = hBar.height() if hBar.isVisible() else 0 + sH = hBar.height() if hBar and hBar.isVisible() else 0 tM = self._vpMargin if CONFIG.textWidth > 0 or SHARED.focusMode: @@ -959,10 +960,9 @@ class GuiDocEditor(QPlainTextEdit): kMod = event.modifiers() okMod = kMod in (QtModNone, QtModShift) okKey = event.key() not in self.MOVE_KEYS - if nPos != cPos and okMod and okKey: - mPos = CONFIG.autoScrollPos*0.01 * self.viewport().height() - if cPos > mPos: - vBar = self.verticalScrollBar() + if nPos != cPos and okMod and okKey and (viewport := self.viewport()): + mPos = CONFIG.autoScrollPos*0.01 * viewport.height() + if cPos > mPos and (vBar := self.verticalScrollBar()): vBar.setValue(vBar.value() + (1 if nPos > cPos else -1)) else: super().keyPressEvent(event) @@ -971,7 +971,7 @@ class GuiDocEditor(QPlainTextEdit): def dragEnterEvent(self, event: QDragEnterEvent) -> None: """Overload drag enter event to handle dragged items.""" - if event.mimeData().hasFormat(nwConst.MIME_HANDLE): + if (data := event.mimeData()) and data.hasFormat(nwConst.MIME_HANDLE): event.acceptProposedAction() else: super().dragEnterEvent(event) @@ -979,7 +979,7 @@ class GuiDocEditor(QPlainTextEdit): def dragMoveEvent(self, event: QDragMoveEvent) -> None: """Overload drag move event to handle dragged items.""" - if event.mimeData().hasFormat(nwConst.MIME_HANDLE): + if (data := event.mimeData()) and data.hasFormat(nwConst.MIME_HANDLE): event.acceptProposedAction() else: super().dragMoveEvent(event) @@ -987,8 +987,8 @@ class GuiDocEditor(QPlainTextEdit): def dropEvent(self, event: QDropEvent) -> None: """Overload drop event to handle dragged items.""" - if event.mimeData().hasFormat(nwConst.MIME_HANDLE): - if handles := decodeMimeHandles(event.mimeData()): + if (data := event.mimeData()) and data.hasFormat(nwConst.MIME_HANDLE): + if handles := decodeMimeHandles(data): if SHARED.project.tree.checkType(handles[0], nwItemType.FILE): self.openDocumentRequest.emit(handles[0], nwDocMode.EDIT, "", True) else: @@ -1098,10 +1098,10 @@ class GuiDocEditor(QPlainTextEdit): # at unwanted times when other changes are made to the document cursor = self.textCursor() bPos = cursor.positionInBlock() - if bPos > 0: + if bPos > 0 and (viewport := self.viewport()): show = self._completer.updateText(text, bPos) point = self.cursorRect().bottomRight() - self._completer.move(self.viewport().mapToGlobal(point)) + self._completer.move(viewport.mapToGlobal(point)) self._completer.setVisible(show) else: self._completer.setVisible(False) @@ -1148,46 +1148,46 @@ class GuiDocEditor(QPlainTextEdit): ctxMenu = QMenu(self) ctxMenu.setObjectName("ContextMenu") if pBlock.userState() == BLOCK_TITLE: - action = ctxMenu.addAction(self.tr("Set as Document Name")) + action = qtAddAction(ctxMenu, self.tr("Set as Document Name")) action.triggered.connect(qtLambda(self._emitRenameItem, pBlock)) # URL (mData, mType) = self._qDocument.metaDataAtPos(pCursor.position()) if mData and mType == "url": - action = ctxMenu.addAction(self.tr("Open URL")) + action = qtAddAction(ctxMenu, self.tr("Open URL")) action.triggered.connect(qtLambda(SHARED.openWebsite, mData)) ctxMenu.addSeparator() # Follow status = self._processTag(cursor=pCursor, follow=False) if status == nwTrinary.POSITIVE: - action = ctxMenu.addAction(self.tr("Follow Tag")) + action = qtAddAction(ctxMenu, self.tr("Follow Tag")) action.triggered.connect(qtLambda(self._processTag, cursor=pCursor, follow=True)) ctxMenu.addSeparator() elif status == nwTrinary.NEGATIVE: - action = ctxMenu.addAction(self.tr("Create Note for Tag")) + action = qtAddAction(ctxMenu, self.tr("Create Note for Tag")) action.triggered.connect(qtLambda(self._processTag, cursor=pCursor, create=True)) ctxMenu.addSeparator() # Cut, Copy and Paste if uCursor.hasSelection(): - action = ctxMenu.addAction(self.tr("Cut")) + action = qtAddAction(ctxMenu, self.tr("Cut")) action.triggered.connect(qtLambda(self.docAction, nwDocAction.CUT)) - action = ctxMenu.addAction(self.tr("Copy")) + action = qtAddAction(ctxMenu, self.tr("Copy")) action.triggered.connect(qtLambda(self.docAction, nwDocAction.COPY)) - action = ctxMenu.addAction(self.tr("Paste")) + action = qtAddAction(ctxMenu, self.tr("Paste")) action.triggered.connect(qtLambda(self.docAction, nwDocAction.PASTE)) ctxMenu.addSeparator() # Selections - action = ctxMenu.addAction(self.tr("Select All")) + action = qtAddAction(ctxMenu, self.tr("Select All")) action.triggered.connect(qtLambda(self.docAction, nwDocAction.SEL_ALL)) - action = ctxMenu.addAction(self.tr("Select Word")) + action = qtAddAction(ctxMenu, self.tr("Select Word")) action.triggered.connect(qtLambda( self._makePosSelection, QTextCursor.SelectionType.WordUnderCursor, pos, )) - action = ctxMenu.addAction(self.tr("Select Paragraph")) + action = qtAddAction(ctxMenu, self.tr("Select Paragraph")) action.triggered.connect(qtLambda( self._makePosSelection, QTextCursor.SelectionType.BlockUnderCursor, pos )) @@ -1203,23 +1203,24 @@ class GuiDocEditor(QPlainTextEdit): sCursor.movePosition(QtMoveRight, QtKeepAnchor, cLen) if suggest: ctxMenu.addSeparator() - ctxMenu.addAction(self.tr("Spelling Suggestion(s)")) + qtAddAction(ctxMenu, self.tr("Spelling Suggestion(s)")) for option in suggest[:15]: - action = ctxMenu.addAction(f"{nwUnicode.U_ENDASH} {option}") + action = qtAddAction(ctxMenu, f"{nwUnicode.U_ENDASH} {option}") action.triggered.connect(qtLambda(self._correctWord, sCursor, option)) else: trNone = self.tr("No Suggestions") - ctxMenu.addAction(f"{nwUnicode.U_ENDASH} {trNone}") + qtAddAction(ctxMenu, f"{nwUnicode.U_ENDASH} {trNone}") ctxMenu.addSeparator() - action = ctxMenu.addAction(self.tr("Ignore Word")) + action = qtAddAction(ctxMenu, self.tr("Ignore Word")) action.triggered.connect(qtLambda(self._addWord, word, block, False)) - action = ctxMenu.addAction(self.tr("Add Word to Dictionary")) + action = qtAddAction(ctxMenu, self.tr("Add Word to Dictionary")) action.triggered.connect(qtLambda(self._addWord, word, block, True)) # Execute the context menu - ctxMenu.exec(self.viewport().mapToGlobal(pos)) - ctxMenu.deleteLater() + if viewport := self.viewport(): + ctxMenu.exec(viewport.mapToGlobal(pos)) + ctxMenu.deleteLater() return @@ -2007,8 +2008,8 @@ class GuiDocEditor(QPlainTextEdit): sPos = cPos for i in range(cPos - bPos): sPos = cPos - i - 1 - cOne = self._qDocument.characterAt(sPos) - cTwo = self._qDocument.characterAt(sPos - 1) + cOne = str(self._qDocument.characterAt(sPos)) + cTwo = str(self._qDocument.characterAt(sPos - 1)) if not (cOne.isalnum() or cOne in apos and cTwo.isalnum()): sPos += 1 break @@ -2017,8 +2018,8 @@ class GuiDocEditor(QPlainTextEdit): ePos = cPos for i in range(bPos + bLen - cPos): ePos = cPos + i - cOne = self._qDocument.characterAt(ePos) - cTwo = self._qDocument.characterAt(ePos + 1) + cOne = str(self._qDocument.characterAt(ePos)) + cTwo = str(self._qDocument.characterAt(ePos + 1)) if not (cOne.isalnum() or cOne in apos and cTwo.isalnum()): break @@ -2122,7 +2123,7 @@ class MetaCompleter(QMenu): for value in sorted(options): rep = value + suffix - action = self.addAction(value) + action = qtAddAction(self, value) action.triggered.connect(qtLambda(self._emitComplete, offset, length, rep)) return True @@ -2339,7 +2340,6 @@ class GuiDocToolBar(QWidget): logger.debug("Create: GuiDocToolBar") iSz = SHARED.theme.baseIconSize - cM = CONFIG.pxInt(4) self.setContentsMargins(0, 0, 0, 0) # General Buttons @@ -2412,7 +2412,7 @@ class GuiDocToolBar(QWidget): self.outerBox.addWidget(self.tbBoldMD) self.outerBox.addWidget(self.tbItalicMD) self.outerBox.addWidget(self.tbStrikeMD) - self.outerBox.addSpacing(cM) + self.outerBox.addSpacing(4) self.outerBox.addWidget(self.tbBold) self.outerBox.addWidget(self.tbItalic) self.outerBox.addWidget(self.tbStrike) @@ -2420,8 +2420,8 @@ class GuiDocToolBar(QWidget): self.outerBox.addWidget(self.tbMark) self.outerBox.addWidget(self.tbSuperscript) self.outerBox.addWidget(self.tbSubscript) - self.outerBox.setContentsMargins(cM, cM, cM, cM) - self.outerBox.setSpacing(cM) + self.outerBox.setContentsMargins(4, 4, 4, 4) + self.outerBox.setSpacing(4) self.setLayout(self.outerBox) self.updateTheme() @@ -2472,7 +2472,6 @@ class GuiDocEditSearch(QFrame): self.docEditor = docEditor iSz = SHARED.theme.baseIconSize - mPx = CONFIG.pxInt(6) self.setContentsMargins(0, 0, 0, 0) self.setAutoFillBackground(True) @@ -2498,7 +2497,7 @@ class GuiDocEditSearch(QFrame): self.searchOpt.setContentsMargins(0, 0, 0, 0) self.searchLabel = QLabel(self.tr("Search"), self) - self.searchLabel.setIndent(CONFIG.pxInt(6)) + self.searchLabel.setIndent(6) self.resultLabel = QLabel("?/?", self) @@ -2575,12 +2574,11 @@ class GuiDocEditSearch(QFrame): self.mainBox.setColumnStretch(3, 0) self.mainBox.setColumnStretch(4, 0) self.mainBox.setColumnStretch(5, 0) - self.mainBox.setSpacing(CONFIG.pxInt(2)) - self.mainBox.setContentsMargins(mPx, mPx, mPx, mPx) + self.mainBox.setSpacing(2) + self.mainBox.setContentsMargins(6, 6, 6, 6) - boxWidth = CONFIG.pxInt(200) - self.searchBox.setFixedWidth(boxWidth) - self.replaceBox.setFixedWidth(boxWidth) + self.searchBox.setFixedWidth(200) + self.replaceBox.setFixedWidth(200) self.replaceBox.setVisible(False) self.replaceButton.setVisible(False) self.adjustSize() @@ -2848,7 +2846,6 @@ class GuiDocEditHeader(QWidget): iPx = SHARED.theme.baseIconHeight iSz = SHARED.theme.baseIconSize - mPx = CONFIG.pxInt(4) # Main Widget Settings self.setAutoFillBackground(True) @@ -2895,13 +2892,13 @@ class GuiDocEditHeader(QWidget): self.outerBox.addWidget(self.tbButton, 0) self.outerBox.addWidget(self.outlineButton, 0) self.outerBox.addWidget(self.searchButton, 0) - self.outerBox.addSpacing(mPx) + self.outerBox.addSpacing(4) self.outerBox.addWidget(self.itemTitle, 1) - self.outerBox.addSpacing(mPx) + self.outerBox.addSpacing(4) self.outerBox.addSpacing(iPx) self.outerBox.addWidget(self.minmaxButton, 0) self.outerBox.addWidget(self.closeButton, 0) - self.outerBox.setContentsMargins(mPx, mPx, mPx, mPx) + self.outerBox.setContentsMargins(4, 4, 4, 4) self.outerBox.setSpacing(0) self.setLayout(self.outerBox) @@ -2912,7 +2909,7 @@ class GuiDocEditHeader(QWidget): # Fix Margins and Size # This is needed for high DPI systems. See issue #499. self.setContentsMargins(0, 0, 0, 0) - self.setMinimumHeight(iPx + 2*mPx) + self.setMinimumHeight(iPx + 8) self.updateFont() self.updateTheme() @@ -2945,7 +2942,7 @@ class GuiDocEditHeader(QWidget): tStart = time() self.outlineMenu.clear() for number, text in data.items(): - action = self.outlineMenu.addAction(text) + action = qtAddAction(self.outlineMenu, text) action.triggered.connect(qtLambda(self._gotoBlock, number)) self._docOutline = data logger.debug("Document outline updated in %.3f ms", 1000*(time() - tStart)) @@ -3070,9 +3067,6 @@ class GuiDocEditFooter(QWidget): iPx = round(0.9*SHARED.theme.baseIconHeight) fPx = int(0.9*SHARED.theme.fontPixelSize) - mPx = CONFIG.pxInt(8) - bSp = CONFIG.pxInt(4) - hSp = CONFIG.pxInt(6) # Cached Translations self._trLineCount = self.tr("Line: {0} ({1})") @@ -3127,23 +3121,23 @@ class GuiDocEditFooter(QWidget): # Assemble Layout self.outerBox = QHBoxLayout() - self.outerBox.setSpacing(bSp) + self.outerBox.setSpacing(4) self.outerBox.addWidget(self.statusIcon) self.outerBox.addWidget(self.statusText) self.outerBox.addStretch(1) self.outerBox.addWidget(self.linesIcon) self.outerBox.addWidget(self.linesText) - self.outerBox.addSpacing(hSp) + self.outerBox.addSpacing(6) self.outerBox.addWidget(self.wordsIcon) self.outerBox.addWidget(self.wordsText) - self.outerBox.setContentsMargins(mPx, mPx, mPx, mPx) + self.outerBox.setContentsMargins(8, 8, 8, 8) self.setLayout(self.outerBox) # Fix Margins and Size # This is needed for high DPI systems. See issue #499. self.setContentsMargins(0, 0, 0, 0) - self.setMinimumHeight(fPx + 2*mPx) + self.setMinimumHeight(fPx + 16) # Fix the Colours self.updateFont() @@ -3226,12 +3220,13 @@ class GuiDocEditFooter(QWidget): def updateLineCount(self, cursor: QTextCursor) -> None: """Update the line and document position counter.""" - cPos = cursor.position() + 1 - cLine = cursor.blockNumber() + 1 - cCount = max(cursor.document().characterCount(), 1) - self.linesText.setText( - self._trLineCount.format(f"{cLine:n}", f"{100*cPos//cCount:d} %") - ) + if document := cursor.document(): + cPos = cursor.position() + 1 + cLine = cursor.blockNumber() + 1 + cCount = max(document.characterCount(), 1) + self.linesText.setText( + self._trLineCount.format(f"{cLine:n}", f"{100*cPos//cCount:d} %") + ) return def updateWordCount(self, wCount: int, selection: bool) -> None: diff --git a/novelwriter/gui/dochighlight.py b/novelwriter/gui/dochighlight.py index de9d9f27..5ee1ea7e 100644 --- a/novelwriter/gui/dochighlight.py +++ b/novelwriter/gui/dochighlight.py @@ -272,14 +272,14 @@ class GuiDocHighlighter(QSyntaxHighlighter): """Loop through all blocks and re-highlight those of a given content type. """ - qDoc = self.document() - nBlocks = qDoc.blockCount() - tStart = time() - for i in range(nBlocks): - block = qDoc.findBlockByNumber(i) - if block.userState() & cType > 0: - self.rehighlightBlock(block) - logger.debug("Document highlighted in %.3f ms" % (1000*(time() - tStart))) + if document := self.document(): + nBlocks = document.blockCount() + tStart = time() + for i in range(nBlocks): + block = document.findBlockByNumber(i) + if block.userState() & cType > 0: + self.rehighlightBlock(block) + logger.debug("Document highlighted in %.3f ms" % (1000*(time() - tStart))) return ## diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py index e813a5c4..0be61bf4 100644 --- a/novelwriter/gui/docviewer.py +++ b/novelwriter/gui/docviewer.py @@ -32,8 +32,8 @@ from enum import Enum from PyQt6.QtCore import QPoint, Qt, QUrl, pyqtSignal, pyqtSlot from PyQt6.QtGui import ( - QAction, QCursor, QDesktopServices, QDragEnterEvent, QDragMoveEvent, - QDropEvent, QMouseEvent, QPalette, QResizeEvent, QTextCursor + QCursor, QDesktopServices, QDragEnterEvent, QDragMoveEvent, QDropEvent, + QMouseEvent, QPalette, QResizeEvent, QTextCursor ) from PyQt6.QtWidgets import ( QApplication, QFrame, QHBoxLayout, QMenu, QTextBrowser, QToolButton, @@ -41,7 +41,7 @@ from PyQt6.QtWidgets import ( ) from novelwriter import CONFIG, SHARED -from novelwriter.common import decodeMimeHandles, qtLambda +from novelwriter.common import decodeMimeHandles, qtAddAction, qtLambda from novelwriter.constants import nwConst, nwStyles, nwUnicode from novelwriter.enum import nwChange, nwDocAction, nwDocMode, nwItemType from novelwriter.error import logException @@ -79,7 +79,7 @@ class GuiDocViewer(QTextBrowser): self._docTheme = TextDocumentTheme() # Settings - self.setMinimumWidth(CONFIG.pxInt(300)) + self.setMinimumWidth(300) self.setAutoFillBackground(True) self.setOpenLinks(False) self.setOpenExternalLinks(False) @@ -124,8 +124,7 @@ class GuiDocViewer(QTextBrowser): @property def scrollPosition(self) -> int: """Return the scrollbar position.""" - vBar = self.verticalScrollBar() - if vBar.isVisible(): + if (vBar := self.verticalScrollBar()) and vBar.isVisible(): return vBar.value() return 0 @@ -163,12 +162,13 @@ class GuiDocViewer(QTextBrowser): palette.setColor(QPalette.ColorRole.Text, syntax.text) self.setPalette(palette) - palette = self.viewport().palette() - palette.setColor(QPalette.ColorRole.Base, syntax.back) - palette.setColor(QPalette.ColorRole.Text, syntax.text) - self.viewport().setPalette(palette) - self.docHeader.matchColours() - self.docFooter.matchColours() + if viewport := self.viewport(): + palette = viewport.palette() + palette.setColor(QPalette.ColorRole.Base, syntax.back) + palette.setColor(QPalette.ColorRole.Text, syntax.text) + viewport.setPalette(palette) + self.docHeader.matchColours() + self.docFooter.matchColours() # Update theme colours self._docTheme.text = syntax.text @@ -186,7 +186,8 @@ class GuiDocViewer(QTextBrowser): self._docTheme.altdialog = syntax.dialA # Set default text margins - self.document().setDocumentMargin(0) + if document := self.document(): + document.setDocumentMargin(0) # Scroll bars if CONFIG.hideVScroll: @@ -217,7 +218,9 @@ class GuiDocViewer(QTextBrowser): logger.debug("Generating preview for item '%s'", tHandle) QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor)) - sPos = self.verticalScrollBar().value() + vBar = self.verticalScrollBar() + sPos = vBar.value() if vBar else 0 + qDoc = ToQTextDocument(SHARED.project) qDoc.setJustify(CONFIG.doJustify) qDoc.setDialogHighlight(True) @@ -252,9 +255,9 @@ class GuiDocViewer(QTextBrowser): self.setDocument(qDoc.document) self.setTabStopDistance(CONFIG.tabWidth) - if self._docHandle == tHandle: + if self._docHandle == tHandle and vBar: # This is a refresh, so we set the scrollbar back to where it was - self.verticalScrollBar().setValue(sPos) + vBar.setValue(sPos) self._docHandle = tHandle SHARED.project.data.setLastHandle(tHandle, "viewer") @@ -311,10 +314,10 @@ class GuiDocViewer(QTextBrowser): cM = CONFIG.textMargin vBar = self.verticalScrollBar() - sW = vBar.width() if vBar.isVisible() else 0 + sW = vBar.width() if vBar and vBar.isVisible() else 0 hBar = self.horizontalScrollBar() - sH = hBar.height() if hBar.isVisible() else 0 + sH = hBar.height() if hBar and hBar.isVisible() else 0 tM = cM if CONFIG.textWidth > 0: @@ -339,8 +342,7 @@ class GuiDocViewer(QTextBrowser): def setScrollPosition(self, pos: int) -> None: """Set the scrollbar position.""" - vBar = self.verticalScrollBar() - if vBar.isVisible(): + if (vBar := self.verticalScrollBar()) and vBar.isVisible(): vBar.setValue(pos) return @@ -402,31 +404,27 @@ class GuiDocViewer(QTextBrowser): ctxMenu = QMenu(self) if userSelection: - mnuCopy = QAction(self.tr("Copy"), ctxMenu) - mnuCopy.triggered.connect(qtLambda(self.docAction, nwDocAction.COPY)) - ctxMenu.addAction(mnuCopy) - + action = qtAddAction(ctxMenu, self.tr("Copy")) + action.triggered.connect(qtLambda(self.docAction, nwDocAction.COPY)) ctxMenu.addSeparator() - mnuSelAll = QAction(self.tr("Select All"), ctxMenu) - mnuSelAll.triggered.connect(qtLambda(self.docAction, nwDocAction.SEL_ALL)) - ctxMenu.addAction(mnuSelAll) + action = qtAddAction(ctxMenu, self.tr("Select All")) + action.triggered.connect(qtLambda(self.docAction, nwDocAction.SEL_ALL)) - mnuSelWord = QAction(self.tr("Select Word"), ctxMenu) - mnuSelWord.triggered.connect(qtLambda( + action = qtAddAction(ctxMenu, self.tr("Select Word")) + action.triggered.connect(qtLambda( self._makePosSelection, QTextCursor.SelectionType.WordUnderCursor, point )) - ctxMenu.addAction(mnuSelWord) - mnuSelPara = QAction(self.tr("Select Paragraph"), ctxMenu) - mnuSelPara.triggered.connect(qtLambda( + action = qtAddAction(ctxMenu, self.tr("Select Paragraph")) + action.triggered.connect(qtLambda( self._makePosSelection, QTextCursor.SelectionType.BlockUnderCursor, point )) - ctxMenu.addAction(mnuSelPara) # Open the context menu - ctxMenu.exec(self.viewport().mapToGlobal(point)) - ctxMenu.deleteLater() + if viewport := self.viewport(): + ctxMenu.exec(viewport.mapToGlobal(point)) + ctxMenu.deleteLater() return @@ -452,7 +450,7 @@ class GuiDocViewer(QTextBrowser): def dragEnterEvent(self, event: QDragEnterEvent) -> None: """Overload drag enter event to handle dragged items.""" - if event.mimeData().hasFormat(nwConst.MIME_HANDLE): + if (data := event.mimeData()) and data.hasFormat(nwConst.MIME_HANDLE): event.acceptProposedAction() else: super().dragEnterEvent(event) @@ -460,7 +458,7 @@ class GuiDocViewer(QTextBrowser): def dragMoveEvent(self, event: QDragMoveEvent) -> None: """Overload drag move event to handle dragged items.""" - if event.mimeData().hasFormat(nwConst.MIME_HANDLE): + if (data := event.mimeData()) and data.hasFormat(nwConst.MIME_HANDLE): event.acceptProposedAction() else: super().dragMoveEvent(event) @@ -468,8 +466,8 @@ class GuiDocViewer(QTextBrowser): def dropEvent(self, event: QDropEvent) -> None: """Overload drop event to handle dragged items.""" - if event.mimeData().hasFormat(nwConst.MIME_HANDLE): - if handles := decodeMimeHandles(event.mimeData()): + if (data := event.mimeData()) and data.hasFormat(nwConst.MIME_HANDLE): + if handles := decodeMimeHandles(data): if SHARED.project.tree.checkType(handles[0], nwItemType.FILE): self.openDocumentRequest.emit(handles[0], nwDocMode.VIEW, "", True) else: @@ -640,7 +638,6 @@ class GuiDocViewHeader(QWidget): iPx = SHARED.theme.baseIconHeight iSz = SHARED.theme.baseIconSize - mPx = CONFIG.pxInt(4) # Main Widget Settings self.setAutoFillBackground(True) @@ -692,9 +689,9 @@ class GuiDocViewHeader(QWidget): self.outerBox.addWidget(self.outlineButton, 0) self.outerBox.addWidget(self.backButton, 0) self.outerBox.addWidget(self.forwardButton, 0) - self.outerBox.addSpacing(mPx) + self.outerBox.addSpacing(4) self.outerBox.addWidget(self.itemTitle, 1) - self.outerBox.addSpacing(mPx) + self.outerBox.addSpacing(4) self.outerBox.addWidget(self.editButton, 0) self.outerBox.addWidget(self.refreshButton, 0) self.outerBox.addWidget(self.closeButton, 0) @@ -705,8 +702,8 @@ class GuiDocViewHeader(QWidget): # Fix Margins and Size # This is needed for high DPI systems. See issue #499. self.setContentsMargins(0, 0, 0, 0) - self.outerBox.setContentsMargins(mPx, mPx, mPx, mPx) - self.setMinimumHeight(iPx + 2*mPx) + self.outerBox.setContentsMargins(4, 4, 4, 4) + self.setMinimumHeight(iPx + 8) self.updateFont() self.updateTheme() @@ -747,7 +744,7 @@ class GuiDocViewHeader(QWidget): minLevel = min(minLevel, level) for title, text, level in entries[:30]: indent = " "*(level - minLevel) - action = self.outlineMenu.addAction(f"{indent}{text}") + action = qtAddAction(self.outlineMenu, f"{indent}{text}") action.triggered.connect( lambda _, title=title: self.docViewer.navigateTo(f"#{tHandle}:{title}") ) @@ -885,8 +882,6 @@ class GuiDocViewFooter(QWidget): iPx = SHARED.theme.baseIconHeight iSz = SHARED.theme.baseIconSize - hSp = CONFIG.pxInt(4) - mPx = CONFIG.pxInt(4) # Main Widget Settings self.setContentsMargins(0, 0, 0, 0) @@ -923,14 +918,14 @@ class GuiDocViewFooter(QWidget): self.outerBox.addStretch(1) self.outerBox.addWidget(self.showComments, 0) self.outerBox.addWidget(self.showSynopsis, 0) - self.outerBox.setSpacing(hSp) + self.outerBox.setSpacing(4) self.setLayout(self.outerBox) # Fix Margins and Size # This is needed for high DPI systems. See issue #499. self.setContentsMargins(0, 0, 0, 0) - self.outerBox.setContentsMargins(mPx, mPx, mPx, mPx) - self.setMinimumHeight(iPx + 2*mPx) + self.outerBox.setContentsMargins(4, 4, 4, 4) + self.setMinimumHeight(iPx + 8) self.updateFont() self.updateTheme() diff --git a/novelwriter/gui/docviewerpanel.py b/novelwriter/gui/docviewerpanel.py index 91723b91..54284d10 100644 --- a/novelwriter/gui/docviewerpanel.py +++ b/novelwriter/gui/docviewerpanel.py @@ -33,8 +33,8 @@ from PyQt6.QtWidgets import ( QTreeWidgetItem, QVBoxLayout, QWidget ) -from novelwriter import CONFIG, SHARED -from novelwriter.common import checkInt +from novelwriter import SHARED +from novelwriter.common import checkInt, qtAddAction from novelwriter.constants import nwLabels, nwLists, nwStyles, trConst from novelwriter.core.index import IndexHeading, IndexItem from novelwriter.enum import nwChange, nwDocMode, nwItemClass @@ -63,7 +63,7 @@ class GuiDocViewerPanel(QWidget): self.optsMenu = QMenu(self) - self.aInactive = self.optsMenu.addAction(self.tr("Hide Inactive Tags")) + self.aInactive = qtAddAction(self.optsMenu, self.tr("Hide Inactive Tags")) self.aInactive.setCheckable(True) self.aInactive.toggled.connect(self._toggleHideInactive) @@ -248,7 +248,6 @@ class _ViewPanelBackRefs(QTreeWidget): iPx = SHARED.theme.baseIconHeight iSz = SHARED.theme.baseIconSize - cMg = CONFIG.pxInt(6) self.setHeaderLabels([self.tr("Document"), "", "", self.tr("First Heading")]) self.setIndentation(0) @@ -257,16 +256,16 @@ class _ViewPanelBackRefs(QTreeWidget): self.setFrameStyle(QFrame.Shape.NoFrame) # Set Header Sizes - treeHeader = self.header() - treeHeader.setStretchLastSection(True) - treeHeader.setMinimumSectionSize(iPx + cMg) # See Issue #1627 - treeHeader.setSectionResizeMode(self.C_DOC, QtHeaderToContents) - treeHeader.setSectionResizeMode(self.C_EDIT, QtHeaderFixed) - treeHeader.setSectionResizeMode(self.C_VIEW, QtHeaderFixed) - treeHeader.setSectionResizeMode(self.C_TITLE, QtHeaderToContents) - treeHeader.resizeSection(self.C_EDIT, iPx + cMg) - treeHeader.resizeSection(self.C_VIEW, iPx + cMg) - treeHeader.setSectionsMovable(False) + if header := self.header(): + header.setStretchLastSection(True) + header.setMinimumSectionSize(iPx + 6) # See Issue #1627 + header.setSectionResizeMode(self.C_DOC, QtHeaderToContents) + header.setSectionResizeMode(self.C_EDIT, QtHeaderFixed) + header.setSectionResizeMode(self.C_VIEW, QtHeaderFixed) + header.setSectionResizeMode(self.C_TITLE, QtHeaderToContents) + header.resizeSection(self.C_EDIT, iPx + 6) + header.resizeSection(self.C_VIEW, iPx + 6) + header.setSectionsMovable(False) # Cache Icons Locally self._editIcon = SHARED.theme.getIcon("edit", "green") @@ -385,7 +384,6 @@ class _ViewPanelKeyWords(QTreeWidget): iPx = SHARED.theme.baseIconHeight iSz = SHARED.theme.baseIconSize - cMg = CONFIG.pxInt(6) self.setHeaderLabels([ self.tr("Tag"), "", "", self.tr("Importance"), self.tr("Document"), @@ -401,14 +399,14 @@ class _ViewPanelKeyWords(QTreeWidget): self.sortByColumn(self.C_NAME, Qt.SortOrder.AscendingOrder) # Set Header Sizes - treeHeader = self.header() - treeHeader.setStretchLastSection(True) - treeHeader.setMinimumSectionSize(iPx + cMg) # See Issue #1627 - treeHeader.setSectionResizeMode(self.C_EDIT, QtHeaderFixed) - treeHeader.setSectionResizeMode(self.C_VIEW, QtHeaderFixed) - treeHeader.resizeSection(self.C_EDIT, iPx + cMg) - treeHeader.resizeSection(self.C_VIEW, iPx + cMg) - treeHeader.setSectionsMovable(False) + if header := self.header(): + header.setStretchLastSection(True) + header.setMinimumSectionSize(iPx + 6) # See Issue #1627 + header.setSectionResizeMode(self.C_EDIT, QtHeaderFixed) + header.setSectionResizeMode(self.C_VIEW, QtHeaderFixed) + header.resizeSection(self.C_EDIT, iPx + 6) + header.resizeSection(self.C_VIEW, iPx + 6) + header.setSectionsMovable(False) # Cache Icons Locally self.updateTheme() @@ -482,19 +480,19 @@ class _ViewPanelKeyWords(QTreeWidget): def setColumnWidths(self, widths: list[int]) -> None: """Set the column widths.""" if isinstance(widths, list) and len(widths) >= 4: - self.setColumnWidth(self.C_NAME, CONFIG.pxInt(checkInt(widths[0], 100))) - self.setColumnWidth(self.C_IMPORT, CONFIG.pxInt(checkInt(widths[1], 100))) - self.setColumnWidth(self.C_DOC, CONFIG.pxInt(checkInt(widths[2], 100))) - self.setColumnWidth(self.C_TITLE, CONFIG.pxInt(checkInt(widths[3], 100))) + self.setColumnWidth(self.C_NAME, checkInt(widths[0], 100)) + self.setColumnWidth(self.C_IMPORT, checkInt(widths[1], 100)) + self.setColumnWidth(self.C_DOC, checkInt(widths[2], 100)) + self.setColumnWidth(self.C_TITLE, checkInt(widths[3], 100)) return def getColumnWidths(self) -> list[int]: """Get the widths of the user-adjustable columns.""" return [ - CONFIG.rpxInt(self.columnWidth(self.C_NAME)), - CONFIG.rpxInt(self.columnWidth(self.C_IMPORT)), - CONFIG.rpxInt(self.columnWidth(self.C_DOC)), - CONFIG.rpxInt(self.columnWidth(self.C_TITLE)), + self.columnWidth(self.C_NAME), + self.columnWidth(self.C_IMPORT), + self.columnWidth(self.C_DOC), + self.columnWidth(self.C_TITLE), ] ## diff --git a/novelwriter/gui/itemdetails.py b/novelwriter/gui/itemdetails.py index 8cc264b7..bb33b65d 100644 --- a/novelwriter/gui/itemdetails.py +++ b/novelwriter/gui/itemdetails.py @@ -30,7 +30,7 @@ from enum import Enum from PyQt6.QtCore import pyqtSlot from PyQt6.QtWidgets import QGridLayout, QLabel, QWidget -from novelwriter import CONFIG, SHARED +from novelwriter import SHARED from novelwriter.common import elide from novelwriter.constants import nwLabels, nwStats, trConst from novelwriter.enum import nwChange @@ -53,9 +53,6 @@ class GuiItemDetails(QWidget): self._handle = None # Sizes - hSp = CONFIG.pxInt(6) - vSp = CONFIG.pxInt(1) - mPx = CONFIG.pxInt(6) fPt = SHARED.theme.fontPointSize fntLabel = self.font() @@ -176,9 +173,9 @@ class GuiItemDetails(QWidget): self.mainBox.setColumnStretch(3, 0) self.mainBox.setColumnStretch(4, 0) - self.mainBox.setHorizontalSpacing(hSp) - self.mainBox.setVerticalSpacing(vSp) - self.mainBox.setContentsMargins(mPx, mPx, mPx, mPx) + self.mainBox.setHorizontalSpacing(6) + self.mainBox.setVerticalSpacing(1) + self.mainBox.setContentsMargins(6, 6, 6, 6) self.setLayout(self.mainBox) diff --git a/novelwriter/gui/mainmenu.py b/novelwriter/gui/mainmenu.py index f477217e..9cca259d 100644 --- a/novelwriter/gui/mainmenu.py +++ b/novelwriter/gui/mainmenu.py @@ -33,7 +33,7 @@ from PyQt6.QtGui import QAction from PyQt6.QtWidgets import QMenuBar from novelwriter import CONFIG, SHARED -from novelwriter.common import openExternalPath, qtLambda +from novelwriter.common import openExternalPath, qtAddAction, qtAddMenu, qtLambda from novelwriter.constants import ( nwConst, nwKeyWords, nwLabels, nwShortcode, nwStats, nwStyles, nwUnicode, trConst @@ -128,21 +128,21 @@ class GuiMainMenu(QMenuBar): def _buildProjectMenu(self) -> None: """Assemble the Project menu.""" # Project - self.projMenu = self.addMenu(self.tr("&Project")) + self.projMenu = qtAddMenu(self, self.tr("&Project")) # Project > Create or Open Project - self.aOpenProject = self.projMenu.addAction(self.tr("Create or Open Project")) + self.aOpenProject = qtAddAction(self.projMenu, self.tr("Create or Open Project")) self.aOpenProject.setShortcut("Ctrl+Shift+O") self.aOpenProject.triggered.connect(self.mainGui.showWelcomeDialog) # Project > Save Project - self.aSaveProject = self.projMenu.addAction(self.tr("Save Project")) + self.aSaveProject = qtAddAction(self.projMenu, self.tr("Save Project")) self.aSaveProject.setShortcut("Ctrl+Shift+S") self.aSaveProject.triggered.connect(qtLambda(self.mainGui.saveProject)) self.mainGui.addAction(self.aSaveProject) # Project > Close Project - self.aCloseProject = self.projMenu.addAction(self.tr("Close Project")) + self.aCloseProject = qtAddAction(self.projMenu, self.tr("Close Project")) self.aCloseProject.setShortcut("Ctrl+Shift+W") self.aCloseProject.triggered.connect(qtLambda(self.mainGui.closeProject, False)) @@ -150,12 +150,12 @@ class GuiMainMenu(QMenuBar): self.projMenu.addSeparator() # Project > Project Settings - self.aProjectSettings = self.projMenu.addAction(self.tr("Project Settings")) + self.aProjectSettings = qtAddAction(self.projMenu, self.tr("Project Settings")) self.aProjectSettings.setShortcut("Ctrl+Shift+,") self.aProjectSettings.triggered.connect(self.mainGui.showProjectSettingsDialog) # Project > Novel Details - self.aNovelDetails = self.projMenu.addAction(self.tr("Novel Details")) + self.aNovelDetails = qtAddAction(self.projMenu, self.tr("Novel Details")) self.aNovelDetails.setShortcut("Shift+F6") self.aNovelDetails.triggered.connect(self.mainGui.showNovelDetailsDialog) @@ -163,16 +163,16 @@ class GuiMainMenu(QMenuBar): self.projMenu.addSeparator() # Project > Edit - self.aRenameItem = self.projMenu.addAction(self.tr("Rename Item")) + self.aRenameItem = qtAddAction(self.projMenu, self.tr("Rename Item")) self.aRenameItem.setShortcut("F2") # Project > Delete - self.aDeleteItem = self.projMenu.addAction(self.tr("Delete Item")) + self.aDeleteItem = qtAddAction(self.projMenu, self.tr("Delete Item")) self.aDeleteItem.setShortcut("Del") self.aDeleteItem.setShortcutContext(Qt.ShortcutContext.WidgetShortcut) # Project > Empty Trash - self.aEmptyTrash = self.projMenu.addAction(self.tr("Empty Trash")) + self.aEmptyTrash = qtAddAction(self.projMenu, self.tr("Empty Trash")) self.mainGui.projView.connectMenuActions( self.aRenameItem, self.aDeleteItem, self.aEmptyTrash @@ -182,7 +182,7 @@ class GuiMainMenu(QMenuBar): self.projMenu.addSeparator() # Project > Exit - self.aExitNW = self.projMenu.addAction(self.tr("Exit")) + self.aExitNW = qtAddAction(self.projMenu, self.tr("Exit")) self.aExitNW.setShortcut("Ctrl+Q") self.aExitNW.setMenuRole(QAction.MenuRole.QuitRole) self.aExitNW.triggered.connect(qtLambda(self.mainGui.closeMain)) @@ -193,21 +193,21 @@ class GuiMainMenu(QMenuBar): def _buildDocumentMenu(self) -> None: """Assemble the Document menu.""" # Document - self.docuMenu = self.addMenu(self.tr("&Document")) + self.docuMenu = qtAddMenu(self, self.tr("&Document")) # Document > Open - self.aOpenDoc = self.docuMenu.addAction(self.tr("Open Document")) + self.aOpenDoc = qtAddAction(self.docuMenu, self.tr("Open Document")) self.aOpenDoc.setShortcut("Ctrl+O") self.aOpenDoc.triggered.connect(self.mainGui.openSelectedItem) # Document > Save - self.aSaveDoc = self.docuMenu.addAction(self.tr("Save Document")) + self.aSaveDoc = qtAddAction(self.docuMenu, self.tr("Save Document")) self.aSaveDoc.setShortcut("Ctrl+S") self.aSaveDoc.triggered.connect(self.mainGui.forceSaveDocument) self.mainGui.addAction(self.aSaveDoc) # Document > Close - self.aCloseDoc = self.docuMenu.addAction(self.tr("Close Document")) + self.aCloseDoc = qtAddAction(self.docuMenu, self.tr("Close Document")) self.aCloseDoc.setShortcut("Ctrl+W") self.aCloseDoc.triggered.connect(self.mainGui.closeDocEditor) self.mainGui.addAction(self.aCloseDoc) @@ -216,12 +216,12 @@ class GuiMainMenu(QMenuBar): self.docuMenu.addSeparator() # Document > Preview - self.aViewDoc = self.docuMenu.addAction(self.tr("View Document")) + self.aViewDoc = qtAddAction(self.docuMenu, self.tr("View Document")) self.aViewDoc.setShortcut("Ctrl+R") self.aViewDoc.triggered.connect(qtLambda(self.mainGui.viewDocument, None)) # Document > Close Preview - self.aCloseView = self.docuMenu.addAction(self.tr("Close Document View")) + self.aCloseView = qtAddAction(self.docuMenu, self.tr("Close Document View")) self.aCloseView.setShortcut("Ctrl+Shift+R") self.aCloseView.triggered.connect(self.mainGui.closeDocViewer) @@ -229,11 +229,11 @@ class GuiMainMenu(QMenuBar): self.docuMenu.addSeparator() # Document > Show File Details - self.aFileDetails = self.docuMenu.addAction(self.tr("Show File Details")) + self.aFileDetails = qtAddAction(self.docuMenu, self.tr("Show File Details")) self.aFileDetails.triggered.connect(qtLambda(self.mainGui.docEditor.revealLocation)) # Document > Import From File - self.aImportFile = self.docuMenu.addAction(self.tr("Import Text from File")) + self.aImportFile = qtAddAction(self.docuMenu, self.tr("Import Text from File")) self.aImportFile.triggered.connect(qtLambda(self.mainGui.importDocument)) return @@ -241,10 +241,10 @@ class GuiMainMenu(QMenuBar): def _buildEditMenu(self) -> None: """Assemble the Edit menu.""" # Edit - self.editMenu = self.addMenu(self.tr("&Edit")) + self.editMenu = qtAddMenu(self, self.tr("&Edit")) # Edit > Undo - self.aEditUndo = self.editMenu.addAction(self.tr("Undo")) + self.aEditUndo = qtAddAction(self.editMenu, self.tr("Undo")) self.aEditUndo.setShortcut("Ctrl+Z") self.aEditUndo.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.UNDO) @@ -252,7 +252,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aEditUndo) # Edit > Redo - self.aEditRedo = self.editMenu.addAction(self.tr("Redo")) + self.aEditRedo = qtAddAction(self.editMenu, self.tr("Redo")) self.aEditRedo.setShortcut("Ctrl+Y") self.aEditRedo.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.REDO) @@ -263,7 +263,7 @@ class GuiMainMenu(QMenuBar): self.editMenu.addSeparator() # Edit > Cut - self.aEditCut = self.editMenu.addAction(self.tr("Cut")) + self.aEditCut = qtAddAction(self.editMenu, self.tr("Cut")) self.aEditCut.setShortcut("Ctrl+X") self.aEditCut.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.CUT) @@ -271,7 +271,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aEditCut) # Edit > Copy - self.aEditCopy = self.editMenu.addAction(self.tr("Copy")) + self.aEditCopy = qtAddAction(self.editMenu, self.tr("Copy")) self.aEditCopy.setShortcut("Ctrl+C") self.aEditCopy.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.COPY) @@ -279,7 +279,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aEditCopy) # Edit > Paste - self.aEditPaste = self.editMenu.addAction(self.tr("Paste")) + self.aEditPaste = qtAddAction(self.editMenu, self.tr("Paste")) self.aEditPaste.setShortcut("Ctrl+V") self.aEditPaste.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.PASTE) @@ -290,7 +290,7 @@ class GuiMainMenu(QMenuBar): self.editMenu.addSeparator() # Edit > Select All - self.aSelectAll = self.editMenu.addAction(self.tr("Select All")) + self.aSelectAll = qtAddAction(self.editMenu, self.tr("Select All")) self.aSelectAll.setShortcut("Ctrl+A") self.aSelectAll.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.SEL_ALL) @@ -298,7 +298,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aSelectAll) # Edit > Select Paragraph - self.aSelectPar = self.editMenu.addAction(self.tr("Select Paragraph")) + self.aSelectPar = qtAddAction(self.editMenu, self.tr("Select Paragraph")) self.aSelectPar.setShortcut("Ctrl+Shift+A") self.aSelectPar.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.SEL_PARA) @@ -310,24 +310,24 @@ class GuiMainMenu(QMenuBar): def _buildViewMenu(self) -> None: """Assemble the View menu.""" # View - self.viewMenu = self.addMenu(self.tr("&View")) + self.viewMenu = qtAddMenu(self, self.tr("&View")) # View > TreeView - self.aFocusTree = self.viewMenu.addAction(self.tr("Go to Tree View")) + self.aFocusTree = qtAddAction(self.viewMenu, self.tr("Go to Tree View")) self.aFocusTree.setShortcut("Ctrl+T") self.aFocusTree.triggered.connect( lambda: self.requestFocusChange.emit(nwFocus.TREE) ) # View > Document Editor - self.aFocusDocument = self.viewMenu.addAction(self.tr("Go to Document")) + self.aFocusDocument = qtAddAction(self.viewMenu, self.tr("Go to Document")) self.aFocusDocument.setShortcut("Ctrl+E") self.aFocusDocument.triggered.connect( lambda: self.requestFocusChange.emit(nwFocus.DOCUMENT) ) # View > Outline - self.aFocusOutline = self.viewMenu.addAction(self.tr("Go to Outline")) + self.aFocusOutline = qtAddAction(self.viewMenu, self.tr("Go to Outline")) self.aFocusOutline.setShortcut("Ctrl+Shift+T") self.aFocusOutline.triggered.connect( lambda: self.requestFocusChange.emit(nwFocus.OUTLINE) @@ -337,14 +337,14 @@ class GuiMainMenu(QMenuBar): self.viewMenu.addSeparator() # View > Go Backward - self.aViewPrev = self.viewMenu.addAction(self.tr("Navigate Backward")) + self.aViewPrev = qtAddAction(self.viewMenu, self.tr("Navigate Backward")) self.aViewPrev.setShortcut("Alt+Left") self.aViewPrev.setShortcutContext(Qt.ShortcutContext.WidgetShortcut) self.aViewPrev.triggered.connect(self.mainGui.docViewer.navBackward) self.mainGui.docViewer.addAction(self.aViewPrev) # View > Go Forward - self.aViewNext = self.viewMenu.addAction(self.tr("Navigate Forward")) + self.aViewNext = qtAddAction(self.viewMenu, self.tr("Navigate Forward")) self.aViewNext.setShortcut("Alt+Right") self.aViewNext.setShortcutContext(Qt.ShortcutContext.WidgetShortcut) self.aViewNext.triggered.connect(self.mainGui.docViewer.navForward) @@ -354,13 +354,13 @@ class GuiMainMenu(QMenuBar): self.viewMenu.addSeparator() # View > Focus Mode - self.aFocusMode = self.viewMenu.addAction(self.tr("Focus Mode")) + self.aFocusMode = qtAddAction(self.viewMenu, self.tr("Focus Mode")) self.aFocusMode.setShortcut("F8") self.aFocusMode.triggered.connect(self.mainGui.toggleFocusMode) self.mainGui.addAction(self.aFocusMode) # View > Toggle Full Screen - self.aFullScreen = self.viewMenu.addAction(self.tr("Full Screen Mode")) + self.aFullScreen = qtAddAction(self.viewMenu, self.tr("Full Screen Mode")) self.aFullScreen.setShortcut("F11") self.aFullScreen.triggered.connect(self.mainGui.toggleFullScreenMode) self.mainGui.addAction(self.aFullScreen) @@ -370,13 +370,13 @@ class GuiMainMenu(QMenuBar): def _buildInsertMenu(self) -> None: """Assemble the Insert menu.""" # Insert - self.insMenu = self.addMenu(self.tr("&Insert")) + self.insMenu = qtAddMenu(self, self.tr("&Insert")) # Insert > Dashes and Dots - self.mInsDashes = self.insMenu.addMenu(self.tr("Dashes")) + self.mInsDashes = qtAddMenu(self.insMenu, self.tr("Dashes")) # Insert > Short Dash - self.aInsENDash = self.mInsDashes.addAction(self.tr("Short Dash")) + self.aInsENDash = qtAddAction(self.mInsDashes, self.tr("Short Dash")) self.aInsENDash.setShortcut("Ctrl+K, -") self.aInsENDash.triggered.connect( lambda: self.requestDocInsertText.emit(nwUnicode.U_ENDASH) @@ -384,7 +384,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aInsENDash) # Insert > Long Dash - self.aInsEMDash = self.mInsDashes.addAction(self.tr("Long Dash")) + self.aInsEMDash = qtAddAction(self.mInsDashes, self.tr("Long Dash")) self.aInsEMDash.setShortcut("Ctrl+K, _") self.aInsEMDash.triggered.connect( lambda: self.requestDocInsertText.emit(nwUnicode.U_EMDASH) @@ -392,7 +392,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aInsEMDash) # Insert > Long Dash - self.aInsHorBar = self.mInsDashes.addAction(self.tr("Horizontal Bar")) + self.aInsHorBar = qtAddAction(self.mInsDashes, self.tr("Horizontal Bar")) self.aInsHorBar.setShortcut("Ctrl+K, Ctrl+_") self.aInsHorBar.triggered.connect( lambda: self.requestDocInsertText.emit(nwUnicode.U_HBAR) @@ -400,7 +400,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aInsHorBar) # Insert > Figure Dash - self.aInsFigDash = self.mInsDashes.addAction(self.tr("Figure Dash")) + self.aInsFigDash = qtAddAction(self.mInsDashes, self.tr("Figure Dash")) self.aInsFigDash.setShortcut("Ctrl+K, ~") self.aInsFigDash.triggered.connect( lambda: self.requestDocInsertText.emit(nwUnicode.U_FGDASH) @@ -408,10 +408,10 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aInsFigDash) # Insert > Quote Marks - self.mInsQuotes = self.insMenu.addMenu(self.tr("Quote Marks")) + self.mInsQuotes = qtAddMenu(self.insMenu, self.tr("Quote Marks")) # Insert > Left Single Quote - self.aInsQuoteLS = self.mInsQuotes.addAction(self.tr("Left Single Quote")) + self.aInsQuoteLS = qtAddAction(self.mInsQuotes, self.tr("Left Single Quote")) self.aInsQuoteLS.setShortcut("Ctrl+K, 1") self.aInsQuoteLS.triggered.connect( lambda: self.requestDocInsert.emit(nwDocInsert.QUOTE_LS) @@ -419,7 +419,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aInsQuoteLS) # Insert > Right Single Quote - self.aInsQuoteRS = self.mInsQuotes.addAction(self.tr("Right Single Quote")) + self.aInsQuoteRS = qtAddAction(self.mInsQuotes, self.tr("Right Single Quote")) self.aInsQuoteRS.setShortcut("Ctrl+K, 2") self.aInsQuoteRS.triggered.connect( lambda: self.requestDocInsert.emit(nwDocInsert.QUOTE_RS) @@ -427,7 +427,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aInsQuoteRS) # Insert > Left Double Quote - self.aInsQuoteLD = self.mInsQuotes.addAction(self.tr("Left Double Quote")) + self.aInsQuoteLD = qtAddAction(self.mInsQuotes, self.tr("Left Double Quote")) self.aInsQuoteLD.setShortcut("Ctrl+K, 3") self.aInsQuoteLD.triggered.connect( lambda: self.requestDocInsert.emit(nwDocInsert.QUOTE_LD) @@ -435,7 +435,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aInsQuoteLD) # Insert > Right Double Quote - self.aInsQuoteRD = self.mInsQuotes.addAction(self.tr("Right Double Quote")) + self.aInsQuoteRD = qtAddAction(self.mInsQuotes, self.tr("Right Double Quote")) self.aInsQuoteRD.setShortcut("Ctrl+K, 4") self.aInsQuoteRD.triggered.connect( lambda: self.requestDocInsert.emit(nwDocInsert.QUOTE_RD) @@ -443,7 +443,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aInsQuoteRD) # Insert > Alternative Apostrophe - self.aInsMSApos = self.mInsQuotes.addAction(self.tr("Alternative Apostrophe")) + self.aInsMSApos = qtAddAction(self.mInsQuotes, self.tr("Alternative Apostrophe")) self.aInsMSApos.setShortcut("Ctrl+K, '") self.aInsMSApos.triggered.connect( lambda: self.requestDocInsertText.emit(nwUnicode.U_MAPOS) @@ -451,10 +451,10 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aInsMSApos) # Insert > Symbols - self.mInsPunct = self.insMenu.addMenu(self.tr("General Punctuation")) + self.mInsPunct = qtAddMenu(self.insMenu, self.tr("General Punctuation")) # Insert > Ellipsis - self.aInsEllipsis = self.mInsPunct.addAction(self.tr("Ellipsis")) + self.aInsEllipsis = qtAddAction(self.mInsPunct, self.tr("Ellipsis")) self.aInsEllipsis.setShortcut("Ctrl+K, .") self.aInsEllipsis.triggered.connect( lambda: self.requestDocInsertText.emit(nwUnicode.U_HELLIP) @@ -462,7 +462,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aInsEllipsis) # Insert > Prime - self.aInsPrime = self.mInsPunct.addAction(self.tr("Prime")) + self.aInsPrime = qtAddAction(self.mInsPunct, self.tr("Prime")) self.aInsPrime.setShortcut("Ctrl+K, Ctrl+'") self.aInsPrime.triggered.connect( lambda: self.requestDocInsertText.emit(nwUnicode.U_PRIME) @@ -470,7 +470,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aInsPrime) # Insert > Double Prime - self.aInsDPrime = self.mInsPunct.addAction(self.tr("Double Prime")) + self.aInsDPrime = qtAddAction(self.mInsPunct, self.tr("Double Prime")) self.aInsDPrime.setShortcut("Ctrl+K, Ctrl+\"") self.aInsDPrime.triggered.connect( lambda: self.requestDocInsertText.emit(nwUnicode.U_DPRIME) @@ -478,10 +478,10 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aInsDPrime) # Insert > White Spaces - self.mInsSpace = self.insMenu.addMenu(self.tr("White Spaces")) + self.mInsSpace = qtAddMenu(self.insMenu, self.tr("White Spaces")) # Insert > Non-Breaking Space - self.aInsNBSpace = self.mInsSpace.addAction(self.tr("Non-Breaking Space")) + self.aInsNBSpace = qtAddAction(self.mInsSpace, self.tr("Non-Breaking Space")) self.aInsNBSpace.setShortcut("Ctrl+K, Space") self.aInsNBSpace.triggered.connect( lambda: self.requestDocInsertText.emit(nwUnicode.U_NBSP) @@ -489,7 +489,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aInsNBSpace) # Insert > Thin Space - self.aInsThinSpace = self.mInsSpace.addAction(self.tr("Thin Space")) + self.aInsThinSpace = qtAddAction(self.mInsSpace, self.tr("Thin Space")) self.aInsThinSpace.setShortcut("Ctrl+K, Shift+Space") self.aInsThinSpace.triggered.connect( lambda: self.requestDocInsertText.emit(nwUnicode.U_THSP) @@ -497,7 +497,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aInsThinSpace) # Insert > Thin Non-Breaking Space - self.aInsThinNBSpace = self.mInsSpace.addAction(self.tr("Thin Non-Breaking Space")) + self.aInsThinNBSpace = qtAddAction(self.mInsSpace, self.tr("Thin Non-Breaking Space")) self.aInsThinNBSpace.setShortcut("Ctrl+K, Ctrl+Space") self.aInsThinNBSpace.triggered.connect( lambda: self.requestDocInsertText.emit(nwUnicode.U_THNBSP) @@ -505,10 +505,10 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aInsThinNBSpace) # Insert > Symbols - self.mInsSymbol = self.insMenu.addMenu(self.tr("Other Symbols")) + self.mInsSymbol = qtAddMenu(self.insMenu, self.tr("Other Symbols")) # Insert > List Bullet - self.aInsBullet = self.mInsSymbol.addAction(self.tr("List Bullet")) + self.aInsBullet = qtAddAction(self.mInsSymbol, self.tr("List Bullet")) self.aInsBullet.setShortcut("Ctrl+K, *") self.aInsBullet.triggered.connect( lambda: self.requestDocInsertText.emit(nwUnicode.U_BULL) @@ -516,7 +516,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aInsBullet) # Insert > Hyphen Bullet - self.aInsHyBull = self.mInsSymbol.addAction(self.tr("Hyphen Bullet")) + self.aInsHyBull = qtAddAction(self.mInsSymbol, self.tr("Hyphen Bullet")) self.aInsHyBull.setShortcut("Ctrl+K, Ctrl+-") self.aInsHyBull.triggered.connect( lambda: self.requestDocInsertText.emit(nwUnicode.U_HYBULL) @@ -524,7 +524,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aInsHyBull) # Insert > Flower Mark - self.aInsFlower = self.mInsSymbol.addAction(self.tr("Flower Mark")) + self.aInsFlower = qtAddAction(self.mInsSymbol, self.tr("Flower Mark")) self.aInsFlower.setShortcut("Ctrl+K, Ctrl+*") self.aInsFlower.triggered.connect( lambda: self.requestDocInsertText.emit(nwUnicode.U_FLOWER) @@ -532,7 +532,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aInsFlower) # Insert > Per Mille - self.aInsPerMille = self.mInsSymbol.addAction(self.tr("Per Mille")) + self.aInsPerMille = qtAddAction(self.mInsSymbol, self.tr("Per Mille")) self.aInsPerMille.setShortcut("Ctrl+K, %") self.aInsPerMille.triggered.connect( lambda: self.requestDocInsertText.emit(nwUnicode.U_PERMIL) @@ -540,7 +540,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aInsPerMille) # Insert > Degree Symbol - self.aInsDegree = self.mInsSymbol.addAction(self.tr("Degree Symbol")) + self.aInsDegree = qtAddAction(self.mInsSymbol, self.tr("Degree Symbol")) self.aInsDegree.setShortcut("Ctrl+K, Ctrl+O") self.aInsDegree.triggered.connect( lambda: self.requestDocInsertText.emit(nwUnicode.U_DEGREE) @@ -548,7 +548,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aInsDegree) # Insert > Minus Sign - self.aInsMinus = self.mInsSymbol.addAction(self.tr("Minus Sign")) + self.aInsMinus = qtAddAction(self.mInsSymbol, self.tr("Minus Sign")) self.aInsMinus.setShortcut("Ctrl+K, Ctrl+M") self.aInsMinus.triggered.connect( lambda: self.requestDocInsertText.emit(nwUnicode.U_MINUS) @@ -556,7 +556,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aInsMinus) # Insert > Times Sign - self.aInsTimes = self.mInsSymbol.addAction(self.tr("Times Sign")) + self.aInsTimes = qtAddAction(self.mInsSymbol, self.tr("Times Sign")) self.aInsTimes.setShortcut("Ctrl+K, Ctrl+X") self.aInsTimes.triggered.connect( lambda: self.requestDocInsertText.emit(nwUnicode.U_TIMES) @@ -564,7 +564,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aInsTimes) # Insert > Division - self.aInsDivide = self.mInsSymbol.addAction(self.tr("Division Sign")) + self.aInsDivide = qtAddAction(self.mInsSymbol, self.tr("Division Sign")) self.aInsDivide.setShortcut("Ctrl+K, Ctrl+D") self.aInsDivide.triggered.connect( lambda: self.requestDocInsertText.emit(nwUnicode.U_DIVIDE) @@ -572,18 +572,18 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aInsDivide) # Insert > Tags and References - self.mInsKeywords = self.insMenu.addMenu(self.tr("Tags and References")) + self.mInsKeywords = qtAddMenu(self.insMenu, self.tr("Tags and References")) for key in nwKeyWords.ALL_KEYS: - action = self.mInsKeywords.addAction(trConst(nwLabels.KEY_NAME[key])) + action = qtAddAction(self.mInsKeywords, trConst(nwLabels.KEY_NAME[key])) action.setShortcut(nwLabels.KEY_SHORTCUT[key]) action.triggered.connect(qtLambda(self.requestDocKeyWordInsert.emit, key)) self.mainGui.addAction(action) # Insert > Special Comments - self.mInsComments = self.insMenu.addMenu(self.tr("Special Comments")) + self.mInsComments = qtAddMenu(self.insMenu, self.tr("Special Comments")) # Insert > Synopsis Comment - self.aInsSynopsis = self.mInsComments.addAction(self.tr("Synopsis Comment")) + self.aInsSynopsis = qtAddAction(self.mInsComments, self.tr("Synopsis Comment")) self.aInsSynopsis.setShortcut("Ctrl+K, S") self.aInsSynopsis.triggered.connect( lambda: self.requestDocInsert.emit(nwDocInsert.SYNOPSIS) @@ -591,7 +591,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aInsSynopsis) # Insert > Short Description Comment - self.aInsShort = self.mInsComments.addAction(self.tr("Short Description Comment")) + self.aInsShort = qtAddAction(self.mInsComments, self.tr("Short Description Comment")) self.aInsShort.setShortcut("Ctrl+K, H") self.aInsShort.triggered.connect( lambda: self.requestDocInsert.emit(nwDocInsert.SHORT) @@ -599,47 +599,47 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aInsShort) # Insert > Word/Character Count - self.mInsField = self.insMenu.addMenu(self.tr("Word/Character Count")) + self.mInsField = qtAddMenu(self.insMenu, self.tr("Word/Character Count")) for field in nwStats.ALL_FIELDS: value = nwShortcode.FIELD_VALUE.format(field) - action = self.mInsField.addAction(trConst(nwLabels.STATS_NAME[field])) + action = qtAddAction(self.mInsField, trConst(nwLabels.STATS_NAME[field])) action.triggered.connect(qtLambda(self.requestDocInsertText.emit, value)) # Insert > Breaks and Vertical Space - self.mInsBreaks = self.insMenu.addMenu(self.tr("Breaks and Vertical Space")) + self.mInsBreaks = qtAddMenu(self.insMenu, self.tr("Breaks and Vertical Space")) # Insert > New Page - self.aInsNewPage = self.mInsBreaks.addAction(self.tr("Page Break")) + self.aInsNewPage = qtAddAction(self.mInsBreaks, self.tr("Page Break")) self.aInsNewPage.triggered.connect( lambda: self.requestDocInsert.emit(nwDocInsert.NEW_PAGE) ) # Insert > Forced Line Break - self.aInsLineBreak = self.mInsBreaks.addAction(self.tr("Forced Line Break")) + self.aInsLineBreak = qtAddAction(self.mInsBreaks, self.tr("Forced Line Break")) self.aInsLineBreak.triggered.connect( lambda: self.requestDocInsert.emit(nwDocInsert.LINE_BRK) ) # Insert > Vertical Space (Single) - self.aInsVSpaceS = self.mInsBreaks.addAction(self.tr("Vertical Space (Single)")) + self.aInsVSpaceS = qtAddAction(self.mInsBreaks, self.tr("Vertical Space (Single)")) self.aInsVSpaceS.triggered.connect( lambda: self.requestDocInsert.emit(nwDocInsert.VSPACE_S) ) # Insert > Vertical Space (Multi) - self.aInsVSpaceM = self.mInsBreaks.addAction(self.tr("Vertical Space (Multi)")) + self.aInsVSpaceM = qtAddAction(self.mInsBreaks, self.tr("Vertical Space (Multi)")) self.aInsVSpaceM.triggered.connect( lambda: self.requestDocInsert.emit(nwDocInsert.VSPACE_M) ) # Insert > Placeholder Text - self.aLipsumText = self.insMenu.addAction(self.tr("Placeholder Text")) + self.aLipsumText = qtAddAction(self.insMenu, self.tr("Placeholder Text")) self.aLipsumText.triggered.connect( lambda: self.requestDocInsert.emit(nwDocInsert.LIPSUM) ) # Insert > Footnote - self.aFootnote = self.insMenu.addAction(self.tr("Footnote")) + self.aFootnote = qtAddAction(self.insMenu, self.tr("Footnote")) self.aFootnote.triggered.connect( lambda: self.requestDocInsert.emit(nwDocInsert.FOOTNOTE) ) @@ -649,10 +649,10 @@ class GuiMainMenu(QMenuBar): def _buildFormatMenu(self) -> None: """Assemble the Format menu.""" # Format - self.fmtMenu = self.addMenu(self.tr("&Format")) + self.fmtMenu = qtAddMenu(self, self.tr("&Format")) # Format > Bold - self.aFmtBold = self.fmtMenu.addAction(self.tr("Bold")) + self.aFmtBold = qtAddAction(self.fmtMenu, self.tr("Bold")) self.aFmtBold.setShortcut("Ctrl+B") self.aFmtBold.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.MD_BOLD) @@ -660,7 +660,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aFmtBold) # Format > Italic - self.aFmtItalic = self.fmtMenu.addAction(self.tr("Italic")) + self.aFmtItalic = qtAddAction(self.fmtMenu, self.tr("Italic")) self.aFmtItalic.setShortcut("Ctrl+I") self.aFmtItalic.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.MD_ITALIC) @@ -668,7 +668,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aFmtItalic) # Format > Strikethrough - self.aFmtStrike = self.fmtMenu.addAction(self.tr("Strikethrough")) + self.aFmtStrike = qtAddAction(self.fmtMenu, self.tr("Strikethrough")) self.aFmtStrike.setShortcut("Ctrl+D") self.aFmtStrike.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.MD_STRIKE) @@ -679,7 +679,7 @@ class GuiMainMenu(QMenuBar): self.fmtMenu.addSeparator() # Format > Double Quotes - self.aFmtDQuote = self.fmtMenu.addAction(self.tr("Wrap Double Quotes")) + self.aFmtDQuote = qtAddAction(self.fmtMenu, self.tr("Wrap Double Quotes")) self.aFmtDQuote.setShortcut("Ctrl+\"") self.aFmtDQuote.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.D_QUOTE) @@ -687,7 +687,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aFmtDQuote) # Format > Single Quotes - self.aFmtSQuote = self.fmtMenu.addAction(self.tr("Wrap Single Quotes")) + self.aFmtSQuote = qtAddAction(self.fmtMenu, self.tr("Wrap Single Quotes")) self.aFmtSQuote.setShortcut("Ctrl+'") self.aFmtSQuote.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.S_QUOTE) @@ -698,46 +698,46 @@ class GuiMainMenu(QMenuBar): self.fmtMenu.addSeparator() # Shortcodes - self.mShortcodes = self.fmtMenu.addMenu(self.tr("More Formats ...")) + self.mShortcodes = qtAddMenu(self.fmtMenu, self.tr("More Formats ...")) # Shortcode Bold - self.aScBold = self.mShortcodes.addAction(self.tr("Bold (Shortcode)")) + self.aScBold = qtAddAction(self.mShortcodes, self.tr("Bold (Shortcode)")) self.aScBold.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.SC_BOLD) ) # Shortcode Italic - self.aScItalic = self.mShortcodes.addAction(self.tr("Italics (Shortcode)")) + self.aScItalic = qtAddAction(self.mShortcodes, self.tr("Italics (Shortcode)")) self.aScItalic.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.SC_ITALIC) ) # Shortcode Strikethrough - self.aScStrike = self.mShortcodes.addAction(self.tr("Strikethrough (Shortcode)")) + self.aScStrike = qtAddAction(self.mShortcodes, self.tr("Strikethrough (Shortcode)")) self.aScStrike.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.SC_STRIKE) ) # Shortcode Underline - self.aScULine = self.mShortcodes.addAction(self.tr("Underline")) + self.aScULine = qtAddAction(self.mShortcodes, self.tr("Underline")) self.aScULine.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.SC_ULINE) ) # Shortcode Mark - self.aScMark = self.mShortcodes.addAction(self.tr("Highlight")) + self.aScMark = qtAddAction(self.mShortcodes, self.tr("Highlight")) self.aScMark.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.SC_MARK) ) # Shortcode Superscript - self.aScSuper = self.mShortcodes.addAction(self.tr("Superscript")) + self.aScSuper = qtAddAction(self.mShortcodes, self.tr("Superscript")) self.aScSuper.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.SC_SUP) ) # Shortcode Subscript - self.aScSub = self.mShortcodes.addAction(self.tr("Subscript")) + self.aScSub = qtAddAction(self.mShortcodes, self.tr("Subscript")) self.aScSub.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.SC_SUB) ) @@ -746,7 +746,7 @@ class GuiMainMenu(QMenuBar): self.fmtMenu.addSeparator() # Format > Heading 1 (Partition) - self.aFmtHead1 = self.fmtMenu.addAction(trConst(nwStyles.T_LABEL["H1"])) + self.aFmtHead1 = qtAddAction(self.fmtMenu, trConst(nwStyles.T_LABEL["H1"])) self.aFmtHead1.setShortcut("Ctrl+1") self.aFmtHead1.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.BLOCK_H1) @@ -754,7 +754,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aFmtHead1) # Format > Heading 2 (Chapter) - self.aFmtHead2 = self.fmtMenu.addAction(trConst(nwStyles.T_LABEL["H2"])) + self.aFmtHead2 = qtAddAction(self.fmtMenu, trConst(nwStyles.T_LABEL["H2"])) self.aFmtHead2.setShortcut("Ctrl+2") self.aFmtHead2.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.BLOCK_H2) @@ -762,7 +762,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aFmtHead2) # Format > Heading 3 (Scene) - self.aFmtHead3 = self.fmtMenu.addAction(trConst(nwStyles.T_LABEL["H3"])) + self.aFmtHead3 = qtAddAction(self.fmtMenu, trConst(nwStyles.T_LABEL["H3"])) self.aFmtHead3.setShortcut("Ctrl+3") self.aFmtHead3.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.BLOCK_H3) @@ -770,7 +770,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aFmtHead3) # Format > Heading 4 (Section) - self.aFmtHead4 = self.fmtMenu.addAction(trConst(nwStyles.T_LABEL["H4"])) + self.aFmtHead4 = qtAddAction(self.fmtMenu, trConst(nwStyles.T_LABEL["H4"])) self.aFmtHead4.setShortcut("Ctrl+4") self.aFmtHead4.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.BLOCK_H4) @@ -781,19 +781,19 @@ class GuiMainMenu(QMenuBar): self.fmtMenu.addSeparator() # Format > Novel Title - self.aFmtTitle = self.fmtMenu.addAction(self.tr("Novel Title")) + self.aFmtTitle = qtAddAction(self.fmtMenu, self.tr("Novel Title")) self.aFmtTitle.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.BLOCK_TTL) ) # Format > Unnumbered Chapter - self.aFmtUnNum = self.fmtMenu.addAction(self.tr("Unnumbered Chapter")) + self.aFmtUnNum = qtAddAction(self.fmtMenu, self.tr("Unnumbered Chapter")) self.aFmtUnNum.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.BLOCK_UNN) ) # Format > Alternative Scene - self.aFmtHardSc = self.fmtMenu.addAction(self.tr("Alternative Scene")) + self.aFmtHardSc = qtAddAction(self.fmtMenu, self.tr("Alternative Scene")) self.aFmtHardSc.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.BLOCK_HSC) ) @@ -802,7 +802,7 @@ class GuiMainMenu(QMenuBar): self.fmtMenu.addSeparator() # Format > Align Left - self.aFmtAlignLeft = self.fmtMenu.addAction(self.tr("Align Left")) + self.aFmtAlignLeft = qtAddAction(self.fmtMenu, self.tr("Align Left")) self.aFmtAlignLeft.setShortcut("Ctrl+5") self.aFmtAlignLeft.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.ALIGN_L) @@ -810,7 +810,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aFmtAlignLeft) # Format > Align Centre - self.aFmtAlignCentre = self.fmtMenu.addAction(self.tr("Align Centre")) + self.aFmtAlignCentre = qtAddAction(self.fmtMenu, self.tr("Align Centre")) self.aFmtAlignCentre.setShortcut("Ctrl+6") self.aFmtAlignCentre.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.ALIGN_C) @@ -818,7 +818,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aFmtAlignCentre) # Format > Align Right - self.aFmtAlignRight = self.fmtMenu.addAction(self.tr("Align Right")) + self.aFmtAlignRight = qtAddAction(self.fmtMenu, self.tr("Align Right")) self.aFmtAlignRight.setShortcut("Ctrl+7") self.aFmtAlignRight.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.ALIGN_R) @@ -829,7 +829,7 @@ class GuiMainMenu(QMenuBar): self.fmtMenu.addSeparator() # Format > Indent Left - self.aFmtIndentLeft = self.fmtMenu.addAction(self.tr("Indent Left")) + self.aFmtIndentLeft = qtAddAction(self.fmtMenu, self.tr("Indent Left")) self.aFmtIndentLeft.setShortcut("Ctrl+8") self.aFmtIndentLeft.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.INDENT_L) @@ -837,7 +837,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aFmtIndentLeft) # Format > Indent Right - self.aFmtIndentRight = self.fmtMenu.addAction(self.tr("Indent Right")) + self.aFmtIndentRight = qtAddAction(self.fmtMenu, self.tr("Indent Right")) self.aFmtIndentRight.setShortcut("Ctrl+9") self.aFmtIndentRight.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.INDENT_R) @@ -848,7 +848,7 @@ class GuiMainMenu(QMenuBar): self.fmtMenu.addSeparator() # Format > Comment - self.aFmtComment = self.fmtMenu.addAction(self.tr("Toggle Comment")) + self.aFmtComment = qtAddAction(self.fmtMenu, self.tr("Toggle Comment")) self.aFmtComment.setShortcut("Ctrl+/") self.aFmtComment.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.BLOCK_COM) @@ -856,14 +856,14 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aFmtComment) # Format > Ignore Text - self.aFmtIgnore = self.fmtMenu.addAction(self.tr("Toggle Ignore Text")) + self.aFmtIgnore = qtAddAction(self.fmtMenu, self.tr("Toggle Ignore Text")) self.aFmtIgnore.setShortcut("Ctrl+Shift+D") self.aFmtIgnore.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.BLOCK_IGN) ) # Format > Remove Block Format - self.aFmtNoFormat = self.fmtMenu.addAction(self.tr("Remove Block Format")) + self.aFmtNoFormat = qtAddAction(self.fmtMenu, self.tr("Remove Block Format")) self.aFmtNoFormat.setShortcuts(["Ctrl+0", "Ctrl+Shift+/"]) self.aFmtNoFormat.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.BLOCK_TXT) @@ -874,19 +874,19 @@ class GuiMainMenu(QMenuBar): self.fmtMenu.addSeparator() # Format > Replace Straight Single Quotes - self.aFmtReplSng = self.fmtMenu.addAction(self.tr("Replace Straight Single Quotes")) + self.aFmtReplSng = qtAddAction(self.fmtMenu, self.tr("Replace Straight Single Quotes")) self.aFmtReplSng.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.REPL_SNG) ) # Format > Replace Straight Double Quotes - self.aFmtReplDbl = self.fmtMenu.addAction(self.tr("Replace Straight Double Quotes")) + self.aFmtReplDbl = qtAddAction(self.fmtMenu, self.tr("Replace Straight Double Quotes")) self.aFmtReplDbl.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.REPL_DBL) ) # Format > Remove In-Paragraph Breaks - self.aFmtRmBreaks = self.fmtMenu.addAction(self.tr("Remove In-Paragraph Breaks")) + self.aFmtRmBreaks = qtAddAction(self.fmtMenu, self.tr("Remove In-Paragraph Breaks")) self.aFmtRmBreaks.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.RM_BREAKS) ) @@ -896,28 +896,28 @@ class GuiMainMenu(QMenuBar): def _buildSearchMenu(self) -> None: """Assemble the Search menu.""" # Search - self.srcMenu = self.addMenu(self.tr("&Search")) + self.srcMenu = qtAddMenu(self, self.tr("&Search")) # Search > Find - self.aFind = self.srcMenu.addAction(self.tr("Find")) + self.aFind = qtAddAction(self.srcMenu, self.tr("Find")) self.aFind.setShortcut("Ctrl+F") self.aFind.triggered.connect(qtLambda(self.mainGui.docEditor.beginSearch)) self.mainGui.addAction(self.aFind) # Search > Replace - self.aReplace = self.srcMenu.addAction(self.tr("Replace")) + self.aReplace = qtAddAction(self.srcMenu, self.tr("Replace")) self.aReplace.setShortcut("Ctrl+=" if CONFIG.osDarwin else "Ctrl+H") self.aReplace.triggered.connect(qtLambda(self.mainGui.docEditor.beginReplace)) self.mainGui.addAction(self.aReplace) # Search > Find Next - self.aFindNext = self.srcMenu.addAction(self.tr("Find Next")) + self.aFindNext = qtAddAction(self.srcMenu, self.tr("Find Next")) self.aFindNext.setShortcuts(["Ctrl+G", "F3"] if CONFIG.osDarwin else ["F3", "Ctrl+G"]) self.aFindNext.triggered.connect(qtLambda(self.mainGui.docEditor.findNext)) self.mainGui.addAction(self.aFindNext) # Search > Find Prev - self.aFindPrev = self.srcMenu.addAction(self.tr("Find Previous")) + self.aFindPrev = qtAddAction(self.srcMenu, self.tr("Find Previous")) self.aFindPrev.setShortcuts( ["Ctrl+Shift+G", "Shift+F3"] if CONFIG.osDarwin else ["Shift+F3", "Ctrl+Shift+G"] ) @@ -925,7 +925,7 @@ class GuiMainMenu(QMenuBar): self.mainGui.addAction(self.aFindPrev) # Search > Replace Next - self.aReplaceNext = self.srcMenu.addAction(self.tr("Replace Next")) + self.aReplaceNext = qtAddAction(self.srcMenu, self.tr("Replace Next")) self.aReplaceNext.setShortcut("Ctrl+Shift+1") self.aReplaceNext.triggered.connect(qtLambda(self.mainGui.docEditor.replaceNext)) self.mainGui.addAction(self.aReplaceNext) @@ -934,7 +934,7 @@ class GuiMainMenu(QMenuBar): self.srcMenu.addSeparator() # Search > Find in Project - self.aFindProj = self.srcMenu.addAction(self.tr("Find in Project")) + self.aFindProj = qtAddAction(self.srcMenu, self.tr("Find in Project")) self.aFindProj.setShortcut("Ctrl+Shift+F") self.aFindProj.triggered.connect(lambda: self.requestViewChange.emit(nwView.SEARCH)) @@ -943,17 +943,17 @@ class GuiMainMenu(QMenuBar): def _buildToolsMenu(self) -> None: """Assemble the Tools menu.""" # Tools - self.toolsMenu = self.addMenu(self.tr("&Tools")) + self.toolsMenu = qtAddMenu(self, self.tr("&Tools")) # Tools > Check Spelling - self.aSpellCheck = self.toolsMenu.addAction(self.tr("Check Spelling")) + self.aSpellCheck = qtAddAction(self.toolsMenu, self.tr("Check Spelling")) self.aSpellCheck.setCheckable(True) self.aSpellCheck.setChecked(SHARED.project.data.spellCheck) self.aSpellCheck.triggered.connect(self._toggleSpellCheck) # triggered, not toggled! self.aSpellCheck.setShortcut("Ctrl+F7") self.mainGui.addAction(self.aSpellCheck) - self.mSelectLanguage = self.toolsMenu.addMenu(self.tr("Spell Check Language")) + self.mSelectLanguage = qtAddMenu(self.toolsMenu, self.tr("Spell Check Language")) languages = SHARED.spelling.listDictionaries() languages.insert(0, ("None", self.tr("Default"))) for n, (tag, language) in enumerate(languages): @@ -963,25 +963,25 @@ class GuiMainMenu(QMenuBar): self.mSelectLanguage.addAction(aSpell) # Tools > Re-Run Spell Check - self.aReRunSpell = self.toolsMenu.addAction(self.tr("Re-Run Spell Check")) + self.aReRunSpell = qtAddAction(self.toolsMenu, self.tr("Re-Run Spell Check")) self.aReRunSpell.setShortcut("F7") self.aReRunSpell.triggered.connect(qtLambda(self.mainGui.docEditor.spellCheckDocument)) self.mainGui.addAction(self.aReRunSpell) # Tools > Project Word List - self.aEditWordList = self.toolsMenu.addAction(self.tr("Project Word List")) + self.aEditWordList = qtAddAction(self.toolsMenu, self.tr("Project Word List")) self.aEditWordList.triggered.connect(self.mainGui.showProjectWordListDialog) # Tools > Add Dictionaries if CONFIG.osWindows or CONFIG.isDebug: - self.aAddDicts = self.toolsMenu.addAction(self.tr("Add Dictionaries")) + self.aAddDicts = qtAddAction(self.toolsMenu, self.tr("Add Dictionaries")) self.aAddDicts.triggered.connect(self.mainGui.showDictionariesDialog) # Tools > Separator self.toolsMenu.addSeparator() # Tools > Rebuild Index - self.aRebuildIndex = self.toolsMenu.addAction(self.tr("Rebuild Index")) + self.aRebuildIndex = qtAddAction(self.toolsMenu, self.tr("Rebuild Index")) self.aRebuildIndex.setShortcut("F9") self.aRebuildIndex.triggered.connect(qtLambda(self.mainGui.rebuildIndex)) @@ -989,21 +989,21 @@ class GuiMainMenu(QMenuBar): self.toolsMenu.addSeparator() # Tools > Backup Project - self.aBackupProject = self.toolsMenu.addAction(self.tr("Backup Project")) + self.aBackupProject = qtAddAction(self.toolsMenu, self.tr("Backup Project")) self.aBackupProject.triggered.connect(qtLambda(SHARED.project.backupProject, True)) # Tools > Build Manuscript - self.aBuildManuscript = self.toolsMenu.addAction(self.tr("Build Manuscript")) + self.aBuildManuscript = qtAddAction(self.toolsMenu, self.tr("Build Manuscript")) self.aBuildManuscript.setShortcut("F5") self.aBuildManuscript.triggered.connect(self.mainGui.showBuildManuscriptDialog) # Tools > Writing Statistics - self.aWritingStats = self.toolsMenu.addAction(self.tr("Writing Statistics")) + self.aWritingStats = qtAddAction(self.toolsMenu, self.tr("Writing Statistics")) self.aWritingStats.setShortcut("F6") self.aWritingStats.triggered.connect(self.mainGui.showWritingStatsDialog) # Tools > Preferences - self.aPreferences = self.toolsMenu.addAction(self.tr("Preferences")) + self.aPreferences = qtAddAction(self.toolsMenu, self.tr("Preferences")) self.aPreferences.setShortcut("Ctrl+,") self.aPreferences.setMenuRole(QAction.MenuRole.PreferencesRole) self.aPreferences.triggered.connect(self.mainGui.showPreferencesDialog) @@ -1014,15 +1014,15 @@ class GuiMainMenu(QMenuBar): def _buildHelpMenu(self) -> None: """Assemble the Help menu.""" # Help - self.helpMenu = self.addMenu(self.tr("&Help")) + self.helpMenu = qtAddMenu(self, self.tr("&Help")) # Help > About - self.aAboutNW = self.helpMenu.addAction(self.tr("About novelWriter")) + self.aAboutNW = qtAddAction(self.helpMenu, self.tr("About novelWriter")) self.aAboutNW.setMenuRole(QAction.MenuRole.AboutRole) self.aAboutNW.triggered.connect(self.mainGui.showAboutNWDialog) # Help > About Qt - self.aAboutQt = self.helpMenu.addAction(self.tr("About Qt")) + self.aAboutQt = qtAddAction(self.helpMenu, self.tr("About Qt")) self.aAboutQt.setMenuRole(QAction.MenuRole.AboutQtRole) self.aAboutQt.triggered.connect(self.mainGui.showAboutQtDialog) @@ -1030,14 +1030,14 @@ class GuiMainMenu(QMenuBar): self.helpMenu.addSeparator() # Help > User Manual (Online) - self.aHelpDocs = self.helpMenu.addAction(self.tr("User Manual (Online)")) + self.aHelpDocs = qtAddAction(self.helpMenu, self.tr("User Manual (Online)")) self.aHelpDocs.setShortcut("F1") self.aHelpDocs.triggered.connect(qtLambda(SHARED.openWebsite, nwConst.URL_DOCS)) self.mainGui.addAction(self.aHelpDocs) # Help > User Manual (PDF) if isinstance(CONFIG.pdfDocs, Path): - self.aPdfDocs = self.helpMenu.addAction(self.tr("User Manual (PDF)")) + self.aPdfDocs = qtAddAction(self.helpMenu, self.tr("User Manual (PDF)")) self.aPdfDocs.setShortcut("Shift+F1") self.aPdfDocs.triggered.connect(self._openUserManualFile) self.mainGui.addAction(self.aPdfDocs) @@ -1046,15 +1046,15 @@ class GuiMainMenu(QMenuBar): self.helpMenu.addSeparator() # Document > Report an Issue - self.aIssue = self.helpMenu.addAction(self.tr("Report an Issue (GitHub)")) + self.aIssue = qtAddAction(self.helpMenu, self.tr("Report an Issue (GitHub)")) self.aIssue.triggered.connect(qtLambda(SHARED.openWebsite, nwConst.URL_REPORT)) # Document > Ask a Question - self.aQuestion = self.helpMenu.addAction(self.tr("Ask a Question (GitHub)")) + self.aQuestion = qtAddAction(self.helpMenu, self.tr("Ask a Question (GitHub)")) self.aQuestion.triggered.connect(qtLambda(SHARED.openWebsite, nwConst.URL_HELP)) # Document > Main Website - self.aWebsite = self.helpMenu.addAction(self.tr("The novelWriter Website")) + self.aWebsite = qtAddAction(self.helpMenu, self.tr("The novelWriter Website")) self.aWebsite.triggered.connect(qtLambda(SHARED.openWebsite, nwConst.URL_WEB)) return diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index e2e321c5..619700cb 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -38,7 +38,7 @@ from PyQt6.QtWidgets import ( ) from novelwriter import CONFIG, SHARED -from novelwriter.common import minmax, qtLambda +from novelwriter.common import minmax, qtAddAction, qtAddMenu, qtLambda from novelwriter.constants import nwKeyWords, nwLabels, nwStyles, trConst from novelwriter.core.index import IndexHeading from novelwriter.enum import nwChange, nwDocMode, nwItemClass, nwOutline @@ -199,7 +199,6 @@ class GuiNovelToolBar(QWidget): self.novelView = novelView iSz = SHARED.theme.baseIconSize - mPx = CONFIG.pxInt(2) self.setContentsMargins(0, 0, 0, 0) self.setAutoFillBackground(True) @@ -211,7 +210,7 @@ class GuiNovelToolBar(QWidget): self.novelValue = NovelSelector(self) self.novelValue.setFont(selFont) self.novelValue.setListFormat(self.tr("Outline of {0}")) - self.novelValue.setMinimumWidth(CONFIG.pxInt(150)) + self.novelValue.setMinimumWidth(150) self.novelValue.setSizePolicy(QtSizeExpanding, QtSizeExpanding) self.novelValue.novelSelectionChanged.connect(self.setCurrentRoot) @@ -227,7 +226,7 @@ class GuiNovelToolBar(QWidget): # More Options Menu self.mMore = QMenu(self) - self.mLastCol = self.mMore.addMenu(self.tr("Last Column")) + self.mLastCol = qtAddMenu(self.mMore, self.tr("Last Column")) self.gLastCol = QActionGroup(self.mMore) self.aLastCol = {} self._addLastColAction(NovelTreeColumn.HIDDEN, self.tr("Hidden")) @@ -236,7 +235,7 @@ class GuiNovelToolBar(QWidget): self._addLastColAction(NovelTreeColumn.PLOT, self.tr("Novel Plot")) self.mLastCol.addSeparator() - self.aLastColSize = self.mLastCol.addAction(self.tr("Column Size")) + self.aLastColSize = qtAddAction(self.mLastCol, self.tr("Column Size")) self.aLastColSize.triggered.connect(self._selectLastColumnSize) self.tbMore = NIconToolButton(self, iSz) @@ -249,7 +248,7 @@ class GuiNovelToolBar(QWidget): self.outerBox.addWidget(self.tbNovel) self.outerBox.addWidget(self.tbRefresh) self.outerBox.addWidget(self.tbMore) - self.outerBox.setContentsMargins(mPx, mPx, 0, mPx) + self.outerBox.setContentsMargins(2, 2, 0, 2) self.outerBox.setSpacing(0) self.setLayout(self.outerBox) @@ -343,7 +342,7 @@ class GuiNovelToolBar(QWidget): def _addLastColAction(self, colType: NovelTreeColumn, actionLabel: str) -> None: """Add a column selection entry to the last column menu.""" - aLast = self.mLastCol.addAction(actionLabel) + aLast = qtAddAction(self.mLastCol, actionLabel) aLast.setCheckable(True) aLast.setActionGroup(self.gLastCol) aLast.triggered.connect(qtLambda(self.setLastColType, colType)) @@ -388,7 +387,6 @@ class GuiNovelTree(QTreeWidget): iPx = SHARED.theme.baseIconHeight iSz = SHARED.theme.baseIconSize - cMg = CONFIG.pxInt(6) self.setIconSize(iSz) self.setFrameStyle(QFrame.Shape.NoFrame) @@ -403,13 +401,13 @@ class GuiNovelTree(QTreeWidget): self.setDragEnabled(False) # Lock the column sizes - treeHeader = self.header() - treeHeader.setStretchLastSection(False) - treeHeader.setMinimumSectionSize(iPx + cMg) - treeHeader.setSectionResizeMode(self.C_TITLE, QtHeaderStretch) - treeHeader.setSectionResizeMode(self.C_WORDS, QtHeaderToContents) - treeHeader.setSectionResizeMode(self.C_EXTRA, QtHeaderToContents) - treeHeader.setSectionResizeMode(self.C_MORE, QtHeaderToContents) + if header := self.header(): + header.setStretchLastSection(False) + header.setMinimumSectionSize(iPx + 6) + header.setSectionResizeMode(self.C_TITLE, QtHeaderStretch) + header.setSectionResizeMode(self.C_WORDS, QtHeaderToContents) + header.setSectionResizeMode(self.C_EXTRA, QtHeaderToContents) + header.setSectionResizeMode(self.C_MORE, QtHeaderToContents) # Pre-Generate Tree Formatting fH1 = self.font() @@ -678,8 +676,9 @@ class GuiNovelTree(QTreeWidget): return - def _updateTreeItemValues(self, trItem: QTreeWidgetItem, idxItem: IndexHeading, - tHandle: str, sTitle: str) -> None: + def _updateTreeItemValues( + self, trItem: QTreeWidgetItem, idxItem: IndexHeading, tHandle: str, sTitle: str + ) -> None: """Set the tree item values from the index entry.""" iLevel = nwStyles.H_LEVEL.get(idxItem.level, 0) hDec = SHARED.theme.getHeaderDecoration(iLevel) @@ -691,7 +690,8 @@ class GuiNovelTree(QTreeWidget): trItem.setData(self.C_MORE, QtDecoration, self._pMore) # Custom column - mW = int(self._lastColSize * self.viewport().width()) + viewport = self.viewport() + mW = int(self._lastColSize * (viewport.width() if viewport else 100)) lastText, toolTip = self._getLastColumnText(tHandle, sTitle) elideText = self.fontMetrics().elidedText(lastText, Qt.TextElideMode.ElideRight, mW) trItem.setText(self.C_EXTRA, elideText) diff --git a/novelwriter/gui/outline.py b/novelwriter/gui/outline.py index a237e5fd..8b1e9a0d 100644 --- a/novelwriter/gui/outline.py +++ b/novelwriter/gui/outline.py @@ -78,7 +78,7 @@ class GuiOutlineView(QWidget): # Assemble self.outerBox = QVBoxLayout() - self.outerBox.setContentsMargins(0, 0, CONFIG.pxInt(4), 0) + self.outerBox.setContentsMargins(0, 0, 4, 0) self.outerBox.addWidget(self.outlineBar) self.outerBox.addWidget(self.splitOutline) @@ -225,11 +225,11 @@ class GuiOutlineToolBar(QToolBar): self.novelLabel = NColourLabel( self.tr("Outline of"), self, scale=NColourLabel.HEADER_SCALE, bold=True ) - self.novelLabel.setContentsMargins(0, 0, CONFIG.pxInt(12), 0) + self.novelLabel.setContentsMargins(0, 0, 12, 0) self.novelValue = NovelSelector(self) self.novelValue.setIncludeAll(True) - self.novelValue.setMinimumWidth(CONFIG.pxInt(200)) + self.novelValue.setMinimumWidth(200) self.novelValue.novelSelectionChanged.connect(self._novelValueChanged) # Actions @@ -390,8 +390,8 @@ class GuiOutlineTree(QTreeWidget): self.setIconSize(SHARED.theme.baseIconSize) self.setIndentation(0) - self.treeHead = self.header() - self.treeHead.sectionMoved.connect(self._columnMoved) + if header := self.header(): + header.sectionMoved.connect(self._columnMoved) # Pre-Generate Tree Formatting fH1 = self.font() @@ -623,7 +623,7 @@ class GuiOutlineTree(QTreeWidget): continue tmpOrder.append(nwOutline[name]) tmpHidden[nwOutline[name]] = hidden - tmpWidth[nwOutline[name]] = CONFIG.pxInt(width) + tmpWidth[nwOutline[name]] = width except Exception: logger.error("Invalid column state") logException() @@ -647,26 +647,22 @@ class GuiOutlineTree(QTreeWidget): save the current width of hidden columns though. This preserves the last known width in case they're unhidden again. """ - # If we haven't built the tree, there is nothing to save. - if self._lastBuild == 0: - return - - colState = {} - for iCol in range(self.columnCount()): - hItem = self._treeOrder[iCol] - iLog = self.treeHead.logicalIndex(iCol) - logHidden = self.isColumnHidden(iLog) - orgWidth = CONFIG.rpxInt(self._colWidth[hItem]) - logWidth = CONFIG.rpxInt(self.columnWidth(iLog)) - colState[hItem.name] = [ - logHidden, orgWidth if logHidden and logWidth == 0 else logWidth - ] - - logger.debug("Saving State: GuiOutline") - pOptions = SHARED.project.options - pOptions.setValue("GuiOutline", "columnState", colState) - pOptions.saveSettings() + if self._lastBuild > 0 and (header := self.header()): + colState = {} + for iCol in range(self.columnCount()): + hItem = self._treeOrder[iCol] + iLog = header.logicalIndex(iCol) + logHidden = self.isColumnHidden(iLog) + orgWidth = self._colWidth[hItem] + logWidth = self.columnWidth(iLog) + colState[hItem.name] = [ + logHidden, orgWidth if logHidden and logWidth == 0 else logWidth + ] + logger.debug("Saving State: GuiOutline") + pOptions = SHARED.project.options + pOptions.setValue("GuiOutline", "columnState", colState) + pOptions.saveSettings() return def _populateTree(self, rootHandle: str | None) -> None: @@ -819,8 +815,6 @@ class GuiOutlineDetails(QScrollArea): minTitle = 30*SHARED.theme.textNWidth maxTitle = 40*SHARED.theme.textNWidth wCount = SHARED.theme.getTextWidth("999,999") - hSpace = int(CONFIG.pxInt(10)) - vSpace = int(CONFIG.pxInt(4)) bFont = SHARED.theme.guiFontB @@ -899,8 +893,8 @@ class GuiOutlineDetails(QScrollArea): self.mainForm.setColumnStretch(1, 1) self.mainForm.setRowStretch(4, 1) - self.mainForm.setHorizontalSpacing(hSpace) - self.mainForm.setVerticalSpacing(vSpace) + self.mainForm.setHorizontalSpacing(10) + self.mainForm.setVerticalSpacing(4) # Selected Item Tags self.tagsForm = QGridLayout() @@ -923,8 +917,8 @@ class GuiOutlineDetails(QScrollArea): self.tagsForm.setColumnStretch(1, 1) self.tagsForm.setRowStretch(len(self.tagValues), 1) - self.tagsForm.setHorizontalSpacing(hSpace) - self.tagsForm.setVerticalSpacing(vSpace) + self.tagsForm.setHorizontalSpacing(10) + self.tagsForm.setVerticalSpacing(4) # Assemble self.mainSplit = QSplitter(Qt.Orientation.Horizontal) @@ -965,21 +959,18 @@ class GuiOutlineDetails(QScrollArea): width = parent.width() if isinstance(parent, QWidget) else 1000 pOptions = SHARED.project.options self.mainSplit.setSizes([ - CONFIG.pxInt(pOptions.getInt("GuiOutlineDetails", "detailsWidth", width//3)), - CONFIG.pxInt(pOptions.getInt("GuiOutlineDetails", "tagsWidth", 2*width//3)) + pOptions.getInt("GuiOutlineDetails", "detailsWidth", width//3), + pOptions.getInt("GuiOutlineDetails", "tagsWidth", 2*width//3), ]) return def saveGuiSettings(self) -> None: """Run close project tasks.""" - mainSplit = self.mainSplit.sizes() - detailsWidth = CONFIG.rpxInt(mainSplit[0]) - tagsWidth = CONFIG.rpxInt(mainSplit[1]) - logger.debug("Saving State: GuiOutlineDetails") + mainSplit = self.mainSplit.sizes() pOptions = SHARED.project.options - pOptions.setValue("GuiOutlineDetails", "detailsWidth", detailsWidth) - pOptions.setValue("GuiOutlineDetails", "tagsWidth", tagsWidth) + pOptions.setValue("GuiOutlineDetails", "detailsWidth", mainSplit[0]) + pOptions.setValue("GuiOutlineDetails", "tagsWidth", mainSplit[1]) return def clearDetails(self) -> None: diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 6acaa264..2b28442d 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -40,7 +40,7 @@ from PyQt6.QtWidgets import ( ) from novelwriter import CONFIG, SHARED -from novelwriter.common import qtLambda +from novelwriter.common import qtAddAction, qtAddMenu, qtLambda from novelwriter.constants import nwLabels, nwStyles, nwUnicode, trConst from novelwriter.core.coretools import DocDuplicator, DocMerger, DocSplitter from novelwriter.core.item import NWItem @@ -243,7 +243,6 @@ class GuiProjectToolBar(QWidget): self.projTree = projView.projTree iSz = SHARED.theme.baseIconSize - mPx = CONFIG.pxInt(2) self.setContentsMargins(0, 0, 0, 0) self.setAutoFillBackground(True) @@ -274,27 +273,27 @@ class GuiProjectToolBar(QWidget): # Add Item Menu self.mAdd = QMenu(self) - self.aAddEmpty = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["document"])) + self.aAddEmpty = qtAddAction(self.mAdd, trConst(nwLabels.ITEM_DESCRIPTION["document"])) self.aAddEmpty.triggered.connect( qtLambda(self.projTree.newTreeItem, nwItemType.FILE, hLevel=0, isNote=False) ) - self.aAddChap = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["doc_h2"])) + self.aAddChap = qtAddAction(self.mAdd, trConst(nwLabels.ITEM_DESCRIPTION["doc_h2"])) self.aAddChap.triggered.connect( qtLambda(self.projTree.newTreeItem, nwItemType.FILE, hLevel=2, isNote=False) ) - self.aAddScene = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["doc_h3"])) + self.aAddScene = qtAddAction(self.mAdd, trConst(nwLabels.ITEM_DESCRIPTION["doc_h3"])) self.aAddScene.triggered.connect( qtLambda(self.projTree.newTreeItem, nwItemType.FILE, hLevel=3, isNote=False) ) - self.aAddNote = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["note"])) + self.aAddNote = qtAddAction(self.mAdd, trConst(nwLabels.ITEM_DESCRIPTION["note"])) self.aAddNote.triggered.connect( qtLambda(self.projTree.newTreeItem, nwItemType.FILE, hLevel=1, isNote=True) ) - self.aAddFolder = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["folder"])) + self.aAddFolder = qtAddAction(self.mAdd, trConst(nwLabels.ITEM_DESCRIPTION["folder"])) self.aAddFolder.triggered.connect( qtLambda(self.projTree.newTreeItem, nwItemType.FOLDER) ) @@ -304,7 +303,7 @@ class GuiProjectToolBar(QWidget): self.mTemplates.menuItemTriggered.connect(lambda h: self.newDocumentFromTemplate.emit(h)) self.mAdd.addMenu(self.mTemplates) - self.mAddRoot = self.mAdd.addMenu(trConst(nwLabels.ITEM_DESCRIPTION["root"])) + self.mAddRoot = qtAddMenu(self.mAdd, trConst(nwLabels.ITEM_DESCRIPTION["root"])) self._buildRootMenu() self.tbAdd = NIconToolButton(self, iSz) @@ -315,13 +314,13 @@ class GuiProjectToolBar(QWidget): # More Options Menu self.mMore = QMenu(self) - self.aExpand = self.mMore.addAction(self.tr("Expand All")) + self.aExpand = qtAddAction(self.mMore, self.tr("Expand All")) self.aExpand.triggered.connect(self.projTree.expandAll) - self.aCollapse = self.mMore.addAction(self.tr("Collapse All")) + self.aCollapse = qtAddAction(self.mMore, self.tr("Collapse All")) self.aCollapse.triggered.connect(self.projTree.collapseAll) - self.aEmptyTrash = self.mMore.addAction(self.tr("Empty Trash")) + self.aEmptyTrash = qtAddAction(self.mMore, self.tr("Empty Trash")) self.aEmptyTrash.triggered.connect(self.projTree.emptyTrash) self.tbMore = NIconToolButton(self, iSz) @@ -336,7 +335,7 @@ class GuiProjectToolBar(QWidget): self.outerBox.addWidget(self.tbMoveD) self.outerBox.addWidget(self.tbAdd) self.outerBox.addWidget(self.tbMore) - self.outerBox.setContentsMargins(mPx, mPx, 0, mPx) + self.outerBox.setContentsMargins(2, 2, 0, 2) self.outerBox.setSpacing(0) self.setLayout(self.outerBox) @@ -392,7 +391,7 @@ class GuiProjectToolBar(QWidget): logger.debug("Rebuilding quick links menu") self.mQuick.clear() for tHandle, nwItem in SHARED.project.tree.iterRoots(None): - action = self.mQuick.addAction(nwItem.itemName) + action = qtAddAction(self.mQuick, nwItem.itemName) action.setData(tHandle) action.setIcon(SHARED.theme.getIcon(nwLabels.CLASS_ICON[nwItem.itemClass], "root")) action.triggered.connect( @@ -440,7 +439,7 @@ class GuiProjectToolBar(QWidget): def _buildRootMenu(self) -> None: """Build the rood folder menu.""" def addClass(itemClass: nwItemClass) -> None: - aNew = self.mAddRoot.addAction(trConst(nwLabels.CLASS_NAME[itemClass])) + aNew = qtAddAction(self.mAddRoot, trConst(nwLabels.CLASS_NAME[itemClass])) aNew.setIcon(SHARED.theme.getIcon(nwLabels.CLASS_ICON[itemClass], "root")) aNew.triggered.connect( qtLambda(self.projTree.newTreeItem, nwItemType.ROOT, itemClass) @@ -561,17 +560,16 @@ class GuiProjectTree(QTreeView): # Lock the column sizes iPx = SHARED.theme.baseIconHeight - cMg = CONFIG.pxInt(6) - treeHeader = self.header() - treeHeader.setStretchLastSection(False) - treeHeader.setMinimumSectionSize(iPx + cMg) - treeHeader.setSectionResizeMode(ProjectNode.C_NAME, QtHeaderStretch) - treeHeader.setSectionResizeMode(ProjectNode.C_COUNT, QtHeaderToContents) - treeHeader.setSectionResizeMode(ProjectNode.C_ACTIVE, QtHeaderFixed) - treeHeader.setSectionResizeMode(ProjectNode.C_STATUS, QtHeaderFixed) - treeHeader.resizeSection(ProjectNode.C_ACTIVE, iPx + cMg) - treeHeader.resizeSection(ProjectNode.C_STATUS, iPx + cMg) + if header := self.header(): + header.setStretchLastSection(False) + header.setMinimumSectionSize(iPx + 6) + header.setSectionResizeMode(ProjectNode.C_NAME, QtHeaderStretch) + header.setSectionResizeMode(ProjectNode.C_COUNT, QtHeaderToContents) + header.setSectionResizeMode(ProjectNode.C_ACTIVE, QtHeaderFixed) + header.setSectionResizeMode(ProjectNode.C_STATUS, QtHeaderFixed) + header.resizeSection(ProjectNode.C_ACTIVE, iPx + 6) + header.resizeSection(ProjectNode.C_STATUS, iPx + 6) self.restoreExpandedState() @@ -969,8 +967,9 @@ class GuiProjectTree(QTreeView): else: ctxMenu.buildSingleSelectMenu() - ctxMenu.exec(self.viewport().mapToGlobal(point)) - ctxMenu.deleteLater() + if viewport := self.viewport(): + ctxMenu.exec(viewport.mapToGlobal(point)) + ctxMenu.deleteLater() return @@ -1091,7 +1090,8 @@ class _UpdatableMenu(QMenu): def setActionsVisible(self, value: bool) -> None: """Set the visibility of root action.""" - self.menuAction().setVisible(value) + if action := self.menuAction(): + action.setVisible(value) return ## @@ -1135,7 +1135,7 @@ class _TreeContextMenu(QMenu): def buildTrashMenu(self) -> None: """Build the special menu for the Trash folder.""" - action = self.addAction(self.tr("Empty Trash")) + action = qtAddAction(self, self.tr("Empty Trash")) action.triggered.connect(self._tree.emptyTrash) if self._children: self._expandCollapse() @@ -1156,7 +1156,7 @@ class _TreeContextMenu(QMenu): self.addSeparator() # Edit Item Settings - action = self.addAction(self.tr("Rename")) + action = qtAddAction(self, self.tr("Rename")) action.triggered.connect(qtLambda(self._view.renameTreeItem, self._handle)) if isFile: self._itemHeader() @@ -1171,7 +1171,7 @@ class _TreeContextMenu(QMenu): # Process Item if self._children: self._expandCollapse() - action = self.addAction(self.tr("Duplicate")) + action = qtAddAction(self, self.tr("Duplicate")) action.triggered.connect(qtLambda(self._tree.duplicateFromHandle, self._handle)) self._deleteOrTrash() @@ -1191,12 +1191,12 @@ class _TreeContextMenu(QMenu): def _docActions(self) -> None: """Add document actions.""" - action = self.addAction(self.tr("Open Document")) + action = qtAddAction(self, self.tr("Open Document")) action.triggered.connect(qtLambda( self._view.openDocumentRequest.emit, self._handle, nwDocMode.EDIT, "", True )) - action = self.addAction(self.tr("View Document")) + action = qtAddAction(self, self.tr("View Document")) action.triggered.connect(qtLambda( self._view.openDocumentRequest.emit, self._handle, nwDocMode.VIEW, "", False @@ -1205,7 +1205,7 @@ class _TreeContextMenu(QMenu): def _itemCreation(self) -> None: """Add create item actions.""" - menu = self.addMenu(self.tr("Create New ...")) + menu = qtAddMenu(self, self.tr("Create New ...")) menu.addAction(self._view.projBar.aAddEmpty) menu.addAction(self._view.projBar.aAddChap) menu.addAction(self._view.projBar.aAddScene) @@ -1217,7 +1217,7 @@ class _TreeContextMenu(QMenu): """Check if there is a header that can be used for rename.""" SHARED.saveEditor() if hItem := SHARED.project.index.getItemHeading(self._handle, "T0001"): - action = self.addAction(self.tr("Rename to Heading")) + action = qtAddAction(self, self.tr("Rename to Heading")) action.triggered.connect( qtLambda(self._view.renameTreeItem, self._handle, hItem.title) ) @@ -1226,50 +1226,54 @@ class _TreeContextMenu(QMenu): def _itemActive(self) -> None: """Add Active/Inactive actions.""" if len(self._indices) > 1: - mSub = self.addMenu(self.tr("Set Active to ...")) - aOne = mSub.addAction(SHARED.theme.getIcon("checked"), self._tree.trActive) + mSub = qtAddMenu(self, self.tr("Set Active to ...")) + aOne = qtAddAction(mSub, self._tree.trActive) + aOne.setIcon(SHARED.theme.getIcon("checked")) aOne.triggered.connect(qtLambda(self._iterItemActive, True)) - aTwo = mSub.addAction(SHARED.theme.getIcon("unchecked"), self._tree.trInactive) + aTwo = qtAddAction(mSub, self._tree.trInactive) + aTwo.setIcon(SHARED.theme.getIcon("unchecked")) aTwo.triggered.connect(qtLambda(self._iterItemActive, False)) else: - action = self.addAction(self.tr("Toggle Active")) + action = qtAddAction(self, self.tr("Toggle Active")) action.triggered.connect(self._toggleItemActive) return def _itemStatusImport(self, multi: bool) -> None: """Add actions for changing status or importance.""" if self._item.isNovelLike(): - menu = self.addMenu(self.tr("Set Status to ...")) + menu = qtAddMenu(self, self.tr("Set Status to ...")) current = self._item.itemStatus for key, entry in SHARED.project.data.itemStatus.iterItems(): name = entry.name if not multi and current == key: name += f" ({nwUnicode.U_CHECK})" - action = menu.addAction(entry.icon, name) + action = qtAddAction(menu, name) + action.setIcon(entry.icon) if multi: action.triggered.connect(qtLambda(self._iterSetItemStatus, key)) else: action.triggered.connect(qtLambda(self._changeItemStatus, key)) menu.addSeparator() - action = menu.addAction(self.tr("Manage Labels ...")) + action = qtAddAction(menu, self.tr("Manage Labels ...")) action.triggered.connect(qtLambda( self._view.projectSettingsRequest.emit, GuiProjectSettings.PAGE_STATUS )) else: - menu = self.addMenu(self.tr("Set Importance to ...")) + menu = qtAddMenu(self, self.tr("Set Importance to ...")) current = self._item.itemImport for key, entry in SHARED.project.data.itemImport.iterItems(): name = entry.name if not multi and current == key: name += f" ({nwUnicode.U_CHECK})" - action = menu.addAction(entry.icon, name) + action = qtAddAction(menu, name) + action.setIcon(entry.icon) if multi: action.triggered.connect(qtLambda(self._iterSetItemImport, key)) else: action.triggered.connect(qtLambda(self._changeItemImport, key)) menu.addSeparator() - action = menu.addAction(self.tr("Manage Labels ...")) + action = qtAddAction(menu, self.tr("Manage Labels ...")) action.triggered.connect(qtLambda( self._view.projectSettingsRequest.emit, GuiProjectSettings.PAGE_IMPORT @@ -1278,7 +1282,7 @@ class _TreeContextMenu(QMenu): def _itemTransform(self, isFile: bool, isFolder: bool) -> None: """Add actions for the Transform menu.""" - menu = self.addMenu(self.tr("Transform ...")) + menu = qtAddMenu(self, self.tr("Transform ...")) trDoc = trConst(nwLabels.LAYOUT_NAME[nwItemLayout.DOCUMENT]) trNote = trConst(nwLabels.LAYOUT_NAME[nwItemLayout.NOTE]) @@ -1288,42 +1292,42 @@ class _TreeContextMenu(QMenu): isNoteFile = isFile and self._item.isNoteLayout() if isNoteFile and self._item.documentAllowed(): - action = menu.addAction(self.tr("Convert to {0}").format(trDoc)) + action = qtAddAction(menu, self.tr("Convert to {0}").format(trDoc)) action.triggered.connect(qtLambda(self._changeItemLayout, loDoc)) if isDocFile: - action = menu.addAction(self.tr("Convert to {0}").format(trNote)) + action = qtAddAction(menu, self.tr("Convert to {0}").format(trNote)) action.triggered.connect(qtLambda(self._changeItemLayout, loNote)) if isFolder and self._item.documentAllowed(): - action = menu.addAction(self.tr("Convert to {0}").format(trDoc)) + action = qtAddAction(menu, self.tr("Convert to {0}").format(trDoc)) action.triggered.connect(qtLambda(self._convertFolderToFile, loDoc)) if isFolder: - action = menu.addAction(self.tr("Convert to {0}").format(trNote)) + action = qtAddAction(menu, self.tr("Convert to {0}").format(trNote)) action.triggered.connect(qtLambda(self._convertFolderToFile, loNote)) if self._children and isFile: - action = menu.addAction(self.tr("Merge Child Items into Self")) + action = qtAddAction(menu, self.tr("Merge Child Items into Self")) action.triggered.connect(qtLambda(self._tree.mergeDocuments, self._handle, False)) - action = menu.addAction(self.tr("Merge Child Items into New")) + action = qtAddAction(menu, self.tr("Merge Child Items into New")) action.triggered.connect(qtLambda(self._tree.mergeDocuments, self._handle, True)) if self._children and isFolder: - action = menu.addAction(self.tr("Merge Documents in Folder")) + action = qtAddAction(menu, self.tr("Merge Documents in Folder")) action.triggered.connect(qtLambda(self._tree.mergeDocuments, self._handle, True)) if isFile: - action = menu.addAction(self.tr("Split Document by Headings")) + action = qtAddAction(menu, self.tr("Split Document by Headings")) action.triggered.connect(qtLambda(self._tree.splitDocument, self._handle)) return def _expandCollapse(self) -> None: """Add actions for expand and collapse.""" - action = self.addAction(self.tr("Expand All")) + action = qtAddAction(self, self.tr("Expand All")) action.triggered.connect(qtLambda(self._tree.expandFromIndex, self._indices[0])) - action = self.addAction(self.tr("Collapse All")) + action = qtAddAction(self, self.tr("Collapse All")) action.triggered.connect(qtLambda(self._tree.collapseFromIndex, self._indices[0])) return @@ -1336,7 +1340,7 @@ class _TreeContextMenu(QMenu): text = self.tr("Delete Permanently") else: text = self.tr("Move to Trash") - action = self.addAction(text) + action = qtAddAction(self, text) action.triggered.connect(self._tree.processDeleteRequest) return diff --git a/novelwriter/gui/search.py b/novelwriter/gui/search.py index 78f06838..3c44c82a 100644 --- a/novelwriter/gui/search.py +++ b/novelwriter/gui/search.py @@ -28,14 +28,14 @@ import logging from time import time from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot -from PyQt6.QtGui import QCursor, QKeyEvent +from PyQt6.QtGui import QAction, QCursor, QKeyEvent from PyQt6.QtWidgets import ( QApplication, QFrame, QHBoxLayout, QLabel, QLineEdit, QToolBar, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget ) from novelwriter import CONFIG, SHARED -from novelwriter.common import checkInt, cssCol +from novelwriter.common import checkInt, cssCol, qtAddAction from novelwriter.core.coretools import DocSearch from novelwriter.core.item import NWItem from novelwriter.types import ( @@ -65,8 +65,6 @@ class GuiProjectSearch(QWidget): iPx = SHARED.theme.baseIconHeight iSz = SHARED.theme.baseIconSize - mPx = CONFIG.pxInt(2) - tPx = CONFIG.pxInt(4) self._time = time() self._search = DocSearch() @@ -76,7 +74,7 @@ class GuiProjectSearch(QWidget): # Header self.viewLabel = QLabel(self.tr("Project Search"), self) self.viewLabel.setFont(SHARED.theme.guiFontB) - self.viewLabel.setContentsMargins(mPx, tPx, 0, mPx) + self.viewLabel.setContentsMargins(2, 4, 0, 2) # Options self.searchOpt = QToolBar(self) @@ -84,30 +82,30 @@ class GuiProjectSearch(QWidget): self.searchOpt.setIconSize(iSz) self.searchOpt.setContentsMargins(0, 0, 0, 0) - self.toggleCase = self.searchOpt.addAction(self.tr("Case Sensitive")) + self.toggleCase = qtAddAction(self.searchOpt, self.tr("Case Sensitive")) self.toggleCase.setCheckable(True) self.toggleCase.setChecked(CONFIG.searchProjCase) self.toggleCase.toggled.connect(self._toggleCase) - self.toggleWord = self.searchOpt.addAction(self.tr("Whole Words Only")) + self.toggleWord = qtAddAction(self.searchOpt, self.tr("Whole Words Only")) self.toggleWord.setCheckable(True) self.toggleWord.setChecked(CONFIG.searchProjWord) self.toggleWord.toggled.connect(self._toggleWord) - self.toggleRegEx = self.searchOpt.addAction(self.tr("RegEx Mode")) + self.toggleRegEx = qtAddAction(self.searchOpt, self.tr("RegEx Mode")) self.toggleRegEx.setCheckable(True) self.toggleRegEx.setChecked(CONFIG.searchProjRegEx) self.toggleRegEx.toggled.connect(self._toggleRegEx) # Search Box + self.searchAction = QAction("", self) + self.searchAction.setIcon(SHARED.theme.getIcon("search", "blue")) + self.searchAction.triggered.connect(self._processSearch) + self.searchText = QLineEdit(self) self.searchText.setPlaceholderText(self.tr("Search for")) self.searchText.setClearButtonEnabled(True) - - self.searchAction = self.searchText.addAction( - SHARED.theme.getIcon("search", "blue"), QLineEdit.ActionPosition.TrailingPosition - ) - self.searchAction.triggered.connect(self._processSearch) + self.searchText.addAction(self.searchAction, QLineEdit.ActionPosition.TrailingPosition) # Search Result self.searchResult = QTreeWidget(self) @@ -121,10 +119,10 @@ class GuiProjectSearch(QWidget): self.searchResult.itemDoubleClicked.connect(self._searchResultDoubleClicked) self.searchResult.itemSelectionChanged.connect(self._searchResultSelected) - treeHeader = self.searchResult.header() - treeHeader.setStretchLastSection(False) - treeHeader.setSectionResizeMode(self.C_NAME, QtHeaderStretch) - treeHeader.setSectionResizeMode(self.C_COUNT, QtHeaderToContents) + if header := self.searchResult.header(): + header.setStretchLastSection(False) + header.setSectionResizeMode(self.C_NAME, QtHeaderStretch) + header.setSectionResizeMode(self.C_COUNT, QtHeaderToContents) # Assemble self.headerBox = QHBoxLayout() @@ -138,7 +136,7 @@ class GuiProjectSearch(QWidget): self.outerBox.addWidget(self.searchText, 0) self.outerBox.addWidget(self.searchResult, 1) self.outerBox.setContentsMargins(0, 0, 0, 0) - self.outerBox.setSpacing(mPx) + self.outerBox.setSpacing(2) self.setLayout(self.outerBox) self.updateTheme() @@ -153,8 +151,6 @@ class GuiProjectSearch(QWidget): def updateTheme(self) -> None: """Update theme elements.""" - bPx = CONFIG.pxInt(1) - mPx = CONFIG.pxInt(2) qPalette = self.palette() colBase = cssCol(qPalette.base().color()) @@ -162,8 +158,8 @@ class GuiProjectSearch(QWidget): self.setStyleSheet( "QToolBar {padding: 0; background: none;} " - f"QLineEdit {{border: {bPx}px solid {colBase}; padding: {mPx}px;}} " - f"QLineEdit:focus {{border: {bPx}px solid {colFocus};}} " + f"QLineEdit {{border: 1px solid {colBase}; padding: 2px;}} " + f"QLineEdit:focus {{border: 1px solid {colFocus};}} " ) self.searchAction.setIcon(SHARED.theme.getIcon("search", "blue")) diff --git a/novelwriter/gui/sidebar.py b/novelwriter/gui/sidebar.py index cf9d26bf..71e06973 100644 --- a/novelwriter/gui/sidebar.py +++ b/novelwriter/gui/sidebar.py @@ -31,7 +31,7 @@ from PyQt6.QtCore import QEvent, QPoint, QSize, pyqtSignal from PyQt6.QtGui import QPalette from PyQt6.QtWidgets import QMenu, QVBoxLayout, QWidget -from novelwriter import CONFIG, SHARED +from novelwriter import SHARED from novelwriter.common import qtLambda from novelwriter.enum import nwView from novelwriter.extensions.eventfilters import StatusTipFilter @@ -114,7 +114,7 @@ class GuiSideBar(QWidget): self.outerBox.addWidget(self.tbStats) self.outerBox.addWidget(self.tbSettings) self.outerBox.setContentsMargins(0, 0, 0, 0) - self.outerBox.setSpacing(CONFIG.pxInt(6)) + self.outerBox.setSpacing(6) self.setLayout(self.outerBox) self.updateTheme() diff --git a/novelwriter/gui/statusbar.py b/novelwriter/gui/statusbar.py index 413b93df..6dd75830 100644 --- a/novelwriter/gui/statusbar.py +++ b/novelwriter/gui/statusbar.py @@ -57,13 +57,11 @@ class GuiMainStatus(QStatusBar): # Permanent Widgets # ================= - xM = CONFIG.pxInt(8) - # The Spell Checker Language self.langIcon = QLabel("", self) self.langText = QLabel(self.tr("None"), self) self.langIcon.setContentsMargins(0, 0, 0, 0) - self.langText.setContentsMargins(0, 0, xM, 0) + self.langText.setContentsMargins(0, 0, 8, 0) self.addPermanentWidget(self.langIcon) self.addPermanentWidget(self.langText) @@ -71,7 +69,7 @@ class GuiMainStatus(QStatusBar): self.docIcon = StatusLED(iPx, iPx, self) self.docText = QLabel(self.tr("Editor"), self) self.docIcon.setContentsMargins(0, 0, 0, 0) - self.docText.setContentsMargins(0, 0, xM, 0) + self.docText.setContentsMargins(0, 0, 8, 0) self.addPermanentWidget(self.docIcon) self.addPermanentWidget(self.docText) @@ -79,7 +77,7 @@ class GuiMainStatus(QStatusBar): self.projIcon = StatusLED(iPx, iPx, self) self.projText = QLabel(self.tr("Project"), self) self.projIcon.setContentsMargins(0, 0, 0, 0) - self.projText.setContentsMargins(0, 0, xM, 0) + self.projText.setContentsMargins(0, 0, 8, 0) self.addPermanentWidget(self.projIcon) self.addPermanentWidget(self.projText) @@ -87,7 +85,7 @@ class GuiMainStatus(QStatusBar): self.statsIcon = QLabel(self) self.statsText = QLabel("", self) self.statsIcon.setContentsMargins(0, 0, 0, 0) - self.statsText.setContentsMargins(0, 0, xM, 0) + self.statsText.setContentsMargins(0, 0, 8, 0) self.addPermanentWidget(self.statsIcon) self.addPermanentWidget(self.statsText) diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index 3f85e2c1..0348d70b 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -544,31 +544,26 @@ class GuiTheme: """Build default style sheets.""" self._styleSheets = {} - aPx = CONFIG.pxInt(2) - bPx = CONFIG.pxInt(4) - cPx = CONFIG.pxInt(6) - dPx = CONFIG.pxInt(8) - tCol = palette.text().color() hCol = palette.highlight().color() # Flat Tab Widget and Tab Bar: self._styleSheets[STYLES_FLAT_TABS] = ( "QTabWidget::pane {border: 0;} " - f"QTabWidget QTabBar::tab {{border: 0; padding: {bPx}px {dPx}px;}} " + "QTabWidget QTabBar::tab {border: 0; padding: 4px 8px;} " f"QTabWidget QTabBar::tab:selected {{color: {cssCol(hCol)};}} " ) # Minimal Tool Button self._styleSheets[STYLES_MIN_TOOLBUTTON] = ( - f"QToolButton {{padding: {aPx}px; margin: 0; border: none; background: transparent;}} " + "QToolButton {padding: 2px; margin: 0; border: none; background: transparent;} " f"QToolButton:hover {{border: none; background: {cssCol(tCol, 48)};}} " "QToolButton::menu-indicator {image: none;} " ) # Big Tool Button self._styleSheets[STYLES_BIG_TOOLBUTTON] = ( - f"QToolButton {{padding: {cPx}px; margin: 0; border: none; background: transparent;}} " + "QToolButton {padding: 6px; margin: 0; border: none; background: transparent;} " f"QToolButton:hover {{border: none; background: {cssCol(tCol, 48)};}} " "QToolButton::menu-indicator {image: none;} " ) From 7e280b23713141ad0e0efb95f94a3609c1a0e675 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Mon, 13 Jan 2025 02:53:55 +0100 Subject: [PATCH 6/8] Clean up warnings and deprecations in gui tools --- novelwriter/config.py | 8 -- novelwriter/tools/dictionaries.py | 17 ++-- novelwriter/tools/lipsum.py | 21 +++-- novelwriter/tools/manusbuild.py | 59 +++++-------- novelwriter/tools/manuscript.py | 135 +++++++++++++---------------- novelwriter/tools/manussettings.py | 98 ++++++++++----------- novelwriter/tools/noveldetails.py | 82 +++++++----------- novelwriter/tools/welcome.py | 63 ++++++-------- novelwriter/tools/writingstats.py | 75 +++++++--------- 9 files changed, 236 insertions(+), 322 deletions(-) diff --git a/novelwriter/config.py b/novelwriter/config.py index b5d09e2d..bf32577e 100644 --- a/novelwriter/config.py +++ b/novelwriter/config.py @@ -399,14 +399,6 @@ class Config: # Methods ## - def pxInt(self, value: int) -> int: - """Deprecated. Do not use.""" - return value - - def rpxInt(self, value: int) -> int: - """Deprecated. Do not use.""" - return value - def homePath(self) -> Path: """The user's home folder.""" return self._homePath diff --git a/novelwriter/tools/dictionaries.py b/novelwriter/tools/dictionaries.py index ddb371e1..c956846c 100644 --- a/novelwriter/tools/dictionaries.py +++ b/novelwriter/tools/dictionaries.py @@ -57,12 +57,9 @@ class GuiDictionaries(NNonBlockingDialog): self._currDicts = set() iSz = SHARED.theme.baseIconSize - iPx = CONFIG.pxInt(4) - mPx = CONFIG.pxInt(8) - sPx = CONFIG.pxInt(16) - self.setMinimumWidth(CONFIG.pxInt(500)) - self.setMinimumHeight(CONFIG.pxInt(300)) + self.setMinimumWidth(500) + self.setMinimumHeight(300) # Hunspell Dictionaries foUrl = "https://www.freeoffice.com/en/download/dictionaries" @@ -84,7 +81,7 @@ class GuiDictionaries(NNonBlockingDialog): self.huPathBox = QHBoxLayout() self.huPathBox.addWidget(self.huInput) self.huPathBox.addWidget(self.huBrowse) - self.huPathBox.setSpacing(iPx) + self.huPathBox.setSpacing(4) self.huAddBox = QHBoxLayout() self.huAddBox.addStretch(1) self.huAddBox.addWidget(self.huImport) @@ -99,7 +96,7 @@ class GuiDictionaries(NNonBlockingDialog): self.inBox = QHBoxLayout() self.inBox.addWidget(self.inPath) self.inBox.addWidget(self.inBrowse) - self.inBox.setSpacing(iPx) + self.inBox.setSpacing(4) # Info Box self.infoBox = QPlainTextEdit(self) @@ -116,17 +113,17 @@ class GuiDictionaries(NNonBlockingDialog): self.innerBox.addWidget(self.huInfo) self.innerBox.addLayout(self.huPathBox) self.innerBox.addLayout(self.huAddBox) - self.innerBox.addSpacing(mPx) + self.innerBox.addSpacing(8) self.innerBox.addWidget(self.inInfo) self.innerBox.addLayout(self.inBox) self.innerBox.addWidget(self.infoBox) - self.innerBox.setSpacing(iPx) + self.innerBox.setSpacing(4) self.outerBox = QVBoxLayout() self.outerBox.addLayout(self.innerBox, 0) self.outerBox.addStretch(1) self.outerBox.addWidget(self.buttonBox, 0) - self.outerBox.setSpacing(sPx) + self.outerBox.setSpacing(16) self.setLayout(self.outerBox) diff --git a/novelwriter/tools/lipsum.py b/novelwriter/tools/lipsum.py index f9144d49..48aefa2d 100644 --- a/novelwriter/tools/lipsum.py +++ b/novelwriter/tools/lipsum.py @@ -52,18 +52,15 @@ class GuiLipsum(NDialog): self._lipsumText = "" - vSp = CONFIG.pxInt(4) - nPx = CONFIG.pxInt(64) - self.innerBox = QHBoxLayout() - self.innerBox.setSpacing(CONFIG.pxInt(16)) + self.innerBox.setSpacing(16) # Icon self.docIcon = QLabel(self) - self.docIcon.setPixmap(SHARED.theme.getPixmap("text", (nPx, nPx), "blue")) + self.docIcon.setPixmap(SHARED.theme.getPixmap("text", (64, 64), "blue")) self.leftBox = QVBoxLayout() - self.leftBox.setSpacing(vSp) + self.leftBox.setSpacing(4) self.leftBox.addWidget(self.docIcon) self.leftBox.addStretch(1) self.innerBox.addLayout(self.leftBox) @@ -87,7 +84,7 @@ class GuiLipsum(NDialog): self.formBox.addWidget(self.paraCount, 1, 1, 1, 1, QtAlignRight) self.formBox.addWidget(self.randLabel, 2, 0, 1, 1, QtAlignLeft) self.formBox.addWidget(self.randSwitch, 2, 1, 1, 1, QtAlignRight) - self.formBox.setVerticalSpacing(vSp) + self.formBox.setVerticalSpacing(4) self.formBox.setRowStretch(3, 1) self.innerBox.addLayout(self.formBox) @@ -96,17 +93,19 @@ class GuiLipsum(NDialog): self.buttonBox.rejected.connect(self.reject) self.btnClose = self.buttonBox.addButton(QtDialogClose) - self.btnClose.setAutoDefault(False) + if self.btnClose: + self.btnClose.setAutoDefault(False) self.btnInsert = self.buttonBox.addButton(self.tr("Insert"), QtRoleAction) - self.btnInsert.clicked.connect(self._doInsert) - self.btnInsert.setAutoDefault(False) + if self.btnInsert: + self.btnInsert.clicked.connect(self._doInsert) + self.btnInsert.setAutoDefault(False) # Assemble self.outerBox = QVBoxLayout() self.outerBox.addLayout(self.innerBox) self.outerBox.addWidget(self.buttonBox) - self.outerBox.setSpacing(CONFIG.pxInt(16)) + self.outerBox.setSpacing(16) self.setLayout(self.outerBox) logger.debug("Ready: GuiLipsum") diff --git a/novelwriter/tools/manusbuild.py b/novelwriter/tools/manusbuild.py index 40e6eefc..f45d7a1f 100644 --- a/novelwriter/tools/manusbuild.py +++ b/novelwriter/tools/manusbuild.py @@ -35,7 +35,7 @@ from PyQt6.QtWidgets import ( QPushButton, QSplitter, QVBoxLayout, QWidget ) -from novelwriter import CONFIG, SHARED +from novelwriter import SHARED from novelwriter.common import makeFileNameSafe, openExternalPath from novelwriter.constants import nwLabels from novelwriter.core.buildsettings import BuildSettings @@ -68,21 +68,16 @@ class GuiManuscriptBuild(NDialog): self._build = build self.setWindowTitle(self.tr("Build Manuscript")) - self.setMinimumWidth(CONFIG.pxInt(500)) - self.setMinimumHeight(CONFIG.pxInt(300)) + self.setMinimumWidth(500) + self.setMinimumHeight(300) iSz = SHARED.theme.baseIconSize bSz = SHARED.theme.buttonIconSize - sp4 = CONFIG.pxInt(4) - sp8 = CONFIG.pxInt(8) - sp16 = CONFIG.pxInt(16) - wWin = CONFIG.pxInt(620) - hWin = CONFIG.pxInt(360) pOptions = SHARED.project.options self.resize( - CONFIG.pxInt(pOptions.getInt("GuiManuscriptBuild", "winWidth", wWin)), - CONFIG.pxInt(pOptions.getInt("GuiManuscriptBuild", "winHeight", hWin)) + pOptions.getInt("GuiManuscriptBuild", "winWidth", 620), + pOptions.getInt("GuiManuscriptBuild", "winHeight", 360), ) # Output Format @@ -149,7 +144,7 @@ class GuiManuscriptBuild(NDialog): self.pathBox = QHBoxLayout() self.pathBox.addWidget(self.buildPath) self.pathBox.addWidget(self.btnBrowse) - self.pathBox.setSpacing(sp8) + self.pathBox.setSpacing(8) # Build Name self.lblName = QLabel(self.tr("File Name"), self) @@ -160,14 +155,14 @@ class GuiManuscriptBuild(NDialog): self.nameBox = QHBoxLayout() self.nameBox.addWidget(self.buildName) self.nameBox.addWidget(self.btnReset) - self.nameBox.setSpacing(sp8) + self.nameBox.setSpacing(8) # Build Progress self.buildProgress = NProgressSimple(self) self.buildProgress.setMinimum(0) self.buildProgress.setValue(0) self.buildProgress.setTextVisible(False) - self.buildProgress.setFixedHeight(sp8) + self.buildProgress.setFixedHeight(8) # Build Box self.buildBox = QGridLayout() @@ -175,8 +170,8 @@ class GuiManuscriptBuild(NDialog): self.buildBox.addLayout(self.pathBox, 0, 1) self.buildBox.addWidget(self.lblName, 1, 0) self.buildBox.addLayout(self.nameBox, 1, 1) - self.buildBox.setHorizontalSpacing(sp8) - self.buildBox.setVerticalSpacing(sp4) + self.buildBox.setHorizontalSpacing(8) + self.buildBox.setVerticalSpacing(4) # Dialog Buttons self.buttonBox = QDialogButtonBox(self) @@ -196,7 +191,8 @@ class GuiManuscriptBuild(NDialog): self.buttonBox.addButton(self.btnBuild, QtRoleAction) self.btnClose = self.buttonBox.addButton(QtDialogClose) - self.btnClose.setAutoDefault(False) + if self.btnClose: + self.btnClose.setAutoDefault(False) # Assemble GUI # ============ @@ -204,25 +200,25 @@ class GuiManuscriptBuild(NDialog): self.mainSplit = QSplitter(self) self.mainSplit.addWidget(self.formatWidget) self.mainSplit.addWidget(self.contentWidget) - self.mainSplit.setHandleWidth(sp16) + self.mainSplit.setHandleWidth(16) self.mainSplit.setCollapsible(0, False) self.mainSplit.setCollapsible(1, False) self.mainSplit.setStretchFactor(0, 0) self.mainSplit.setStretchFactor(1, 1) self.mainSplit.setSizes([ - CONFIG.pxInt(pOptions.getInt("GuiManuscriptBuild", "fmtWidth", wWin//2)), - CONFIG.pxInt(pOptions.getInt("GuiManuscriptBuild", "sumWidth", wWin//2)), + pOptions.getInt("GuiManuscriptBuild", "fmtWidth", 360), + pOptions.getInt("GuiManuscriptBuild", "sumWidth", 360), ]) self.outerBox = QVBoxLayout() self.outerBox.addWidget(self.lblMain, 0, QtAlignCenter) - self.outerBox.addSpacing(sp16) + self.outerBox.addSpacing(16) self.outerBox.addWidget(self.mainSplit, 1) - self.outerBox.addSpacing(sp4) + self.outerBox.addSpacing(4) self.outerBox.addWidget(self.buildProgress, 0) - self.outerBox.addSpacing(sp4) + self.outerBox.addSpacing(4) self.outerBox.addLayout(self.buildBox, 0) - self.outerBox.addSpacing(sp16) + self.outerBox.addSpacing(16) self.outerBox.addWidget(self.buttonBox, 0) self.outerBox.setSpacing(0) @@ -363,21 +359,14 @@ class GuiManuscriptBuild(NDialog): def _saveSettings(self) -> None: """Save the user GUI settings.""" - winWidth = CONFIG.rpxInt(self.width()) - winHeight = CONFIG.rpxInt(self.height()) - - mainSplit = self.mainSplit.sizes() - fmtWidth = CONFIG.rpxInt(mainSplit[0]) - sumWidth = CONFIG.rpxInt(mainSplit[1]) - logger.debug("Saving State: GuiManuscriptBuild") + mainSplit = self.mainSplit.sizes() pOptions = SHARED.project.options - pOptions.setValue("GuiManuscriptBuild", "winWidth", winWidth) - pOptions.setValue("GuiManuscriptBuild", "winHeight", winHeight) - pOptions.setValue("GuiManuscriptBuild", "fmtWidth", fmtWidth) - pOptions.setValue("GuiManuscriptBuild", "sumWidth", sumWidth) + pOptions.setValue("GuiManuscriptBuild", "winWidth", self.width()) + pOptions.setValue("GuiManuscriptBuild", "winHeight", self.height()) + pOptions.setValue("GuiManuscriptBuild", "fmtWidth", mainSplit[0]) + pOptions.setValue("GuiManuscriptBuild", "sumWidth", mainSplit[1]) pOptions.saveSettings() - return def _populateContentList(self) -> None: diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py index 448b8db2..d0d0708c 100644 --- a/novelwriter/tools/manuscript.py +++ b/novelwriter/tools/manuscript.py @@ -85,18 +85,16 @@ class GuiManuscript(NToolDialog): self._buildMap: dict[str, QListWidgetItem] = {} self.setWindowTitle(self.tr("Build Manuscript")) - self.setMinimumWidth(CONFIG.pxInt(600)) - self.setMinimumHeight(CONFIG.pxInt(500)) + self.setMinimumWidth(600) + self.setMinimumHeight(500) iPx = SHARED.theme.baseIconHeight iSz = SHARED.theme.baseIconSize - wWin = CONFIG.pxInt(900) - hWin = CONFIG.pxInt(600) pOptions = SHARED.project.options self.resize( - CONFIG.pxInt(pOptions.getInt("GuiManuscript", "winWidth", wWin)), - CONFIG.pxInt(pOptions.getInt("GuiManuscript", "winHeight", hWin)) + pOptions.getInt("GuiManuscript", "winWidth", 900), + pOptions.getInt("GuiManuscript", "winHeight", 600), ) # Build Controls @@ -153,9 +151,7 @@ class GuiManuscript(NToolDialog): # ============ self.buildDetails = _DetailsWidget(self) - self.buildDetails.setColumnWidth( - CONFIG.pxInt(pOptions.getInt("GuiManuscript", "detailsWidth", 100)), - ) + self.buildDetails.setColumnWidth(pOptions.getInt("GuiManuscript", "detailsWidth", 100)) self.buildOutline = _OutlineWidget(self) @@ -168,8 +164,8 @@ class GuiManuscript(NToolDialog): self.buildSplit.addWidget(self.buildList) self.buildSplit.addWidget(self.detailsTabs) self.buildSplit.setSizes([ - CONFIG.pxInt(pOptions.getInt("GuiManuscript", "listHeight", 50)), - CONFIG.pxInt(pOptions.getInt("GuiManuscript", "detailsHeight", 50)), + pOptions.getInt("GuiManuscript", "listHeight", 50), + pOptions.getInt("GuiManuscript", "detailsHeight", 50), ]) # Process Controls @@ -240,8 +236,8 @@ class GuiManuscript(NToolDialog): self.mainSplit.setStretchFactor(0, 0) self.mainSplit.setStretchFactor(1, 1) self.mainSplit.setSizes([ - CONFIG.pxInt(pOptions.getInt("GuiManuscript", "optsWidth", wWin//4)), - CONFIG.pxInt(pOptions.getInt("GuiManuscript", "viewWidth", 3*wWin//4)), + pOptions.getInt("GuiManuscript", "optsWidth", 225), + pOptions.getInt("GuiManuscript", "viewWidth", 675), ]) self.outerBox = QVBoxLayout() @@ -441,28 +437,20 @@ class GuiManuscript(NToolDialog): self._builds.setBuildsState(lastBuild, buildOrder) - winWidth = CONFIG.rpxInt(self.width()) - winHeight = CONFIG.rpxInt(self.height()) - mainSplit = self.mainSplit.sizes() - optsWidth = CONFIG.rpxInt(mainSplit[0]) - viewWidth = CONFIG.rpxInt(mainSplit[1]) - buildSplit = self.buildSplit.sizes() - listHeight = CONFIG.rpxInt(buildSplit[0]) - detailsHeight = CONFIG.rpxInt(buildSplit[1]) - detailsWidth = CONFIG.rpxInt(self.buildDetails.getColumnWidth()) + detailsWidth = self.buildDetails.getColumnWidth() detailsExpanded = self.buildDetails.getExpandedState() showNewPage = self.swtNewPage.isChecked() logger.debug("Saving State: GuiManuscript") pOptions = SHARED.project.options - pOptions.setValue("GuiManuscript", "winWidth", winWidth) - pOptions.setValue("GuiManuscript", "winHeight", winHeight) - pOptions.setValue("GuiManuscript", "optsWidth", optsWidth) - pOptions.setValue("GuiManuscript", "viewWidth", viewWidth) - pOptions.setValue("GuiManuscript", "listHeight", listHeight) - pOptions.setValue("GuiManuscript", "detailsHeight", detailsHeight) + pOptions.setValue("GuiManuscript", "winWidth", self.width()) + pOptions.setValue("GuiManuscript", "winHeight", self.height()) + pOptions.setValue("GuiManuscript", "optsWidth", mainSplit[0]) + pOptions.setValue("GuiManuscript", "viewWidth", mainSplit[1]) + pOptions.setValue("GuiManuscript", "listHeight", buildSplit[0]) + pOptions.setValue("GuiManuscript", "detailsHeight", buildSplit[1]) pOptions.setValue("GuiManuscript", "detailsWidth", detailsWidth) pOptions.setValue("GuiManuscript", "detailsExpanded", detailsExpanded) pOptions.setValue("GuiManuscript", "showNewPage", showNewPage) @@ -693,33 +681,31 @@ class _OutlineWidget(QWidget): hFont.setBold(True) hFont.setUnderline(True) - root = self.listView.invisibleRootItem() - parent = root - indent = False - for anchor, entry in data.items(): - prefix, _, text = entry.partition("|") - if prefix in ("TT", "PT", "CH", "SC", "H1", "H2"): - item = QTreeWidgetItem([text]) - item.setData(0, self.D_LINE, anchor) - if prefix == "TT": - item.setFont(0, tFont) - item.setForeground(0, tBrush) - root.addChild(item) - parent = root - elif prefix == "PT": - item.setFont(0, hFont) - root.addChild(item) - parent = root - elif prefix in ("CH", "H1"): - root.addChild(item) - parent = item - elif prefix in ("SC", "H2"): - parent.addChild(item) - indent = True + if root := self.listView.invisibleRootItem(): + parent = root + indent = False + for anchor, entry in data.items(): + prefix, _, text = entry.partition("|") + if prefix in ("TT", "PT", "CH", "SC", "H1", "H2"): + item = QTreeWidgetItem([text]) + item.setData(0, self.D_LINE, anchor) + if prefix == "TT": + item.setFont(0, tFont) + item.setForeground(0, tBrush) + root.addChild(item) + parent = root + elif prefix == "PT": + item.setFont(0, hFont) + root.addChild(item) + parent = root + elif prefix in ("CH", "H1"): + root.addChild(item) + parent = item + elif prefix in ("SC", "H2"): + parent.addChild(item) + indent = True - self.listView.setIndentation( - SHARED.theme.baseIconHeight if indent else CONFIG.pxInt(4) - ) + self.listView.setIndentation(SHARED.theme.baseIconHeight if indent else 4) self._outline = data return @@ -754,7 +740,9 @@ class _PreviewWidget(QTextBrowser): self.setOpenExternalLinks(False) self.setOpenLinks(False) - self.document().setDocumentMargin(CONFIG.textMargin) + if document := self.document(): + document.setDocumentMargin(CONFIG.textMargin) + self.setPlaceholderText(self.tr( "Press the \"Preview\" button to generate ..." )) @@ -781,7 +769,7 @@ class _PreviewWidget(QTextBrowser): self.ageLabel.setFixedHeight(int(2.1*SHARED.theme.fontPixelSize)) # Progress - self.buildProgress = NProgressCircle(self, CONFIG.pxInt(160), CONFIG.pxInt(16)) + self.buildProgress = NProgressCircle(self, 160, 16) self.buildProgress.setVisible(False) self.buildProgress.setMaximum(1) self.buildProgress.setValue(0) @@ -834,7 +822,8 @@ class _PreviewWidget(QTextBrowser): self.buildProgress.setValue(0) self.buildProgress.setCentreText(None) self.buildProgress.setVisible(True) - self._scrollPos = self.verticalScrollBar().value() + if vBar := self.verticalScrollBar(): + self._scrollPos = vBar.value() self.setPlaceholderText("") self.clear() return @@ -883,10 +872,11 @@ class _PreviewWidget(QTextBrowser): @pyqtSlot("QPrinter*") def printPreview(self, printer: QPrinter) -> None: """Connect the print preview painter to the document viewer.""" - QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor)) - printer.setPageOrientation(QPageLayout.Orientation.Portrait) - self.document().print(printer) - QApplication.restoreOverrideCursor() + if document := self.document(): + QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor)) + printer.setPageOrientation(QPageLayout.Orientation.Portrait) + document.print(printer) + QApplication.restoreOverrideCursor() return @pyqtSlot(str) @@ -928,7 +918,8 @@ class _PreviewWidget(QTextBrowser): def _postUpdate(self) -> None: """Run tasks after content update.""" self.buildProgress.setVisible(False) - self.verticalScrollBar().setValue(self._scrollPos) + if vBar := self.verticalScrollBar(): + vBar.setValue(self._scrollPos) return ## @@ -941,7 +932,7 @@ class _PreviewWidget(QTextBrowser): """ vBar = self.verticalScrollBar() tB = self.frameWidth() - vW = self.width() - 2*tB - vBar.width() + vW = self.width() - 2*tB - (vBar.width() if vBar else 0) vH = self.height() - 2*tB tH = self.ageLabel.height() pS = self.buildProgress.width() @@ -1033,10 +1024,6 @@ class _StatsWidget(QWidget): def _buildBottomPanel(self) -> None: """Build the bottom page.""" - mPx = CONFIG.pxInt(8) - hPx = CONFIG.pxInt(12) - vPx = CONFIG.pxInt(4) - trAllChars = trConst(nwLabels.STATS_NAME[nwStats.CHARS]) trTextChars = trConst(nwLabels.STATS_NAME[nwStats.CHARS_TEXT]) trTitleChars = trConst(nwLabels.STATS_NAME[nwStats.CHARS_TITLE]) @@ -1073,8 +1060,8 @@ class _StatsWidget(QWidget): self.leftForm.addRow("", QLabel(self)) self.leftForm.addRow(trTitleCount, self.maxTitleCount) self.leftForm.addRow(trParagraphCount, self.maxParCount) - self.leftForm.setHorizontalSpacing(hPx) - self.leftForm.setVerticalSpacing(vPx) + self.leftForm.setHorizontalSpacing(12) + self.leftForm.setVerticalSpacing(4) # Maximal Form, Right Column self.maxTotalChars = QLabel(self) @@ -1098,25 +1085,25 @@ class _StatsWidget(QWidget): self.rightForm.addRow(trAllWordChars, self.maxTotalWordChars) self.rightForm.addRow(trTitleWordChars, self.maxHeadWordChars) self.rightForm.addRow(trTextWordChars, self.maxTextWordChars) - self.rightForm.setHorizontalSpacing(hPx) - self.rightForm.setVerticalSpacing(vPx) + self.rightForm.setHorizontalSpacing(12) + self.rightForm.setVerticalSpacing(4) # Assemble self.minLayout = QHBoxLayout() self.minLayout.addWidget(QLabel(trAllWords, self)) self.minLayout.addWidget(self.minWordCount) - self.minLayout.addSpacing(mPx) + self.minLayout.addSpacing(8) self.minLayout.addWidget(QLabel(trAllChars, self)) self.minLayout.addWidget(self.minCharCount) self.minLayout.addStretch(1) - self.minLayout.setSpacing(mPx) + self.minLayout.setSpacing(8) self.minLayout.setContentsMargins(0, 0, 0, 0) self.maxLayout = QHBoxLayout() self.maxLayout.addLayout(self.leftForm) self.maxLayout.addLayout(self.rightForm) self.maxLayout.addStretch(1) - self.maxLayout.setSpacing(CONFIG.pxInt(32)) + self.maxLayout.setSpacing(32) self.maxLayout.setContentsMargins(0, 0, 0, 0) self.minWidget.setLayout(self.minLayout) diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py index 549d39d1..78d59725 100644 --- a/novelwriter/tools/manussettings.py +++ b/novelwriter/tools/manussettings.py @@ -37,7 +37,7 @@ from PyQt6.QtWidgets import ( ) from novelwriter import CONFIG, SHARED -from novelwriter.common import describeFont, fontMatcher, qtLambda +from novelwriter.common import describeFont, fontMatcher, qtAddAction, qtLambda from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels, nwStyles, trConst from novelwriter.core.buildsettings import BuildSettings, FilterMode from novelwriter.extensions.configlayout import ( @@ -83,21 +83,18 @@ class GuiBuildSettings(NToolDialog): self._build = build self.setWindowTitle(self.tr("Manuscript Build Settings")) - self.setMinimumSize(CONFIG.pxInt(700), CONFIG.pxInt(400)) - - wWin = CONFIG.pxInt(750) - hWin = CONFIG.pxInt(550) + self.setMinimumSize(700, 400) options = SHARED.project.options self.resize( - CONFIG.pxInt(options.getInt("GuiBuildSettings", "winWidth", wWin)), - CONFIG.pxInt(options.getInt("GuiBuildSettings", "winHeight", hWin)) + options.getInt("GuiBuildSettings", "winWidth", 750), + options.getInt("GuiBuildSettings", "winHeight", 550), ) # Title self.titleLabel = NColourLabel( self.tr("Manuscript Build Settings"), self, color=SHARED.theme.helpText, - scale=NColourLabel.HEADER_SCALE, indent=CONFIG.pxInt(4) + scale=NColourLabel.HEADER_SCALE, indent=4, ) # Settings Name @@ -145,7 +142,7 @@ class GuiBuildSettings(NToolDialog): self.outerBox.addLayout(self.topBox) self.outerBox.addLayout(self.mainBox) self.outerBox.addWidget(self.buttonBox) - self.outerBox.setSpacing(CONFIG.pxInt(12)) + self.outerBox.setSpacing(12) self.setLayout(self.outerBox) @@ -240,14 +237,11 @@ class GuiBuildSettings(NToolDialog): def _saveSettings(self) -> None: """Save the various user settings.""" - winWidth = CONFIG.rpxInt(self.width()) - winHeight = CONFIG.rpxInt(self.height()) treeWidth, filterWidth = self.optTabSelect.mainSplitSizes() - logger.debug("Saving State: GuiBuildSettings") pOptions = SHARED.project.options - pOptions.setValue("GuiBuildSettings", "winWidth", winWidth) - pOptions.setValue("GuiBuildSettings", "winHeight", winHeight) + pOptions.setValue("GuiBuildSettings", "winWidth", self.width()) + pOptions.setValue("GuiBuildSettings", "winHeight", self.height()) pOptions.setValue("GuiBuildSettings", "treeWidth", treeWidth) pOptions.setValue("GuiBuildSettings", "filterWidth", filterWidth) pOptions.saveSettings() @@ -300,7 +294,6 @@ class _FilterTab(NFixedPage): iSz = SHARED.theme.baseIconSize iPx = SHARED.theme.baseIconHeight - cMg = CONFIG.pxInt(6) # Tree Widget self.optTree = QTreeWidget(self) @@ -311,14 +304,14 @@ class _FilterTab(NFixedPage): self.optTree.setIndentation(iPx) self.optTree.setColumnCount(3) - treeHeader = self.optTree.header() - treeHeader.setStretchLastSection(False) - treeHeader.setMinimumSectionSize(iPx + cMg) # See Issue #1551 - treeHeader.setSectionResizeMode(self.C_NAME, QtHeaderStretch) - treeHeader.setSectionResizeMode(self.C_ACTIVE, QtHeaderFixed) - treeHeader.setSectionResizeMode(self.C_STATUS, QtHeaderFixed) - treeHeader.resizeSection(self.C_ACTIVE, iPx + cMg) - treeHeader.resizeSection(self.C_STATUS, iPx + cMg) + if header := self.optTree.header(): + header.setStretchLastSection(False) + header.setMinimumSectionSize(iPx + 6) # See Issue #1551 + header.setSectionResizeMode(self.C_NAME, QtHeaderStretch) + header.setSectionResizeMode(self.C_ACTIVE, QtHeaderFixed) + header.setSectionResizeMode(self.C_STATUS, QtHeaderFixed) + header.resizeSection(self.C_ACTIVE, iPx + 6) + header.resizeSection(self.C_STATUS, iPx + 6) self.optTree.setDragDropMode(QAbstractItemView.DragDropMode.NoDragDrop) self.optTree.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) @@ -347,7 +340,7 @@ class _FilterTab(NFixedPage): self.modeBox.addWidget(self.includedButton) self.modeBox.addWidget(self.excludedButton) self.modeBox.addWidget(self.resetButton) - self.modeBox.setSpacing(CONFIG.pxInt(4)) + self.modeBox.setSpacing(4) # Filer Options self.filterOpt = NSwitchBox(self, iPx) @@ -375,8 +368,8 @@ class _FilterTab(NFixedPage): self.mainSplit.setStretchFactor(0, 1) self.mainSplit.setStretchFactor(1, 0) self.mainSplit.setSizes([ - CONFIG.pxInt(pOptions.getInt("GuiBuildSettings", "treeWidth", 300)), - CONFIG.pxInt(pOptions.getInt("GuiBuildSettings", "filterWidth", 300)) + pOptions.getInt("GuiBuildSettings", "treeWidth", 300), + pOptions.getInt("GuiBuildSettings", "filterWidth", 300), ]) self.setCentralWidget(self.mainSplit) @@ -393,7 +386,7 @@ class _FilterTab(NFixedPage): """Extract the sizes of the main splitter.""" sizes = self.mainSplit.sizes() m, n = (sizes[0], sizes[1]) if len(sizes) >= 2 else (0, 0) - return CONFIG.rpxInt(m), CONFIG.rpxInt(n) + return m, n ## # Slots @@ -558,15 +551,12 @@ class _HeadingsTab(NScrollablePage): iPx = SHARED.theme.baseIconHeight iSz = SHARED.theme.baseIconSize - sSp = CONFIG.pxInt(16) - vSp = CONFIG.pxInt(12) - bSp = CONFIG.pxInt(6) trHide = self.tr("Hide") # Format Boxes # ============ self.formatBox = QGridLayout() - self.formatBox.setHorizontalSpacing(bSp) + self.formatBox.setHorizontalSpacing(6) # Title Heading self.lblPart = QLabel(self._build.getLabel("headings.fmtPart"), self) @@ -575,7 +565,7 @@ class _HeadingsTab(NScrollablePage): self.btnPart = NIconToolButton(self, iSz, "edit", "green") self.btnPart.clicked.connect(qtLambda(self._editHeading, self.EDIT_TITLE)) self.hdePart = QLabel(trHide, self) - self.hdePart.setIndent(bSp) + self.hdePart.setIndent(6) self.swtPart = NSwitch(self, height=iPx) self.formatBox.addWidget(self.lblPart, 0, 0) @@ -591,7 +581,7 @@ class _HeadingsTab(NScrollablePage): self.btnChapter = NIconToolButton(self, iSz, "edit", "green") self.btnChapter.clicked.connect(qtLambda(self._editHeading, self.EDIT_CHAPTER)) self.hdeChapter = QLabel(trHide, self) - self.hdeChapter.setIndent(bSp) + self.hdeChapter.setIndent(6) self.swtChapter = NSwitch(self, height=iPx) self.formatBox.addWidget(self.lblChapter, 1, 0) @@ -607,7 +597,7 @@ class _HeadingsTab(NScrollablePage): self.btnUnnumbered = NIconToolButton(self, iSz, "edit", "green") self.btnUnnumbered.clicked.connect(qtLambda(self._editHeading, self.EDIT_UNNUM)) self.hdeUnnumbered = QLabel(trHide, self) - self.hdeUnnumbered.setIndent(bSp) + self.hdeUnnumbered.setIndent(6) self.swtUnnumbered = NSwitch(self, height=iPx) self.formatBox.addWidget(self.lblUnnumbered, 2, 0) @@ -623,7 +613,7 @@ class _HeadingsTab(NScrollablePage): self.btnScene = NIconToolButton(self, iSz, "edit", "green") self.btnScene.clicked.connect(qtLambda(self._editHeading, self.EDIT_SCENE)) self.hdeScene = QLabel(trHide, self) - self.hdeScene.setIndent(bSp) + self.hdeScene.setIndent(6) self.swtScene = NSwitch(self, height=iPx) self.formatBox.addWidget(self.lblScene, 3, 0) @@ -639,7 +629,7 @@ class _HeadingsTab(NScrollablePage): self.btnAScene = NIconToolButton(self, iSz, "edit", "green") self.btnAScene.clicked.connect(qtLambda(self._editHeading, self.EDIT_HSCENE)) self.hdeAScene = QLabel(trHide, self) - self.hdeAScene.setIndent(bSp) + self.hdeAScene.setIndent(6) self.swtAScene = NSwitch(self, height=iPx) self.formatBox.addWidget(self.lblAScene, 4, 0) @@ -655,7 +645,7 @@ class _HeadingsTab(NScrollablePage): self.btnSection = NIconToolButton(self, iSz, "edit", "green") self.btnSection.clicked.connect(qtLambda(self._editHeading, self.EDIT_SECTION)) self.hdeSection = QLabel(trHide, self) - self.hdeSection.setIndent(bSp) + self.hdeSection.setIndent(6) self.swtSection = NSwitch(self, height=iPx) self.formatBox.addWidget(self.lblSection, 5, 0) @@ -675,16 +665,16 @@ class _HeadingsTab(NScrollablePage): self.formSyntax = _HeadingSyntaxHighlighter(self.editTextBox.document()) - self.menuInsert = QMenu(self) - self.aInsTitle = self.menuInsert.addAction(self.tr("Title")) - self.aInsChNum = self.menuInsert.addAction(self.tr("Chapter Number")) - self.aInsChWord = self.menuInsert.addAction(self.tr("Chapter Number (Word)")) - self.aInsChRomU = self.menuInsert.addAction(self.tr("Chapter Number (Upper Case Roman)")) - self.aInsChRomL = self.menuInsert.addAction(self.tr("Chapter Number (Lower Case Roman)")) - self.aInsScNum = self.menuInsert.addAction(self.tr("Scene Number (In Chapter)")) - self.aInsScAbs = self.menuInsert.addAction(self.tr("Scene Number (Absolute)")) - self.aInsCharPOV = self.menuInsert.addAction(self.tr("Point of View Character")) - self.aInsCharFocus = self.menuInsert.addAction(self.tr("Focus Character")) + self.mInsert = QMenu(self) + self.aInsTitle = qtAddAction(self.mInsert, self.tr("Title")) + self.aInsChNum = qtAddAction(self.mInsert, self.tr("Chapter Number")) + self.aInsChWord = qtAddAction(self.mInsert, self.tr("Chapter Number (Word)")) + self.aInsChRomU = qtAddAction(self.mInsert, self.tr("Chapter Number (Upper Case Roman)")) + self.aInsChRomL = qtAddAction(self.mInsert, self.tr("Chapter Number (Lower Case Roman)")) + self.aInsScNum = qtAddAction(self.mInsert, self.tr("Scene Number (In Chapter)")) + self.aInsScAbs = qtAddAction(self.mInsert, self.tr("Scene Number (Absolute)")) + self.aInsCharPOV = qtAddAction(self.mInsert, self.tr("Point of View Character")) + self.aInsCharFocus = qtAddAction(self.mInsert, self.tr("Focus Character")) self.aInsTitle.triggered.connect(qtLambda(self._insertIntoForm, nwHeadFmt.TITLE)) self.aInsChNum.triggered.connect(qtLambda(self._insertIntoForm, nwHeadFmt.CH_NUM)) @@ -697,7 +687,7 @@ class _HeadingsTab(NScrollablePage): self.aInsCharFocus.triggered.connect(qtLambda(self._insertIntoForm, nwHeadFmt.CHAR_FOCUS)) self.btnInsert = QPushButton(self.tr("Insert"), self) - self.btnInsert.setMenu(self.menuInsert) + self.btnInsert.setMenu(self.mInsert) self.btnApply = QPushButton(self.tr("Apply"), self) self.btnApply.clicked.connect(self._saveFormat) @@ -715,8 +705,8 @@ class _HeadingsTab(NScrollablePage): # Layout Matrix # ============= self.layoutMatrix = QGridLayout() - self.layoutMatrix.setVerticalSpacing(vSp) - self.layoutMatrix.setHorizontalSpacing(vSp) + self.layoutMatrix.setVerticalSpacing(12) + self.layoutMatrix.setHorizontalSpacing(12) self.layoutMatrix.addWidget(QLabel(self.tr("Centre"), self), 0, 1) self.layoutMatrix.addWidget(QLabel(self.tr("Page Break"), self), 0, 2) @@ -764,9 +754,9 @@ class _HeadingsTab(NScrollablePage): self.outerBox = QVBoxLayout() self.outerBox.addLayout(self.formatBox) - self.outerBox.addSpacing(sSp) + self.outerBox.addSpacing(16) self.outerBox.addLayout(self.editFormBox) - self.outerBox.addSpacing(sSp) + self.outerBox.addSpacing(16) self.outerBox.addLayout(self.layoutMatrix) self.outerBox.addStretch(1) @@ -904,7 +894,7 @@ class _HeadingsTab(NScrollablePage): class _HeadingSyntaxHighlighter(QSyntaxHighlighter): - def __init__(self, document: QTextDocument) -> None: + def __init__(self, document: QTextDocument | None) -> None: super().__init__(document) syntax = SHARED.theme.syntaxTheme self._fmtSymbol = QTextCharFormat() @@ -1223,7 +1213,7 @@ class _FormattingTab(NScrollableForm): # Header self.odtPageHeader = QLineEdit(self) - self.odtPageHeader.setMinimumWidth(CONFIG.pxInt(200)) + self.odtPageHeader.setMinimumWidth(200) self.btnPageHeader = NIconToolButton(self, iSz, "revert", "green") self.btnPageHeader.clicked.connect(self._resetPageHeader) self.addRow( diff --git a/novelwriter/tools/noveldetails.py b/novelwriter/tools/noveldetails.py index a71539e5..c23a513e 100644 --- a/novelwriter/tools/noveldetails.py +++ b/novelwriter/tools/noveldetails.py @@ -34,7 +34,7 @@ from PyQt6.QtWidgets import ( QVBoxLayout, QWidget ) -from novelwriter import CONFIG, SHARED +from novelwriter import SHARED from novelwriter.common import formatTime, numberToRoman from novelwriter.constants import nwUnicode from novelwriter.extensions.configlayout import NColourLabel, NFixedPage, NScrollablePage @@ -60,16 +60,16 @@ class GuiNovelDetails(NNonBlockingDialog): self.setWindowTitle(self.tr("Novel Details")) options = SHARED.project.options - self.setMinimumSize(CONFIG.pxInt(500), CONFIG.pxInt(400)) + self.setMinimumSize(500, 400) self.resize( - CONFIG.pxInt(options.getInt("GuiNovelDetails", "winWidth", CONFIG.pxInt(650))), - CONFIG.pxInt(options.getInt("GuiNovelDetails", "winHeight", CONFIG.pxInt(500))) + options.getInt("GuiNovelDetails", "winWidth", 650), + options.getInt("GuiNovelDetails", "winHeight", 500), ) # Title self.titleLabel = NColourLabel( self.tr("Novel Details"), self, color=SHARED.theme.helpText, - scale=NColourLabel.HEADER_SCALE, indent=CONFIG.pxInt(4) + scale=NColourLabel.HEADER_SCALE, indent=4, ) # Novel Selector @@ -114,7 +114,7 @@ class GuiNovelDetails(NNonBlockingDialog): self.outerBox.addLayout(self.topBox) self.outerBox.addLayout(self.mainBox) self.outerBox.addWidget(self.buttonBox) - self.outerBox.setSpacing(CONFIG.pxInt(8)) + self.outerBox.setSpacing(8) self.setLayout(self.outerBox) self.setSizeGripEnabled(True) @@ -172,18 +172,13 @@ class GuiNovelDetails(NNonBlockingDialog): def _saveSettings(self) -> None: """Save the user GUI settings.""" - winWidth = CONFIG.rpxInt(self.width()) - winHeight = CONFIG.rpxInt(self.height()) - novelRoot = self.novelSelector.handle - logger.debug("Saving State: GuiNovelDetails") + novelRoot = self.novelSelector.handle options = SHARED.project.options - options.setValue("GuiNovelDetails", "winWidth", winWidth) - options.setValue("GuiNovelDetails", "winHeight", winHeight) + options.setValue("GuiNovelDetails", "winWidth", self.width()) + options.setValue("GuiNovelDetails", "winHeight", self.height()) options.setValue("GuiNovelDetails", "novelRoot", novelRoot) - self.contentsPage.saveSettings() - return @@ -192,11 +187,6 @@ class _OverviewPage(NScrollablePage): def __init__(self, parent: QWidget) -> None: super().__init__(parent=parent) - mPx = CONFIG.pxInt(8) - sPx = CONFIG.pxInt(16) - hPx = CONFIG.pxInt(24) - vPx = CONFIG.pxInt(4) - # Project Info self.projLabel = NColourLabel( self.tr("Project"), self, color=SHARED.theme.helpText, @@ -217,9 +207,9 @@ class _OverviewPage(NScrollablePage): self.projForm.addRow("{0}".format(self.tr("Word Count")), self.projWords) self.projForm.addRow("\u2026 {0}".format(self.tr("In Novels")), self.projNovels) self.projForm.addRow("\u2026 {0}".format(self.tr("In Notes")), self.projNotes) - self.projForm.setContentsMargins(mPx, 0, 0, 0) - self.projForm.setHorizontalSpacing(hPx) - self.projForm.setVerticalSpacing(vPx) + self.projForm.setContentsMargins(8, 0, 0, 0) + self.projForm.setHorizontalSpacing(24) + self.projForm.setVerticalSpacing(4) # Novel Info self.novelLabel = NColourLabel( @@ -237,9 +227,9 @@ class _OverviewPage(NScrollablePage): self.novelForm.addRow("{0}".format(self.tr("Word Count")), self.novelWords) self.novelForm.addRow("{0}".format(self.tr("Chapters")), self.novelChapters) self.novelForm.addRow("{0}".format(self.tr("Scenes")), self.novelScenes) - self.novelForm.setContentsMargins(mPx, 0, 0, 0) - self.novelForm.setHorizontalSpacing(hPx) - self.novelForm.setVerticalSpacing(vPx) + self.novelForm.setContentsMargins(8, 0, 0, 0) + self.novelForm.setHorizontalSpacing(24) + self.novelForm.setVerticalSpacing(4) # Assemble self.outerBox = QVBoxLayout() @@ -247,7 +237,7 @@ class _OverviewPage(NScrollablePage): self.outerBox.addLayout(self.projForm) self.outerBox.addWidget(self.novelLabel) self.outerBox.addLayout(self.novelForm) - self.outerBox.setSpacing(sPx) + self.outerBox.setSpacing(16) self.outerBox.addStretch(1) self.setCentralLayout(self.outerBox) @@ -309,8 +299,6 @@ class _ContentsPage(NFixedPage): iPx = SHARED.theme.baseIconHeight iSz = SHARED.theme.baseIconSize - hPx = CONFIG.pxInt(12) - vPx = CONFIG.pxInt(4) options = SHARED.project.options # Title @@ -341,22 +329,22 @@ class _ContentsPage(NFixedPage): treeHeadItem.setTextAlignment(self.C_PAGE, QtAlignRight) treeHeadItem.setTextAlignment(self.C_PROG, QtAlignRight) - treeHeader = self.tocTree.header() - treeHeader.setStretchLastSection(True) - treeHeader.setMinimumSectionSize(hPx) + if header := self.tocTree.header(): + header.setStretchLastSection(True) + header.setMinimumSectionSize(12) - wCol0 = CONFIG.pxInt(options.getInt("GuiNovelDetails", "widthCol0", 200)) - wCol1 = CONFIG.pxInt(options.getInt("GuiNovelDetails", "widthCol1", 60)) - wCol2 = CONFIG.pxInt(options.getInt("GuiNovelDetails", "widthCol2", 60)) - wCol3 = CONFIG.pxInt(options.getInt("GuiNovelDetails", "widthCol3", 60)) - wCol4 = CONFIG.pxInt(options.getInt("GuiNovelDetails", "widthCol4", 90)) + wCol0 = options.getInt("GuiNovelDetails", "widthCol0", 200) + wCol1 = options.getInt("GuiNovelDetails", "widthCol1", 60) + wCol2 = options.getInt("GuiNovelDetails", "widthCol2", 60) + wCol3 = options.getInt("GuiNovelDetails", "widthCol3", 60) + wCol4 = options.getInt("GuiNovelDetails", "widthCol4", 90) self.tocTree.setColumnWidth(0, wCol0) self.tocTree.setColumnWidth(1, wCol1) self.tocTree.setColumnWidth(2, wCol2) self.tocTree.setColumnWidth(3, wCol3) self.tocTree.setColumnWidth(4, wCol4) - self.tocTree.setColumnWidth(5, hPx) + self.tocTree.setColumnWidth(5, 12) # Options wordsPerPage = options.getInt("GuiNovelDetails", "wordsPerPage", 350) @@ -394,8 +382,8 @@ class _ContentsPage(NFixedPage): self.optionsBox.addWidget(self.dblValue, 0, 4) self.optionsBox.addWidget(self.poLabel, 1, 0) self.optionsBox.addWidget(self.poValue, 1, 1) - self.optionsBox.setHorizontalSpacing(hPx) - self.optionsBox.setVerticalSpacing(vPx) + self.optionsBox.setHorizontalSpacing(12) + self.optionsBox.setVerticalSpacing(4) self.optionsBox.setColumnStretch(2, 1) # Assemble @@ -410,18 +398,12 @@ class _ContentsPage(NFixedPage): def saveSettings(self) -> None: """Save the user GUI settings.""" - widthCol0 = CONFIG.rpxInt(self.tocTree.columnWidth(0)) - widthCol1 = CONFIG.rpxInt(self.tocTree.columnWidth(1)) - widthCol2 = CONFIG.rpxInt(self.tocTree.columnWidth(2)) - widthCol3 = CONFIG.rpxInt(self.tocTree.columnWidth(3)) - widthCol4 = CONFIG.rpxInt(self.tocTree.columnWidth(4)) - options = SHARED.project.options - options.setValue("GuiNovelDetails", "widthCol0", widthCol0) - options.setValue("GuiNovelDetails", "widthCol1", widthCol1) - options.setValue("GuiNovelDetails", "widthCol2", widthCol2) - options.setValue("GuiNovelDetails", "widthCol3", widthCol3) - options.setValue("GuiNovelDetails", "widthCol4", widthCol4) + options.setValue("GuiNovelDetails", "widthCol0", self.tocTree.columnWidth(0)) + options.setValue("GuiNovelDetails", "widthCol1", self.tocTree.columnWidth(1)) + options.setValue("GuiNovelDetails", "widthCol2", self.tocTree.columnWidth(2)) + options.setValue("GuiNovelDetails", "widthCol3", self.tocTree.columnWidth(3)) + options.setValue("GuiNovelDetails", "widthCol4", self.tocTree.columnWidth(4)) options.setValue("GuiNovelDetails", "wordsPerPage", self.wpValue.value()) options.setValue("GuiNovelDetails", "countFrom", self.poValue.value()) options.setValue("GuiNovelDetails", "clearDouble", self.dblValue.isChecked()) diff --git a/novelwriter/tools/welcome.py b/novelwriter/tools/welcome.py index 88126797..32791caf 100644 --- a/novelwriter/tools/welcome.py +++ b/novelwriter/tools/welcome.py @@ -40,7 +40,7 @@ from PyQt6.QtWidgets import ( ) from novelwriter import CONFIG, SHARED -from novelwriter.common import cssCol, formatInt, makeFileNameSafe, qtLambda +from novelwriter.common import cssCol, formatInt, makeFileNameSafe, qtAddAction, qtLambda from novelwriter.constants import nwFiles from novelwriter.core.coretools import ProjectBuilder from novelwriter.enum import nwItemClass @@ -66,29 +66,21 @@ class GuiWelcome(NDialog): self.setObjectName("GuiWelcome") self.setWindowTitle(self.tr("Welcome")) - self.setMinimumWidth(CONFIG.pxInt(650)) - self.setMinimumHeight(CONFIG.pxInt(450)) - - hA = CONFIG.pxInt(8) - hB = CONFIG.pxInt(16) - hC = CONFIG.pxInt(24) - hD = CONFIG.pxInt(36) - hE = CONFIG.pxInt(48) - hF = CONFIG.pxInt(128) - btnIconSize = SHARED.theme.buttonIconSize - - self._hPx = CONFIG.pxInt(600) + self.setMinimumWidth(650) + self.setMinimumHeight(450) self.resize(*CONFIG.welcomeWinSize) + btnIconSize = SHARED.theme.buttonIconSize + # Elements # ======== self.bgImage = SHARED.theme.loadDecoration("welcome") - self.nwImage = SHARED.theme.loadDecoration("nw-text", h=hD) + self.nwImage = SHARED.theme.loadDecoration("nw-text", h=36) self.bgColor = QColor(54, 54, 54) if SHARED.theme.isDarkTheme else QColor(255, 255, 255) self.nwLogo = QLabel(self) - self.nwLogo.setPixmap(SHARED.theme.getPixmap("novelwriter", (hF, hF))) + self.nwLogo.setPixmap(SHARED.theme.getPixmap("novelwriter", (128, 128))) self.nwLabel = QLabel(self) self.nwLabel.setPixmap(self.nwImage) @@ -152,18 +144,18 @@ class GuiWelcome(NDialog): # ======== self.innerBox = QVBoxLayout() - self.innerBox.addSpacing(hB) + self.innerBox.addSpacing(16) self.innerBox.addWidget(self.nwLabel) self.innerBox.addWidget(self.nwInfo) - self.innerBox.addSpacing(hA) + self.innerBox.addSpacing(8) self.innerBox.addWidget(self.mainStack) - self.innerBox.addSpacing(hB) + self.innerBox.addSpacing(16) self.innerBox.addLayout(self.btnBox) self.outerBox = QHBoxLayout() self.outerBox.addWidget(self.nwLogo, 3, QtAlignRightTop) self.outerBox.addLayout(self.innerBox, 9) - self.outerBox.setContentsMargins(hF, hE, hC, hE) + self.outerBox.setContentsMargins(128, 48, 24, 48) self.setLayout(self.outerBox) self.setSizeGripEnabled(True) @@ -183,7 +175,7 @@ class GuiWelcome(NDialog): def paintEvent(self, event: QPaintEvent) -> None: """Overload the paint event to draw the background image.""" hWin = self.height() - hPix = min(hWin, self._hPx) + hPix = min(hWin, 600) tMode = Qt.TransformationMode.SmoothTransformation painter = QPainter(self) painter.fillRect(self.rect(), self.bgColor) @@ -309,11 +301,10 @@ class _OpenProjectPage(QWidget): self._selectFirstItem() - mPx = CONFIG.pxInt(4) baseCol = cssCol(self.palette().base().color(), PANEL_ALPHA) self.setStyleSheet( f"QListView {{border: none; background: {baseCol};}} " - f"QLineEdit {{border: none; background: {baseCol}; padding: {mPx}px;}} " + f"QLineEdit {{border: none; background: {baseCol}; padding: 4px;}} " ) return @@ -370,9 +361,9 @@ class _OpenProjectPage(QWidget): """Open the custom context menu.""" ctxMenu = QMenu(self) ctxMenu.setObjectName("ContextMenu") # Used for testing - action = ctxMenu.addAction(self.tr("Open Project")) + action = qtAddAction(ctxMenu, self.tr("Open Project")) action.triggered.connect(self.openSelectedItem) - action = ctxMenu.addAction(self.tr("Remove Project")) + action = qtAddAction(ctxMenu, self.tr("Remove Project")) action.triggered.connect(self._deleteSelectedItem) ctxMenu.exec(self.mapToGlobal(pos)) ctxMenu.deleteLater() @@ -400,11 +391,10 @@ class _ProjectListItem(QStyledItemDelegate): fPx = SHARED.theme.fontPixelSize fPt = SHARED.theme.fontPointSize tPx = round(1.2 * fPx) - mPx = CONFIG.pxInt(4) iPx = tPx + fPx - self._pPx = (mPx//2, 3*mPx//2, iPx + mPx, mPx, mPx + tPx) # Painter coordinates - self._hPx = 2*mPx + tPx + fPx # Fixed height + self._pPx = (iPx + 4, tPx + 4) # Painter coordinates + self._hPx = 8 + tPx + fPx # Fixed height self._tFont = QApplication.font() self._tFont.setPointSizeF(1.2*fPt) @@ -423,7 +413,7 @@ class _ProjectListItem(QStyledItemDelegate): rect = opt.rect title, _, details = index.data() tFlag = Qt.TextFlag.TextSingleLine - ix, iy, x, y1, y2 = self._pPx + x, y = self._pPx painter.save() if opt.state & QtSelected == QtSelected: @@ -431,12 +421,12 @@ class _ProjectListItem(QStyledItemDelegate): painter.fillRect(rect, QApplication.palette().highlight()) painter.setOpacity(1.0) - painter.drawPixmap(ix, rect.top() + iy, self._icon) + painter.drawPixmap(2, rect.top() + 6, self._icon) painter.setFont(self._tFont) - painter.drawText(rect.adjusted(x, y1, 0, 0), tFlag, title) + painter.drawText(rect.adjusted(x, 4, 0, 0), tFlag, title) painter.setFont(self._dFont) painter.setPen(self._dPen) - painter.drawText(rect.adjusted(x, y2, 0, 0), tFlag, details) + painter.drawText(rect.adjusted(x, y, 0, 0), tFlag, details) painter.restore() return @@ -557,7 +547,6 @@ class _NewProjectForm(QWidget): iPx = SHARED.theme.baseIconHeight iSz = SHARED.theme.baseIconSize - sPx = CONFIG.pxInt(16) # Project Settings # ================ @@ -592,15 +581,15 @@ class _NewProjectForm(QWidget): self.fillMenu = QMenu(self.browseFill) - self.fillBlank = self.fillMenu.addAction(self.tr("Create a fresh project")) + self.fillBlank = qtAddAction(self.fillMenu, self.tr("Create a fresh project")) self.fillBlank.setIcon(SHARED.theme.getIcon("document")) self.fillBlank.triggered.connect(self._setFillBlank) - self.fillSample = self.fillMenu.addAction(self.tr("Create an example project")) + self.fillSample = qtAddAction(self.fillMenu, self.tr("Create an example project")) self.fillSample.setIcon(SHARED.theme.getIcon("document_add", "blue")) self.fillSample.triggered.connect(self._setFillSample) - self.fillCopy = self.fillMenu.addAction(self.tr("Copy an existing project")) + self.fillCopy = qtAddAction(self.fillMenu, self.tr("Copy an existing project")) self.fillCopy.setIcon(SHARED.theme.getIcon("project_copy", "green")) self.fillCopy.triggered.connect(self._setFillCopy) @@ -675,7 +664,7 @@ class _NewProjectForm(QWidget): self.extraBox = QVBoxLayout() self.extraBox.addWidget(QLabel("{0}".format(self.tr("Chapters and Scenes")), self)) self.extraBox.addLayout(self.novelForm) - self.extraBox.addSpacing(sPx) + self.extraBox.addSpacing(16) self.extraBox.addWidget(QLabel("{0}".format(self.tr("Project Notes")), self)) self.extraBox.addLayout(self.notesForm) self.extraBox.setContentsMargins(0, 0, 0, 0) @@ -687,7 +676,7 @@ class _NewProjectForm(QWidget): self.formBox = QVBoxLayout() self.formBox.addWidget(QLabel("{0}".format(self.tr("Create New Project")), self)) self.formBox.addLayout(self.projectForm) - self.formBox.addSpacing(sPx) + self.formBox.addSpacing(16) self.formBox.addWidget(self.extraWidget) self.formBox.addStretch(1) diff --git a/novelwriter/tools/writingstats.py b/novelwriter/tools/writingstats.py index bc59ab62..acf8d308 100644 --- a/novelwriter/tools/writingstats.py +++ b/novelwriter/tools/writingstats.py @@ -82,26 +82,18 @@ class GuiWritingStats(NToolDialog): pOptions = SHARED.project.options self.setWindowTitle(self.tr("Writing Statistics")) - self.setMinimumWidth(CONFIG.pxInt(420)) - self.setMinimumHeight(CONFIG.pxInt(400)) + self.setMinimumWidth(420) + self.setMinimumHeight(400) self.resize( - CONFIG.pxInt(pOptions.getInt("GuiWritingStats", "winWidth", 550)), - CONFIG.pxInt(pOptions.getInt("GuiWritingStats", "winHeight", 500)) + pOptions.getInt("GuiWritingStats", "winWidth", 550), + pOptions.getInt("GuiWritingStats", "winHeight", 500), ) # List Box - wCol0 = CONFIG.pxInt( - pOptions.getInt("GuiWritingStats", "widthCol0", 180) - ) - wCol1 = CONFIG.pxInt( - pOptions.getInt("GuiWritingStats", "widthCol1", 80) - ) - wCol2 = CONFIG.pxInt( - pOptions.getInt("GuiWritingStats", "widthCol2", 80) - ) - wCol3 = CONFIG.pxInt( - pOptions.getInt("GuiWritingStats", "widthCol3", 80) - ) + wCol0 = pOptions.getInt("GuiWritingStats", "widthCol0", 180) + wCol1 = pOptions.getInt("GuiWritingStats", "widthCol1", 80) + wCol2 = pOptions.getInt("GuiWritingStats", "widthCol2", 80) + wCol3 = pOptions.getInt("GuiWritingStats", "widthCol3", 80) self.listBox = QTreeWidget(self) self.listBox.setHeaderLabels([ @@ -133,7 +125,6 @@ class GuiWritingStats(NToolDialog): # Word Bar self.barHeight = int(round(0.5*SHARED.theme.fontPixelSize)) - self.barWidth = CONFIG.pxInt(200) self.barImage = QPixmap(self.barHeight, self.barHeight) self.barImage.fill(self.palette().highlight().color()) @@ -262,25 +253,27 @@ class GuiWritingStats(NToolDialog): self.optsBox.addWidget(self.histMax, 0) # Buttons + self.saveJSON = QAction(self.tr("JSON Data File (.json)"), self) + self.saveJSON.triggered.connect(qtLambda(self._saveData, self.FMT_JSON)) + + self.saveCSV = QAction(self.tr("CSV Data File (.csv)"), self) + self.saveCSV.triggered.connect(qtLambda(self._saveData, self.FMT_CSV)) + + self.saveMenu = QMenu(self) + self.saveMenu.addAction(self.saveJSON) + self.saveMenu.addAction(self.saveCSV) + self.buttonBox = QDialogButtonBox(self) self.buttonBox.rejected.connect(self._doClose) self.btnClose = self.buttonBox.addButton(QtDialogClose) - self.btnClose.setAutoDefault(False) + if self.btnClose: + self.btnClose.setAutoDefault(False) self.btnSave = self.buttonBox.addButton(self.tr("Save As"), QtRoleAction) - self.btnSave.setAutoDefault(False) - - self.saveMenu = QMenu(self) - self.btnSave.setMenu(self.saveMenu) - - self.saveJSON = QAction(self.tr("JSON Data File (.json)"), self) - self.saveJSON.triggered.connect(qtLambda(self._saveData, self.FMT_JSON)) - self.saveMenu.addAction(self.saveJSON) - - self.saveCSV = QAction(self.tr("CSV Data File (.csv)"), self) - self.saveCSV.triggered.connect(qtLambda(self._saveData, self.FMT_CSV)) - self.saveMenu.addAction(self.saveCSV) + if self.btnSave: + self.btnSave.setAutoDefault(False) + self.btnSave.setMenu(self.saveMenu) # Assemble self.outerBox = QGridLayout() @@ -328,14 +321,10 @@ class GuiWritingStats(NToolDialog): """Save the state of the window, clear cache, end close.""" self.logData = [] - winWidth = CONFIG.rpxInt(self.width()) - winHeight = CONFIG.rpxInt(self.height()) - widthCol0 = CONFIG.rpxInt(self.listBox.columnWidth(0)) - widthCol1 = CONFIG.rpxInt(self.listBox.columnWidth(1)) - widthCol2 = CONFIG.rpxInt(self.listBox.columnWidth(2)) - widthCol3 = CONFIG.rpxInt(self.listBox.columnWidth(3)) + header = self.listBox.header() + sortCol = self.listBox.sortColumn() - sortOrder = self.listBox.header().sortIndicatorOrder() + sortOrder = header.sortIndicatorOrder() if header else 0 incNovel = self.incNovel.isChecked() incNotes = self.incNotes.isChecked() hideZeros = self.hideZeros.isChecked() @@ -346,12 +335,12 @@ class GuiWritingStats(NToolDialog): logger.debug("Saving State: GuiWritingStats") pOptions = SHARED.project.options - pOptions.setValue("GuiWritingStats", "winWidth", winWidth) - pOptions.setValue("GuiWritingStats", "winHeight", winHeight) - pOptions.setValue("GuiWritingStats", "widthCol0", widthCol0) - pOptions.setValue("GuiWritingStats", "widthCol1", widthCol1) - pOptions.setValue("GuiWritingStats", "widthCol2", widthCol2) - pOptions.setValue("GuiWritingStats", "widthCol3", widthCol3) + pOptions.setValue("GuiWritingStats", "winWidth", self.width()) + pOptions.setValue("GuiWritingStats", "winHeight", self.height()) + pOptions.setValue("GuiWritingStats", "widthCol0", self.listBox.columnWidth(0)) + pOptions.setValue("GuiWritingStats", "widthCol1", self.listBox.columnWidth(1)) + pOptions.setValue("GuiWritingStats", "widthCol2", self.listBox.columnWidth(2)) + pOptions.setValue("GuiWritingStats", "widthCol3", self.listBox.columnWidth(3)) pOptions.setValue("GuiWritingStats", "sortCol", sortCol) pOptions.setValue("GuiWritingStats", "sortOrder", sortOrder) pOptions.setValue("GuiWritingStats", "incNovel", incNovel) From 72ed5d84f6c1a4569fe0d6964c10778a12ed47b9 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Mon, 13 Jan 2025 19:42:48 +0100 Subject: [PATCH 7/8] Fix minor issues --- novelwriter/dialogs/docmerge.py | 2 +- novelwriter/dialogs/projectsettings.py | 8 +++----- novelwriter/gui/projtree.py | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/novelwriter/dialogs/docmerge.py b/novelwriter/dialogs/docmerge.py index fe77dc60..2033b118 100644 --- a/novelwriter/dialogs/docmerge.py +++ b/novelwriter/dialogs/docmerge.py @@ -88,7 +88,7 @@ class GuiDocMerge(NDialog): self.buttonBox.rejected.connect(self.reject) self.resetButton = self.buttonBox.addButton(QtDialogReset) - if self.resetButton is not None: + if self.resetButton: self.resetButton.clicked.connect(self._resetList) # Assemble diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py index 1c95d242..414edaa3 100644 --- a/novelwriter/dialogs/projectsettings.py +++ b/novelwriter/dialogs/projectsettings.py @@ -210,16 +210,14 @@ class GuiProjectSettings(NDialog): def _saveSettings(self) -> None: """Save GUI settings.""" - winWidth = self.width() - winHeight = self.height() statusColW = self.statusPage.columnWidth() importColW = self.importPage.columnWidth() replaceColW = self.replacePage.columnWidth() logger.debug("Saving State: GuiProjectSettings") options = SHARED.project.options - options.setValue("GuiProjectSettings", "winWidth", winWidth) - options.setValue("GuiProjectSettings", "winHeight", winHeight) + options.setValue("GuiProjectSettings", "winWidth", self.width()) + options.setValue("GuiProjectSettings", "winHeight", self.height()) options.setValue("GuiProjectSettings", "statusColW", statusColW) options.setValue("GuiProjectSettings", "importColW", importColW) options.setValue("GuiProjectSettings", "replaceColW", replaceColW) @@ -391,7 +389,7 @@ class _StatusPage(NFixedPage): self.labelText.textEdited.connect(self._onNameEdit) buttonStyle = ( - f"QToolButton {{padding: 0 {4}px;}} " + "QToolButton {padding: 0 4px;} " "QToolButton::menu-indicator {image: none;}" ) diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 2b28442d..181f6f31 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -1228,10 +1228,10 @@ class _TreeContextMenu(QMenu): if len(self._indices) > 1: mSub = qtAddMenu(self, self.tr("Set Active to ...")) aOne = qtAddAction(mSub, self._tree.trActive) - aOne.setIcon(SHARED.theme.getIcon("checked")) + aOne.setIcon(SHARED.theme.getIcon("checked", "green")) aOne.triggered.connect(qtLambda(self._iterItemActive, True)) aTwo = qtAddAction(mSub, self._tree.trInactive) - aTwo.setIcon(SHARED.theme.getIcon("unchecked")) + aTwo.setIcon(SHARED.theme.getIcon("unchecked", "red")) aTwo.triggered.connect(qtLambda(self._iterItemActive, False)) else: action = qtAddAction(self, self.tr("Toggle Active")) From 0b7c31b6a9587f8fe3a6fe6b599f9514619f8413 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Mon, 13 Jan 2025 19:43:04 +0100 Subject: [PATCH 8/8] Fix warnings in tests --- tests/test_core/test_core_buildsettings.py | 2 +- tests/test_dialogs/test_dlg_about.py | 4 +- tests/test_dialogs/test_dlg_preferences.py | 13 +- tests/test_gui/test_gui_doceditor.py | 17 +- tests/test_gui/test_gui_docviewer.py | 13 +- tests/test_gui/test_gui_projtree.py | 4 +- tests/test_tools/test_tools_manusbuild.py | 8 +- tests/test_tools/test_tools_manuscript.py | 20 +- tests/test_tools/test_tools_manussettings.py | 80 ++++-- tests/test_tools/test_tools_writingstats.py | 273 +++++++++++++++---- tests/tools.py | 4 +- 11 files changed, 336 insertions(+), 102 deletions(-) diff --git a/tests/test_core/test_core_buildsettings.py b/tests/test_core/test_core_buildsettings.py index 1ae1360a..2b21b673 100644 --- a/tests/test_core/test_core_buildsettings.py +++ b/tests/test_core/test_core_buildsettings.py @@ -398,7 +398,7 @@ def testCoreBuildSettings_Collection(monkeypatch, mockGUI, fncPath: Path, mockRn buildTwo.setName("Build Two") buildIDTwo = buildTwo.buildID - # Check that we can extract infor about the builds + # Check that we can extract info about the builds builds.setBuild(buildTwo) assert len(builds) == 2 assert buildsFile.exists() diff --git a/tests/test_dialogs/test_dlg_about.py b/tests/test_dialogs/test_dlg_about.py index 0f659035..bde9ad7d 100644 --- a/tests/test_dialogs/test_dlg_about.py +++ b/tests/test_dialogs/test_dlg_about.py @@ -41,7 +41,9 @@ def testDlgAbout_NWDialog(qtbot, monkeypatch, nwGUI): msgAbout = SHARED.findTopLevelWidget(GuiAbout) assert isinstance(msgAbout, GuiAbout) - assert msgAbout.txtCredits.document().characterCount() > 100 + document = msgAbout.txtCredits.document() + assert document is not None + assert document.characterCount() > 100 with monkeypatch.context() as mp: mp.setattr("novelwriter.config.Config.assetPath", lambda *a: Path("whatever")) diff --git a/tests/test_dialogs/test_dlg_preferences.py b/tests/test_dialogs/test_dlg_preferences.py index e5bed49a..63dbc91a 100644 --- a/tests/test_dialogs/test_dlg_preferences.py +++ b/tests/test_dialogs/test_dlg_preferences.py @@ -96,6 +96,7 @@ def testDlgPreferences_Actions(qtbot, monkeypatch, nwGUI): # Check Navigation vBar = prefs.mainForm.verticalScrollBar() + assert vBar is not None old = -1 with qtbot.waitSignal(vBar.valueChanged) as value: prefs.sidebar.button(1).click() @@ -119,12 +120,16 @@ def testDlgPreferences_Actions(qtbot, monkeypatch, nwGUI): # Check Save Button prefs.show() with qtbot.waitSignal(prefs.newPreferencesReady) as signal: - prefs.buttonBox.button(QtDialogSave).click() + button = prefs.buttonBox.button(QtDialogSave) + assert button is not None + button.click() assert signal.args == [False, False, False, False] # Check Close Button prefs.show() - prefs.buttonBox.button(QtDialogCancel).click() + button = prefs.buttonBox.button(QtDialogCancel) + assert button is not None + button.click() assert prefs.isHidden() is True # Close Using Escape Key @@ -313,7 +318,9 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths): with monkeypatch.context() as mp: mp.setattr(QFontDatabase, "families", lambda *a: ["TestFont"]) with qtbot.waitSignal(prefs.newPreferencesReady) as signal: - prefs.buttonBox.button(QtDialogSave).click() + button = prefs.buttonBox.button(QtDialogSave) + assert button is not None + button.click() assert signal.args == [True, True, True, True] # Check Settings diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py index 32198bec..428807f3 100644 --- a/tests/test_gui/test_gui_doceditor.py +++ b/tests/test_gui/test_gui_doceditor.py @@ -441,6 +441,9 @@ def testGuiEditor_ContextMenu(monkeypatch, qtbot, nwGUI, projPath, mockRnd): ctxMenu.deleteLater() # Copy Text + clipboard = QApplication.clipboard() + assert clipboard is not None + ctxMenu = getMenuForPos(docEditor, 31, True) assert ctxMenu is not None assert docEditor.textCursor().selectedText() == "text" @@ -448,14 +451,14 @@ def testGuiEditor_ContextMenu(monkeypatch, qtbot, nwGUI, projPath, mockRnd): assert actions == [ "Cut", "Copy", "Paste", "Select All", "Select Word", "Select Paragraph" ] - QApplication.clipboard().clear() + clipboard.clear() ctxMenu.actions()[1].trigger() - assert QApplication.clipboard().text(QClipboard.Mode.Clipboard) == "text" + assert clipboard.text(QClipboard.Mode.Clipboard) == "text" # Cut Text - QApplication.clipboard().clear() + clipboard.clear() ctxMenu.actions()[0].trigger() - assert QApplication.clipboard().text(QClipboard.Mode.Clipboard) == "text" + assert clipboard.text(QClipboard.Mode.Clipboard) == "text" assert "text" not in docEditor.getText() # Paste Text @@ -572,7 +575,9 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd): # Select/Cut/Copy/Paste/Undo/Redo # =============================== - QApplication.clipboard().clear() + clipboard = QApplication.clipboard() + assert clipboard is not None + clipboard.clear() # Select All assert docEditor.docAction(nwDocAction.SEL_ALL) is True @@ -624,7 +629,7 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd): assert newPara[5] == ipsumText[4] assert newPara[6] == ipsumText[2] - QApplication.clipboard().clear() + clipboard.clear() # Emphasis/Undo/Redo # ================== diff --git a/tests/test_gui/test_gui_docviewer.py b/tests/test_gui/test_gui_docviewer.py index 2eb8cb59..65c31a24 100644 --- a/tests/test_gui/test_gui_docviewer.py +++ b/tests/test_gui/test_gui_docviewer.py @@ -91,18 +91,19 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum): docViewer.setTextCursor(cursor) docViewer._makeSelection(QTextCursor.SelectionType.WordUnderCursor) - qClip = QApplication.clipboard() - qClip.clear() + clipboard = QApplication.clipboard() + assert clipboard is not None + clipboard.clear() # Cut assert docViewer.docAction(nwDocAction.CUT) is True - assert qClip.text() == "laoreet" - qClip.clear() + assert clipboard.text() == "laoreet" + clipboard.clear() # Copy assert docViewer.docAction(nwDocAction.COPY) is True - assert qClip.text() == "laoreet" - qClip.clear() + assert clipboard.text() == "laoreet" + clipboard.clear() # Select Paragraph assert docViewer.docAction(nwDocAction.SEL_PARA) is True diff --git a/tests/test_gui/test_gui_projtree.py b/tests/test_gui/test_gui_projtree.py index de3163d2..a38c3644 100644 --- a/tests/test_gui/test_gui_projtree.py +++ b/tests/test_gui/test_gui_projtree.py @@ -1148,7 +1148,9 @@ def testGuiProjTree_ContextMenu(qtbot, monkeypatch, nwGUI, projPath, mockRnd): def getTransformSubMenu(menu: QMenu) -> list[str]: for action in menu.actions(): if action.text() == "Transform ...": - return [x.text() for x in action.menu().actions() if x.text()] + submenu = action.menu() + assert submenu is not None + return [x.text() for x in submenu.actions() if x.text()] return [] # Context Menu on Document File Item diff --git a/tests/test_tools/test_tools_manusbuild.py b/tests/test_tools/test_tools_manusbuild.py index 07b0c1c5..9a86eff6 100644 --- a/tests/test_tools/test_tools_manusbuild.py +++ b/tests/test_tools/test_tools_manusbuild.py @@ -95,7 +95,9 @@ def testToolManuscriptBuild_Main( assert (fncPath / "TestBuild").with_suffix(nwLabels.BUILD_EXT[fmt]).exists() lastFmt = fmt - manus._dialogButtonClicked(manus.buttonBox.button(QtDialogClose)) + button = manus.buttonBox.button(QtDialogClose) + assert button is not None + manus._dialogButtonClicked(button) manus.deleteLater() assert build.lastBuildName == "TestBuild" @@ -150,5 +152,7 @@ def testToolManuscriptBuild_Main( assert lastUrl.startswith("file://") # Finish - manus._dialogButtonClicked(manus.buttonBox.button(QtDialogClose)) + button = manus.buttonBox.button(QtDialogClose) + assert button is not None + manus._dialogButtonClicked(button) # qtbot.stop() diff --git a/tests/test_tools/test_tools_manuscript.py b/tests/test_tools/test_tools_manuscript.py index bdd5b900..4e7959cb 100644 --- a/tests/test_tools/test_tools_manuscript.py +++ b/tests/test_tools/test_tools_manuscript.py @@ -67,7 +67,9 @@ def testToolManuscript_Init(monkeypatch, qtbot, nwGUI, projPath, mockRnd): # Build a preview manus.buildList.clearSelection() manus.buildList.setCurrentRow(0) - with qtbot.waitSignal(manus.docPreview.document().contentsChanged): + document = manus.docPreview.document() + assert document is not None + with qtbot.waitSignal(document.contentsChanged): manus.btnPreview.click() assert manus.docPreview.toPlainText().strip() == allText @@ -113,7 +115,9 @@ def testToolManuscript_Builds(qtbot, nwGUI, projPath): with qtbot.waitSignal(bSettings.newSettingsReady, timeout=5000): bSettings.newSettingsReady.connect(_testNewSettingsReady) - bSettings.buttonBox.button(QtDialogSave).click() + button = bSettings.buttonBox.button(QtDialogSave) + assert button is not None + button.click() assert isinstance(build, BuildSettings) assert build.name == "Test Build" @@ -132,7 +136,9 @@ def testToolManuscript_Builds(qtbot, nwGUI, projPath): with qtbot.waitSignal(bSettings.newSettingsReady, timeout=5000): bSettings.newSettingsReady.connect(_testNewSettingsReady) - bSettings.buttonBox.button(QtDialogApply).click() # Should leave the dialog open + button = bSettings.buttonBox.button(QtDialogApply) + assert button is not None + button.click() # Should leave the dialog open assert isinstance(build, BuildSettings) assert build.name == "Test Build" @@ -210,7 +216,9 @@ def testToolManuscript_Features(monkeypatch, qtbot, nwGUI, projPath, mockRnd): manus._builds.setBuild(build) manus.buildList.setCurrentRow(0) - with qtbot.waitSignal(manus.docPreview.document().contentsChanged): + document = manus.docPreview.document() + assert document is not None + with qtbot.waitSignal(document.contentsChanged): manus.btnPreview.click() assert manus.docPreview.toPlainText().strip() != "" @@ -304,7 +312,9 @@ def testToolManuscript_Print(monkeypatch, qtbot, nwGUI, projPath): manus.loadContent() manus.buildList.setCurrentRow(0) - with qtbot.waitSignal(manus.docPreview.document().contentsChanged): + document = manus.docPreview.document() + assert document is not None + with qtbot.waitSignal(document.contentsChanged): manus.btnPreview.click() assert manus.docPreview.toPlainText().strip() != "" diff --git a/tests/test_tools/test_tools_manussettings.py b/tests/test_tools/test_tools_manussettings.py index 256feee7..13b57464 100644 --- a/tests/test_tools/test_tools_manussettings.py +++ b/tests/test_tools/test_tools_manussettings.py @@ -51,13 +51,19 @@ def testToolBuildSettings_Init(qtbot, nwGUI, projPath, mockRnd): bSettings.loadContent() # Flip through pages - bSettings.sidebar._group.button(bSettings.OPT_FORMATTING + 1).click() + button = bSettings.sidebar._group.button(bSettings.OPT_FORMATTING + 1) + assert button is not None + button.click() assert isinstance(bSettings.toolStack.currentWidget(), _FormattingTab) - bSettings.sidebar._group.button(bSettings.OPT_HEADINGS).click() + button = bSettings.sidebar._group.button(bSettings.OPT_HEADINGS) + assert button is not None + button.click() assert isinstance(bSettings.toolStack.currentWidget(), _HeadingsTab) - bSettings.sidebar._group.button(bSettings.OPT_FILTERS).click() + button = bSettings.sidebar._group.button(bSettings.OPT_FILTERS) + assert button is not None + button.click() assert isinstance(bSettings.toolStack.currentWidget(), _FilterTab) # Check dialog buttons @@ -72,7 +78,9 @@ def testToolBuildSettings_Init(qtbot, nwGUI, projPath, mockRnd): # Capture Apply button with qtbot.waitSignal(bSettings.newSettingsReady, timeout=5000): bSettings.newSettingsReady.connect(_testNewSettingsReady) - bSettings._dialogButtonClicked(bSettings.buttonBox.button(QtDialogApply)) + button = bSettings.buttonBox.button(QtDialogApply) + assert button is not None + bSettings._dialogButtonClicked(button) assert triggered @@ -81,7 +89,9 @@ def testToolBuildSettings_Init(qtbot, nwGUI, projPath, mockRnd): with qtbot.waitSignal(bSettings.newSettingsReady, timeout=5000): bSettings.newSettingsReady.connect(_testNewSettingsReady) - bSettings._dialogButtonClicked(bSettings.buttonBox.button(QtDialogSave)) + button = bSettings.buttonBox.button(QtDialogSave) + assert button is not None + bSettings._dialogButtonClicked(button) assert triggered @@ -98,7 +108,9 @@ def testToolBuildSettings_Init(qtbot, nwGUI, projPath, mockRnd): assert triggered # Finish - bSettings._dialogButtonClicked(bSettings.buttonBox.button(QtDialogClose)) + button = bSettings.buttonBox.button(QtDialogClose) + assert button is not None + bSettings._dialogButtonClicked(button) # qtbot.stop() @@ -129,7 +141,9 @@ def testToolBuildSettings_Filter(qtbot, nwGUI, projPath, mockRnd): bSettings.loadContent() filterTab = bSettings.optTabSelect - bSettings.sidebar._group.button(bSettings.OPT_FILTERS).click() + button = bSettings.sidebar._group.button(bSettings.OPT_FILTERS) + assert button is not None + button.click() assert bSettings.toolStack.currentWidget() is filterTab # Check content @@ -308,7 +322,9 @@ def testToolBuildSettings_Filter(qtbot, nwGUI, projPath, mockRnd): ] # Finish - bSettings._dialogButtonClicked(bSettings.buttonBox.button(QtDialogClose)) + button = bSettings.buttonBox.button(QtDialogClose) + assert button is not None + bSettings._dialogButtonClicked(button) # qtbot.stop() @@ -339,7 +355,9 @@ def testToolBuildSettings_Headings(qtbot, nwGUI): bSettings.loadContent() headTab = bSettings.optTabHeadings - bSettings.sidebar._group.button(bSettings.OPT_HEADINGS).click() + button = bSettings.sidebar._group.button(bSettings.OPT_HEADINGS) + assert button is not None + button.click() assert bSettings.toolStack.currentWidget() is headTab # Check initial values @@ -478,7 +496,9 @@ def testToolBuildSettings_Headings(qtbot, nwGUI): assert build.getBool("headings.hideSection") is True # Finish - bSettings._dialogButtonClicked(bSettings.buttonBox.button(QtDialogClose)) + button = bSettings.buttonBox.button(QtDialogClose) + assert button is not None + bSettings._dialogButtonClicked(button) # qtbot.stop() @@ -501,7 +521,9 @@ def testToolBuildSettings_FormatTextContent(qtbot, nwGUI): bSettings.loadContent() fmtTab = bSettings.optTabFormatting - bSettings.sidebar._group.button(bSettings.OPT_FORMATTING + 1).click() + button = bSettings.sidebar._group.button(bSettings.OPT_FORMATTING + 1) + assert button is not None + button.click() assert bSettings.toolStack.currentWidget() is fmtTab # Check initial values @@ -538,7 +560,9 @@ def testToolBuildSettings_FormatTextContent(qtbot, nwGUI): assert build.getBool("text.addNoteHeadings") is True # Finish - bSettings._dialogButtonClicked(bSettings.buttonBox.button(QtDialogClose)) + button = bSettings.buttonBox.button(QtDialogClose) + assert button is not None + bSettings._dialogButtonClicked(button) # qtbot.stop() @@ -563,7 +587,9 @@ def testToolBuildSettings_FormatTextFormat(monkeypatch, qtbot, nwGUI): bSettings.loadContent() fmtTab = bSettings.optTabFormatting - bSettings.sidebar._group.button(bSettings.OPT_FORMATTING + 2).click() + button = bSettings.sidebar._group.button(bSettings.OPT_FORMATTING + 2) + assert button is not None + button.click() assert bSettings.toolStack.currentWidget() is fmtTab # Check initial values @@ -610,7 +636,9 @@ def testToolBuildSettings_FormatTextFormat(monkeypatch, qtbot, nwGUI): assert fmtTab._textFont == font # Finish - bSettings._dialogButtonClicked(bSettings.buttonBox.button(QtDialogClose)) + button = bSettings.buttonBox.button(QtDialogClose) + assert button is not None + bSettings._dialogButtonClicked(button) # qtbot.stop() @@ -629,7 +657,9 @@ def testToolBuildSettings_FormatFirstLineIndent(monkeypatch, qtbot, nwGUI): bSettings.loadContent() fmtTab = bSettings.optTabFormatting - bSettings.sidebar._group.button(bSettings.OPT_FORMATTING + 3).click() + button = bSettings.sidebar._group.button(bSettings.OPT_FORMATTING + 3) + assert button is not None + button.click() assert bSettings.toolStack.currentWidget() is fmtTab # Check initial values @@ -650,7 +680,9 @@ def testToolBuildSettings_FormatFirstLineIndent(monkeypatch, qtbot, nwGUI): assert build.getBool("format.indentFirstPar") is True # Finish - bSettings._dialogButtonClicked(bSettings.buttonBox.button(QtDialogClose)) + button = bSettings.buttonBox.button(QtDialogClose) + assert button is not None + bSettings._dialogButtonClicked(button) # qtbot.stop() @@ -674,7 +706,9 @@ def testToolBuildSettings_FormatPageLayout(monkeypatch, qtbot, nwGUI): bSettings.loadContent() fmtTab = bSettings.optTabFormatting - bSettings.sidebar._group.button(bSettings.OPT_FORMATTING + 4).click() + button = bSettings.sidebar._group.button(bSettings.OPT_FORMATTING + 4) + assert button is not None + button.click() assert bSettings.toolStack.currentWidget() is fmtTab # Check initial values @@ -704,7 +738,9 @@ def testToolBuildSettings_FormatPageLayout(monkeypatch, qtbot, nwGUI): assert fmtTab.rightMargin.value() == 1.5 # Finish - bSettings._dialogButtonClicked(bSettings.buttonBox.button(QtDialogClose)) + button = bSettings.buttonBox.button(QtDialogClose) + assert button is not None + bSettings._dialogButtonClicked(button) # qtbot.stop() @@ -728,7 +764,9 @@ def testToolBuildSettings_FormatOutput(qtbot, nwGUI): bSettings.loadContent() fmtTab = bSettings.optTabFormatting - bSettings.sidebar._group.button(bSettings.OPT_FORMATTING + 5).click() + button = bSettings.sidebar._group.button(bSettings.OPT_FORMATTING + 5) + assert button is not None + button.click() assert bSettings.toolStack.currentWidget() is fmtTab # Check initial values @@ -769,5 +807,7 @@ def testToolBuildSettings_FormatOutput(qtbot, nwGUI): assert fmtTab.odtPageHeader.text() == nwHeadFmt.DOC_AUTO # Finish - bSettings._dialogButtonClicked(bSettings.buttonBox.button(QtDialogClose)) + button = bSettings.buttonBox.button(QtDialogClose) + assert button is not None + bSettings._dialogButtonClicked(button) # qtbot.stop() diff --git a/tests/test_tools/test_tools_writingstats.py b/tests/test_tools/test_tools_writingstats.py index 4902c3fd..53475726 100644 --- a/tests/test_tools/test_tools_writingstats.py +++ b/tests/test_tools/test_tools_writingstats.py @@ -125,14 +125,37 @@ def testToolWritingStats_Export(qtbot, monkeypatch, nwGUI, projPath, tstPaths): assert sessLog.notesWords.text() == "{:n}".format(275) assert sessLog.totalWords.text() == "{:n}".format(875) - assert sessLog.listBox.topLevelItem(0).text(sessLog.C_COUNT) == "{:n}".format(1) - assert sessLog.listBox.topLevelItem(1).text(sessLog.C_COUNT) == "{:n}".format(-200) - assert sessLog.listBox.topLevelItem(2).text(sessLog.C_COUNT) == "{:n}".format(300) - assert sessLog.listBox.topLevelItem(3).text(sessLog.C_COUNT) == "{:n}".format(-120) - assert sessLog.listBox.topLevelItem(4).text(sessLog.C_COUNT) == "{:n}".format(-20) - assert sessLog.listBox.topLevelItem(5).text(sessLog.C_COUNT) == "{:n}".format(40) - assert sessLog.listBox.topLevelItem(6).text(sessLog.C_COUNT) == "{:n}".format(-400) - assert sessLog.listBox.topLevelItem(7).text(sessLog.C_COUNT) == "{:n}".format(200) + item = sessLog.listBox.topLevelItem(0) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(1) + + item = sessLog.listBox.topLevelItem(1) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(-200) + + item = sessLog.listBox.topLevelItem(2) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(300) + + item = sessLog.listBox.topLevelItem(3) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(-120) + + item = sessLog.listBox.topLevelItem(4) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(-20) + + item = sessLog.listBox.topLevelItem(5) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(40) + + item = sessLog.listBox.topLevelItem(6) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(-400) + + item = sessLog.listBox.topLevelItem(7) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(200) assert sessLog._saveData(sessLog.FMT_CSV) assert sessLog._saveData(sessLog.FMT_JSON) @@ -205,14 +228,37 @@ def testToolWritingStats_Filters(qtbot, monkeypatch, nwGUI, projPath, tstPaths): sessLog.populateGUI() sessLog.listBox.sortByColumn(sessLog.C_TIME, Qt.SortOrder.AscendingOrder) - assert sessLog.listBox.topLevelItem(0).text(sessLog.C_COUNT) == "{:n}".format(1) - assert sessLog.listBox.topLevelItem(1).text(sessLog.C_COUNT) == "{:n}".format(-200) - assert sessLog.listBox.topLevelItem(2).text(sessLog.C_COUNT) == "{:n}".format(300) - assert sessLog.listBox.topLevelItem(3).text(sessLog.C_COUNT) == "{:n}".format(-120) - assert sessLog.listBox.topLevelItem(4).text(sessLog.C_COUNT) == "{:n}".format(-20) - assert sessLog.listBox.topLevelItem(5).text(sessLog.C_COUNT) == "{:n}".format(40) - assert sessLog.listBox.topLevelItem(6).text(sessLog.C_COUNT) == "{:n}".format(-400) - assert sessLog.listBox.topLevelItem(7).text(sessLog.C_COUNT) == "{:n}".format(200) + item = sessLog.listBox.topLevelItem(0) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(1) + + item = sessLog.listBox.topLevelItem(1) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(-200) + + item = sessLog.listBox.topLevelItem(2) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(300) + + item = sessLog.listBox.topLevelItem(3) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(-120) + + item = sessLog.listBox.topLevelItem(4) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(-20) + + item = sessLog.listBox.topLevelItem(5) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(40) + + item = sessLog.listBox.topLevelItem(6) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(-400) + + item = sessLog.listBox.topLevelItem(7) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(200) # No Novel Files qtbot.mouseClick(sessLog.incNovel, QtMouseLeft) @@ -222,14 +268,37 @@ def testToolWritingStats_Filters(qtbot, monkeypatch, nwGUI, projPath, tstPaths): with open(jsonStats, mode="r", encoding="utf-8") as inFile: jsonData = json.loads(inFile.read()) - assert sessLog.listBox.topLevelItem(0).text(sessLog.C_COUNT) == "{:n}".format(1) - assert sessLog.listBox.topLevelItem(1).text(sessLog.C_COUNT) == "{:n}".format(-100) - assert sessLog.listBox.topLevelItem(2).text(sessLog.C_COUNT) == "{:n}".format(150) - assert sessLog.listBox.topLevelItem(3).text(sessLog.C_COUNT) == "{:n}".format(-60) - assert sessLog.listBox.topLevelItem(4).text(sessLog.C_COUNT) == "{:n}".format(-10) - assert sessLog.listBox.topLevelItem(5).text(sessLog.C_COUNT) == "{:n}".format(20) - assert sessLog.listBox.topLevelItem(6).text(sessLog.C_COUNT) == "{:n}".format(-200) - assert sessLog.listBox.topLevelItem(7).text(sessLog.C_COUNT) == "{:n}".format(100) + item = sessLog.listBox.topLevelItem(0) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(1) + + item = sessLog.listBox.topLevelItem(1) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(-100) + + item = sessLog.listBox.topLevelItem(2) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(150) + + item = sessLog.listBox.topLevelItem(3) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(-60) + + item = sessLog.listBox.topLevelItem(4) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(-10) + + item = sessLog.listBox.topLevelItem(5) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(20) + + item = sessLog.listBox.topLevelItem(6) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(-200) + + item = sessLog.listBox.topLevelItem(7) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(100) assert jsonData == [ { @@ -268,14 +337,37 @@ def testToolWritingStats_Filters(qtbot, monkeypatch, nwGUI, projPath, tstPaths): with open(jsonStats, mode="r", encoding="utf-8") as inFile: jsonData = json.load(inFile) - assert sessLog.listBox.topLevelItem(0).text(sessLog.C_COUNT) == "{:n}".format(1) - assert sessLog.listBox.topLevelItem(1).text(sessLog.C_COUNT) == "{:n}".format(-100) - assert sessLog.listBox.topLevelItem(2).text(sessLog.C_COUNT) == "{:n}".format(150) - assert sessLog.listBox.topLevelItem(3).text(sessLog.C_COUNT) == "{:n}".format(-60) - assert sessLog.listBox.topLevelItem(4).text(sessLog.C_COUNT) == "{:n}".format(-10) - assert sessLog.listBox.topLevelItem(5).text(sessLog.C_COUNT) == "{:n}".format(20) - assert sessLog.listBox.topLevelItem(6).text(sessLog.C_COUNT) == "{:n}".format(-200) - assert sessLog.listBox.topLevelItem(7).text(sessLog.C_COUNT) == "{:n}".format(100) + item = sessLog.listBox.topLevelItem(0) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(1) + + item = sessLog.listBox.topLevelItem(1) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(-100) + + item = sessLog.listBox.topLevelItem(2) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(150) + + item = sessLog.listBox.topLevelItem(3) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(-60) + + item = sessLog.listBox.topLevelItem(4) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(-10) + + item = sessLog.listBox.topLevelItem(5) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(20) + + item = sessLog.listBox.topLevelItem(6) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(-200) + + item = sessLog.listBox.topLevelItem(7) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(100) assert jsonData == [ { @@ -314,10 +406,21 @@ def testToolWritingStats_Filters(qtbot, monkeypatch, nwGUI, projPath, tstPaths): with open(jsonStats, mode="r", encoding="utf-8") as inFile: jsonData = json.load(inFile) - assert sessLog.listBox.topLevelItem(0).text(sessLog.C_COUNT) == "{:n}".format(1) - assert sessLog.listBox.topLevelItem(1).text(sessLog.C_COUNT) == "{:n}".format(300) - assert sessLog.listBox.topLevelItem(2).text(sessLog.C_COUNT) == "{:n}".format(40) - assert sessLog.listBox.topLevelItem(3).text(sessLog.C_COUNT) == "{:n}".format(200) + item = sessLog.listBox.topLevelItem(0) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(1) + + item = sessLog.listBox.topLevelItem(1) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(300) + + item = sessLog.listBox.topLevelItem(2) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(40) + + item = sessLog.listBox.topLevelItem(3) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(200) assert jsonData == [ { @@ -344,16 +447,45 @@ def testToolWritingStats_Filters(qtbot, monkeypatch, nwGUI, projPath, tstPaths): with open(jsonStats, mode="r", encoding="utf-8") as inFile: jsonData = json.load(inFile) - assert sessLog.listBox.topLevelItem(0).text(sessLog.C_COUNT) == "{:n}".format(1) - assert sessLog.listBox.topLevelItem(1).text(sessLog.C_COUNT) == "{:n}".format(0) - assert sessLog.listBox.topLevelItem(2).text(sessLog.C_COUNT) == "{:n}".format(-200) - assert sessLog.listBox.topLevelItem(3).text(sessLog.C_COUNT) == "{:n}".format(300) - assert sessLog.listBox.topLevelItem(4).text(sessLog.C_COUNT) == "{:n}".format(-120) - assert sessLog.listBox.topLevelItem(5).text(sessLog.C_COUNT) == "{:n}".format(-20) - assert sessLog.listBox.topLevelItem(6).text(sessLog.C_COUNT) == "{:n}".format(40) - assert sessLog.listBox.topLevelItem(7).text(sessLog.C_COUNT) == "{:n}".format(-400) - assert sessLog.listBox.topLevelItem(8).text(sessLog.C_COUNT) == "{:n}".format(200) - assert sessLog.listBox.topLevelItem(9).text(sessLog.C_COUNT) == "{:n}".format(0) + item = sessLog.listBox.topLevelItem(0) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(1) + + item = sessLog.listBox.topLevelItem(1) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(0) + + item = sessLog.listBox.topLevelItem(2) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(-200) + + item = sessLog.listBox.topLevelItem(3) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(300) + + item = sessLog.listBox.topLevelItem(4) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(-120) + + item = sessLog.listBox.topLevelItem(5) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(-20) + + item = sessLog.listBox.topLevelItem(6) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(40) + + item = sessLog.listBox.topLevelItem(7) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(-400) + + item = sessLog.listBox.topLevelItem(8) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(200) + + item = sessLog.listBox.topLevelItem(9) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(0) assert jsonData == [ { @@ -390,9 +522,15 @@ def testToolWritingStats_Filters(qtbot, monkeypatch, nwGUI, projPath, tstPaths): ] # Toggle Idle Time - assert sessLog.listBox.topLevelItem(7).text(sessLog.C_IDLE) == "4 %" + item = sessLog.listBox.topLevelItem(7) + assert item is not None + assert item.text(sessLog.C_IDLE) == "4 %" + qtbot.mouseClick(sessLog.showIdleTime, QtMouseLeft) - assert sessLog.listBox.topLevelItem(7).text(sessLog.C_IDLE) == "00:01:10" + + item = sessLog.listBox.topLevelItem(7) + assert item is not None + assert item.text(sessLog.C_IDLE) == "00:01:10" # Group by Day qtbot.mouseClick(sessLog.groupByDay, QtMouseLeft) @@ -402,14 +540,37 @@ def testToolWritingStats_Filters(qtbot, monkeypatch, nwGUI, projPath, tstPaths): with open(jsonStats, mode="r", encoding="utf-8") as inFile: jsonData = json.load(inFile) - assert sessLog.listBox.topLevelItem(0).text(sessLog.C_COUNT) == "{:n}".format(1) - assert sessLog.listBox.topLevelItem(1).text(sessLog.C_COUNT) == "{:n}".format(-200) - assert sessLog.listBox.topLevelItem(2).text(sessLog.C_COUNT) == "{:n}".format(180) - assert sessLog.listBox.topLevelItem(3).text(sessLog.C_COUNT) == "{:n}".format(-20) - assert sessLog.listBox.topLevelItem(4).text(sessLog.C_COUNT) == "{:n}".format(40) - assert sessLog.listBox.topLevelItem(5).text(sessLog.C_COUNT) == "{:n}".format(-400) - assert sessLog.listBox.topLevelItem(6).text(sessLog.C_COUNT) == "{:n}".format(200) - assert sessLog.listBox.topLevelItem(7).text(sessLog.C_COUNT) == "{:n}".format(0) + item = sessLog.listBox.topLevelItem(0) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(1) + + item = sessLog.listBox.topLevelItem(1) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(-200) + + item = sessLog.listBox.topLevelItem(2) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(180) + + item = sessLog.listBox.topLevelItem(3) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(-20) + + item = sessLog.listBox.topLevelItem(4) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(40) + + item = sessLog.listBox.topLevelItem(5) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(-400) + + item = sessLog.listBox.topLevelItem(6) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(200) + + item = sessLog.listBox.topLevelItem(7) + assert item is not None + assert item.text(sessLog.C_COUNT) == "{:n}".format(0) assert jsonData == [ { diff --git a/tests/tools.py b/tests/tools.py index a21ac432..3be25040 100644 --- a/tests/tools.py +++ b/tests/tools.py @@ -241,5 +241,7 @@ class SimpleDialog(QDialog): def addWidget(self, widget: QWidget) -> None: self._widget = widget - self.layout().addWidget(widget) + layout = self.layout() + assert layout is not None + layout.addWidget(widget) return