From f7d0adad1b86ed6aad04ba363d8c06d2748e39ff Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Tue, 18 Jun 2024 22:39:33 +0200 Subject: [PATCH 1/8] Shuffle around private slots in main GUI --- novelwriter/guimain.py | 18 +++++++++--------- tests/test_gui/test_gui_doceditor.py | 2 +- tests/test_gui/test_gui_guimain.py | 22 +++++++++++----------- tests/test_gui/test_gui_noveltree.py | 2 +- tests/test_gui/test_gui_projtree.py | 4 ++-- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index ec549b5c..faee427b 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -227,7 +227,7 @@ class GuiMain(QMainWindow): self.mainMenu.requestDocInsert.connect(self._passDocumentInsert) self.mainMenu.requestDocInsertText.connect(self._passDocumentInsert) self.mainMenu.requestDocKeyWordInsert.connect(self.docEditor.insertKeyWord) - self.mainMenu.requestFocusChange.connect(self.switchFocus) + self.mainMenu.requestFocusChange.connect(self._switchFocus) self.mainMenu.requestViewChange.connect(self._changeView) self.sideBar.requestViewChange.connect(self._changeView) @@ -944,14 +944,18 @@ class GuiMain(QMainWindow): SHARED.setFocusMode(not SHARED.focusMode) return + ## + # Private Slots + ## + @pyqtSlot(bool) def _focusModeChanged(self, focusMode: bool) -> None: - """Handle change of focus mode. The Main GUI Focus Mode hides tree, - view, statusbar and menu. + """Handle change of focus mode. The Main GUI Focus Mode hides + tree, view, statusbar and menu. """ if focusMode: logger.debug("Activating Focus Mode") - self.switchFocus(nwWidget.EDITOR) + self._switchFocus(nwWidget.EDITOR) else: logger.debug("Deactivating Focus Mode") @@ -975,7 +979,7 @@ class GuiMain(QMainWindow): return @pyqtSlot(nwWidget) - def switchFocus(self, paneNo: nwWidget) -> None: + def _switchFocus(self, paneNo: nwWidget) -> None: """Switch focus between main GUI views.""" if paneNo == nwWidget.TREE: if self.projStack.currentWidget() is self.projView: @@ -1004,10 +1008,6 @@ class GuiMain(QMainWindow): self.outlineView.setTreeFocus() return - ## - # Private Slots - ## - @pyqtSlot(bool, bool, bool, bool) def _processConfigChanges(self, restart: bool, tree: bool, theme: bool, syntax: bool) -> None: """Refresh GUI based on flags from the Preferences dialog.""" diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py index 2ed80e96..a35cc16c 100644 --- a/tests/test_gui/test_gui_doceditor.py +++ b/tests/test_gui/test_gui_doceditor.py @@ -1678,7 +1678,7 @@ def testGuiEditor_Completer(qtbot, nwGUI, projPath, mockRnd): completer = docEditor._completer # Create Scene - nwGUI.switchFocus(nwWidget.EDITOR) + nwGUI._switchFocus(nwWidget.EDITOR) for c in "### Scene One": qtbot.keyClick(docEditor, c, delay=KEY_DELAY) qtbot.keyClick(docEditor, Qt.Key_Return, delay=KEY_DELAY) diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index 4baf2758..1f011c37 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -113,7 +113,7 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd): # Project Tree has focus nwGUI._changeView(nwView.PROJECT) - nwGUI.switchFocus(nwWidget.TREE) + nwGUI._switchFocus(nwWidget.TREE) nwGUI.projStack.setCurrentIndex(0) with monkeypatch.context() as mp: mp.setattr(GuiProjectTree, "hasFocus", lambda *a: True) @@ -137,7 +137,7 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd): # Project Outline has focus nwGUI._changeView(nwView.OUTLINE) - nwGUI.switchFocus(nwWidget.OUTLINE) + nwGUI._switchFocus(nwWidget.OUTLINE) with monkeypatch.context() as mp: mp.setattr(GuiOutlineView, "treeHasFocus", lambda *a: True) assert nwGUI.docEditor.docHandle is None @@ -230,7 +230,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd): CONFIG.autoScroll = True # Add a Character File - nwGUI.switchFocus(nwWidget.TREE) + nwGUI._switchFocus(nwWidget.TREE) nwGUI.projView.projTree.clearSelection() nwGUI.projView.projTree._getTreeItem(C.hCharRoot).setSelected(True) nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, None, isNote=True) @@ -250,7 +250,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd): docEditor._qDocument.syntaxHighlighter.initHighlighter() # Type something into the document - nwGUI.switchFocus(nwWidget.EDITOR) + nwGUI._switchFocus(nwWidget.EDITOR) qtbot.keyClick(docEditor, "a", modifier=Qt.ControlModifier, delay=KEY_DELAY) for c in "# Jane Doe": qtbot.keyClick(docEditor, c, delay=KEY_DELAY) @@ -265,14 +265,14 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd): qtbot.keyClick(docEditor, Qt.Key_Return, delay=KEY_DELAY) # Add a Plot File - nwGUI.switchFocus(nwWidget.TREE) + nwGUI._switchFocus(nwWidget.TREE) nwGUI.projView.projTree.clearSelection() nwGUI.projView.projTree._getTreeItem(C.hPlotRoot).setSelected(True) nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, None, isNote=True) nwGUI.openSelectedItem() # Type something into the document - nwGUI.switchFocus(nwWidget.EDITOR) + nwGUI._switchFocus(nwWidget.EDITOR) qtbot.keyClick(docEditor, "a", modifier=Qt.ControlModifier, delay=KEY_DELAY) for c in "# Main Plot": qtbot.keyClick(docEditor, c, delay=KEY_DELAY) @@ -287,7 +287,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd): qtbot.keyClick(docEditor, Qt.Key_Return, delay=KEY_DELAY) # Add a World File - nwGUI.switchFocus(nwWidget.TREE) + nwGUI._switchFocus(nwWidget.TREE) nwGUI.projView.projTree.clearSelection() nwGUI.projView.projTree._getTreeItem(C.hWorldRoot).setSelected(True) nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, None, isNote=True) @@ -299,7 +299,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd): docEditor.replaceText("") # Type something into the document - nwGUI.switchFocus(nwWidget.EDITOR) + nwGUI._switchFocus(nwWidget.EDITOR) qtbot.keyClick(docEditor, "a", modifier=Qt.ControlModifier, delay=KEY_DELAY) for c in "# Main Location": qtbot.keyClick(docEditor, c, delay=KEY_DELAY) @@ -318,7 +318,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd): nwGUI._autoSaveProject() # Select the 'New Scene' file - nwGUI.switchFocus(nwWidget.TREE) + nwGUI._switchFocus(nwWidget.TREE) nwGUI.projView.projTree.clearSelection() nwGUI.projView.projTree._getTreeItem(C.hNovelRoot).setExpanded(True) nwGUI.projView.projTree._getTreeItem(C.hChapterDir).setExpanded(True) @@ -326,7 +326,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd): nwGUI.openSelectedItem() # Type something into the document - nwGUI.switchFocus(nwWidget.EDITOR) + nwGUI._switchFocus(nwWidget.EDITOR) qtbot.keyClick(docEditor, "a", modifier=Qt.ControlModifier, delay=KEY_DELAY) for c in "# Novel": qtbot.keyClick(docEditor, c, delay=KEY_DELAY) @@ -535,7 +535,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd): nwGUI.rebuildIndex() # Open and view the edited document - nwGUI.switchFocus(nwWidget.VIEWER) + nwGUI._switchFocus(nwWidget.VIEWER) assert nwGUI.openDocument(C.hSceneDoc) assert nwGUI.viewDocument(C.hSceneDoc) assert nwGUI.saveProject() diff --git a/tests/test_gui/test_gui_noveltree.py b/tests/test_gui/test_gui_noveltree.py index 8878c540..593de829 100644 --- a/tests/test_gui/test_gui_noveltree.py +++ b/tests/test_gui/test_gui_noveltree.py @@ -44,7 +44,7 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd): buildTestProject(nwGUI, projPath) - nwGUI.switchFocus(nwWidget.TREE) + nwGUI._switchFocus(nwWidget.TREE) nwGUI.projView.projTree.clearSelection() nwGUI.projView.projTree._getTreeItem(C.hCharRoot).setSelected(True) nwGUI.projView.projTree.newTreeItem(nwItemType.FILE) diff --git a/tests/test_gui/test_gui_projtree.py b/tests/test_gui/test_gui_projtree.py index 5d8b6923..8342150f 100644 --- a/tests/test_gui/test_gui_projtree.py +++ b/tests/test_gui/test_gui_projtree.py @@ -1111,7 +1111,7 @@ def testGuiProjTree_ContextMenu(qtbot, monkeypatch, nwGUI, projPath, mockRnd): # Create a project buildTestProject(nwGUI, projPath) nwGUI.openProject(projPath) - nwGUI.switchFocus(nwWidget.TREE) + nwGUI._switchFocus(nwWidget.TREE) # Handles for new objects hCharNote = "0000000000011" @@ -1408,7 +1408,7 @@ def testGuiProjTree_Templates(qtbot, monkeypatch, nwGUI, projPath, mockRnd): # Create a project buildTestProject(nwGUI, projPath) nwGUI.openProject(projPath) - nwGUI.switchFocus(nwWidget.TREE) + nwGUI._switchFocus(nwWidget.TREE) nwGUI.show() project = SHARED.project From 869cdd2fb0ab5465406c98cd10817bda14ffbd25 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Tue, 18 Jun 2024 22:41:29 +0200 Subject: [PATCH 2/8] Extend colour label widget to accept two colour states --- novelwriter/dialogs/about.py | 2 +- novelwriter/dialogs/docmerge.py | 2 +- novelwriter/dialogs/docsplit.py | 2 +- novelwriter/dialogs/preferences.py | 4 ++-- novelwriter/dialogs/projectsettings.py | 10 ++++----- novelwriter/dialogs/wordlist.py | 2 +- novelwriter/extensions/configlayout.py | 28 ++++++++++++++++++++++---- novelwriter/gui/outline.py | 2 +- novelwriter/tools/manussettings.py | 4 ++-- novelwriter/tools/noveldetails.py | 16 +++++++-------- 10 files changed, 46 insertions(+), 26 deletions(-) diff --git a/novelwriter/dialogs/about.py b/novelwriter/dialogs/about.py index 628c4e4e..25e0d2ef 100644 --- a/novelwriter/dialogs/about.py +++ b/novelwriter/dialogs/about.py @@ -75,7 +75,7 @@ class GuiAbout(NDialog): # Credits self.lblCredits = NColourLabel( - self.tr("Credits"), scale=1.6, parent=self, bold=True + self.tr("Credits"), self, scale=1.6, bold=True ) self.txtCredits = QTextBrowser(self) diff --git a/novelwriter/dialogs/docmerge.py b/novelwriter/dialogs/docmerge.py index 32b52a8d..d745dadb 100644 --- a/novelwriter/dialogs/docmerge.py +++ b/novelwriter/dialogs/docmerge.py @@ -58,7 +58,7 @@ class GuiDocMerge(NDialog): self.headLabel.setFont(SHARED.theme.guiFontB) self.helpLabel = NColourLabel( self.tr("Drag and drop items to change the order, or uncheck to exclude."), - SHARED.theme.helpText, parent=self, wrap=True + self, color=SHARED.theme.helpText, wrap=True ) iPx = SHARED.theme.baseIconHeight diff --git a/novelwriter/dialogs/docsplit.py b/novelwriter/dialogs/docsplit.py index 5c4e586b..5dae422a 100644 --- a/novelwriter/dialogs/docsplit.py +++ b/novelwriter/dialogs/docsplit.py @@ -62,7 +62,7 @@ class GuiDocSplit(NDialog): self.headLabel.setFont(SHARED.theme.guiFontB) self.helpLabel = NColourLabel( self.tr("Select the maximum level to split into files."), - SHARED.theme.helpText, parent=self, wrap=True + self, color=SHARED.theme.helpText, wrap=True ) # Values diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index d08498b2..2904476e 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -66,8 +66,8 @@ class GuiPreferences(NDialog): # Title self.titleLabel = NColourLabel( - self.tr("Preferences"), SHARED.theme.helpText, - parent=self, scale=NColourLabel.HEADER_SCALE, indent=CONFIG.pxInt(4) + self.tr("Preferences"), self, color=SHARED.theme.helpText, + scale=NColourLabel.HEADER_SCALE, indent=CONFIG.pxInt(4) ) # Search Box diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py index c8d72d88..e718634f 100644 --- a/novelwriter/dialogs/projectsettings.py +++ b/novelwriter/dialogs/projectsettings.py @@ -76,8 +76,8 @@ class GuiProjectSettings(NDialog): # Title self.titleLabel = NColourLabel( - self.tr("Project Settings"), SHARED.theme.helpText, - parent=self, scale=NColourLabel.HEADER_SCALE, indent=CONFIG.pxInt(4) + self.tr("Project Settings"), self, color=SHARED.theme.helpText, + scale=NColourLabel.HEADER_SCALE, indent=CONFIG.pxInt(4) ) # SideBar @@ -345,7 +345,7 @@ class _StatusPage(NFixedPage): # Title self.pageTitle = NColourLabel( - pageLabel, SHARED.theme.helpText, parent=self, + pageLabel, self, color=SHARED.theme.helpText, scale=NColourLabel.HEADER_SCALE ) @@ -637,8 +637,8 @@ class _ReplacePage(NFixedPage): # Title self.pageTitle = NColourLabel( - self.tr("Text Auto-Replace for Preview and Build"), - SHARED.theme.helpText, parent=self, scale=NColourLabel.HEADER_SCALE + self.tr("Text Auto-Replace for Preview and Build"), self, + color=SHARED.theme.helpText, scale=NColourLabel.HEADER_SCALE ) # List Box diff --git a/novelwriter/dialogs/wordlist.py b/novelwriter/dialogs/wordlist.py index 8f3f53f5..8009d157 100644 --- a/novelwriter/dialogs/wordlist.py +++ b/novelwriter/dialogs/wordlist.py @@ -69,7 +69,7 @@ class GuiWordList(NDialog): # Header self.headLabel = NColourLabel( - self.tr("Project Word List"), SHARED.theme.helpText, parent=self, + self.tr("Project Word List"), self, color=SHARED.theme.helpText, scale=NColourLabel.HEADER_SCALE ) diff --git a/novelwriter/extensions/configlayout.py b/novelwriter/extensions/configlayout.py index e9f8194d..ca9d582e 100644 --- a/novelwriter/extensions/configlayout.py +++ b/novelwriter/extensions/configlayout.py @@ -203,7 +203,7 @@ class NScrollableForm(QScrollArea): if helpText: qHelp = NColourLabel( - str(helpText), color=self._helpCol, parent=self, + str(helpText), self, color=self._helpCol, scale=self._fontScale, wrap=True, indent=self._indent ) labelBox = QVBoxLayout() @@ -252,11 +252,20 @@ class NColourLabel(QLabel): HELP_SCALE = DEFAULT_SCALE HEADER_SCALE = 1.25 - def __init__(self, text: str, color: QColor | None = None, parent: QWidget | None = None, - scale: float = HELP_SCALE, wrap: bool = False, indent: int = 0, - bold: bool = False) -> None: + _state = None + + def __init__( + self, text: str, parent: QWidget, *, + color: QColor | None = None, faded: QColor | None = None, + scale: float = HELP_SCALE, wrap: bool = False, indent: int = 0, + bold: bool = False + ) -> None: super().__init__(text, parent=parent) + default = self.palette().windowText().color() + self._color = color or default + self._faded = faded or default + font = self.font() font.setPointSizeF(scale*font.pointSizeF()) font.setWeight(QFont.Weight.Bold if bold else QFont.Weight.Normal) @@ -268,9 +277,20 @@ class NColourLabel(QLabel): self.setFont(font) self.setIndent(indent) self.setWordWrap(wrap) + self.setColorState(True) return + def setColorState(self, state: bool) -> None: + """Change the colour state.""" + if self._state is not state: + self._state = state + print("State:", state, type(self.parent()).__name__) + colour = self.palette() + colour.setColor(QPalette.ColorRole.WindowText, self._color if state else self._faded) + self.setPalette(colour) + return + class NWrappedWidgetBox(QHBoxLayout): """Extension: A Text-Wrapped Widget Box diff --git a/novelwriter/gui/outline.py b/novelwriter/gui/outline.py index aa3d756d..3a411f35 100644 --- a/novelwriter/gui/outline.py +++ b/novelwriter/gui/outline.py @@ -215,7 +215,7 @@ class GuiOutlineToolBar(QToolBar): # Novel Selector self.novelLabel = NColourLabel( - self.tr("Outline of"), parent=self, scale=NColourLabel.HEADER_SCALE, bold=True + self.tr("Outline of"), self, scale=NColourLabel.HEADER_SCALE, bold=True ) self.novelLabel.setContentsMargins(0, 0, CONFIG.pxInt(12), 0) diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py index f060a7b0..8964dcf4 100644 --- a/novelwriter/tools/manussettings.py +++ b/novelwriter/tools/manussettings.py @@ -98,8 +98,8 @@ class GuiBuildSettings(NToolDialog): # Title self.titleLabel = NColourLabel( - self.tr("Manuscript Build Settings"), SHARED.theme.helpText, - parent=self, scale=NColourLabel.HEADER_SCALE, indent=CONFIG.pxInt(4) + self.tr("Manuscript Build Settings"), self, color=SHARED.theme.helpText, + scale=NColourLabel.HEADER_SCALE, indent=CONFIG.pxInt(4) ) # Settings Name diff --git a/novelwriter/tools/noveldetails.py b/novelwriter/tools/noveldetails.py index cd7f5f38..a7cb80c4 100644 --- a/novelwriter/tools/noveldetails.py +++ b/novelwriter/tools/noveldetails.py @@ -68,8 +68,8 @@ class GuiNovelDetails(NNonBlockingDialog): # Title self.titleLabel = NColourLabel( - self.tr("Novel Details"), SHARED.theme.helpText, - parent=self, scale=NColourLabel.HEADER_SCALE, indent=CONFIG.pxInt(4) + self.tr("Novel Details"), self, color=SHARED.theme.helpText, + scale=NColourLabel.HEADER_SCALE, indent=CONFIG.pxInt(4) ) # Novel Selector @@ -199,8 +199,8 @@ class _OverviewPage(NScrollablePage): # Project Info self.projLabel = NColourLabel( - self.tr("Project"), SHARED.theme.helpText, - parent=self, scale=NColourLabel.HEADER_SCALE + self.tr("Project"), self, color=SHARED.theme.helpText, + scale=NColourLabel.HEADER_SCALE ) self.projName = QLabel("", self) @@ -223,8 +223,8 @@ class _OverviewPage(NScrollablePage): # Novel Info self.novelLabel = NColourLabel( - self.tr("Selected Novel"), SHARED.theme.helpText, - parent=self, scale=NColourLabel.HEADER_SCALE + self.tr("Selected Novel"), self, color=SHARED.theme.helpText, + scale=NColourLabel.HEADER_SCALE ) self.novelName = QLabel("", self) @@ -315,8 +315,8 @@ class _ContentsPage(NFixedPage): # Title self.contentLabel = NColourLabel( - self.tr("Table of Contents"), SHARED.theme.helpText, - parent=self, scale=NColourLabel.HEADER_SCALE + self.tr("Table of Contents"), self, color=SHARED.theme.helpText, + scale=NColourLabel.HEADER_SCALE ) # Contents Tree From 930c60ae4ae0b22dea63385e644c10d94e92cccf Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Tue, 18 Jun 2024 22:42:19 +0200 Subject: [PATCH 3/8] Add faded colour to theme --- novelwriter/assets/themes/cyberpunk_night.conf | 1 + novelwriter/assets/themes/default_dark.conf | 1 + novelwriter/assets/themes/default_light.conf | 1 + novelwriter/assets/themes/dracula.conf | 1 + novelwriter/assets/themes/solarized_dark.conf | 1 + novelwriter/assets/themes/solarized_light.conf | 1 + novelwriter/gui/theme.py | 3 +++ 7 files changed, 9 insertions(+) diff --git a/novelwriter/assets/themes/cyberpunk_night.conf b/novelwriter/assets/themes/cyberpunk_night.conf index e0b0f2cc..3cfc7c2a 100644 --- a/novelwriter/assets/themes/cyberpunk_night.conf +++ b/novelwriter/assets/themes/cyberpunk_night.conf @@ -25,6 +25,7 @@ linkvisited = 50, 0, 80 [GUI] helptext = 97, 97, 97 +fadedtext = 97, 97, 97 errortext = 255, 77, 77 statusnone = 50, 50, 50 statussaved = 77, 255, 77 diff --git a/novelwriter/assets/themes/default_dark.conf b/novelwriter/assets/themes/default_dark.conf index 6a908741..8fcc4b18 100644 --- a/novelwriter/assets/themes/default_dark.conf +++ b/novelwriter/assets/themes/default_dark.conf @@ -26,6 +26,7 @@ linkvisited = 102, 153, 204 [GUI] helptext = 164, 164, 164 +fadedtext = 128, 128, 128 errortext = 255, 164, 164 statusnone = 150, 152, 150 statussaved = 39, 135, 78 diff --git a/novelwriter/assets/themes/default_light.conf b/novelwriter/assets/themes/default_light.conf index f8240bcb..6f221c86 100644 --- a/novelwriter/assets/themes/default_light.conf +++ b/novelwriter/assets/themes/default_light.conf @@ -26,6 +26,7 @@ linkvisited = 66, 113, 174 [GUI] helptext = 92, 92, 92 +fadedtext = 128, 128, 128 errortext = 255, 92, 92 statusnone = 120, 120, 120 statussaved = 200, 15, 39 diff --git a/novelwriter/assets/themes/dracula.conf b/novelwriter/assets/themes/dracula.conf index 4946b218..a787dcd4 100644 --- a/novelwriter/assets/themes/dracula.conf +++ b/novelwriter/assets/themes/dracula.conf @@ -41,6 +41,7 @@ linkvisited = 139, 233, 253 [GUI] helptext = 204, 172, 249 +fadedtext = 98, 114, 164 errortext = 255, 85, 85 statusnone = 98, 114, 164 statussaved = 80, 250, 123 diff --git a/novelwriter/assets/themes/solarized_dark.conf b/novelwriter/assets/themes/solarized_dark.conf index b3599913..5a4df68b 100644 --- a/novelwriter/assets/themes/solarized_dark.conf +++ b/novelwriter/assets/themes/solarized_dark.conf @@ -25,6 +25,7 @@ linkvisited = 38, 139, 210 [GUI] helptext = 166, 161, 149 +fadedtext = 166, 161, 149 errortext = 255, 161, 149 statusnone = 88, 110, 117 statussaved = 42, 161, 152 diff --git a/novelwriter/assets/themes/solarized_light.conf b/novelwriter/assets/themes/solarized_light.conf index b9c68ec7..ca314428 100644 --- a/novelwriter/assets/themes/solarized_light.conf +++ b/novelwriter/assets/themes/solarized_light.conf @@ -25,6 +25,7 @@ linkvisited = 38, 139, 210 [GUI] helptext = 78, 91, 95 +fadedtext = 78, 91, 95 errortext = 255, 91, 95 statusnone = 88, 110, 117 statussaved = 42, 161, 152 diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index 72a95178..4576255a 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -75,6 +75,7 @@ class GuiTheme: self.statUnsaved = QColor(0, 0, 0) self.statSaved = QColor(0, 0, 0) self.helpText = QColor(0, 0, 0) + self.fadedText = QColor(0, 0, 0) self.errorText = QColor(255, 0, 0) # Loaded Syntax Settings @@ -263,6 +264,7 @@ class GuiTheme: sec = "GUI" if parser.has_section(sec): self.helpText = self._parseColour(parser, sec, "helptext") + self.fadedText = self._parseColour(parser, sec, "fadedtext") self.errorText = self._parseColour(parser, sec, "errortext") self.statNone = self._parseColour(parser, sec, "statusnone") self.statUnsaved = self._parseColour(parser, sec, "statusunsaved") @@ -405,6 +407,7 @@ class GuiTheme: self.statUnsaved = QColor(200, 15, 39) self.statSaved = QColor(2, 133, 37) self.helpText = QColor(0, 0, 0) + self.fadedText = QColor(128, 128, 128) self.errorText = QColor(255, 0, 0) return From c6569b63e5832a70849834fe28f4eb2ae99e2f70 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Tue, 18 Jun 2024 22:42:52 +0200 Subject: [PATCH 4/8] Add focus indication to editor and viewer header --- novelwriter/gui/doceditor.py | 10 ++++++++-- novelwriter/gui/docviewer.py | 15 +++++++++++---- novelwriter/guimain.py | 23 +++++++++++++++++++++++ tests/test_base/test_base_init.py | 20 +++++++++++--------- 4 files changed, 53 insertions(+), 15 deletions(-) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 5288b068..39adc29d 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -55,6 +55,7 @@ from novelwriter.common import minmax, transferCase from novelwriter.constants import nwConst, nwKeyWords, nwShortcode, nwUnicode from novelwriter.core.document import NWDocument from novelwriter.enum import nwComment, nwDocAction, nwDocInsert, nwDocMode, nwItemClass, nwTrinary +from novelwriter.extensions.configlayout import NColourLabel from novelwriter.extensions.eventfilters import WheelEventFilter from novelwriter.extensions.modified import NIconToggleButton, NIconToolButton from novelwriter.gui.dochighlight import BLOCK_META, BLOCK_TITLE @@ -210,6 +211,7 @@ class GuiDocEditor(QPlainTextEdit): # Function Mapping self.closeSearch = self.docSearch.closeSearch self.searchVisible = self.docSearch.isVisible + self.changeFocusState = self.docHeader.changeFocusState # Finalise self.updateSyntaxColours() @@ -2785,8 +2787,7 @@ class GuiDocEditHeader(QWidget): self.setAutoFillBackground(True) # Title Label - self.itemTitle = QLabel("", self) - self.itemTitle.setIndent(0) + self.itemTitle = NColourLabel("", self, faded=SHARED.theme.fadedText) self.itemTitle.setMargin(0) self.itemTitle.setContentsMargins(0, 0, 0, 0) self.itemTitle.setAutoFillBackground(True) @@ -2924,6 +2925,11 @@ class GuiDocEditHeader(QWidget): return + def changeFocusState(self, state: bool) -> None: + """Toggle focus state.""" + self.itemTitle.setColorState(state) + return + def setHandle(self, tHandle: str) -> None: """Set the document title from the handle, or alternatively, set the whole document path within the project. diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py index 20e1888b..1029a152 100644 --- a/novelwriter/gui/docviewer.py +++ b/novelwriter/gui/docviewer.py @@ -33,7 +33,7 @@ from enum import Enum from PyQt5.QtCore import QPoint, Qt, QUrl, pyqtSignal, pyqtSlot from PyQt5.QtGui import QCursor, QMouseEvent, QPalette, QResizeEvent, QTextCursor from PyQt5.QtWidgets import ( - QAction, QApplication, QFrame, QHBoxLayout, QLabel, QMenu, QTextBrowser, + QAction, QApplication, QFrame, QHBoxLayout, QMenu, QTextBrowser, QToolButton, QWidget ) @@ -42,6 +42,7 @@ from novelwriter.constants import nwHeaders, nwUnicode from novelwriter.core.toqdoc import TextDocumentTheme, ToQTextDocument from novelwriter.enum import nwDocAction, nwDocMode, nwItemType from novelwriter.error import logException +from novelwriter.extensions.configlayout import NColourLabel from novelwriter.extensions.eventfilters import WheelEventFilter from novelwriter.extensions.modified import NIconToolButton from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON @@ -92,6 +93,9 @@ class GuiDocViewer(QTextBrowser): self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.customContextMenuRequested.connect(self._openContextMenu) + # Function Mapping + self.changeFocusState = self.docHeader.changeFocusState + self.initViewer() logger.debug("Ready: GuiDocViewer") @@ -597,9 +601,7 @@ class GuiDocViewHeader(QWidget): self.setAutoFillBackground(True) # Title Label - self.itemTitle = QLabel(self) - self.itemTitle.setText("") - self.itemTitle.setIndent(0) + self.itemTitle = NColourLabel("", self, faded=SHARED.theme.fadedText) self.itemTitle.setMargin(0) self.itemTitle.setContentsMargins(0, 0, 0, 0) self.itemTitle.setAutoFillBackground(True) @@ -738,6 +740,11 @@ class GuiDocViewHeader(QWidget): self.itemTitle.setPalette(palette) return + def changeFocusState(self, state: bool) -> None: + """Toggle focus state.""" + self.itemTitle.setColorState(state) + return + def setHandle(self, tHandle: str) -> None: """Sets the document title from the handle, or alternatively, set the whole document path. diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index faee427b..00932967 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -326,6 +326,11 @@ class GuiMain(QMainWindow): def postLaunchTasks(self, cmdOpen: str | None) -> None: """Process tasks after the main window has been created.""" + QApplication.processEvents() + app = QApplication.instance() + if isinstance(app, QApplication): + app.focusChanged.connect(self._appFocusChanged) + # Check that config loaded fine if CONFIG.hasError: SHARED.error(CONFIG.errorText()) @@ -948,6 +953,24 @@ class GuiMain(QMainWindow): # Private Slots ## + @pyqtSlot("QWidget*", "QWidget*") + def _appFocusChanged(self, old: QWidget, new: QWidget) -> None: + """Alert main widgets that they have received or lost focus.""" + if isinstance(new, QWidget): + docEditor = False + docViewer = False + if self.docEditor.isAncestorOf(new): + docEditor = True + elif self.docViewer.isAncestorOf(new): + docViewer = True + + self.docEditor.changeFocusState(docEditor) + self.docViewer.changeFocusState(docViewer) + + logger.debug("Main focus switched to: %s", type(new).__name__) + + return + @pyqtSlot(bool) def _focusModeChanged(self, focusMode: bool) -> None: """Handle change of focus mode. The Main GUI Focus Mode hides diff --git a/tests/test_base/test_base_init.py b/tests/test_base/test_base_init.py index 2b7ebc47..0ff69505 100644 --- a/tests/test_base/test_base_init.py +++ b/tests/test_base/test_base_init.py @@ -62,15 +62,17 @@ def testBaseInit_Launch(caplog, monkeypatch, fncPath): CONFIG.osWindows = osWindows # Normal Launch - monkeypatch.setattr("PyQt5.QtWidgets.QApplication.__init__", lambda *a: None) - monkeypatch.setattr("PyQt5.QtWidgets.QApplication.setApplicationName", lambda *a: None) - monkeypatch.setattr("PyQt5.QtWidgets.QApplication.setApplicationVersion", lambda *a: None) - monkeypatch.setattr("PyQt5.QtWidgets.QApplication.setWindowIcon", lambda *a: None) - monkeypatch.setattr("PyQt5.QtWidgets.QApplication.setOrganizationDomain", lambda *a: None) - monkeypatch.setattr("PyQt5.QtWidgets.QApplication.exec", lambda *a: 0) - with pytest.raises(SystemExit) as ex: - main([f"--config={fncPath}", f"--data={fncPath}"]) - assert ex.value.code == 0 + with monkeypatch.context() as mp: + mp.setattr("PyQt5.QtWidgets.QApplication.__init__", lambda *a: None) + mp.setattr("PyQt5.QtWidgets.QApplication.setApplicationName", lambda *a: None) + mp.setattr("PyQt5.QtWidgets.QApplication.setApplicationVersion", lambda *a: None) + mp.setattr("PyQt5.QtWidgets.QApplication.setWindowIcon", lambda *a: None) + mp.setattr("PyQt5.QtWidgets.QApplication.setOrganizationDomain", lambda *a: None) + mp.setattr("PyQt5.QtWidgets.QApplication.exec", lambda *a: 0) + # mp.setattr("PyQt5.QtWidgets.QApplication.focusChange.connect", lambda *a: None) + with pytest.raises(SystemExit) as ex: + main([f"--config={fncPath}", f"--data={fncPath}"]) + assert ex.value.code == 0 @pytest.mark.base From ea7f3fe51c6b0143041f2030a053df5a8e8fef97 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 19 Jun 2024 20:42:23 +0200 Subject: [PATCH 5/8] Add editor/viewer toggle feature --- novelwriter/assets/themes/default_dark.conf | 2 +- novelwriter/assets/themes/default_light.conf | 2 +- novelwriter/enum.py | 9 ++++--- novelwriter/extensions/configlayout.py | 5 ++-- novelwriter/gui/docviewer.py | 4 ++++ novelwriter/gui/mainmenu.py | 18 +++++++------- novelwriter/guimain.py | 25 +++++++++++--------- tests/test_gui/test_gui_doceditor.py | 6 ++--- tests/test_gui/test_gui_guimain.py | 24 +++++++++---------- tests/test_gui/test_gui_noveltree.py | 4 ++-- tests/test_gui/test_gui_projtree.py | 6 ++--- 11 files changed, 54 insertions(+), 51 deletions(-) diff --git a/novelwriter/assets/themes/default_dark.conf b/novelwriter/assets/themes/default_dark.conf index 8fcc4b18..906cc9fe 100644 --- a/novelwriter/assets/themes/default_dark.conf +++ b/novelwriter/assets/themes/default_dark.conf @@ -26,7 +26,7 @@ linkvisited = 102, 153, 204 [GUI] helptext = 164, 164, 164 -fadedtext = 128, 128, 128 +fadedtext = 148, 148, 148 errortext = 255, 164, 164 statusnone = 150, 152, 150 statussaved = 39, 135, 78 diff --git a/novelwriter/assets/themes/default_light.conf b/novelwriter/assets/themes/default_light.conf index 6f221c86..a02a12f9 100644 --- a/novelwriter/assets/themes/default_light.conf +++ b/novelwriter/assets/themes/default_light.conf @@ -26,7 +26,7 @@ linkvisited = 66, 113, 174 [GUI] helptext = 92, 92, 92 -fadedtext = 128, 128, 128 +fadedtext = 108, 108, 108 errortext = 255, 92, 92 statusnone = 120, 120, 120 statussaved = 200, 15, 39 diff --git a/novelwriter/enum.py b/novelwriter/enum.py index 53155d2a..649509a1 100644 --- a/novelwriter/enum.py +++ b/novelwriter/enum.py @@ -148,12 +148,11 @@ class nwView(Enum): SEARCH = 4 -class nwWidget(Enum): +class nwFocus(Enum): - TREE = 1 - EDITOR = 2 - VIEWER = 3 - OUTLINE = 4 + TREE = 1 + DOCUMENT = 2 + OUTLINE = 3 class nwOutline(Enum): diff --git a/novelwriter/extensions/configlayout.py b/novelwriter/extensions/configlayout.py index ca9d582e..3bb7b289 100644 --- a/novelwriter/extensions/configlayout.py +++ b/novelwriter/extensions/configlayout.py @@ -34,7 +34,7 @@ from PyQt5.QtWidgets import ( QVBoxLayout, QWidget ) -from novelwriter import CONFIG +from novelwriter import CONFIG, SHARED DEFAULT_SCALE = 0.9 @@ -266,7 +266,7 @@ class NColourLabel(QLabel): self._color = color or default self._faded = faded or default - font = self.font() + font = SHARED.theme.guiFont font.setPointSizeF(scale*font.pointSizeF()) font.setWeight(QFont.Weight.Bold if bold else QFont.Weight.Normal) if color: @@ -285,7 +285,6 @@ class NColourLabel(QLabel): """Change the colour state.""" if self._state is not state: self._state = state - print("State:", state, type(self.parent()).__name__) colour = self.palette() colour.setColor(QPalette.ColorRole.WindowText, self._color if state else self._faded) self.setPalette(colour) diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py index 1029a152..b28411ab 100644 --- a/novelwriter/gui/docviewer.py +++ b/novelwriter/gui/docviewer.py @@ -282,6 +282,10 @@ class GuiDocViewer(QTextBrowser): return False return True + def anyFocus(self) -> bool: + """Check if any widget or child widget has focus.""" + return self.hasFocus() or self.isAncestorOf(QApplication.focusWidget()) + def clearNavHistory(self) -> None: """Clear the navigation history.""" self.docHistory.clear() diff --git a/novelwriter/gui/mainmenu.py b/novelwriter/gui/mainmenu.py index 56dc45d7..4a71c4f1 100644 --- a/novelwriter/gui/mainmenu.py +++ b/novelwriter/gui/mainmenu.py @@ -35,7 +35,7 @@ from PyQt5.QtWidgets import QAction, QMenuBar from novelwriter import CONFIG, SHARED from novelwriter.common import openExternalPath from novelwriter.constants import nwConst, nwKeyWords, nwLabels, nwUnicode, trConst -from novelwriter.enum import nwDocAction, nwDocInsert, nwView, nwWidget +from novelwriter.enum import nwDocAction, nwDocInsert, nwFocus, nwView from novelwriter.extensions.eventfilters import StatusTipFilter if TYPE_CHECKING: # pragma: no cover @@ -54,7 +54,7 @@ class GuiMainMenu(QMenuBar): requestDocInsert = pyqtSignal(nwDocInsert) requestDocInsertText = pyqtSignal(str) requestDocKeyWordInsert = pyqtSignal(str) - requestFocusChange = pyqtSignal(nwWidget) + requestFocusChange = pyqtSignal(nwFocus) requestViewChange = pyqtSignal(nwView) def __init__(self, mainGui: GuiMain) -> None: @@ -303,24 +303,24 @@ class GuiMainMenu(QMenuBar): self.viewMenu = self.addMenu(self.tr("&View")) # View > TreeView - self.aFocusTree = self.viewMenu.addAction(self.tr("Go to Project Tree")) + self.aFocusTree = self.viewMenu.addAction(self.tr("Go to Tree View")) self.aFocusTree.setShortcut("Ctrl+T") self.aFocusTree.triggered.connect( - lambda: self.requestFocusChange.emit(nwWidget.TREE) + lambda: self.requestFocusChange.emit(nwFocus.TREE) ) # View > Document Editor - self.aFocusEditor = self.viewMenu.addAction(self.tr("Go to Document Editor")) - self.aFocusEditor.setShortcut("Ctrl+E") - self.aFocusEditor.triggered.connect( - lambda: self.requestFocusChange.emit(nwWidget.EDITOR) + self.aFocusDocument = self.viewMenu.addAction(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.setShortcut("Ctrl+Shift+T") self.aFocusOutline.triggered.connect( - lambda: self.requestFocusChange.emit(nwWidget.OUTLINE) + lambda: self.requestFocusChange.emit(nwFocus.OUTLINE) ) # View > Separator diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 00932967..443d3236 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -44,7 +44,7 @@ from novelwriter.dialogs.about import GuiAbout from novelwriter.dialogs.preferences import GuiPreferences from novelwriter.dialogs.projectsettings import GuiProjectSettings from novelwriter.dialogs.wordlist import GuiWordList -from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwItemType, nwView, nwWidget +from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwFocus, nwItemType, nwView from novelwriter.gui.doceditor import GuiDocEditor from novelwriter.gui.docviewer import GuiDocViewer from novelwriter.gui.docviewerpanel import GuiDocViewerPanel @@ -978,7 +978,8 @@ class GuiMain(QMainWindow): """ if focusMode: logger.debug("Activating Focus Mode") - self._switchFocus(nwWidget.EDITOR) + self._changeView(nwView.EDITOR) + self.docEditor.setFocus() else: logger.debug("Deactivating Focus Mode") @@ -1001,10 +1002,10 @@ class GuiMain(QMainWindow): self.docEditor.ensureCursorVisibleNoCentre() return - @pyqtSlot(nwWidget) - def _switchFocus(self, paneNo: nwWidget) -> None: + @pyqtSlot(nwFocus) + def _switchFocus(self, paneNo: nwFocus) -> None: """Switch focus between main GUI views.""" - if paneNo == nwWidget.TREE: + if paneNo == nwFocus.TREE: if self.projStack.currentWidget() is self.projView: if self.projView.treeHasFocus(): self._changeView(nwView.NOVEL) @@ -1020,13 +1021,15 @@ class GuiMain(QMainWindow): else: self._changeView(nwView.PROJECT) self.projView.setTreeFocus() - elif paneNo == nwWidget.EDITOR: + elif paneNo == nwFocus.DOCUMENT: self._changeView(nwView.EDITOR) - self.docEditor.setFocus() - elif paneNo == nwWidget.VIEWER: - self._changeView(nwView.EDITOR) - self.docViewer.setFocus() - elif paneNo == nwWidget.OUTLINE: + if self.docEditor.anyFocus(): + self.docViewer.setFocus() + elif self.docViewer.anyFocus(): + self.docEditor.setFocus() + else: + self.docEditor.setFocus() + elif paneNo == nwFocus.OUTLINE: self._changeView(nwView.OUTLINE) self.outlineView.setTreeFocus() return diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py index a35cc16c..c4300be8 100644 --- a/tests/test_gui/test_gui_doceditor.py +++ b/tests/test_gui/test_gui_doceditor.py @@ -29,9 +29,7 @@ from PyQt5.QtWidgets import QAction, QApplication, QMenu from novelwriter import CONFIG, SHARED from novelwriter.constants import nwKeyWords, nwUnicode from novelwriter.dialogs.editlabel import GuiEditLabel -from novelwriter.enum import ( - nwDocAction, nwDocInsert, nwItemClass, nwItemLayout, nwTrinary, nwWidget -) +from novelwriter.enum import nwDocAction, nwDocInsert, nwItemClass, nwItemLayout, nwTrinary from novelwriter.gui.doceditor import GuiDocEditor from novelwriter.text.counting import standardCounter from novelwriter.types import ( @@ -1678,7 +1676,7 @@ def testGuiEditor_Completer(qtbot, nwGUI, projPath, mockRnd): completer = docEditor._completer # Create Scene - nwGUI._switchFocus(nwWidget.EDITOR) + nwGUI.docEditor.setFocus() for c in "### Scene One": qtbot.keyClick(docEditor, c, delay=KEY_DELAY) qtbot.keyClick(docEditor, Qt.Key_Return, delay=KEY_DELAY) diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index 1f011c37..683ce18f 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -32,7 +32,7 @@ from PyQt5.QtWidgets import QInputDialog, QMenu from novelwriter import CONFIG, SHARED from novelwriter.dialogs.editlabel import GuiEditLabel -from novelwriter.enum import nwItemType, nwView, nwWidget +from novelwriter.enum import nwFocus, nwItemType, nwView from novelwriter.gui.doceditor import GuiDocEditor from novelwriter.gui.noveltree import GuiNovelView from novelwriter.gui.outline import GuiOutlineView @@ -113,7 +113,7 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd): # Project Tree has focus nwGUI._changeView(nwView.PROJECT) - nwGUI._switchFocus(nwWidget.TREE) + nwGUI._switchFocus(nwFocus.TREE) nwGUI.projStack.setCurrentIndex(0) with monkeypatch.context() as mp: mp.setattr(GuiProjectTree, "hasFocus", lambda *a: True) @@ -137,7 +137,7 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd): # Project Outline has focus nwGUI._changeView(nwView.OUTLINE) - nwGUI._switchFocus(nwWidget.OUTLINE) + nwGUI._switchFocus(nwFocus.OUTLINE) with monkeypatch.context() as mp: mp.setattr(GuiOutlineView, "treeHasFocus", lambda *a: True) assert nwGUI.docEditor.docHandle is None @@ -230,7 +230,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd): CONFIG.autoScroll = True # Add a Character File - nwGUI._switchFocus(nwWidget.TREE) + nwGUI._switchFocus(nwFocus.TREE) nwGUI.projView.projTree.clearSelection() nwGUI.projView.projTree._getTreeItem(C.hCharRoot).setSelected(True) nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, None, isNote=True) @@ -250,7 +250,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd): docEditor._qDocument.syntaxHighlighter.initHighlighter() # Type something into the document - nwGUI._switchFocus(nwWidget.EDITOR) + nwGUI.docEditor.setFocus() qtbot.keyClick(docEditor, "a", modifier=Qt.ControlModifier, delay=KEY_DELAY) for c in "# Jane Doe": qtbot.keyClick(docEditor, c, delay=KEY_DELAY) @@ -265,14 +265,14 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd): qtbot.keyClick(docEditor, Qt.Key_Return, delay=KEY_DELAY) # Add a Plot File - nwGUI._switchFocus(nwWidget.TREE) + nwGUI._switchFocus(nwFocus.TREE) nwGUI.projView.projTree.clearSelection() nwGUI.projView.projTree._getTreeItem(C.hPlotRoot).setSelected(True) nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, None, isNote=True) nwGUI.openSelectedItem() # Type something into the document - nwGUI._switchFocus(nwWidget.EDITOR) + nwGUI.docEditor.setFocus() qtbot.keyClick(docEditor, "a", modifier=Qt.ControlModifier, delay=KEY_DELAY) for c in "# Main Plot": qtbot.keyClick(docEditor, c, delay=KEY_DELAY) @@ -287,7 +287,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd): qtbot.keyClick(docEditor, Qt.Key_Return, delay=KEY_DELAY) # Add a World File - nwGUI._switchFocus(nwWidget.TREE) + nwGUI._switchFocus(nwFocus.TREE) nwGUI.projView.projTree.clearSelection() nwGUI.projView.projTree._getTreeItem(C.hWorldRoot).setSelected(True) nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, None, isNote=True) @@ -299,7 +299,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd): docEditor.replaceText("") # Type something into the document - nwGUI._switchFocus(nwWidget.EDITOR) + nwGUI.docEditor.setFocus() qtbot.keyClick(docEditor, "a", modifier=Qt.ControlModifier, delay=KEY_DELAY) for c in "# Main Location": qtbot.keyClick(docEditor, c, delay=KEY_DELAY) @@ -318,7 +318,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd): nwGUI._autoSaveProject() # Select the 'New Scene' file - nwGUI._switchFocus(nwWidget.TREE) + nwGUI._switchFocus(nwFocus.TREE) nwGUI.projView.projTree.clearSelection() nwGUI.projView.projTree._getTreeItem(C.hNovelRoot).setExpanded(True) nwGUI.projView.projTree._getTreeItem(C.hChapterDir).setExpanded(True) @@ -326,7 +326,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd): nwGUI.openSelectedItem() # Type something into the document - nwGUI._switchFocus(nwWidget.EDITOR) + nwGUI.docEditor.setFocus() qtbot.keyClick(docEditor, "a", modifier=Qt.ControlModifier, delay=KEY_DELAY) for c in "# Novel": qtbot.keyClick(docEditor, c, delay=KEY_DELAY) @@ -535,7 +535,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd): nwGUI.rebuildIndex() # Open and view the edited document - nwGUI._switchFocus(nwWidget.VIEWER) + nwGUI.docViewer.setFocus() assert nwGUI.openDocument(C.hSceneDoc) assert nwGUI.viewDocument(C.hSceneDoc) assert nwGUI.saveProject() diff --git a/tests/test_gui/test_gui_noveltree.py b/tests/test_gui/test_gui_noveltree.py index 593de829..288702c6 100644 --- a/tests/test_gui/test_gui_noveltree.py +++ b/tests/test_gui/test_gui_noveltree.py @@ -30,7 +30,7 @@ from PyQt5.QtWidgets import QInputDialog, QToolTip from novelwriter import CONFIG, SHARED from novelwriter.dialogs.editlabel import GuiEditLabel -from novelwriter.enum import nwItemType, nwWidget +from novelwriter.enum import nwFocus, nwItemType from novelwriter.gui.noveltree import GuiNovelTree, NovelTreeColumn from novelwriter.types import QtMouseLeft @@ -44,7 +44,7 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd): buildTestProject(nwGUI, projPath) - nwGUI._switchFocus(nwWidget.TREE) + nwGUI._switchFocus(nwFocus.TREE) nwGUI.projView.projTree.clearSelection() nwGUI.projView.projTree._getTreeItem(C.hCharRoot).setSelected(True) nwGUI.projView.projTree.newTreeItem(nwItemType.FILE) diff --git a/tests/test_gui/test_gui_projtree.py b/tests/test_gui/test_gui_projtree.py index 8342150f..29a945cf 100644 --- a/tests/test_gui/test_gui_projtree.py +++ b/tests/test_gui/test_gui_projtree.py @@ -34,7 +34,7 @@ from novelwriter.core.project import NWProject from novelwriter.dialogs.docmerge import GuiDocMerge from novelwriter.dialogs.docsplit import GuiDocSplit from novelwriter.dialogs.editlabel import GuiEditLabel -from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType, nwWidget +from novelwriter.enum import nwFocus, nwItemClass, nwItemLayout, nwItemType from novelwriter.gui.projtree import GuiProjectTree, GuiProjectView, _TreeContextMenu from novelwriter.guimain import GuiMain from novelwriter.types import QtAccepted, QtModNone, QtMouseLeft, QtMouseMiddle, QtRejected @@ -1111,7 +1111,7 @@ def testGuiProjTree_ContextMenu(qtbot, monkeypatch, nwGUI, projPath, mockRnd): # Create a project buildTestProject(nwGUI, projPath) nwGUI.openProject(projPath) - nwGUI._switchFocus(nwWidget.TREE) + nwGUI._switchFocus(nwFocus.TREE) # Handles for new objects hCharNote = "0000000000011" @@ -1408,7 +1408,7 @@ def testGuiProjTree_Templates(qtbot, monkeypatch, nwGUI, projPath, mockRnd): # Create a project buildTestProject(nwGUI, projPath) nwGUI.openProject(projPath) - nwGUI._switchFocus(nwWidget.TREE) + nwGUI._switchFocus(nwFocus.TREE) nwGUI.show() project = SHARED.project From 5665efd9d7100ec5e73d9b452702fb6f9acab322 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 20 Jun 2024 23:32:02 +0200 Subject: [PATCH 6/8] Update docs and test coverage --- docs/source/more_customise.rst | 3 +- novelwriter/guimain.py | 13 ++- tests/test_gui/test_gui_guimain.py | 131 ++++++++++++++++++++++++++++- 3 files changed, 137 insertions(+), 10 deletions(-) diff --git a/docs/source/more_customise.rst b/docs/source/more_customise.rst index d0d02941..6ea7ca76 100644 --- a/docs/source/more_customise.rst +++ b/docs/source/more_customise.rst @@ -132,6 +132,7 @@ A GUI theme ``.conf`` file consists of the following settings: [GUI] helptext = 0, 0, 0 + fadedtext = 128, 128, 128 errortext = 255, 0, 0 statusnone = 120, 120, 120 statussaved = 2, 133, 37 @@ -149,7 +150,7 @@ colour values are RGB numbers on the format ``r, g, b`` where each is an integer not defined, it is computed as a colour between the ``window`` and ``windowtext`` colour. .. versionadded:: 2.5 - The ``errortext`` theme colour entry was added. + The ``fadedtext`` and ``errortext`` theme colour entries were added. Custom Syntax Theme diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 443d3236..b7ccfedf 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -1172,16 +1172,13 @@ class GuiMain(QMainWindow): @pyqtSlot(nwDocAction) def _passDocumentAction(self, action: nwDocAction) -> None: - """Pass on a document action to the document viewer if it has - focus, or pass it to the document editor if it or any of its - child widgets have focus. If neither has focus, ignore it. + """Pass on a document action to the editor or viewer based on + which one has focus, or if neither has focus, ignore it. """ - if self.docViewer.hasFocus(): - self.docViewer.docAction(action) - elif self.docEditor.hasFocus(): + if self.docEditor.hasFocus(): self.docEditor.docAction(action) - else: - logger.debug("Action cancelled as neither editor nor viewer has focus") + elif self.docViewer.hasFocus(): + self.docViewer.docAction(action) return @pyqtSlot(str) diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index 683ce18f..80c3cf57 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -32,7 +32,7 @@ from PyQt5.QtWidgets import QInputDialog, QMenu from novelwriter import CONFIG, SHARED from novelwriter.dialogs.editlabel import GuiEditLabel -from novelwriter.enum import nwFocus, nwItemType, nwView +from novelwriter.enum import nwDocAction, nwFocus, nwItemType, nwView from novelwriter.gui.doceditor import GuiDocEditor from novelwriter.gui.noveltree import GuiNovelView from novelwriter.gui.outline import GuiOutlineView @@ -64,7 +64,21 @@ def testGuiMain_Launch(qtbot, monkeypatch, nwGUI, projPath): # Open Lipsum project nwGUI.postLaunchTasks(projPath) + assert SHARED.hasProject is True nwGUI.closeProject() + assert SHARED.hasProject is False + + # Open as if called from Welcome + nwGUI._openProjectFromWelcome(projPath) + assert SHARED.hasProject is True + nwGUI.closeProject() + assert SHARED.hasProject is False + + # Open as if called from Welcome, invalid path + with monkeypatch.context() as mp: + mp.setattr(nwGUI, "showWelcomeDialog", lambda *a: None) + nwGUI._openProjectFromWelcome(None) + assert SHARED.hasProject is False # Project open fails with monkeypatch.context() as mp: @@ -689,3 +703,118 @@ def testGuiMain_Features(qtbot, nwGUI, projPath, mockRnd): nwGUI.sideBar.mSettings.hide() # qtbot.stop() + + +@pytest.mark.gui +def testGuiMain_FocusView(qtbot, monkeypatch, nwGUI, projPath, mockRnd): + """Test switching focus and view of the main window.""" + buildTestProject(nwGUI, projPath) + + nwGUI.openDocument(C.hSceneDoc) + nwGUI.viewDocument(C.hSceneDoc) + + # Toggle Focus + # ============ + nwGUI.docEditor.setFocus() + assert nwGUI.docEditor.anyFocus() + + # Simulate focus change to viewer + nwGUI._appFocusChanged(None, nwGUI.docViewer) + assert nwGUI.docEditor.docHeader.itemTitle._state is False + assert nwGUI.docViewer.docHeader.itemTitle._state is True + + # Simulate focus change to editor + nwGUI._appFocusChanged(None, nwGUI.docEditor) + assert nwGUI.docEditor.docHeader.itemTitle._state is True + assert nwGUI.docViewer.docHeader.itemTitle._state is False + + # Focus Tree + # ========== + assert nwGUI.projStack.currentWidget() == nwGUI.projView + + # Switch from editor to project tree + nwGUI.docEditor.setFocus() + nwGUI._switchFocus(nwFocus.TREE) + assert nwGUI.projStack.currentWidget() == nwGUI.projView + + # Triggering again should switch to novel view + nwGUI._switchFocus(nwFocus.TREE) + assert nwGUI.projStack.currentWidget() == nwGUI.novelView + + # Switch from editor to novel view + nwGUI.docEditor.setFocus() + nwGUI._switchFocus(nwFocus.TREE) + assert nwGUI.projStack.currentWidget() == nwGUI.novelView + + # Triggering again should switch back to project tree + nwGUI._switchFocus(nwFocus.TREE) + assert nwGUI.projStack.currentWidget() == nwGUI.projView + + # If in search mode, should default to project tree + nwGUI._changeView(nwView.SEARCH) + nwGUI._switchFocus(nwFocus.TREE) + assert nwGUI.projStack.currentWidget() == nwGUI.projView + + # Focus Document + # ============== + nwGUI._switchFocus(nwFocus.TREE) + + def mockEmitEditorFocus(*a): + nwGUI._appFocusChanged(None, nwGUI.docEditor) + + def mockEmitViewerFocus(*a): + nwGUI._appFocusChanged(None, nwGUI.docViewer) + + # Switch to viewer + with monkeypatch.context() as mp: + mp.setattr(nwGUI.docEditor, "hasFocus", lambda *a: True) + mp.setattr(nwGUI.docViewer, "hasFocus", lambda *a: False) + mp.setattr(nwGUI.docEditor, "setFocus", mockEmitEditorFocus) + mp.setattr(nwGUI.docViewer, "setFocus", mockEmitViewerFocus) + nwGUI._switchFocus(nwFocus.DOCUMENT) + assert nwGUI.docEditor.docHeader.itemTitle._state is False + assert nwGUI.docViewer.docHeader.itemTitle._state is True + + # Call again to switch to editor + with monkeypatch.context() as mp: + mp.setattr(nwGUI.docEditor, "hasFocus", lambda *a: False) + mp.setattr(nwGUI.docViewer, "hasFocus", lambda *a: True) + mp.setattr(nwGUI.docEditor, "setFocus", mockEmitEditorFocus) + mp.setattr(nwGUI.docViewer, "setFocus", mockEmitViewerFocus) + nwGUI._switchFocus(nwFocus.DOCUMENT) + assert nwGUI.docEditor.docHeader.itemTitle._state is True + assert nwGUI.docViewer.docHeader.itemTitle._state is False + + # Default to editor + with monkeypatch.context() as mp: + mp.setattr(nwGUI.docEditor, "hasFocus", lambda *a: False) + mp.setattr(nwGUI.docViewer, "hasFocus", lambda *a: False) + mp.setattr(nwGUI.docEditor, "setFocus", mockEmitEditorFocus) + mp.setattr(nwGUI.docViewer, "setFocus", mockEmitViewerFocus) + nwGUI._switchFocus(nwFocus.DOCUMENT) + assert nwGUI.docEditor.docHeader.itemTitle._state is True + assert nwGUI.docViewer.docHeader.itemTitle._state is False + + # Focus Outline + # ============= + nwGUI._switchFocus(nwFocus.OUTLINE) + assert nwGUI.mainStack.currentWidget() == nwGUI.outlineView + + # Pass Actions + # ============ + + # Pass to editor + with monkeypatch.context() as mp: + mp.setattr(nwGUI.docEditor, "hasFocus", lambda *a: True) + mp.setattr(nwGUI.docViewer, "hasFocus", lambda *a: False) + nwGUI._passDocumentAction(nwDocAction.SEL_ALL) + assert nwGUI.docEditor.textCursor().hasSelection() is True + + # Pass to viewer + with monkeypatch.context() as mp: + mp.setattr(nwGUI.docEditor, "hasFocus", lambda *a: False) + mp.setattr(nwGUI.docViewer, "hasFocus", lambda *a: True) + nwGUI._passDocumentAction(nwDocAction.SEL_ALL) + assert nwGUI.docViewer.textCursor().hasSelection() is True + + # qtbot.stop() From d07543926f5c8dea198b43a88dac2e452b5dc0c2 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Fri, 21 Jun 2024 00:18:10 +0200 Subject: [PATCH 7/8] Disable failing fast in Linux test matrix --- .github/workflows/test_linux.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml index 9112966e..897d1c0e 100644 --- a/.github/workflows/test_linux.yml +++ b/.github/workflows/test_linux.yml @@ -15,6 +15,7 @@ jobs: strategy: matrix: python-version: ["3.9", "3.10", "3.11", "3.12"] + fail-fast: false runs-on: ubuntu-latest steps: - name: Python Setup From 802069b4e572e3a5649b3eef315f04ebefdf666f Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Fri, 21 Jun 2024 00:21:45 +0200 Subject: [PATCH 8/8] Try adding deleteLater to i18n test --- tests/test_gui/test_gui_i18n.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_gui/test_gui_i18n.py b/tests/test_gui/test_gui_i18n.py index 83638f13..f41d0970 100644 --- a/tests/test_gui/test_gui_i18n.py +++ b/tests/test_gui/test_gui_i18n.py @@ -64,6 +64,8 @@ def testGuiI18n_Localisation(qtbot, monkeypatch, language, nwGUI, projPath): qtbot.waitUntil(lambda: SHARED.findTopLevelWidget(dType) is not None, timeout=1000) dialog = SHARED.findTopLevelWidget(dType) assert isinstance(dialog, dType) + assert dialog is not None + dialog.deleteLater() showDialog(nwGUI.showWelcomeDialog, GuiWelcome) showDialog(nwGUI.showPreferencesDialog, GuiPreferences)