Hide session timer (#2149)
This commit is contained in:
@@ -388,3 +388,16 @@ application like for instance Libre Office Calc or Excel.
|
|||||||
definition of idle here is that the novelWriter main window loses focus, or the user hasn't made
|
definition of idle here is that the novelWriter main window loses focus, or the user hasn't made
|
||||||
any changes to the currently open document in five minutes. The number of minutes can be altered
|
any changes to the currently open document in five minutes. The number of minutes can be altered
|
||||||
in **Preferences**.
|
in **Preferences**.
|
||||||
|
|
||||||
|
|
||||||
|
Session Timer
|
||||||
|
-------------
|
||||||
|
|
||||||
|
A session timer is by default visible in the status bar. The icon will show you a clock icon when
|
||||||
|
you are active, and a pause icon when you are considered "idle" per the criteria mentioned above.
|
||||||
|
|
||||||
|
If you do not wish to see the timer, you can click on it once to hide it. The icon will still be
|
||||||
|
visible. Click the icon once more to display the timer again.
|
||||||
|
|
||||||
|
.. versionadded:: 2.6
|
||||||
|
As of version 2.6, clicking the timer text or icon in the status bar will toggle its visibility.
|
||||||
|
|||||||
@@ -194,6 +194,7 @@ class Config:
|
|||||||
# State
|
# State
|
||||||
self.showViewerPanel = True # The panel for the viewer is visible
|
self.showViewerPanel = True # The panel for the viewer is visible
|
||||||
self.showEditToolBar = False # The document editor toolbar visibility
|
self.showEditToolBar = False # The document editor toolbar visibility
|
||||||
|
self.showSessionTime = True # Show the session time in the status bar
|
||||||
self.viewComments = True # Comments are shown in the viewer
|
self.viewComments = True # Comments are shown in the viewer
|
||||||
self.viewSynopsis = True # Synopsis is shown in the viewer
|
self.viewSynopsis = True # Synopsis is shown in the viewer
|
||||||
|
|
||||||
@@ -674,6 +675,7 @@ class Config:
|
|||||||
sec = "State"
|
sec = "State"
|
||||||
self.showViewerPanel = conf.rdBool(sec, "showviewerpanel", self.showViewerPanel)
|
self.showViewerPanel = conf.rdBool(sec, "showviewerpanel", self.showViewerPanel)
|
||||||
self.showEditToolBar = conf.rdBool(sec, "showedittoolbar", self.showEditToolBar)
|
self.showEditToolBar = conf.rdBool(sec, "showedittoolbar", self.showEditToolBar)
|
||||||
|
self.showSessionTime = conf.rdBool(sec, "showsessiontime", self.showSessionTime)
|
||||||
self.viewComments = conf.rdBool(sec, "viewcomments", self.viewComments)
|
self.viewComments = conf.rdBool(sec, "viewcomments", self.viewComments)
|
||||||
self.viewSynopsis = conf.rdBool(sec, "viewsynopsis", self.viewSynopsis)
|
self.viewSynopsis = conf.rdBool(sec, "viewsynopsis", self.viewSynopsis)
|
||||||
self.searchCase = conf.rdBool(sec, "searchcase", self.searchCase)
|
self.searchCase = conf.rdBool(sec, "searchcase", self.searchCase)
|
||||||
@@ -784,6 +786,7 @@ class Config:
|
|||||||
conf["State"] = {
|
conf["State"] = {
|
||||||
"showviewerpanel": str(self.showViewerPanel),
|
"showviewerpanel": str(self.showViewerPanel),
|
||||||
"showedittoolbar": str(self.showEditToolBar),
|
"showedittoolbar": str(self.showEditToolBar),
|
||||||
|
"showsessiontime": str(self.showSessionTime),
|
||||||
"viewcomments": str(self.viewComments),
|
"viewcomments": str(self.viewComments),
|
||||||
"viewsynopsis": str(self.viewSynopsis),
|
"viewsynopsis": str(self.viewSynopsis),
|
||||||
"searchcase": str(self.searchCase),
|
"searchcase": str(self.searchCase),
|
||||||
|
|||||||
@@ -30,14 +30,15 @@ from __future__ import annotations
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from PyQt5.QtCore import QSize, Qt, pyqtSlot
|
from PyQt5.QtCore import QSize, Qt, pyqtSignal, pyqtSlot
|
||||||
from PyQt5.QtGui import QWheelEvent
|
from PyQt5.QtGui import QMouseEvent, QWheelEvent
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QApplication, QComboBox, QDialog, QDoubleSpinBox, QSpinBox, QToolButton,
|
QApplication, QComboBox, QDialog, QDoubleSpinBox, QLabel, QSpinBox,
|
||||||
QWidget
|
QToolButton, QWidget
|
||||||
)
|
)
|
||||||
|
|
||||||
from novelwriter import CONFIG, SHARED
|
from novelwriter import CONFIG, SHARED
|
||||||
|
from novelwriter.types import QtMouseLeft
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma: no cover
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
from novelwriter.guimain import GuiMain
|
from novelwriter.guimain import GuiMain
|
||||||
@@ -196,3 +197,14 @@ class NIconToggleButton(QToolButton):
|
|||||||
iconSize = self.iconSize()
|
iconSize = self.iconSize()
|
||||||
self.setIcon(SHARED.theme.getToggleIcon(iconKey, (iconSize.width(), iconSize.height())))
|
self.setIcon(SHARED.theme.getToggleIcon(iconKey, (iconSize.width(), iconSize.height())))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
class NClickableLabel(QLabel):
|
||||||
|
|
||||||
|
mouseClicked = pyqtSignal()
|
||||||
|
|
||||||
|
def mousePressEvent(self, event: QMouseEvent) -> None:
|
||||||
|
"""Capture a left mouse click and emit its signal."""
|
||||||
|
if event.button() == QtMouseLeft:
|
||||||
|
self.mouseClicked.emit()
|
||||||
|
return super().mousePressEvent(event)
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ from novelwriter import CONFIG, SHARED
|
|||||||
from novelwriter.common import formatTime
|
from novelwriter.common import formatTime
|
||||||
from novelwriter.constants import nwConst
|
from novelwriter.constants import nwConst
|
||||||
from novelwriter.enum import nwTrinary
|
from novelwriter.enum import nwTrinary
|
||||||
|
from novelwriter.extensions.modified import NClickableLabel
|
||||||
from novelwriter.extensions.statusled import StatusLED
|
from novelwriter.extensions.statusled import StatusLED
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -92,12 +93,16 @@ class GuiMainStatus(QStatusBar):
|
|||||||
|
|
||||||
# The Session Clock
|
# The Session Clock
|
||||||
# Set the minimum width so the label doesn't rescale every second
|
# Set the minimum width so the label doesn't rescale every second
|
||||||
self.timeIcon = QLabel(self)
|
self.timeIcon = NClickableLabel(self)
|
||||||
self.timeText = QLabel("", self)
|
self.timeIcon.mouseClicked.connect(self._onClickTimerLabel)
|
||||||
|
|
||||||
|
self.timeText = NClickableLabel("", self)
|
||||||
self.timeText.setToolTip(self.tr("Session Time"))
|
self.timeText.setToolTip(self.tr("Session Time"))
|
||||||
self.timeText.setMinimumWidth(SHARED.theme.getTextWidth("00:00:00:"))
|
self.timeText.setMinimumWidth(SHARED.theme.getTextWidth("00:00:00:"))
|
||||||
self.timeIcon.setContentsMargins(0, 0, 0, 0)
|
self.timeIcon.setContentsMargins(0, 0, 0, 0)
|
||||||
self.timeText.setContentsMargins(0, 0, 0, 0)
|
self.timeText.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.timeText.setVisible(CONFIG.showSessionTime)
|
||||||
|
self.timeText.mouseClicked.connect(self._onClickTimerLabel)
|
||||||
self.addPermanentWidget(self.timeIcon)
|
self.addPermanentWidget(self.timeIcon)
|
||||||
self.addPermanentWidget(self.timeText)
|
self.addPermanentWidget(self.timeText)
|
||||||
|
|
||||||
@@ -224,6 +229,18 @@ class GuiMainStatus(QStatusBar):
|
|||||||
self.setDocumentStatus(nwTrinary.NEGATIVE if status else nwTrinary.POSITIVE)
|
self.setDocumentStatus(nwTrinary.NEGATIVE if status else nwTrinary.POSITIVE)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Private Slots
|
||||||
|
##
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def _onClickTimerLabel(self) -> None:
|
||||||
|
"""Process mouse click on timer label."""
|
||||||
|
state = not CONFIG.showSessionTime
|
||||||
|
self.timeText.setVisible(state)
|
||||||
|
CONFIG.showSessionTime = state
|
||||||
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
# Debug
|
# Debug
|
||||||
##
|
##
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[Meta]
|
[Meta]
|
||||||
timestamp = 2024-11-03 21:45:08
|
timestamp = 2024-12-29 17:30:10
|
||||||
|
|
||||||
[Main]
|
[Main]
|
||||||
font =
|
font =
|
||||||
@@ -71,6 +71,7 @@ useridletime = 300
|
|||||||
[State]
|
[State]
|
||||||
showviewerpanel = True
|
showviewerpanel = True
|
||||||
showedittoolbar = False
|
showedittoolbar = False
|
||||||
|
showsessiontime = True
|
||||||
viewcomments = True
|
viewcomments = True
|
||||||
viewsynopsis = True
|
viewsynopsis = True
|
||||||
searchcase = False
|
searchcase = False
|
||||||
|
|||||||
@@ -23,11 +23,13 @@ from __future__ import annotations
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PyQt5.QtCore import QEvent, QPoint, QPointF, Qt
|
from PyQt5.QtCore import QEvent, QPoint, QPointF, Qt
|
||||||
from PyQt5.QtGui import QKeyEvent, QWheelEvent
|
from PyQt5.QtGui import QKeyEvent, QMouseEvent, QWheelEvent
|
||||||
from PyQt5.QtWidgets import QWidget
|
from PyQt5.QtWidgets import QWidget
|
||||||
|
|
||||||
from novelwriter.extensions.modified import NComboBox, NDialog, NDoubleSpinBox, NSpinBox
|
from novelwriter.extensions.modified import (
|
||||||
from novelwriter.types import QtModNone, QtRejected
|
NClickableLabel, NComboBox, NDialog, NDoubleSpinBox, NSpinBox
|
||||||
|
)
|
||||||
|
from novelwriter.types import QtModNone, QtMouseLeft, QtRejected
|
||||||
|
|
||||||
from tests.tools import SimpleDialog
|
from tests.tools import SimpleDialog
|
||||||
|
|
||||||
@@ -139,3 +141,19 @@ def testExtModified_NDoubleSpinBox(qtbot, monkeypatch):
|
|||||||
assert event.ignored is True
|
assert event.ignored is True
|
||||||
|
|
||||||
# qtbot.stop()
|
# qtbot.stop()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gui
|
||||||
|
def testExtModified_NClickableLabel(qtbot, monkeypatch):
|
||||||
|
"""Test the NClickableLabel class."""
|
||||||
|
widget = NClickableLabel()
|
||||||
|
dialog = SimpleDialog(widget)
|
||||||
|
dialog.show()
|
||||||
|
|
||||||
|
position = widget.rect().center()
|
||||||
|
event = QMouseEvent(
|
||||||
|
QEvent.Type.MouseButtonPress, position, QtMouseLeft, QtMouseLeft, QtModNone
|
||||||
|
)
|
||||||
|
|
||||||
|
with qtbot.waitSignal(widget.mouseClicked):
|
||||||
|
widget.mousePressEvent(event)
|
||||||
|
|||||||
@@ -39,56 +39,68 @@ def testGuiStatusBar_Main(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
|||||||
newDoc.writeDocument("# A Note\n\n")
|
newDoc.writeDocument("# A Note\n\n")
|
||||||
nwGUI.rebuildIndex(beQuiet=True)
|
nwGUI.rebuildIndex(beQuiet=True)
|
||||||
|
|
||||||
|
status = nwGUI.mainStatus
|
||||||
|
|
||||||
# Reference Time
|
# Reference Time
|
||||||
refTime = time.time()
|
refTime = time.time()
|
||||||
nwGUI.mainStatus.setRefTime(refTime)
|
status.setRefTime(refTime)
|
||||||
assert nwGUI.mainStatus._refTime == refTime
|
assert status._refTime == refTime
|
||||||
|
|
||||||
# Project Status
|
# Project Status
|
||||||
nwGUI.mainStatus.setProjectStatus(nwTrinary.NEUTRAL)
|
status.setProjectStatus(nwTrinary.NEUTRAL)
|
||||||
assert nwGUI.mainStatus.projIcon.state == nwTrinary.NEUTRAL
|
assert status.projIcon.state == nwTrinary.NEUTRAL
|
||||||
nwGUI.mainStatus.setProjectStatus(nwTrinary.NEGATIVE)
|
status.setProjectStatus(nwTrinary.NEGATIVE)
|
||||||
assert nwGUI.mainStatus.projIcon.state == nwTrinary.NEGATIVE
|
assert status.projIcon.state == nwTrinary.NEGATIVE
|
||||||
nwGUI.mainStatus.setProjectStatus(nwTrinary.POSITIVE)
|
status.setProjectStatus(nwTrinary.POSITIVE)
|
||||||
assert nwGUI.mainStatus.projIcon.state == nwTrinary.POSITIVE
|
assert status.projIcon.state == nwTrinary.POSITIVE
|
||||||
|
|
||||||
# Document Status
|
# Document Status
|
||||||
nwGUI.mainStatus.setDocumentStatus(nwTrinary.NEUTRAL)
|
status.setDocumentStatus(nwTrinary.NEUTRAL)
|
||||||
assert nwGUI.mainStatus.docIcon.state == nwTrinary.NEUTRAL
|
assert status.docIcon.state == nwTrinary.NEUTRAL
|
||||||
nwGUI.mainStatus.setDocumentStatus(nwTrinary.NEGATIVE)
|
status.setDocumentStatus(nwTrinary.NEGATIVE)
|
||||||
assert nwGUI.mainStatus.docIcon.state == nwTrinary.NEGATIVE
|
assert status.docIcon.state == nwTrinary.NEGATIVE
|
||||||
nwGUI.mainStatus.setDocumentStatus(nwTrinary.POSITIVE)
|
status.setDocumentStatus(nwTrinary.POSITIVE)
|
||||||
assert nwGUI.mainStatus.docIcon.state == nwTrinary.POSITIVE
|
assert status.docIcon.state == nwTrinary.POSITIVE
|
||||||
|
|
||||||
# Idle Status
|
# Idle Status
|
||||||
CONFIG.stopWhenIdle = False
|
CONFIG.stopWhenIdle = False
|
||||||
nwGUI.mainStatus.setUserIdle(True)
|
status.setUserIdle(True)
|
||||||
nwGUI.mainStatus.updateTime()
|
status.updateTime()
|
||||||
assert nwGUI.mainStatus._userIdle is False
|
assert status._userIdle is False
|
||||||
assert nwGUI.mainStatus.timeText.text() == "00:00:00"
|
assert status.timeText.text() == "00:00:00"
|
||||||
|
|
||||||
CONFIG.stopWhenIdle = True
|
CONFIG.stopWhenIdle = True
|
||||||
nwGUI.mainStatus.setUserIdle(True)
|
status.setUserIdle(True)
|
||||||
nwGUI.mainStatus.updateTime(5)
|
status.updateTime(5)
|
||||||
assert nwGUI.mainStatus._userIdle is True
|
assert status._userIdle is True
|
||||||
assert nwGUI.mainStatus.timeText.text() != "00:00:00"
|
assert status.timeText.text() != "00:00:00"
|
||||||
|
|
||||||
nwGUI.mainStatus.setUserIdle(False)
|
status.setUserIdle(False)
|
||||||
nwGUI.mainStatus.updateTime(5)
|
status.updateTime(5)
|
||||||
assert nwGUI.mainStatus._userIdle is False
|
assert status._userIdle is False
|
||||||
assert nwGUI.mainStatus.timeText.text() != "00:00:00"
|
assert status.timeText.text() != "00:00:00"
|
||||||
|
|
||||||
|
# Show/Hide Timer
|
||||||
|
assert status.timeText.isVisible() is True
|
||||||
|
assert CONFIG.showSessionTime is True
|
||||||
|
status._onClickTimerLabel()
|
||||||
|
assert status.timeText.isVisible() is False
|
||||||
|
assert CONFIG.showSessionTime is False
|
||||||
|
status._onClickTimerLabel()
|
||||||
|
assert status.timeText.isVisible() is True
|
||||||
|
assert CONFIG.showSessionTime is True
|
||||||
|
|
||||||
# Language
|
# Language
|
||||||
nwGUI.mainStatus.setLanguage("None", "None")
|
status.setLanguage("None", "None")
|
||||||
assert nwGUI.mainStatus.langText.text() == "None"
|
assert status.langText.text() == "None"
|
||||||
nwGUI.mainStatus.setLanguage("en", "None")
|
status.setLanguage("en", "None")
|
||||||
assert nwGUI.mainStatus.langText.text() == "American English"
|
assert status.langText.text() == "American English"
|
||||||
|
|
||||||
# Project Stats
|
# Project Stats
|
||||||
CONFIG.incNotesWCount = False
|
CONFIG.incNotesWCount = False
|
||||||
nwGUI._lastTotalCount = 0
|
nwGUI._lastTotalCount = 0
|
||||||
nwGUI._updateStatusWordCount()
|
nwGUI._updateStatusWordCount()
|
||||||
assert nwGUI.mainStatus.statsText.text() == "Words: 9 (+9)"
|
assert status.statsText.text() == "Words: 9 (+9)"
|
||||||
|
|
||||||
# Update again, but through time tick
|
# Update again, but through time tick
|
||||||
with monkeypatch.context() as mp:
|
with monkeypatch.context() as mp:
|
||||||
@@ -96,6 +108,6 @@ def testGuiStatusBar_Main(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
|||||||
CONFIG.incNotesWCount = True
|
CONFIG.incNotesWCount = True
|
||||||
nwGUI._lastTotalCount = 0
|
nwGUI._lastTotalCount = 0
|
||||||
nwGUI._timeTick()
|
nwGUI._timeTick()
|
||||||
assert nwGUI.mainStatus.statsText.text() == "Words: 11 (+11)"
|
assert status.statsText.text() == "Words: 11 (+11)"
|
||||||
|
|
||||||
# qtbot.stop()
|
# qtbot.stop()
|
||||||
|
|||||||
Reference in New Issue
Block a user