diff --git a/nw/config.py b/nw/config.py index 5ed12655..b4c14b27 100644 --- a/nw/config.py +++ b/nw/config.py @@ -128,6 +128,7 @@ class Config: self.doReplaceDots = True self.scrollPastEnd = True self.scollWithCursor = False + self.scollToPoint = 40 self.wordCountTimer = 5.0 self.showTabsNSpaces = False @@ -467,6 +468,9 @@ class Config: self.scollWithCursor = self._parseLine( cnfParse, cnfSec, "scollwithcursor", self.CNF_BOOL, self.scollWithCursor ) + self.scollToPoint = self._parseLine( + cnfParse, cnfSec, "scolltopoint", self.CNF_INT, self.scollToPoint + ) self.fmtSingleQuotes = self._parseLine( cnfParse, cnfSec, "fmtsinglequote", self.CNF_LIST, self.fmtSingleQuotes ) @@ -613,6 +617,7 @@ class Config: cnfParse.set(cnfSec, "repdots", str(self.doReplaceDots)) cnfParse.set(cnfSec, "scrollpastend", str(self.scrollPastEnd)) cnfParse.set(cnfSec, "scollwithcursor", str(self.scollWithCursor)) + cnfParse.set(cnfSec, "scolltopoint", str(self.scollToPoint)) cnfParse.set(cnfSec, "fmtsinglequote", self._packList(self.fmtSingleQuotes)) cnfParse.set(cnfSec, "fmtdoublequote", self._packList(self.fmtDoubleQuotes)) cnfParse.set(cnfSec, "spelltool", str(self.spellTool)) diff --git a/nw/gui/build.py b/nw/gui/build.py index 754553d8..37880cca 100644 --- a/nw/gui/build.py +++ b/nw/gui/build.py @@ -1124,17 +1124,6 @@ class GuiBuildNovelDocView(QTextBrowser): else: self.setTabStopWidth(self.mainConf.getTabWidth()) - # Scroll bars - if self.mainConf.hideVScroll: - self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - else: - self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) - - if self.mainConf.hideHScroll: - self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - else: - self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) - docPalette = self.palette() docPalette.setColor(QPalette.Base, QColor(255, 255, 255)) docPalette.setColor(QPalette.Text, QColor(0, 0, 0)) diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index d65c89df..a7779845 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -88,8 +88,6 @@ class GuiDocEditor(QTextEdit): self.bigDoc = False # Flag for very large document size self.doReplace = False # Switch to temporarily disable auto-replace self.queuePos = None # Used for delayed change of cursor position - self.cursorLast = 0 # The last known vertical position of the cursor - self.lengthLast = 0 # Typography self.typDQOpen = self.mainConf.fmtDoubleQuotes[0] @@ -102,8 +100,6 @@ class GuiDocEditor(QTextEdit): self.qDocument.contentsChange.connect(self._docChange) self.qDocument.documentLayout().documentSizeChanged.connect(self._docSizeChanged) - self.verticalScrollBar().sliderMoved.connect(self._doVerticalScroll) - # Document Title self.docHeader = GuiDocEditHeader(self) self.docFooter = GuiDocEditFooter(self) @@ -440,7 +436,8 @@ class GuiDocEditor(QTextEdit): if self.mainConf.scrollPastEnd: docFrame = self.qDocument.rootFrame().frameFormat() - docFrame.setBottomMargin(wH - uM - lM - 4*tB - self.theTheme.fontPixelSize) + docMargin = wH - uM - lM - 4*tB - 5*self.theTheme.fontPixelSize + docFrame.setBottomMargin(max(0, docMargin)) self.qDocument.rootFrame().setFrameFormat(docFrame) return @@ -495,7 +492,6 @@ class GuiDocEditor(QTextEdit): theCursor.setPosition(thePosition) self.setTextCursor(theCursor) self.docFooter.updateLineCount() - self.cursorLast = self.cursorRect().center().y() return True @@ -776,20 +772,13 @@ class GuiDocEditor(QTextEdit): self.docFooter.updateLineCount() if self.mainConf.scollWithCursor: - docLen = self.qDocument.characterCount() - if docLen == self.lengthLast: - # No change, so just update last position - self.cursorLast = self.cursorRect().center().y() - else: - # The user typed something, so check if we need to - # scroll, and move the scroll bar the same distance - self.lengthLast = docLen - self.ensureCursorVisible() + kMod = keyEvent.modifiers() + if kMod == Qt.NoModifier or kMod == Qt.ShiftModifier: cPos = self.cursorRect().center().y() - if cPos != self.cursorLast: - vBar = self.verticalScrollBar() - vBar.setValue(vBar.value() + cPos - self.cursorLast) - self.cursorLast = self.cursorRect().center().y() + mPos = self.mainConf.scollToPoint * self.viewport().height() + vBar = self.verticalScrollBar() + vBar.setValue(vBar.value() + cPos - round(mPos * 0.01)) + self.ensureCursorVisible() return @@ -816,18 +805,9 @@ class GuiDocEditor(QTextEdit): QTextEdit.mouseReleaseEvent(self, mEvent) self.docFooter.updateLineCount() - self.cursorLast = self.cursorRect().center().y() return - def wheelEvent(self, theEvent): - """Briefly capture the mouse wheel event to capture the cursor - position. - """ - QTextEdit.wheelEvent(self, theEvent) - self.cursorLast = self.cursorRect().center().y() - return - def resizeEvent(self, theEvent): """If the text editor is resize, we must make sure the document has its margins adjusted according to user preferences. @@ -862,13 +842,6 @@ class GuiDocEditor(QTextEdit): self._docAutoReplace(self.qDocument.findBlock(thePos)) return - @pyqtSlot(int) - def _doVerticalScroll(self, theChange): - """Update the cursor position on vertical scrolling. - """ - self.cursorLast = self.cursorRect().center().y() - return - @pyqtSlot("QPoint") def _openContextMenu(self, thePos): """Triggered by right click to open the context menu. Also diff --git a/nw/gui/preferences.py b/nw/gui/preferences.py index f1848bb3..2a1cf653 100644 --- a/nw/gui/preferences.py +++ b/nw/gui/preferences.py @@ -555,6 +555,10 @@ class GuiConfigEditLayoutTab(QWidget): theUnit="px" ) + # Scroll Behaviour + # ================ + self.mainForm.addGroupLabel("Scroll Behaviour") + ## Scroll Past End self.scrollPastEnd = QSwitch() self.scrollPastEnd.setChecked(self.mainConf.scrollPastEnd) @@ -568,9 +572,22 @@ class GuiConfigEditLayoutTab(QWidget): self.scollWithCursor = QSwitch() self.scollWithCursor.setChecked(self.mainConf.scollWithCursor) self.mainForm.addRow( - "Typewriter style scrolling", + "Typewriter style scrolling when you type", self.scollWithCursor, - "Scrolls up when the cursor moves to a new line." + "Tries to keep the cursor at a fixed vertical position." + ) + + ## Font Size + self.scollToPoint = QSpinBox(self) + self.scollToPoint.setMinimum(10) + self.scollToPoint.setMaximum(90) + self.scollToPoint.setSingleStep(1) + self.scollToPoint.setValue(self.mainConf.scollToPoint) + self.mainForm.addRow( + "Position in the editor to keep the cursor", + self.scollToPoint, + "In units of percentage of the editor height.", + theUnit = "%" ) return @@ -592,6 +609,7 @@ class GuiConfigEditLayoutTab(QWidget): tabWidth = self.tabWidth.value() scrollPastEnd = self.scrollPastEnd.isChecked() scollWithCursor = self.scollWithCursor.isChecked() + scollToPoint = self.scollToPoint.value() self.mainConf.textFont = textFont self.mainConf.textSize = textSize @@ -604,6 +622,7 @@ class GuiConfigEditLayoutTab(QWidget): self.mainConf.tabWidth = tabWidth self.mainConf.scrollPastEnd = scrollPastEnd self.mainConf.scollWithCursor = scollWithCursor + self.mainConf.scollToPoint = scollToPoint self.mainConf.confChanged = True diff --git a/tests/reference/novelwriter.conf b/tests/reference/novelwriter.conf index f4bc8d61..bb141482 100644 --- a/tests/reference/novelwriter.conf +++ b/tests/reference/novelwriter.conf @@ -1,5 +1,5 @@ [Main] -timestamp = 2020-10-11 18:29:34 +timestamp = 2020-10-11 22:50:45 theme = default syntax = default_light icons = typicons_colour_light @@ -41,6 +41,7 @@ repdash = True repdots = True scrollpastend = True scollwithcursor = False +scolltopoint = 40 fmtsinglequote = ‘, ’ fmtdoublequote = “, ” spelltool = internal diff --git a/tests/reference/novelwriter_prefs.conf b/tests/reference/novelwriter_prefs.conf index 0e1d41f6..ee591782 100644 --- a/tests/reference/novelwriter_prefs.conf +++ b/tests/reference/novelwriter_prefs.conf @@ -41,6 +41,7 @@ repdash = True repdots = True scrollpastend = False scollwithcursor = True +scolltopoint = 40 fmtsinglequote = ‘, ’ fmtdoublequote = “, ” spelltool = internal