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
|
||||
any changes to the currently open document in five minutes. The number of minutes can be altered
|
||||
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
|
||||
self.showViewerPanel = True # The panel for the viewer is visible
|
||||
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.viewSynopsis = True # Synopsis is shown in the viewer
|
||||
|
||||
@@ -674,6 +675,7 @@ class Config:
|
||||
sec = "State"
|
||||
self.showViewerPanel = conf.rdBool(sec, "showviewerpanel", self.showViewerPanel)
|
||||
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.viewSynopsis = conf.rdBool(sec, "viewsynopsis", self.viewSynopsis)
|
||||
self.searchCase = conf.rdBool(sec, "searchcase", self.searchCase)
|
||||
@@ -784,6 +786,7 @@ class Config:
|
||||
conf["State"] = {
|
||||
"showviewerpanel": str(self.showViewerPanel),
|
||||
"showedittoolbar": str(self.showEditToolBar),
|
||||
"showsessiontime": str(self.showSessionTime),
|
||||
"viewcomments": str(self.viewComments),
|
||||
"viewsynopsis": str(self.viewSynopsis),
|
||||
"searchcase": str(self.searchCase),
|
||||
|
||||
@@ -30,14 +30,15 @@ from __future__ import annotations
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtCore import QSize, Qt, pyqtSlot
|
||||
from PyQt5.QtGui import QWheelEvent
|
||||
from PyQt5.QtCore import QSize, Qt, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtGui import QMouseEvent, QWheelEvent
|
||||
from PyQt5.QtWidgets import (
|
||||
QApplication, QComboBox, QDialog, QDoubleSpinBox, QSpinBox, QToolButton,
|
||||
QWidget
|
||||
QApplication, QComboBox, QDialog, QDoubleSpinBox, QLabel, QSpinBox,
|
||||
QToolButton, QWidget
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.types import QtMouseLeft
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from novelwriter.guimain import GuiMain
|
||||
@@ -196,3 +197,14 @@ class NIconToggleButton(QToolButton):
|
||||
iconSize = self.iconSize()
|
||||
self.setIcon(SHARED.theme.getToggleIcon(iconKey, (iconSize.width(), iconSize.height())))
|
||||
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.constants import nwConst
|
||||
from novelwriter.enum import nwTrinary
|
||||
from novelwriter.extensions.modified import NClickableLabel
|
||||
from novelwriter.extensions.statusled import StatusLED
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -92,12 +93,16 @@ class GuiMainStatus(QStatusBar):
|
||||
|
||||
# The Session Clock
|
||||
# Set the minimum width so the label doesn't rescale every second
|
||||
self.timeIcon = QLabel(self)
|
||||
self.timeText = QLabel("", self)
|
||||
self.timeIcon = NClickableLabel(self)
|
||||
self.timeIcon.mouseClicked.connect(self._onClickTimerLabel)
|
||||
|
||||
self.timeText = NClickableLabel("", self)
|
||||
self.timeText.setToolTip(self.tr("Session Time"))
|
||||
self.timeText.setMinimumWidth(SHARED.theme.getTextWidth("00:00:00:"))
|
||||
self.timeIcon.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.timeText)
|
||||
|
||||
@@ -224,6 +229,18 @@ class GuiMainStatus(QStatusBar):
|
||||
self.setDocumentStatus(nwTrinary.NEGATIVE if status else nwTrinary.POSITIVE)
|
||||
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
|
||||
##
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[Meta]
|
||||
timestamp = 2024-11-03 21:45:08
|
||||
timestamp = 2024-12-29 17:30:10
|
||||
|
||||
[Main]
|
||||
font =
|
||||
@@ -71,6 +71,7 @@ useridletime = 300
|
||||
[State]
|
||||
showviewerpanel = True
|
||||
showedittoolbar = False
|
||||
showsessiontime = True
|
||||
viewcomments = True
|
||||
viewsynopsis = True
|
||||
searchcase = False
|
||||
|
||||
@@ -23,11 +23,13 @@ from __future__ import annotations
|
||||
import pytest
|
||||
|
||||
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 novelwriter.extensions.modified import NComboBox, NDialog, NDoubleSpinBox, NSpinBox
|
||||
from novelwriter.types import QtModNone, QtRejected
|
||||
from novelwriter.extensions.modified import (
|
||||
NClickableLabel, NComboBox, NDialog, NDoubleSpinBox, NSpinBox
|
||||
)
|
||||
from novelwriter.types import QtModNone, QtMouseLeft, QtRejected
|
||||
|
||||
from tests.tools import SimpleDialog
|
||||
|
||||
@@ -139,3 +141,19 @@ def testExtModified_NDoubleSpinBox(qtbot, monkeypatch):
|
||||
assert event.ignored is True
|
||||
|
||||
# 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")
|
||||
nwGUI.rebuildIndex(beQuiet=True)
|
||||
|
||||
status = nwGUI.mainStatus
|
||||
|
||||
# Reference Time
|
||||
refTime = time.time()
|
||||
nwGUI.mainStatus.setRefTime(refTime)
|
||||
assert nwGUI.mainStatus._refTime == refTime
|
||||
status.setRefTime(refTime)
|
||||
assert status._refTime == refTime
|
||||
|
||||
# Project Status
|
||||
nwGUI.mainStatus.setProjectStatus(nwTrinary.NEUTRAL)
|
||||
assert nwGUI.mainStatus.projIcon.state == nwTrinary.NEUTRAL
|
||||
nwGUI.mainStatus.setProjectStatus(nwTrinary.NEGATIVE)
|
||||
assert nwGUI.mainStatus.projIcon.state == nwTrinary.NEGATIVE
|
||||
nwGUI.mainStatus.setProjectStatus(nwTrinary.POSITIVE)
|
||||
assert nwGUI.mainStatus.projIcon.state == nwTrinary.POSITIVE
|
||||
status.setProjectStatus(nwTrinary.NEUTRAL)
|
||||
assert status.projIcon.state == nwTrinary.NEUTRAL
|
||||
status.setProjectStatus(nwTrinary.NEGATIVE)
|
||||
assert status.projIcon.state == nwTrinary.NEGATIVE
|
||||
status.setProjectStatus(nwTrinary.POSITIVE)
|
||||
assert status.projIcon.state == nwTrinary.POSITIVE
|
||||
|
||||
# Document Status
|
||||
nwGUI.mainStatus.setDocumentStatus(nwTrinary.NEUTRAL)
|
||||
assert nwGUI.mainStatus.docIcon.state == nwTrinary.NEUTRAL
|
||||
nwGUI.mainStatus.setDocumentStatus(nwTrinary.NEGATIVE)
|
||||
assert nwGUI.mainStatus.docIcon.state == nwTrinary.NEGATIVE
|
||||
nwGUI.mainStatus.setDocumentStatus(nwTrinary.POSITIVE)
|
||||
assert nwGUI.mainStatus.docIcon.state == nwTrinary.POSITIVE
|
||||
status.setDocumentStatus(nwTrinary.NEUTRAL)
|
||||
assert status.docIcon.state == nwTrinary.NEUTRAL
|
||||
status.setDocumentStatus(nwTrinary.NEGATIVE)
|
||||
assert status.docIcon.state == nwTrinary.NEGATIVE
|
||||
status.setDocumentStatus(nwTrinary.POSITIVE)
|
||||
assert status.docIcon.state == nwTrinary.POSITIVE
|
||||
|
||||
# Idle Status
|
||||
CONFIG.stopWhenIdle = False
|
||||
nwGUI.mainStatus.setUserIdle(True)
|
||||
nwGUI.mainStatus.updateTime()
|
||||
assert nwGUI.mainStatus._userIdle is False
|
||||
assert nwGUI.mainStatus.timeText.text() == "00:00:00"
|
||||
status.setUserIdle(True)
|
||||
status.updateTime()
|
||||
assert status._userIdle is False
|
||||
assert status.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.timeText.text() != "00:00:00"
|
||||
status.setUserIdle(True)
|
||||
status.updateTime(5)
|
||||
assert status._userIdle is True
|
||||
assert status.timeText.text() != "00:00:00"
|
||||
|
||||
nwGUI.mainStatus.setUserIdle(False)
|
||||
nwGUI.mainStatus.updateTime(5)
|
||||
assert nwGUI.mainStatus._userIdle is False
|
||||
assert nwGUI.mainStatus.timeText.text() != "00:00:00"
|
||||
status.setUserIdle(False)
|
||||
status.updateTime(5)
|
||||
assert status._userIdle is False
|
||||
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
|
||||
nwGUI.mainStatus.setLanguage("None", "None")
|
||||
assert nwGUI.mainStatus.langText.text() == "None"
|
||||
nwGUI.mainStatus.setLanguage("en", "None")
|
||||
assert nwGUI.mainStatus.langText.text() == "American English"
|
||||
status.setLanguage("None", "None")
|
||||
assert status.langText.text() == "None"
|
||||
status.setLanguage("en", "None")
|
||||
assert status.langText.text() == "American English"
|
||||
|
||||
# Project Stats
|
||||
CONFIG.incNotesWCount = False
|
||||
nwGUI._lastTotalCount = 0
|
||||
nwGUI._updateStatusWordCount()
|
||||
assert nwGUI.mainStatus.statsText.text() == "Words: 9 (+9)"
|
||||
assert status.statsText.text() == "Words: 9 (+9)"
|
||||
|
||||
# Update again, but through time tick
|
||||
with monkeypatch.context() as mp:
|
||||
@@ -96,6 +108,6 @@ def testGuiStatusBar_Main(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
||||
CONFIG.incNotesWCount = True
|
||||
nwGUI._lastTotalCount = 0
|
||||
nwGUI._timeTick()
|
||||
assert nwGUI.mainStatus.statsText.text() == "Words: 11 (+11)"
|
||||
assert status.statsText.text() == "Words: 11 (+11)"
|
||||
|
||||
# qtbot.stop()
|
||||
|
||||
Reference in New Issue
Block a user