diff --git a/novelwriter/gui/search.py b/novelwriter/gui/search.py index 9c3ed4ca..3bd28945 100644 --- a/novelwriter/gui/search.py +++ b/novelwriter/gui/search.py @@ -23,9 +23,10 @@ along with this program. If not, see . """ from __future__ import annotations -import time import logging +from time import time + from PyQt5.QtCore import QSize, Qt, pyqtSignal, pyqtSlot from PyQt5.QtGui import QKeyEvent, QPalette from PyQt5.QtWidgets import ( @@ -40,6 +41,8 @@ from novelwriter.core.item import NWItem logger = logging.getLogger(__name__) +CACHE_TIMEOUT = 120.0 # 2 minutes + class GuiProjectSearch(QWidget): @@ -50,8 +53,8 @@ class GuiProjectSearch(QWidget): D_HANDLE = Qt.ItemDataRole.UserRole D_RESULT = Qt.ItemDataRole.UserRole + 1 - openDocumentSelectRequest = pyqtSignal(str, int, int, bool) selectedItemChanged = pyqtSignal(str) + openDocumentSelectRequest = pyqtSignal(str, int, int, bool) def __init__(self, parent: QWidget) -> None: super().__init__(parent=parent) @@ -61,7 +64,9 @@ class GuiProjectSearch(QWidget): iPx = SHARED.theme.baseIconSize mPx = CONFIG.pxInt(2) + self._time = time() self._search = DocSearch() + self._blocked = False # Header self.viewLabel = QLabel(self.tr("Project Search")) @@ -130,6 +135,8 @@ class GuiProjectSearch(QWidget): self.setLayout(self.outerBox) self.updateTheme() + SHARED.slowClockTick.connect(self._cacheTimeout) + logger.debug("Ready: GuiProjectSearch") return @@ -205,7 +212,8 @@ class GuiProjectSearch(QWidget): @pyqtSlot(str) def clearSearchCache(self, tHandle: str) -> None: """Process document content change.""" - self._search.clearTextCache(tHandle) + if not self._blocked: + self._search.clearTextCache(tHandle) return ## @@ -215,15 +223,19 @@ class GuiProjectSearch(QWidget): @pyqtSlot() def _processSearch(self) -> None: """Perform a search.""" - start = time.time() - self.searchResult.clear() - if text := self.searchText.text(): - self._search.setUserRegEx(self.toggleRegEx.isChecked()) - self._search.setCaseSensitive(self.toggleCase.isChecked()) - self._search.setWholeWords(self.toggleWord.isChecked()) - for item, results, capped in self._search.iterSearch(SHARED.project, text): - self._appendResultSet(item, results, capped) - logger.debug("Search took %.3f ms", 1000*(time.time() - start)) + if not self._blocked: + start = time() + self._blocked = True + self.searchResult.clear() + if text := self.searchText.text(): + self._search.setUserRegEx(self.toggleRegEx.isChecked()) + self._search.setCaseSensitive(self.toggleCase.isChecked()) + self._search.setWholeWords(self.toggleWord.isChecked()) + for item, results, capped in self._search.iterSearch(SHARED.project, text): + self._appendResultSet(item, results, capped) + logger.debug("Search took %.3f ms", 1000*(time() - start)) + self._time = time() + self._blocked = False return @pyqtSlot() @@ -272,6 +284,13 @@ class GuiProjectSearch(QWidget): CONFIG.searchProjRegEx = state return + @pyqtSlot() + def _cacheTimeout(self) -> None: + """Clear the cache after a period of inactivity.""" + if not self._blocked and time() - self._time > CACHE_TIMEOUT: + self._search.clearTextCache(None) + return + ## # Internal Functions ## diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 49623cd4..978b1581 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -220,6 +220,7 @@ class GuiMain(QMainWindow): SHARED.indexScannedText.connect(self.itemDetails.updateViewBox) SHARED.indexCleared.connect(self.docViewerPanel.indexWasCleared) SHARED.indexAvailable.connect(self.docViewerPanel.indexHasAppeared) + SHARED.mainClockTick.connect(self._timeTick) self.mainMenu.requestDocAction.connect(self._passDocumentAction) self.mainMenu.requestDocInsert.connect(self._passDocumentInsert) @@ -287,12 +288,6 @@ class GuiMain(QMainWindow): self.asDocTimer = QTimer(self) self.asDocTimer.timeout.connect(self._autoSaveDocument) - # Main Clock - self.mainTimer = QTimer(self) - self.mainTimer.setInterval(1000) - self.mainTimer.timeout.connect(self._timeTick) - self.mainTimer.start() - # Shortcuts and Actions self._connectMenuActions() @@ -1212,16 +1207,15 @@ class GuiMain(QMainWindow): @pyqtSlot() def _timeTick(self) -> None: """Process time tick of the main timer.""" - if not SHARED.hasProject: - return - currTime = time() - editIdle = currTime - self.docEditor.lastActive > CONFIG.userIdleTime - userIdle = qApp.applicationState() != Qt.ApplicationActive - self.mainStatus.setUserIdle(editIdle or userIdle) - SHARED.updateIdleTime(currTime, editIdle or userIdle) - self.mainStatus.updateTime(idleTime=SHARED.projectIdleTime) - if CONFIG.memInfo and int(currTime) % 5 == 0: # pragma: no cover - self.mainStatus.memInfo() + if SHARED.hasProject: + currTime = time() + editIdle = currTime - self.docEditor.lastActive > CONFIG.userIdleTime + userIdle = qApp.applicationState() != Qt.ApplicationActive + self.mainStatus.setUserIdle(editIdle or userIdle) + SHARED.updateIdleTime(currTime, editIdle or userIdle) + self.mainStatus.updateTime(idleTime=SHARED.projectIdleTime) + if CONFIG.memInfo and int(currTime) % 5 == 0: # pragma: no cover + self.mainStatus.memInfo() return @pyqtSlot() diff --git a/novelwriter/shared.py b/novelwriter/shared.py index 31b3c88a..88b2e316 100644 --- a/novelwriter/shared.py +++ b/novelwriter/shared.py @@ -30,7 +30,7 @@ from time import time from typing import TYPE_CHECKING, TypeVar from pathlib import Path -from PyQt5.QtCore import QObject, QRunnable, QThreadPool, pyqtSignal +from PyQt5.QtCore import QObject, QRunnable, QThreadPool, QTimer, pyqtSignal from PyQt5.QtWidgets import QFileDialog, QMessageBox, QWidget from novelwriter.common import formatFileFilter @@ -62,6 +62,8 @@ class SharedData(QObject): indexChangedTags = pyqtSignal(list, list) indexCleared = pyqtSignal() indexAvailable = pyqtSignal() + mainClockTick = pyqtSignal() + slowClockTick = pyqtSignal() def __init__(self) -> None: super().__init__() @@ -79,6 +81,14 @@ class SharedData(QObject): self._idleRefTime = time() self._focusMode = False + self._fClock = QTimer(self) + self._fClock.setInterval(1000) + self._fClock.timeout.connect(lambda: self.mainClockTick.emit()) + + self._sClock = QTimer(self) + self._sClock.setInterval(10000) + self._sClock.timeout.connect(lambda: self.slowClockTick.emit()) + return ## @@ -158,6 +168,8 @@ class SharedData(QObject): soon as the Main GUI is created to ensure the SHARED singleton has the properties needed for operation. """ + self._fClock.start() + self._sClock.start() self._gui = gui self._theme = theme self._resetProject() diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py index d9d7342e..fd62e34d 100644 --- a/novelwriter/tools/manuscript.py +++ b/novelwriter/tools/manuscript.py @@ -794,11 +794,7 @@ class _PreviewWidget(QTextBrowser): self._updateDocMargins() self._updateBuildAge() - # Age Timer - self.ageTimer = QTimer(self) - self.ageTimer.setInterval(10) - self.ageTimer.timeout.connect(self._updateBuildAge) - self.ageTimer.start() + SHARED.slowClockTick.connect(self._updateBuildAge) return