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