Improve cursor visibility features (#1608)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -688,6 +688,7 @@ class GuiMain(QMainWindow):
|
||||
logger.debug("Viewing document with handle '%s'", tHandle)
|
||||
if self.docViewer.loadText(tHandle):
|
||||
if not self.splitView.isVisible():
|
||||
cursorVisible = self.docEditor.cursorIsVisible()
|
||||
bPos = self.splitMain.sizes()
|
||||
self.splitView.setVisible(True)
|
||||
vPos = [0, 0]
|
||||
@@ -696,6 +697,11 @@ class GuiMain(QMainWindow):
|
||||
self.splitDocs.setSizes(vPos)
|
||||
self.docViewerPanel.setVisible(CONFIG.showViewerPanel)
|
||||
|
||||
# Since editor width changes, we need to make sure we
|
||||
# restore cursor visibility in the editor. See #1302
|
||||
if cursorVisible:
|
||||
self.docEditor.ensureCursorVisibleNoCentre()
|
||||
|
||||
if sTitle:
|
||||
self.docViewer.navigateTo(f"#{sTitle}")
|
||||
|
||||
@@ -1113,11 +1119,18 @@ class GuiMain(QMainWindow):
|
||||
# Only reset the last handle if the user called this
|
||||
SHARED.project.data.setLastHandle(None, "viewer")
|
||||
|
||||
cursorVisible = self.docEditor.cursorIsVisible()
|
||||
|
||||
# Hide the panel
|
||||
bPos = self.splitMain.sizes()
|
||||
self.splitView.setVisible(False)
|
||||
self.splitDocs.setSizes([bPos[1], 0])
|
||||
|
||||
# Since editor width changes, we need to make sure we restore
|
||||
# cursor visibility in the editor. See #1302
|
||||
if cursorVisible:
|
||||
self.docEditor.ensureCursorVisibleNoCentre()
|
||||
|
||||
return not self.splitView.isVisible()
|
||||
|
||||
def toggleFullScreenMode(self) -> None:
|
||||
@@ -1166,6 +1179,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 +1195,9 @@ class GuiMain(QMainWindow):
|
||||
elif self.docViewer.docHandle is not None:
|
||||
self.splitView.setVisible(True)
|
||||
|
||||
if cursorVisible:
|
||||
self.docEditor.ensureCursorVisibleNoCentre()
|
||||
|
||||
return
|
||||
|
||||
@pyqtSlot(nwWidget)
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user