Improve cursor visibility features (#1608)
This commit is contained in:
@@ -48,8 +48,8 @@ from PyQt5.QtGui import (
|
|||||||
)
|
)
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QAction, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMenu,
|
QAction, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMenu,
|
||||||
QPlainTextEdit, QPushButton, QShortcut, QToolBar, QToolButton, QVBoxLayout, QWidget,
|
QPlainTextEdit, QPushButton, QShortcut, QToolBar, QToolButton, QVBoxLayout,
|
||||||
qApp
|
QWidget, qApp
|
||||||
)
|
)
|
||||||
|
|
||||||
from novelwriter import CONFIG, SHARED
|
from novelwriter import CONFIG, SHARED
|
||||||
@@ -508,6 +508,33 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
|
|
||||||
return True
|
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:
|
def updateDocMargins(self) -> None:
|
||||||
"""Automatically adjust the margins so the text is centred if
|
"""Automatically adjust the margins so the text is centred if
|
||||||
we have a text width set or we're in Focus Mode. Otherwise, just
|
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)
|
logger.debug("Viewing document with handle '%s'", tHandle)
|
||||||
if self.docViewer.loadText(tHandle):
|
if self.docViewer.loadText(tHandle):
|
||||||
if not self.splitView.isVisible():
|
if not self.splitView.isVisible():
|
||||||
|
cursorVisible = self.docEditor.cursorIsVisible()
|
||||||
bPos = self.splitMain.sizes()
|
bPos = self.splitMain.sizes()
|
||||||
self.splitView.setVisible(True)
|
self.splitView.setVisible(True)
|
||||||
vPos = [0, 0]
|
vPos = [0, 0]
|
||||||
@@ -696,6 +697,11 @@ class GuiMain(QMainWindow):
|
|||||||
self.splitDocs.setSizes(vPos)
|
self.splitDocs.setSizes(vPos)
|
||||||
self.docViewerPanel.setVisible(CONFIG.showViewerPanel)
|
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:
|
if sTitle:
|
||||||
self.docViewer.navigateTo(f"#{sTitle}")
|
self.docViewer.navigateTo(f"#{sTitle}")
|
||||||
|
|
||||||
@@ -1113,11 +1119,18 @@ class GuiMain(QMainWindow):
|
|||||||
# Only reset the last handle if the user called this
|
# Only reset the last handle if the user called this
|
||||||
SHARED.project.data.setLastHandle(None, "viewer")
|
SHARED.project.data.setLastHandle(None, "viewer")
|
||||||
|
|
||||||
|
cursorVisible = self.docEditor.cursorIsVisible()
|
||||||
|
|
||||||
# Hide the panel
|
# Hide the panel
|
||||||
bPos = self.splitMain.sizes()
|
bPos = self.splitMain.sizes()
|
||||||
self.splitView.setVisible(False)
|
self.splitView.setVisible(False)
|
||||||
self.splitDocs.setSizes([bPos[1], 0])
|
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()
|
return not self.splitView.isVisible()
|
||||||
|
|
||||||
def toggleFullScreenMode(self) -> None:
|
def toggleFullScreenMode(self) -> None:
|
||||||
@@ -1166,6 +1179,7 @@ class GuiMain(QMainWindow):
|
|||||||
else:
|
else:
|
||||||
logger.debug("Deactivating Focus Mode")
|
logger.debug("Deactivating Focus Mode")
|
||||||
|
|
||||||
|
cursorVisible = self.docEditor.cursorIsVisible()
|
||||||
isVisible = not self.isFocusMode
|
isVisible = not self.isFocusMode
|
||||||
self.treePane.setVisible(isVisible)
|
self.treePane.setVisible(isVisible)
|
||||||
self.mainStatus.setVisible(isVisible)
|
self.mainStatus.setVisible(isVisible)
|
||||||
@@ -1181,6 +1195,9 @@ class GuiMain(QMainWindow):
|
|||||||
elif self.docViewer.docHandle is not None:
|
elif self.docViewer.docHandle is not None:
|
||||||
self.splitView.setVisible(True)
|
self.splitView.setVisible(True)
|
||||||
|
|
||||||
|
if cursorVisible:
|
||||||
|
self.docEditor.ensureCursorVisibleNoCentre()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot(nwWidget)
|
@pyqtSlot(nwWidget)
|
||||||
|
|||||||
@@ -173,9 +173,14 @@ def testGuiEditor_MetaData(qtbot, nwGUI, projPath, mockRnd):
|
|||||||
"More\u00a0text.\u2029"
|
"More\u00a0text.\u2029"
|
||||||
)
|
)
|
||||||
nwGUI.docEditor.replaceText(newText)
|
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.docChanged is True
|
||||||
assert nwGUI.docEditor.docHandle == C.hSceneDoc
|
assert nwGUI.docEditor.docHandle == C.hSceneDoc
|
||||||
assert nwGUI.docEditor.lastActive > 0.0
|
assert nwGUI.docEditor.lastActive > 0.0
|
||||||
@@ -1338,9 +1343,47 @@ def testGuiEditor_Completer(qtbot, nwGUI, projPath, mockRnd):
|
|||||||
# END Test testGuiEditor_Completer
|
# 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
|
@pytest.mark.gui
|
||||||
def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd):
|
def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd):
|
||||||
"""Test saving text from the editor."""
|
"""Test the word counter."""
|
||||||
class MockThreadPool:
|
class MockThreadPool:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user