From 208b9334e1a527ba79b0a441507d02ab313ca4ff Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 18 Nov 2023 15:43:51 +0100 Subject: [PATCH] Ensure cursor is visible after switching focus mode (#1478) --- novelwriter/gui/doceditor.py | 31 ++++++++++++++++-- novelwriter/guimain.py | 4 +++ tests/test_gui/test_gui_doceditor.py | 49 ++++++++++++++++++++++++++-- 3 files changed, 79 insertions(+), 5 deletions(-) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 170fcc86..91e4b57f 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -48,8 +48,8 @@ from PyQt5.QtGui import ( ) from PyQt5.QtWidgets import ( QAction, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMenu, - QPlainTextEdit, QPushButton, QShortcut, QToolBar, QToolButton, QVBoxLayout, QWidget, - qApp + QPlainTextEdit, QPushButton, QShortcut, QToolBar, QToolButton, QVBoxLayout, + QWidget, qApp ) from novelwriter import CONFIG, SHARED @@ -508,6 +508,33 @@ class GuiDocEditor(QPlainTextEdit): return True + def cursorIsVisible(self) -> bool: + """Check if the cursor is visible in the editor.""" + return ( + 0 < self.cursorRect().top() + and self.cursorRect().bottom() < self.viewport().height() + ) + + def ensureCursorVisibleNoCentre(self) -> None: + """Ensure cursor is visible, but don't force it to centre.""" + cT = self.cursorRect().top() + cB = self.cursorRect().bottom() + vH = self.viewport().height() + if cT < 0: + count = 0 + vBar = self.verticalScrollBar() + while self.cursorRect().top() < 0 and count < 100000: + vBar.setValue(vBar.value() - 1) + count += 1 + elif cB > vH: + count = 0 + vBar = self.verticalScrollBar() + while self.cursorRect().bottom() > vH and count < 100000: + vBar.setValue(vBar.value() + 1) + count += 1 + qApp.processEvents() + return + def updateDocMargins(self) -> None: """Automatically adjust the margins so the text is centred if we have a text width set or we're in Focus Mode. Otherwise, just diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 099cb422..586c6309 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -1166,6 +1166,7 @@ class GuiMain(QMainWindow): else: logger.debug("Deactivating Focus Mode") + cursorVisible = self.docEditor.cursorIsVisible() isVisible = not self.isFocusMode self.treePane.setVisible(isVisible) self.mainStatus.setVisible(isVisible) @@ -1181,6 +1182,9 @@ class GuiMain(QMainWindow): elif self.docViewer.docHandle is not None: self.splitView.setVisible(True) + if cursorVisible: + self.docEditor.ensureCursorVisibleNoCentre() + return @pyqtSlot(nwWidget) diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py index 22a67be4..c6464987 100644 --- a/tests/test_gui/test_gui_doceditor.py +++ b/tests/test_gui/test_gui_doceditor.py @@ -173,9 +173,14 @@ def testGuiEditor_MetaData(qtbot, nwGUI, projPath, mockRnd): "More\u00a0text.\u2029" ) nwGUI.docEditor.replaceText(newText) - assert nwGUI.docEditor.getText() == "### New Scene\n\nSome\ntext.\nMore\u00a0text.\n" + assert nwGUI.docEditor.getText() == ( + "### New Scene\n\n" + "Some\n" + "text.\n" + "More\u00a0text.\n" + ) - # Check Propertoes + # Check Properties assert nwGUI.docEditor.docChanged is True assert nwGUI.docEditor.docHandle == C.hSceneDoc assert nwGUI.docEditor.lastActive > 0.0 @@ -1338,9 +1343,47 @@ def testGuiEditor_Completer(qtbot, nwGUI, projPath, mockRnd): # END Test testGuiEditor_Completer +@pytest.mark.gui +def testGuiEditor_CursorVisibility(qtbot, monkeypatch, nwGUI, projPath, mockRnd): + """Test the custom ensure cursor visible feature.""" + buildTestProject(nwGUI, projPath) + nwGUI.openDocument(C.hSceneDoc) + docEditor = nwGUI.docEditor + + docEditor.setPlainText( + "### Scene\n\n" + "".join(["Text\n\n"]*100) + ) + assert docEditor.cursorIsVisible() is True + docEditor.setCenterOnScroll(False) + + # Scroll Down + cursor = docEditor.textCursor() + cursor.setPosition(605) + docEditor.setTextCursor(cursor) + docEditor.verticalScrollBar().setValue(0) + assert docEditor.verticalScrollBar().value() == 0 + docEditor.ensureCursorVisibleNoCentre() + assert docEditor.verticalScrollBar().value() > 0 + assert docEditor.cursorIsVisible() is True + + # Scroll Up + cursor = docEditor.textCursor() + cursor.setPosition(0) + docEditor.setTextCursor(cursor) + docEditor.verticalScrollBar().setValue(200) + assert docEditor.verticalScrollBar().value() > 100 + docEditor.ensureCursorVisibleNoCentre() + assert docEditor.verticalScrollBar().value() == 0 + assert docEditor.cursorIsVisible() is True + + # qtbot.stop() + +# END Test testGuiEditor_CursorVisibility + + @pytest.mark.gui def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd): - """Test saving text from the editor.""" + """Test the word counter.""" class MockThreadPool: def __init__(self):