Simplify editor scrolling features
This commit is contained in:
@@ -144,7 +144,6 @@ class Config:
|
|||||||
self.doReplaceDash = True # Replace multiple hyphens with dashes
|
self.doReplaceDash = True # Replace multiple hyphens with dashes
|
||||||
self.doReplaceDots = True # Replace three dots with ellipsis
|
self.doReplaceDots = True # Replace three dots with ellipsis
|
||||||
|
|
||||||
self.scrollPastEnd = 25 # Number of lines to scroll past end of document
|
|
||||||
self.autoScroll = False # Typewriter-like scrolling
|
self.autoScroll = False # Typewriter-like scrolling
|
||||||
self.autoScrollPos = 30 # Start point for typewriter-like scrolling
|
self.autoScrollPos = 30 # Start point for typewriter-like scrolling
|
||||||
|
|
||||||
@@ -572,7 +571,6 @@ class Config:
|
|||||||
self.doReplaceDQuote = conf.rdBool(sec, "repdquotes", self.doReplaceDQuote)
|
self.doReplaceDQuote = conf.rdBool(sec, "repdquotes", self.doReplaceDQuote)
|
||||||
self.doReplaceDash = conf.rdBool(sec, "repdash", self.doReplaceDash)
|
self.doReplaceDash = conf.rdBool(sec, "repdash", self.doReplaceDash)
|
||||||
self.doReplaceDots = conf.rdBool(sec, "repdots", self.doReplaceDots)
|
self.doReplaceDots = conf.rdBool(sec, "repdots", self.doReplaceDots)
|
||||||
self.scrollPastEnd = conf.rdInt(sec, "scrollpastend", self.scrollPastEnd)
|
|
||||||
self.autoScroll = conf.rdBool(sec, "autoscroll", self.autoScroll)
|
self.autoScroll = conf.rdBool(sec, "autoscroll", self.autoScroll)
|
||||||
self.autoScrollPos = conf.rdInt(sec, "autoscrollpos", self.autoScrollPos)
|
self.autoScrollPos = conf.rdInt(sec, "autoscrollpos", self.autoScrollPos)
|
||||||
self.fmtSQuoteOpen = conf.rdStr(sec, "fmtsquoteopen", self.fmtSQuoteOpen)
|
self.fmtSQuoteOpen = conf.rdStr(sec, "fmtsquoteopen", self.fmtSQuoteOpen)
|
||||||
@@ -695,7 +693,6 @@ class Config:
|
|||||||
"repdquotes": str(self.doReplaceDQuote),
|
"repdquotes": str(self.doReplaceDQuote),
|
||||||
"repdash": str(self.doReplaceDash),
|
"repdash": str(self.doReplaceDash),
|
||||||
"repdots": str(self.doReplaceDots),
|
"repdots": str(self.doReplaceDots),
|
||||||
"scrollpastend": str(self.scrollPastEnd),
|
|
||||||
"autoscroll": str(self.autoScroll),
|
"autoscroll": str(self.autoScroll),
|
||||||
"autoscrollpos": str(self.autoScrollPos),
|
"autoscrollpos": str(self.autoScrollPos),
|
||||||
"fmtsquoteopen": str(self.fmtSQuoteOpen),
|
"fmtsquoteopen": str(self.fmtSQuoteOpen),
|
||||||
|
|||||||
@@ -724,19 +724,6 @@ class GuiPreferencesEditor(QWidget):
|
|||||||
# ================
|
# ================
|
||||||
self.mainForm.addGroupLabel(self.tr("Scroll Behaviour"))
|
self.mainForm.addGroupLabel(self.tr("Scroll Behaviour"))
|
||||||
|
|
||||||
# Scroll Past End
|
|
||||||
self.scrollPastEnd = QSpinBox(self)
|
|
||||||
self.scrollPastEnd.setMinimum(0)
|
|
||||||
self.scrollPastEnd.setMaximum(100)
|
|
||||||
self.scrollPastEnd.setSingleStep(1)
|
|
||||||
self.scrollPastEnd.setValue(int(CONFIG.scrollPastEnd))
|
|
||||||
self.mainForm.addRow(
|
|
||||||
self.tr("Scroll past end of the document"),
|
|
||||||
self.scrollPastEnd,
|
|
||||||
self.tr("Set to 0 to disable this feature."),
|
|
||||||
unit=self.tr("lines")
|
|
||||||
)
|
|
||||||
|
|
||||||
# Typewriter Scrolling
|
# Typewriter Scrolling
|
||||||
self.autoScroll = NSwitch()
|
self.autoScroll = NSwitch()
|
||||||
self.autoScroll.setChecked(CONFIG.autoScroll)
|
self.autoScroll.setChecked(CONFIG.autoScroll)
|
||||||
@@ -775,7 +762,6 @@ class GuiPreferencesEditor(QWidget):
|
|||||||
CONFIG.showLineEndings = self.showLineEndings.isChecked()
|
CONFIG.showLineEndings = self.showLineEndings.isChecked()
|
||||||
|
|
||||||
# Scroll Behaviour
|
# Scroll Behaviour
|
||||||
CONFIG.scrollPastEnd = self.scrollPastEnd.value()
|
|
||||||
CONFIG.autoScroll = self.autoScroll.isChecked()
|
CONFIG.autoScroll = self.autoScroll.isChecked()
|
||||||
CONFIG.autoScrollPos = self.autoScrollPos.value()
|
CONFIG.autoScrollPos = self.autoScrollPos.value()
|
||||||
|
|
||||||
|
|||||||
@@ -37,13 +37,12 @@ from time import time
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from PyQt5.QtCore import (
|
from PyQt5.QtCore import (
|
||||||
pyqtSignal, pyqtSlot, QObject, QPoint, QPropertyAnimation, QRegExp,
|
pyqtSignal, pyqtSlot, QObject, QPoint, QRegExp, QRegularExpression,
|
||||||
QRegularExpression, QRunnable, QSize, Qt, QTimer
|
QRunnable, QSize, Qt, QTimer
|
||||||
)
|
)
|
||||||
from PyQt5.QtGui import (
|
from PyQt5.QtGui import (
|
||||||
QColor, QCursor, QFont, QFontMetrics, QKeyEvent, QKeySequence, QMouseEvent,
|
QColor, QCursor, QFont, QKeyEvent, QKeySequence, QMouseEvent, QPalette,
|
||||||
QPalette, QPixmap, QResizeEvent, QTextBlock, QTextCursor, QTextDocument,
|
QPixmap, QResizeEvent, QTextBlock, QTextCursor, QTextDocument, QTextOption
|
||||||
QTextOption
|
|
||||||
)
|
)
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QAction, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMenu,
|
QAction, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMenu,
|
||||||
@@ -142,6 +141,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
self.setMinimumWidth(CONFIG.pxInt(300))
|
self.setMinimumWidth(CONFIG.pxInt(300))
|
||||||
self.setAutoFillBackground(True)
|
self.setAutoFillBackground(True)
|
||||||
self.setFrameStyle(QFrame.NoFrame)
|
self.setFrameStyle(QFrame.NoFrame)
|
||||||
|
self.setCenterOnScroll(True)
|
||||||
|
|
||||||
# Custom Shortcuts
|
# Custom Shortcuts
|
||||||
self.keyContext = QShortcut(self)
|
self.keyContext = QShortcut(self)
|
||||||
@@ -311,7 +311,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
# Set default text margins
|
# Set default text margins
|
||||||
# Due to cursor visibility, a part of the margin must be
|
# Due to cursor visibility, a part of the margin must be
|
||||||
# allocated to the document itself. See issue #1112.
|
# allocated to the document itself. See issue #1112.
|
||||||
cW = self.cursorWidth()
|
cW = 2*self.cursorWidth()
|
||||||
qDoc = self.document()
|
qDoc = self.document()
|
||||||
qDoc.setDocumentMargin(cW)
|
qDoc.setDocumentMargin(cW)
|
||||||
self._vpMargin = max(CONFIG.getTextMargin() - cW, 0)
|
self._vpMargin = max(CONFIG.getTextMargin() - cW, 0)
|
||||||
@@ -369,13 +369,13 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
self._nwDocument = SHARED.project.storage.getDocument(tHandle)
|
self._nwDocument = SHARED.project.storage.getDocument(tHandle)
|
||||||
self._nwItem = self._nwDocument.nwItem
|
self._nwItem = self._nwDocument.nwItem
|
||||||
|
|
||||||
theDoc = self._nwDocument.readDocument()
|
docText = self._nwDocument.readDocument()
|
||||||
if theDoc is None:
|
if docText is None:
|
||||||
# There was an io error
|
# There was an io error
|
||||||
self.clearEditor()
|
self.clearEditor()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
docSize = len(theDoc)
|
docSize = len(docText)
|
||||||
if docSize > nwConst.MAX_DOCSIZE:
|
if docSize > nwConst.MAX_DOCSIZE:
|
||||||
SHARED.error(self.tr(
|
SHARED.error(self.tr(
|
||||||
"The document you are trying to open is too big. "
|
"The document you are trying to open is too big. "
|
||||||
@@ -389,22 +389,20 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
|
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
|
||||||
|
self._docHandle = tHandle
|
||||||
self.highLight.setHandle(tHandle)
|
self.highLight.setHandle(tHandle)
|
||||||
|
|
||||||
bfTime = time()
|
tStart = time()
|
||||||
self._allowAutoReplace(False)
|
self._allowAutoReplace(False)
|
||||||
self.setPlainText(theDoc)
|
self.setPlainText(docText)
|
||||||
qApp.processEvents()
|
|
||||||
|
|
||||||
self._allowAutoReplace(True)
|
self._allowAutoReplace(True)
|
||||||
afTime = time()
|
logger.debug("Document text loaded in %.3f ms", 1000*(time() - tStart))
|
||||||
logger.debug("Document highlighted in %.3f ms", 1000*(afTime-bfTime))
|
qApp.processEvents()
|
||||||
|
|
||||||
self._lastEdit = time()
|
self._lastEdit = time()
|
||||||
self._lastActive = time()
|
self._lastActive = time()
|
||||||
self._runDocCounter()
|
self._runDocCounter()
|
||||||
self.wcTimerDoc.start()
|
self.wcTimerDoc.start()
|
||||||
self._docHandle = tHandle
|
|
||||||
|
|
||||||
self.setReadOnly(False)
|
self.setReadOnly(False)
|
||||||
self.docHeader.setTitleFromHandle(self._docHandle)
|
self.docHeader.setTitleFromHandle(self._docHandle)
|
||||||
@@ -416,12 +414,6 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
elif isinstance(tLine, int):
|
elif isinstance(tLine, int):
|
||||||
self.setCursorLine(tLine)
|
self.setCursorLine(tLine)
|
||||||
|
|
||||||
if CONFIG.scrollPastEnd > 0:
|
|
||||||
fSize = QFontMetrics(self.font()).lineSpacing()
|
|
||||||
docFrame = self.document().rootFrame().frameFormat()
|
|
||||||
docFrame.setBottomMargin(round(CONFIG.scrollPastEnd * fSize))
|
|
||||||
self.document().rootFrame().setFrameFormat(docFrame)
|
|
||||||
|
|
||||||
self.docFooter.updateLineCount()
|
self.docFooter.updateLineCount()
|
||||||
|
|
||||||
qApp.processEvents()
|
qApp.processEvents()
|
||||||
@@ -636,19 +628,14 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
self._nwItem.setCursorPos(cursPos)
|
self._nwItem.setCursorPos(cursPos)
|
||||||
return
|
return
|
||||||
|
|
||||||
def setCursorLine(self, line: int | None) -> bool:
|
def setCursorLine(self, line: int | None) -> None:
|
||||||
"""Move the cursor to a given line in the document."""
|
"""Move the cursor to a given line in the document."""
|
||||||
if not isinstance(line, int):
|
if isinstance(line, int) and line > 0:
|
||||||
return False
|
block = self.document().findBlockByNumber(line - 1)
|
||||||
|
|
||||||
lineIdx = line - 1 # Block index is 0 offset, lineNo is 1 offset
|
|
||||||
if lineIdx >= 0:
|
|
||||||
block = self.document().findBlockByNumber(lineIdx)
|
|
||||||
if block:
|
if block:
|
||||||
self.setCursorPosition(block.position())
|
self.setCursorPosition(block.position())
|
||||||
logger.debug("Cursor moved to line %d", line)
|
logger.debug("Cursor moved to line %d", line)
|
||||||
|
return
|
||||||
return True
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Spell Checking
|
# Spell Checking
|
||||||
@@ -950,26 +937,16 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if CONFIG.autoScroll:
|
if CONFIG.autoScroll:
|
||||||
|
cPos = self.cursorRect().topLeft().y()
|
||||||
cOld = self.cursorRect().center().y()
|
|
||||||
super().keyPressEvent(event)
|
super().keyPressEvent(event)
|
||||||
|
|
||||||
kMod = event.modifiers()
|
kMod = event.modifiers()
|
||||||
okMod = kMod == Qt.NoModifier or kMod == Qt.ShiftModifier
|
okMod = kMod == Qt.NoModifier or kMod == Qt.ShiftModifier
|
||||||
okKey = event.key() not in self.MOVE_KEYS
|
okKey = event.key() not in self.MOVE_KEYS
|
||||||
if okMod and okKey:
|
if okMod and okKey:
|
||||||
cNew = self.cursorRect().center().y()
|
|
||||||
cMov = cNew - cOld
|
|
||||||
mPos = CONFIG.autoScrollPos*0.01 * self.viewport().height()
|
mPos = CONFIG.autoScrollPos*0.01 * self.viewport().height()
|
||||||
if abs(cMov) > 0 and cOld > mPos:
|
if cPos > mPos:
|
||||||
# Move the scroll bar
|
|
||||||
vBar = self.verticalScrollBar()
|
vBar = self.verticalScrollBar()
|
||||||
doAnim = QPropertyAnimation(vBar, b"value", self)
|
vBar.setValue(vBar.value() + 1)
|
||||||
doAnim.setDuration(120)
|
|
||||||
doAnim.setStartValue(vBar.value())
|
|
||||||
doAnim.setEndValue(vBar.value() + cMov)
|
|
||||||
doAnim.start()
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
super().keyPressEvent(event)
|
super().keyPressEvent(event)
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ repsquotes = True
|
|||||||
repdquotes = True
|
repdquotes = True
|
||||||
repdash = True
|
repdash = True
|
||||||
repdots = True
|
repdots = True
|
||||||
scrollpastend = 25
|
|
||||||
autoscroll = False
|
autoscroll = False
|
||||||
autoscrollpos = 30
|
autoscrollpos = 30
|
||||||
fmtsquoteopen = ‘
|
fmtsquoteopen = ‘
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ repsquotes = True
|
|||||||
repdquotes = True
|
repdquotes = True
|
||||||
repdash = True
|
repdash = True
|
||||||
repdots = True
|
repdots = True
|
||||||
scrollpastend = 0
|
|
||||||
autoscroll = True
|
autoscroll = True
|
||||||
autoscrollpos = 30
|
autoscrollpos = 30
|
||||||
fmtsquoteopen = ‘
|
fmtsquoteopen = ‘
|
||||||
|
|||||||
@@ -156,9 +156,6 @@ def testDlgPreferences_Main(qtbot, monkeypatch, nwGUI, tstPaths):
|
|||||||
qtbot.mouseClick(tabEditor.autoScroll, Qt.LeftButton)
|
qtbot.mouseClick(tabEditor.autoScroll, Qt.LeftButton)
|
||||||
assert tabEditor.autoScroll.isChecked()
|
assert tabEditor.autoScroll.isChecked()
|
||||||
|
|
||||||
qtbot.wait(KEY_DELAY)
|
|
||||||
tabEditor.scrollPastEnd.setValue(0)
|
|
||||||
|
|
||||||
# Syntax Settings
|
# Syntax Settings
|
||||||
qtbot.wait(KEY_DELAY)
|
qtbot.wait(KEY_DELAY)
|
||||||
tabSyntax = nwPrefs.tabSyntax
|
tabSyntax = nwPrefs.tabSyntax
|
||||||
|
|||||||
@@ -200,8 +200,9 @@ def testGuiEditor_MetaData(qtbot, nwGUI, projPath, mockRnd):
|
|||||||
nwGUI.docEditor.saveCursorPosition()
|
nwGUI.docEditor.saveCursorPosition()
|
||||||
assert SHARED.project.tree[C.hSceneDoc].cursorPos == 10
|
assert SHARED.project.tree[C.hSceneDoc].cursorPos == 10
|
||||||
|
|
||||||
assert nwGUI.docEditor.setCursorLine(None) is False
|
nwGUI.docEditor.setCursorLine(None)
|
||||||
assert nwGUI.docEditor.setCursorLine(3) is True
|
assert nwGUI.docEditor.getCursorPosition() == 10
|
||||||
|
nwGUI.docEditor.setCursorLine(3)
|
||||||
assert nwGUI.docEditor.getCursorPosition() == 15
|
assert nwGUI.docEditor.getCursorPosition() == 15
|
||||||
|
|
||||||
# Document Changed Signal
|
# Document Changed Signal
|
||||||
@@ -499,7 +500,7 @@ def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd
|
|||||||
|
|
||||||
theText = "### A Scene\n\n\n%s" % ipsumText[0]
|
theText = "### A Scene\n\n\n%s" % ipsumText[0]
|
||||||
assert nwGUI.docEditor.replaceText(theText) is True
|
assert nwGUI.docEditor.replaceText(theText) is True
|
||||||
assert nwGUI.docEditor.setCursorLine(3)
|
nwGUI.docEditor.setCursorLine(3)
|
||||||
|
|
||||||
# Invalid Keyword
|
# Invalid Keyword
|
||||||
assert nwGUI.docEditor.insertKeyWord("stuff") is False
|
assert nwGUI.docEditor.insertKeyWord("stuff") is False
|
||||||
@@ -1039,7 +1040,7 @@ def testGuiEditor_BlockFormatting(qtbot, monkeypatch, nwGUI, projPath, ipsumText
|
|||||||
# Third Line
|
# Third Line
|
||||||
# This also needs to add a new block
|
# This also needs to add a new block
|
||||||
assert nwGUI.docEditor.replaceText("#### Title\n\nThe Text\n\n") is True
|
assert nwGUI.docEditor.replaceText("#### Title\n\nThe Text\n\n") is True
|
||||||
assert nwGUI.docEditor.setCursorLine(3) is True
|
nwGUI.docEditor.setCursorLine(3)
|
||||||
assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_COM) is True
|
assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_COM) is True
|
||||||
assert nwGUI.docEditor.getText() == "#### Title\n\n% The Text\n\n"
|
assert nwGUI.docEditor.getText() == "#### Title\n\n% The Text\n\n"
|
||||||
|
|
||||||
@@ -1072,11 +1073,11 @@ def testGuiEditor_Tags(qtbot, nwGUI, projPath, ipsumText, mockRnd):
|
|||||||
assert nwGUI.openDocument(C.hSceneDoc) is True
|
assert nwGUI.openDocument(C.hSceneDoc) is True
|
||||||
|
|
||||||
# Empty Block
|
# Empty Block
|
||||||
assert nwGUI.docEditor.setCursorLine(2) is True
|
nwGUI.docEditor.setCursorLine(2)
|
||||||
assert nwGUI.docEditor._followTag() is False
|
assert nwGUI.docEditor._followTag() is False
|
||||||
|
|
||||||
# Not On Tag
|
# Not On Tag
|
||||||
assert nwGUI.docEditor.setCursorLine(1) is True
|
nwGUI.docEditor.setCursorLine(1)
|
||||||
assert nwGUI.docEditor._followTag() is False
|
assert nwGUI.docEditor._followTag() is False
|
||||||
|
|
||||||
# On Tag Keyword
|
# On Tag Keyword
|
||||||
|
|||||||
Reference in New Issue
Block a user