diff --git a/novelwriter/config.py b/novelwriter/config.py index 526938de..c7994d1c 100644 --- a/novelwriter/config.py +++ b/novelwriter/config.py @@ -144,7 +144,6 @@ class Config: self.doReplaceDash = True # Replace multiple hyphens with dashes 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.autoScrollPos = 30 # Start point for typewriter-like scrolling @@ -572,7 +571,6 @@ class Config: self.doReplaceDQuote = conf.rdBool(sec, "repdquotes", self.doReplaceDQuote) self.doReplaceDash = conf.rdBool(sec, "repdash", self.doReplaceDash) 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.autoScrollPos = conf.rdInt(sec, "autoscrollpos", self.autoScrollPos) self.fmtSQuoteOpen = conf.rdStr(sec, "fmtsquoteopen", self.fmtSQuoteOpen) @@ -695,7 +693,6 @@ class Config: "repdquotes": str(self.doReplaceDQuote), "repdash": str(self.doReplaceDash), "repdots": str(self.doReplaceDots), - "scrollpastend": str(self.scrollPastEnd), "autoscroll": str(self.autoScroll), "autoscrollpos": str(self.autoScrollPos), "fmtsquoteopen": str(self.fmtSQuoteOpen), diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index 24bee775..76178d05 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -724,19 +724,6 @@ class GuiPreferencesEditor(QWidget): # ================ 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 self.autoScroll = NSwitch() self.autoScroll.setChecked(CONFIG.autoScroll) @@ -775,7 +762,6 @@ class GuiPreferencesEditor(QWidget): CONFIG.showLineEndings = self.showLineEndings.isChecked() # Scroll Behaviour - CONFIG.scrollPastEnd = self.scrollPastEnd.value() CONFIG.autoScroll = self.autoScroll.isChecked() CONFIG.autoScrollPos = self.autoScrollPos.value() diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 2d9b5847..d74c50c7 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -37,13 +37,12 @@ from time import time from typing import TYPE_CHECKING from PyQt5.QtCore import ( - pyqtSignal, pyqtSlot, QObject, QPoint, QPropertyAnimation, QRegExp, - QRegularExpression, QRunnable, QSize, Qt, QTimer + pyqtSignal, pyqtSlot, QObject, QPoint, QRegExp, QRegularExpression, + QRunnable, QSize, Qt, QTimer ) from PyQt5.QtGui import ( - QColor, QCursor, QFont, QFontMetrics, QKeyEvent, QKeySequence, QMouseEvent, - QPalette, QPixmap, QResizeEvent, QTextBlock, QTextCursor, QTextDocument, - QTextOption + QColor, QCursor, QFont, QKeyEvent, QKeySequence, QMouseEvent, QPalette, + QPixmap, QResizeEvent, QTextBlock, QTextCursor, QTextDocument, QTextOption ) from PyQt5.QtWidgets import ( QAction, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMenu, @@ -142,6 +141,7 @@ class GuiDocEditor(QPlainTextEdit): self.setMinimumWidth(CONFIG.pxInt(300)) self.setAutoFillBackground(True) self.setFrameStyle(QFrame.NoFrame) + self.setCenterOnScroll(True) # Custom Shortcuts self.keyContext = QShortcut(self) @@ -311,7 +311,7 @@ class GuiDocEditor(QPlainTextEdit): # Set default text margins # Due to cursor visibility, a part of the margin must be # allocated to the document itself. See issue #1112. - cW = self.cursorWidth() + cW = 2*self.cursorWidth() qDoc = self.document() qDoc.setDocumentMargin(cW) self._vpMargin = max(CONFIG.getTextMargin() - cW, 0) @@ -369,13 +369,13 @@ class GuiDocEditor(QPlainTextEdit): self._nwDocument = SHARED.project.storage.getDocument(tHandle) self._nwItem = self._nwDocument.nwItem - theDoc = self._nwDocument.readDocument() - if theDoc is None: + docText = self._nwDocument.readDocument() + if docText is None: # There was an io error self.clearEditor() return False - docSize = len(theDoc) + docSize = len(docText) if docSize > nwConst.MAX_DOCSIZE: SHARED.error(self.tr( "The document you are trying to open is too big. " @@ -389,22 +389,20 @@ class GuiDocEditor(QPlainTextEdit): return False qApp.setOverrideCursor(QCursor(Qt.WaitCursor)) + self._docHandle = tHandle self.highLight.setHandle(tHandle) - bfTime = time() + tStart = time() self._allowAutoReplace(False) - self.setPlainText(theDoc) - qApp.processEvents() - + self.setPlainText(docText) self._allowAutoReplace(True) - afTime = time() - logger.debug("Document highlighted in %.3f ms", 1000*(afTime-bfTime)) + logger.debug("Document text loaded in %.3f ms", 1000*(time() - tStart)) + qApp.processEvents() self._lastEdit = time() self._lastActive = time() self._runDocCounter() self.wcTimerDoc.start() - self._docHandle = tHandle self.setReadOnly(False) self.docHeader.setTitleFromHandle(self._docHandle) @@ -416,12 +414,6 @@ class GuiDocEditor(QPlainTextEdit): elif isinstance(tLine, int): 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() qApp.processEvents() @@ -636,19 +628,14 @@ class GuiDocEditor(QPlainTextEdit): self._nwItem.setCursorPos(cursPos) 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.""" - if not isinstance(line, int): - return False - - lineIdx = line - 1 # Block index is 0 offset, lineNo is 1 offset - if lineIdx >= 0: - block = self.document().findBlockByNumber(lineIdx) + if isinstance(line, int) and line > 0: + block = self.document().findBlockByNumber(line - 1) if block: self.setCursorPosition(block.position()) logger.debug("Cursor moved to line %d", line) - - return True + return ## # Spell Checking @@ -950,26 +937,16 @@ class GuiDocEditor(QPlainTextEdit): return if CONFIG.autoScroll: - - cOld = self.cursorRect().center().y() + cPos = self.cursorRect().topLeft().y() super().keyPressEvent(event) - kMod = event.modifiers() okMod = kMod == Qt.NoModifier or kMod == Qt.ShiftModifier okKey = event.key() not in self.MOVE_KEYS if okMod and okKey: - cNew = self.cursorRect().center().y() - cMov = cNew - cOld mPos = CONFIG.autoScrollPos*0.01 * self.viewport().height() - if abs(cMov) > 0 and cOld > mPos: - # Move the scroll bar + if cPos > mPos: vBar = self.verticalScrollBar() - doAnim = QPropertyAnimation(vBar, b"value", self) - doAnim.setDuration(120) - doAnim.setStartValue(vBar.value()) - doAnim.setEndValue(vBar.value() + cMov) - doAnim.start() - + vBar.setValue(vBar.value() + 1) else: super().keyPressEvent(event) diff --git a/tests/reference/baseConfig_novelwriter.conf b/tests/reference/baseConfig_novelwriter.conf index 2f4b05e1..f3434415 100644 --- a/tests/reference/baseConfig_novelwriter.conf +++ b/tests/reference/baseConfig_novelwriter.conf @@ -43,7 +43,6 @@ repsquotes = True repdquotes = True repdash = True repdots = True -scrollpastend = 25 autoscroll = False autoscrollpos = 30 fmtsquoteopen = ‘ diff --git a/tests/reference/guiPreferences_novelwriter.conf b/tests/reference/guiPreferences_novelwriter.conf index a8700427..dfa3f3fd 100644 --- a/tests/reference/guiPreferences_novelwriter.conf +++ b/tests/reference/guiPreferences_novelwriter.conf @@ -43,7 +43,6 @@ repsquotes = True repdquotes = True repdash = True repdots = True -scrollpastend = 0 autoscroll = True autoscrollpos = 30 fmtsquoteopen = ‘ diff --git a/tests/test_dialogs/test_dlg_preferences.py b/tests/test_dialogs/test_dlg_preferences.py index fe6b807a..dc51fd32 100644 --- a/tests/test_dialogs/test_dlg_preferences.py +++ b/tests/test_dialogs/test_dlg_preferences.py @@ -156,9 +156,6 @@ def testDlgPreferences_Main(qtbot, monkeypatch, nwGUI, tstPaths): qtbot.mouseClick(tabEditor.autoScroll, Qt.LeftButton) assert tabEditor.autoScroll.isChecked() - qtbot.wait(KEY_DELAY) - tabEditor.scrollPastEnd.setValue(0) - # Syntax Settings qtbot.wait(KEY_DELAY) tabSyntax = nwPrefs.tabSyntax diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py index 547c7649..056523cf 100644 --- a/tests/test_gui/test_gui_doceditor.py +++ b/tests/test_gui/test_gui_doceditor.py @@ -200,8 +200,9 @@ def testGuiEditor_MetaData(qtbot, nwGUI, projPath, mockRnd): nwGUI.docEditor.saveCursorPosition() assert SHARED.project.tree[C.hSceneDoc].cursorPos == 10 - assert nwGUI.docEditor.setCursorLine(None) is False - assert nwGUI.docEditor.setCursorLine(3) is True + nwGUI.docEditor.setCursorLine(None) + assert nwGUI.docEditor.getCursorPosition() == 10 + nwGUI.docEditor.setCursorLine(3) assert nwGUI.docEditor.getCursorPosition() == 15 # 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] assert nwGUI.docEditor.replaceText(theText) is True - assert nwGUI.docEditor.setCursorLine(3) + nwGUI.docEditor.setCursorLine(3) # Invalid Keyword assert nwGUI.docEditor.insertKeyWord("stuff") is False @@ -1039,7 +1040,7 @@ def testGuiEditor_BlockFormatting(qtbot, monkeypatch, nwGUI, projPath, ipsumText # Third Line # This also needs to add a new block 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.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 # Empty Block - assert nwGUI.docEditor.setCursorLine(2) is True + nwGUI.docEditor.setCursorLine(2) assert nwGUI.docEditor._followTag() is False # Not On Tag - assert nwGUI.docEditor.setCursorLine(1) is True + nwGUI.docEditor.setCursorLine(1) assert nwGUI.docEditor._followTag() is False # On Tag Keyword