mirror of
https://github.com/LorenEteval/Furious.git
synced 2026-10-05 21:37:56 +03:00
Add paused log reading
Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
Vendored
+18
-1
@@ -1618,7 +1618,8 @@ TRANSLATION = {
|
||||
},
|
||||
"Search": {
|
||||
"source": [
|
||||
"Furious.Window.HomePage"
|
||||
"Furious.Window.HomePage",
|
||||
"Furious.Window.LogPage"
|
||||
],
|
||||
"RU": "Поиск",
|
||||
"ZH": "查找",
|
||||
@@ -3366,5 +3367,21 @@ TRANSLATION = {
|
||||
"RU": "Остановить обновления",
|
||||
"ZH": "停止更新",
|
||||
"isReviewed": "True"
|
||||
},
|
||||
"Pause Updates": {
|
||||
"source": [
|
||||
"Furious.Window.LogPage"
|
||||
],
|
||||
"RU": "Приостановить обновление",
|
||||
"ZH": "暂停刷新",
|
||||
"isReviewed": "True"
|
||||
},
|
||||
"Resume Updates": {
|
||||
"source": [
|
||||
"Furious.Window.LogPage"
|
||||
],
|
||||
"RU": "Возобновить обновление",
|
||||
"ZH": "继续刷新",
|
||||
"isReviewed": "True"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,6 +218,7 @@ class LogPage(Mixins.QTranslatable, QMainWindow):
|
||||
)
|
||||
|
||||
self._searchRegex = None
|
||||
self._paused = False
|
||||
|
||||
self.textBrowser = DraculaTextBrowser(
|
||||
fontFamily=fontFamily,
|
||||
@@ -384,6 +385,22 @@ class LogPage(Mixins.QTranslatable, QMainWindow):
|
||||
popupMenu=self._viewMenu,
|
||||
)
|
||||
|
||||
self.pauseButton = AppQPushButton(
|
||||
_('Pause Updates'), icon=bootstrapIcon('pause-circle.svg'), parent=self
|
||||
)
|
||||
self.pauseButton.setCheckable(True)
|
||||
self.pauseButton.toggled.connect(self._pauseChanged)
|
||||
|
||||
self.findAction = AppQAction(
|
||||
_('Search'),
|
||||
parent=self,
|
||||
callback=self.focusSearch,
|
||||
shortcut=QtGui.QKeySequence.StandardKey.Find,
|
||||
)
|
||||
self.findAction.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
|
||||
|
||||
self.addAction(self.findAction)
|
||||
|
||||
self.autoScrollLabel = AppQLabel(_('Auto Scroll Down'))
|
||||
|
||||
self.autoScrollSwitch = AppQSwitch()
|
||||
@@ -400,6 +417,7 @@ class LogPage(Mixins.QTranslatable, QMainWindow):
|
||||
actionLayout.addWidget(self.fileButton)
|
||||
actionLayout.addWidget(self.editButton)
|
||||
actionLayout.addWidget(self.viewButton)
|
||||
actionLayout.addWidget(self.pauseButton)
|
||||
actionLayout.addStretch(1)
|
||||
actionLayout.addWidget(self.autoScrollLabel)
|
||||
actionLayout.addWidget(self.autoScrollSwitch)
|
||||
@@ -435,18 +453,50 @@ class LogPage(Mixins.QTranslatable, QMainWindow):
|
||||
self.retranslate()
|
||||
|
||||
def _registerMenuShortcuts(self, menu):
|
||||
"""Associate popup actions with the page so shortcuts stay active."""
|
||||
"""Keep document shortcuts local to the log browser and its children."""
|
||||
for action in menu.actions():
|
||||
if action.isSeparator():
|
||||
continue
|
||||
|
||||
self.addAction(action)
|
||||
action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
|
||||
|
||||
self.textBrowser.addAction(action)
|
||||
|
||||
submenu = action.menu() if hasattr(action, 'menu') else None
|
||||
|
||||
if submenu is not None:
|
||||
self._registerMenuShortcuts(submenu)
|
||||
|
||||
@QtCore.Slot()
|
||||
def focusSearch(self):
|
||||
"""Focus the enabled search field without interrupting a paused view."""
|
||||
if self.searchLineEdit.isEnabled():
|
||||
self.searchLineEdit.setFocus(QtCore.Qt.ShortcutFocusReason)
|
||||
self.searchLineEdit.selectAll()
|
||||
|
||||
@QtCore.Slot(bool)
|
||||
def _pauseChanged(self, paused):
|
||||
"""Freeze only presentation; the manager retains collection ownership."""
|
||||
self._paused = paused
|
||||
|
||||
self.pauseButton.setText(_('Resume Updates') if paused else _('Pause Updates'))
|
||||
self.pauseButton.setIcon(
|
||||
bootstrapIcon('play-circle.svg' if paused else 'pause-circle.svg')
|
||||
)
|
||||
|
||||
for widget in [self.searchLineEdit, self.filterComboBox, self.filterLabel]:
|
||||
widget.setEnabled(not paused)
|
||||
|
||||
if paused:
|
||||
self._updateTimer.stop()
|
||||
self._highlightTimer.stop()
|
||||
self._scrollTimer.stop()
|
||||
self._followStateTimer.stop()
|
||||
self._setHighlightBusy(False)
|
||||
self._entriesDirty = True
|
||||
else:
|
||||
self._requestRefresh(invalidate=True, immediate=True)
|
||||
|
||||
def _categoryText(self, category) -> str:
|
||||
"""Return a category's translated or literal display label."""
|
||||
if category.id == APPLICATION_LOG_CATEGORY:
|
||||
@@ -480,8 +530,10 @@ class LogPage(Mixins.QTranslatable, QMainWindow):
|
||||
"""Return whether document work can currently reach the screen."""
|
||||
window = self.window()
|
||||
|
||||
return self.isVisible() and not (
|
||||
hasattr(window, 'isMinimized') and window.isMinimized()
|
||||
return (
|
||||
not self._paused
|
||||
and self.isVisible()
|
||||
and not (hasattr(window, 'isMinimized') and window.isMinimized())
|
||||
)
|
||||
|
||||
def _requestRefresh(self, *, invalidate=False, immediate=False):
|
||||
|
||||
@@ -1548,6 +1548,124 @@ class UnifiedLogPageTest(unittest.TestCase):
|
||||
page.close()
|
||||
page.deleteLater()
|
||||
|
||||
def testPausedViewPreservesSelectionAcrossEvictionClearAndHide(self):
|
||||
"""Pause presentation while the shared bounded stream keeps collecting."""
|
||||
with isolatedSettings():
|
||||
manager = LogManager(maximumEntries=3)
|
||||
page = LogPage(manager=manager)
|
||||
self.addCleanup(self.disposePage, page)
|
||||
manager.append('original text', APPLICATION_LOG_CATEGORY)
|
||||
page.show()
|
||||
self.assertRendered(page)
|
||||
page.textBrowser.selectAll()
|
||||
selected = page.textBrowser.textCursor().selectedText()
|
||||
|
||||
QTest.mouseClick(page.pauseButton, QtCore.Qt.LeftButton)
|
||||
manager.clear()
|
||||
for number in range(10):
|
||||
manager.append(f'new {number}', APPLICATION_LOG_CATEGORY)
|
||||
processQtEvents()
|
||||
page.hide()
|
||||
page.show()
|
||||
processQtEvents()
|
||||
|
||||
self.assertEqual(page.plainText(), 'original text')
|
||||
self.assertEqual(page.textBrowser.textCursor().selectedText(), selected)
|
||||
self.assertFalse(page.searchLineEdit.isEnabled())
|
||||
self.assertFalse(page.filterComboBox.isEnabled())
|
||||
self.assertFalse(page._updateTimer.isActive())
|
||||
self.assertFalse(page._highlightTimer.isActive())
|
||||
self.assertFalse(page._scrollTimer.isActive())
|
||||
|
||||
QTest.mouseClick(page.pauseButton, QtCore.Qt.LeftButton)
|
||||
self.assertRendered(page)
|
||||
self.assertEqual(page.plainText().splitlines(), ['new 7', 'new 8', 'new 9'])
|
||||
self.assertTrue(page.searchLineEdit.isEnabled())
|
||||
self.assertTrue(page.filterComboBox.isEnabled())
|
||||
|
||||
def testPausedFilteredViewExportsItsSnapshotAndRetranslates(self):
|
||||
"""Export the frozen filtered document, then resume the same filter."""
|
||||
with isolatedSettings():
|
||||
AppSettings.set('Language', 'EN')
|
||||
manager = LogManager(maximumEntries=4)
|
||||
page = LogPage(manager=manager)
|
||||
self.addCleanup(self.disposePage, page)
|
||||
manager.append('core match', CORE_LOG_CATEGORY)
|
||||
manager.append('application match', APPLICATION_LOG_CATEGORY)
|
||||
page.filterComboBox.setCurrentIndex(
|
||||
page.filterComboBox.findData(CORE_LOG_CATEGORY)
|
||||
)
|
||||
page.searchLineEdit.setText('match')
|
||||
page.show()
|
||||
self.assertRendered(page)
|
||||
QTest.mouseClick(page.pauseButton, QtCore.Qt.LeftButton)
|
||||
manager.append('core later match', CORE_LOG_CATEGORY)
|
||||
processQtEvents()
|
||||
with mock.patch('Furious.Window.LogPage.saveAsFile') as save:
|
||||
page._fileMenu.actions()[0].trigger()
|
||||
save.assert_called_once_with('core match')
|
||||
for language in ('ZH', 'RU', 'EN'):
|
||||
AppSettings.set('Language', language)
|
||||
page.pauseButton.retranslate()
|
||||
self.assertEqual(page.pauseButton.text(), _('Resume Updates'))
|
||||
QTest.mouseClick(page.pauseButton, QtCore.Qt.LeftButton)
|
||||
self.assertRendered(page)
|
||||
self.assertEqual(
|
||||
page.plainText().splitlines(), ['core match', 'core later match']
|
||||
)
|
||||
self.assertEqual(page.pauseButton.text(), _('Pause Updates'))
|
||||
|
||||
def testSearchKeyboardKeepsEditingSeparateFromLogSelection(self):
|
||||
"""Find focuses search; editing shortcuts act on the focused widget."""
|
||||
with isolatedSettings():
|
||||
manager = LogManager(maximumEntries=3)
|
||||
page = LogPage(manager=manager)
|
||||
self.addCleanup(self.disposePage, page)
|
||||
self.addCleanup(QTest.keyRelease, page.textBrowser, QtCore.Qt.Key_Control)
|
||||
manager.append('alpha log', APPLICATION_LOG_CATEGORY)
|
||||
page.show()
|
||||
page.activateWindow()
|
||||
self.assertRendered(page)
|
||||
page.textBrowser.setFocus()
|
||||
processQtEvents()
|
||||
QTest.keyClick(page.textBrowser, QtCore.Qt.Key_F, QtCore.Qt.ControlModifier)
|
||||
self.assertTrue(waitFor(page.searchLineEdit.hasFocus))
|
||||
QTest.keyClicks(page.searchLineEdit, 'alpha')
|
||||
QTest.keyClick(
|
||||
page.searchLineEdit, QtCore.Qt.Key_A, QtCore.Qt.ControlModifier
|
||||
)
|
||||
QTest.keyClick(
|
||||
page.searchLineEdit, QtCore.Qt.Key_C, QtCore.Qt.ControlModifier
|
||||
)
|
||||
self.assertEqual(application().clipboard().text(), 'alpha')
|
||||
self.assertFalse(page.textBrowser.textCursor().hasSelection())
|
||||
self.assertRendered(page)
|
||||
page.textBrowser.setFocus()
|
||||
QTest.keyClick(page.textBrowser, QtCore.Qt.Key_A, QtCore.Qt.ControlModifier)
|
||||
QTest.keyClick(page.textBrowser, QtCore.Qt.Key_C, QtCore.Qt.ControlModifier)
|
||||
self.assertEqual(application().clipboard().text(), 'alpha log')
|
||||
|
||||
def testFindShortcutFollowsTheVisiblePage(self):
|
||||
"""Two persistent pages share one window without ambiguous Find actions."""
|
||||
with isolatedSettings():
|
||||
stack = QStackedWidget()
|
||||
pages = [LogPage(manager=LogManager(maximumEntries=3)) for _ in range(2)]
|
||||
for page in pages:
|
||||
stack.addWidget(page)
|
||||
self.addCleanup(stack.deleteLater)
|
||||
self.addCleanup(stack.close)
|
||||
self.addCleanup(QTest.keyRelease, stack, QtCore.Qt.Key_Control)
|
||||
stack.show()
|
||||
stack.activateWindow()
|
||||
for page in (pages[0], pages[1], pages[0]):
|
||||
stack.setCurrentWidget(page)
|
||||
page.textBrowser.setFocus()
|
||||
processQtEvents()
|
||||
QTest.keyClick(
|
||||
page.textBrowser, QtCore.Qt.Key_F, QtCore.Qt.ControlModifier
|
||||
)
|
||||
self.assertTrue(waitFor(page.searchLineEdit.hasFocus))
|
||||
|
||||
def testLabelsOwnTheirDynamicTranslation(self):
|
||||
"""Let each AppQLabel translate itself without page-level setters."""
|
||||
with isolatedSettings():
|
||||
|
||||
Reference in New Issue
Block a user