Use a signal for status bar messages

This commit is contained in:
Veronica Berglyd Olsen
2023-08-14 00:03:55 +02:00
parent c4140d753c
commit 156a6fac98
9 changed files with 101 additions and 111 deletions
+3
View File
@@ -52,6 +52,9 @@ class nwConst:
URL_HELP = "https://github.com/vkbo/novelWriter/discussions" URL_HELP = "https://github.com/vkbo/novelWriter/discussions"
URL_RELEASE = "https://github.com/vkbo/novelWriter/releases/latest" URL_RELEASE = "https://github.com/vkbo/novelWriter/releases/latest"
# Gui Settings
STATUS_MSG_TIMEOUT = 15000 # milliseconds
# END Class nwConst # END Class nwConst
+7 -8
View File
@@ -58,7 +58,8 @@ logger = logging.getLogger(__name__)
class NWProject(QObject): class NWProject(QObject):
projectStatusChanged = pyqtSignal(bool) statusChanged = pyqtSignal(bool)
statusMessage = pyqtSignal(str)
def __init__(self, mainGui: GuiMain) -> None: def __init__(self, mainGui: GuiMain) -> None:
super().__init__(parent=mainGui) super().__init__(parent=mainGui)
@@ -335,7 +336,7 @@ class NWProject(QObject):
self.setProjectChanged(False) self.setProjectChanged(False)
self._valid = True self._valid = True
self.mainGui.setStatus(self.tr("Opened Project: {0}").format(self._data.name)) self.statusMessage.emit(self.tr("Opened Project: {0}").format(self._data.name))
return True return True
@@ -389,7 +390,7 @@ class NWProject(QObject):
) )
self._storage.writeLockFile() self._storage.writeLockFile()
self.mainGui.setStatus(self.tr("Saved Project: {0}").format(self._data.name)) self.statusMessage.emit(self.tr("Saved Project: {0}").format(self._data.name))
self.setProjectChanged(False) self.setProjectChanged(False)
return True return True
@@ -411,7 +412,7 @@ class NWProject(QObject):
return False return False
logger.info("Backing up project") logger.info("Backing up project")
self.mainGui.setStatus(self.tr("Backing up project ...")) self.statusMessage.emit(self.tr("Backing up project ..."))
if not self._data.name: if not self._data.name:
self.mainGui.makeAlert(self.tr( self.mainGui.makeAlert(self.tr(
@@ -446,9 +447,7 @@ class NWProject(QObject):
), level=nwAlert.ERROR) ), level=nwAlert.ERROR)
return False return False
self.mainGui.setStatus(self.tr( self.statusMessage.emit(self.tr("Project backed up to '{0}'").format(str(archName)))
"Project backed up to '{0}'"
).format(str(archName)))
return True return True
@@ -502,7 +501,7 @@ class NWProject(QObject):
""" """
if isinstance(status, bool): if isinstance(status, bool):
self._changed = status self._changed = status
self.projectStatusChanged.emit(self._changed) self.statusChanged.emit(self._changed)
return self._changed return self._changed
## ##
+9 -13
View File
@@ -71,9 +71,10 @@ class GuiDocEditor(QTextEdit):
) )
# Custom Signals # Custom Signals
spellDictionaryChanged = pyqtSignal(str, str) statusMessage = pyqtSignal(str)
docEditedStatusChanged = pyqtSignal(bool)
docCountsChanged = pyqtSignal(str, int, int, int) docCountsChanged = pyqtSignal(str, int, int, int)
editedStatusChanged = pyqtSignal(bool)
spellDictionaryChanged = pyqtSignal(str, str)
loadDocumentTagRequest = pyqtSignal(str, Enum) loadDocumentTagRequest = pyqtSignal(str, Enum)
novelStructureChanged = pyqtSignal() novelStructureChanged = pyqtSignal()
novelItemMetaChanged = pyqtSignal(str) novelItemMetaChanged = pyqtSignal(str)
@@ -454,9 +455,7 @@ class GuiDocEditor(QTextEdit):
# Update the status bar # Update the status bar
if self._nwItem is not None: if self._nwItem is not None:
self.mainGui.setStatus( self.statusMessage.emit(self.tr("Opened Document: {0}").format(self._nwItem.itemName))
self.tr("Opened Document: {0}").format(self._nwItem.itemName)
)
return True return True
@@ -562,9 +561,7 @@ class GuiDocEditor(QTextEdit):
self.docFooter.updateInfo() self.docFooter.updateInfo()
# Update the status bar # Update the status bar
self.mainGui.setStatus( self.statusMessage.emit(self.tr("Saved Document: {0}").format(self._nwItem.itemName))
self.tr("Saved Document: {0}").format(self._nwItem.itemName)
)
return True return True
@@ -639,7 +636,7 @@ class GuiDocEditor(QTextEdit):
document change signal. document change signal.
""" """
self._docChanged = bValue self._docChanged = bValue
self.docEditedStatusChanged.emit(self._docChanged) self.editedStatusChanged.emit(self._docChanged)
return self._docChanged return self._docChanged
def setCursorPosition(self, position): def setCursorPosition(self, position):
@@ -747,7 +744,7 @@ class GuiDocEditor(QTextEdit):
return True return True
def spellCheckDocument(self): def spellCheckDocument(self) -> None:
"""Rerun the highlighter to update spell checking status of the """Rerun the highlighter to update spell checking status of the
currently loaded text. The fastest way to do this, at least as currently loaded text. The fastest way to do this, at least as
of Qt 5.13, is to clear the text and put it back. This clears of Qt 5.13, is to clear the text and put it back. This clears
@@ -763,9 +760,8 @@ class GuiDocEditor(QTextEdit):
self.highLight.rehighlight() self.highLight.rehighlight()
qApp.restoreOverrideCursor() qApp.restoreOverrideCursor()
logger.debug("Document highlighted in %.3f ms", 1000*(time() - start)) logger.debug("Document highlighted in %.3f ms", 1000*(time() - start))
self.mainGui.mainStatus.setStatus(self.tr("Spell check complete")) self.statusMessage.emit(self.tr("Spell check complete"))
return
return True
## ##
# General Class Methods # General Class Methods
+57 -72
View File
@@ -27,6 +27,7 @@ from __future__ import annotations
import logging import logging
from time import time from time import time
from typing import TYPE_CHECKING, Literal
from PyQt5.QtCore import pyqtSlot, QLocale from PyQt5.QtCore import pyqtSlot, QLocale
from PyQt5.QtGui import QColor from PyQt5.QtGui import QColor
@@ -34,21 +35,24 @@ from PyQt5.QtWidgets import qApp, QStatusBar, QLabel
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.common import formatTime from novelwriter.common import formatTime
from novelwriter.constants import nwConst
from novelwriter.extensions.statusled import StatusLED from novelwriter.extensions.statusled import StatusLED
if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class GuiMainStatus(QStatusBar): class GuiMainStatus(QStatusBar):
def __init__(self, mainGui): def __init__(self, mainGui: GuiMain) -> None:
super().__init__(parent=mainGui) super().__init__(parent=mainGui)
logger.debug("Create: GuiMainStatus") logger.debug("Create: GuiMainStatus")
self.mainGui = mainGui self._refTime = -1.0
self.refTime = None self._userIdle = False
self.userIdle = False
colNone = QColor(*SHARED.theme.statNone) colNone = QColor(*SHARED.theme.statNone)
colSaved = QColor(*SHARED.theme.statSaved) colSaved = QColor(*SHARED.theme.statSaved)
@@ -114,80 +118,59 @@ class GuiMainStatus(QStatusBar):
return return
def clearStatus(self): def clearStatus(self) -> None:
"""Reset all widgets on the status bar to default values. """Reset all widgets on the status bar to default values."""
""" self.setRefTime(-1.0)
self.setRefTime(None)
self.setLanguage(None, "") self.setLanguage(None, "")
self.setProjectStats(0, 0) self.setProjectStats(0, 0)
self.setProjectStatus(StatusLED.S_NONE) self.setProjectStatus(StatusLED.S_NONE)
self.setDocumentStatus(StatusLED.S_NONE) self.setDocumentStatus(StatusLED.S_NONE)
self.updateTime() self.updateTime()
return True return
def updateTheme(self): def updateTheme(self) -> None:
"""Update theme elements. """Update theme elements."""
"""
iPx = SHARED.theme.baseIconSize iPx = SHARED.theme.baseIconSize
self.langIcon.setPixmap(SHARED.theme.getPixmap("status_lang", (iPx, iPx))) self.langIcon.setPixmap(SHARED.theme.getPixmap("status_lang", (iPx, iPx)))
self.statsIcon.setPixmap(SHARED.theme.getPixmap("status_stats", (iPx, iPx))) self.statsIcon.setPixmap(SHARED.theme.getPixmap("status_stats", (iPx, iPx)))
self.timePixmap = SHARED.theme.getPixmap("status_time", (iPx, iPx)) self.timePixmap = SHARED.theme.getPixmap("status_time", (iPx, iPx))
self.idlePixmap = SHARED.theme.getPixmap("status_idle", (iPx, iPx)) self.idlePixmap = SHARED.theme.getPixmap("status_idle", (iPx, iPx))
self.timeIcon.setPixmap(self.timePixmap) self.timeIcon.setPixmap(self.timePixmap)
return return
## ##
# Setters # Setters
## ##
def setRefTime(self, theTime): def setRefTime(self, refTime: float) -> None:
"""Set the reference time for the status bar clock. """Set the reference time for the status bar clock."""
""" self._refTime = refTime
self.refTime = theTime
return return
def setStatus(self, theMessage, timeOut=20.0): def setProjectStatus(self, state: Literal[0, 1, 2]) -> None:
"""Set the status bar message to display for 'timeOut' seconds. """Set the project status colour icon."""
""" self.projIcon.setState(state)
self.showMessage(theMessage, int(timeOut*1000))
qApp.processEvents()
return return
def setProjectStatus(self, theState): def setDocumentStatus(self, state: Literal[0, 1, 2]) -> None:
"""Set the project status colour icon. """Set the document status colour icon."""
""" self.docIcon.setState(state)
self.projIcon.setState(theState)
return return
def setDocumentStatus(self, theState): def setUserIdle(self, idle: bool) -> None:
"""Set the document status colour icon. """Change the idle status icon."""
"""
self.docIcon.setState(theState)
return
def setUserIdle(self, userIdle):
"""Change the idle status icon.
"""
if not CONFIG.stopWhenIdle: if not CONFIG.stopWhenIdle:
userIdle = False idle = False
if self._userIdle != idle:
if self.userIdle != userIdle: if idle:
if userIdle:
self.timeIcon.setPixmap(self.idlePixmap) self.timeIcon.setPixmap(self.idlePixmap)
else: else:
self.timeIcon.setPixmap(self.timePixmap) self.timeIcon.setPixmap(self.timePixmap)
self._userIdle = idle
self.userIdle = userIdle
return return
def setProjectStats(self, pWC, sWC): def setProjectStats(self, pWC: int, sWC: int) -> None:
"""Update the current project statistics. """Update the current project statistics."""
"""
self.statsText.setText(self.tr("Words: {0} ({1})").format(f"{pWC:n}", f"{sWC:+n}")) self.statsText.setText(self.tr("Words: {0} ({1})").format(f"{pWC:n}", f"{sWC:+n}"))
if CONFIG.incNotesWCount: if CONFIG.incNotesWCount:
self.statsText.setToolTip(self.tr("Project word count (session change)")) self.statsText.setToolTip(self.tr("Project word count (session change)"))
@@ -195,53 +178,55 @@ class GuiMainStatus(QStatusBar):
self.statsText.setToolTip(self.tr("Novel word count (session change)")) self.statsText.setToolTip(self.tr("Novel word count (session change)"))
return return
def updateTime(self, idleTime=0.0): def updateTime(self, idleTime: float = 0.0) -> None:
"""Update the session clock. """Update the session clock."""
""" if self._refTime < 0.0:
if self.refTime is None:
self.timeText.setText("00:00:00") self.timeText.setText("00:00:00")
else: else:
if CONFIG.stopWhenIdle: if CONFIG.stopWhenIdle:
sessTime = round(time() - self.refTime - idleTime) sessTime = round(time() - self._refTime - idleTime)
else: else:
sessTime = round(time() - self.refTime) sessTime = round(time() - self._refTime)
self.timeText.setText(formatTime(sessTime)) self.timeText.setText(formatTime(sessTime))
return return
## ##
# Slots # Public Slots
## ##
@pyqtSlot(str)
def setStatusMessage(self, message: str) -> None:
"""Set the status bar message to display."""
self.showMessage(message, nwConst.STATUS_MSG_TIMEOUT)
qApp.processEvents()
return
@pyqtSlot(str, str) @pyqtSlot(str, str)
def setLanguage(self, theLanguage, theProvider): def setLanguage(self, language: str, provider: str) -> None:
"""Set the language code for the spell checker. """Set the language code for the spell checker."""
""" if language == "None":
if theLanguage == "None":
self.langText.setText(self.tr("None")) self.langText.setText(self.tr("None"))
self.langText.setToolTip("") self.langText.setToolTip("")
else: else:
qLocal = QLocale(theLanguage) qLocal = QLocale(language)
spLang = qLocal.nativeLanguageName().title() spLang = qLocal.nativeLanguageName().title()
self.langText.setText(spLang) self.langText.setText(spLang)
if theProvider: if provider:
self.langText.setToolTip("%s (%s)" % (theLanguage, theProvider)) self.langText.setToolTip("%s (%s)" % (language, provider))
else: else:
self.langText.setToolTip(theLanguage) self.langText.setToolTip(language)
return return
@pyqtSlot(bool) @pyqtSlot(bool)
def doUpdateProjectStatus(self, isChanged): def updateProjectStatus(self, status: bool) -> None:
"""Slot for updating the project status. """Update the project status."""
""" self.setProjectStatus(StatusLED.S_BAD if status else StatusLED.S_GOOD)
self.setProjectStatus(StatusLED.S_BAD if isChanged else StatusLED.S_GOOD)
return return
@pyqtSlot(bool) @pyqtSlot(bool)
def doUpdateDocumentStatus(self, isChanged): def updateDocumentStatus(self, status: bool) -> None:
"""Slot for updating the document status. """Update the document status."""
""" self.setDocumentStatus(StatusLED.S_BAD if status else StatusLED.S_GOOD)
self.setDocumentStatus(StatusLED.S_BAD if isChanged else StatusLED.S_GOOD)
return return
# END Class GuiMainStatus # END Class GuiMainStatus
+6 -7
View File
@@ -236,7 +236,8 @@ class GuiMain(QMainWindow):
# Connect Signals # Connect Signals
# =============== # ===============
SHARED.projectStatusChanged.connect(self.mainStatus.doUpdateProjectStatus) SHARED.projectStatusChanged.connect(self.mainStatus.updateProjectStatus)
SHARED.projectStatusMessage.connect(self.mainStatus.setStatusMessage)
self.viewsBar.viewChangeRequested.connect(self._changeView) self.viewsBar.viewChangeRequested.connect(self._changeView)
@@ -255,12 +256,13 @@ class GuiMain(QMainWindow):
self.novelView.openDocumentRequest.connect(self._openDocument) self.novelView.openDocumentRequest.connect(self._openDocument)
self.docEditor.spellDictionaryChanged.connect(self.mainStatus.setLanguage) self.docEditor.spellDictionaryChanged.connect(self.mainStatus.setLanguage)
self.docEditor.docEditedStatusChanged.connect(self.mainStatus.doUpdateDocumentStatus) self.docEditor.editedStatusChanged.connect(self.mainStatus.updateDocumentStatus)
self.docEditor.docCountsChanged.connect(self.itemDetails.updateCounts) self.docEditor.docCountsChanged.connect(self.itemDetails.updateCounts)
self.docEditor.docCountsChanged.connect(self.projView.updateCounts) self.docEditor.docCountsChanged.connect(self.projView.updateCounts)
self.docEditor.loadDocumentTagRequest.connect(self._followTag) self.docEditor.loadDocumentTagRequest.connect(self._followTag)
self.docEditor.novelStructureChanged.connect(self.novelView.refreshTree) self.docEditor.novelStructureChanged.connect(self.novelView.refreshTree)
self.docEditor.novelItemMetaChanged.connect(self.novelView.updateNovelItemMeta) self.docEditor.novelItemMetaChanged.connect(self.novelView.updateNovelItemMeta)
self.docEditor.statusMessage.connect(self.mainStatus.setStatusMessage)
self.docViewer.loadDocumentTagRequest.connect(self._followTag) self.docViewer.loadDocumentTagRequest.connect(self._followTag)
@@ -299,9 +301,6 @@ class GuiMain(QMainWindow):
keyEscape.setKey(QKeySequence(Qt.Key_Escape)) keyEscape.setKey(QKeySequence(Qt.Key_Escape))
keyEscape.activated.connect(self._keyPressEscape) keyEscape.activated.connect(self._keyPressEscape)
# Forward Functions
self.setStatus = self.mainStatus.setStatus
# Cache Alert Pixmaps # Cache Alert Pixmaps
pxSize = (2*iPx, 2*iPx) pxSize = (2*iPx, 2*iPx)
self.alertPix: dict[nwAlert, QPixmap] = { self.alertPix: dict[nwAlert, QPixmap] = {
@@ -333,7 +332,7 @@ class GuiMain(QMainWindow):
), level=nwAlert.WARN) ), level=nwAlert.WARN)
logger.info("novelWriter is ready ...") logger.info("novelWriter is ready ...")
self.setStatus(self.tr("novelWriter is ready ...")) self.mainStatus.setStatusMessage(self.tr("novelWriter is ready ..."))
return return
@@ -838,7 +837,7 @@ class GuiMain(QMainWindow):
self.novelView.refreshTree() self.novelView.refreshTree()
tEnd = time() tEnd = time()
self.setStatus( self.mainStatus.setStatusMessage(
self.tr("Indexing completed in {0} ms").format(f"{(tEnd - tStart)*1000.0:.1f}") self.tr("Indexing completed in {0} ms").format(f"{(tEnd - tStart)*1000.0:.1f}")
) )
self.docEditor.updateTagHighLighting() self.docEditor.updateTagHighLighting()
+12 -3
View File
@@ -41,6 +41,7 @@ logger = logging.getLogger(__name__)
class SharedData(QObject): class SharedData(QObject):
projectStatusChanged = pyqtSignal(bool) projectStatusChanged = pyqtSignal(bool)
projectStatusMessage = pyqtSignal(str)
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
@@ -131,11 +132,17 @@ class SharedData(QObject):
## ##
@pyqtSlot(bool) @pyqtSlot(bool)
def _processProjectStatusChange(self, state: bool) -> None: def _emitProjectStatusChange(self, state: bool) -> None:
"""Forward the project status slot.""" """Forward the project status slot."""
self.projectStatusChanged.emit(state) self.projectStatusChanged.emit(state)
return return
@pyqtSlot(str)
def _emitProjectStatusMeesage(self, message: str) -> None:
"""Forward the project message slot."""
self.projectStatusMessage.emit(message)
return
## ##
# Internal Functions # Internal Functions
## ##
@@ -144,10 +151,12 @@ class SharedData(QObject):
"""Create a new project instance.""" """Create a new project instance."""
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
if isinstance(self._project, NWProject): if isinstance(self._project, NWProject):
self._project.projectStatusChanged.disconnect() self._project.statusChanged.disconnect()
self._project.statusMessage.disconnect()
self._project.deleteLater() self._project.deleteLater()
self._project = NWProject(self.mainGui) self._project = NWProject(self.mainGui)
self._project.projectStatusChanged.connect(self._processProjectStatusChange) self._project.statusChanged.connect(self._emitProjectStatusChange)
self._project.statusMessage.connect(self._emitProjectStatusMeesage)
return return
# END Class SharedData # END Class SharedData
+1 -1
View File
@@ -92,7 +92,7 @@ class MockStatusBar:
def setStatus(self, theText): def setStatus(self, theText):
return return
def doUpdateProjectStatus(self, theStatus): def updateProjectStatus(self, theStatus):
return return
# END Class MockStatusBar # END Class MockStatusBar
+1 -1
View File
@@ -213,7 +213,7 @@ def testGuiEditor_MetaData(qtbot, nwGUI, projPath, mockRnd):
# Document Changed Signal # Document Changed Signal
nwGUI.docEditor._docChanged = False nwGUI.docEditor._docChanged = False
with qtbot.waitSignal(nwGUI.docEditor.docEditedStatusChanged, raising=True, timeout=100): with qtbot.waitSignal(nwGUI.docEditor.editedStatusChanged, raising=True, timeout=100):
nwGUI.docEditor.setDocumentChanged(True) nwGUI.docEditor.setDocumentChanged(True)
assert nwGUI.docEditor._docChanged is True assert nwGUI.docEditor._docChanged is True
+5 -6
View File
@@ -30,8 +30,7 @@ from novelwriter.extensions.statusled import StatusLED
@pytest.mark.gui @pytest.mark.gui
def testGuiStatusBar_Main(qtbot, nwGUI, projPath, mockRnd): def testGuiStatusBar_Main(qtbot, nwGUI, projPath, mockRnd):
"""Test the the various features of the status bar. """Test the the various features of the status bar."""
"""
buildTestProject(nwGUI, projPath) buildTestProject(nwGUI, projPath)
cHandle = nwGUI.project.newFile("A Note", C.hCharRoot) cHandle = nwGUI.project.newFile("A Note", C.hCharRoot)
newDoc = nwGUI.project.storage.getDocument(cHandle) newDoc = nwGUI.project.storage.getDocument(cHandle)
@@ -42,7 +41,7 @@ def testGuiStatusBar_Main(qtbot, nwGUI, projPath, mockRnd):
# Reference Time # Reference Time
refTime = time.time() refTime = time.time()
nwGUI.mainStatus.setRefTime(refTime) nwGUI.mainStatus.setRefTime(refTime)
assert nwGUI.mainStatus.refTime == refTime assert nwGUI.mainStatus._refTime == refTime
# Project Status # Project Status
nwGUI.mainStatus.setProjectStatus(StatusLED.S_NONE) nwGUI.mainStatus.setProjectStatus(StatusLED.S_NONE)
@@ -64,18 +63,18 @@ def testGuiStatusBar_Main(qtbot, nwGUI, projPath, mockRnd):
CONFIG.stopWhenIdle = False CONFIG.stopWhenIdle = False
nwGUI.mainStatus.setUserIdle(True) nwGUI.mainStatus.setUserIdle(True)
nwGUI.mainStatus.updateTime() nwGUI.mainStatus.updateTime()
assert nwGUI.mainStatus.userIdle is False assert nwGUI.mainStatus._userIdle is False
assert nwGUI.mainStatus.timeText.text() == "00:00:00" assert nwGUI.mainStatus.timeText.text() == "00:00:00"
CONFIG.stopWhenIdle = True CONFIG.stopWhenIdle = True
nwGUI.mainStatus.setUserIdle(True) nwGUI.mainStatus.setUserIdle(True)
nwGUI.mainStatus.updateTime(5) nwGUI.mainStatus.updateTime(5)
assert nwGUI.mainStatus.userIdle is True assert nwGUI.mainStatus._userIdle is True
assert nwGUI.mainStatus.timeText.text() != "00:00:00" assert nwGUI.mainStatus.timeText.text() != "00:00:00"
nwGUI.mainStatus.setUserIdle(False) nwGUI.mainStatus.setUserIdle(False)
nwGUI.mainStatus.updateTime(5) nwGUI.mainStatus.updateTime(5)
assert nwGUI.mainStatus.userIdle is False assert nwGUI.mainStatus._userIdle is False
assert nwGUI.mainStatus.timeText.text() != "00:00:00" assert nwGUI.mainStatus.timeText.text() != "00:00:00"
# Language # Language