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] 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