diff --git a/CHANGELOG.md b/CHANGELOG.md index 801417bc..d93b0044 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # novelWriter ChangeLog -## Version 1.0 Release Beta 5 [2020-10-18] +## Version 1.0 Beta 5 [2020-10-18] **Important Notes** diff --git a/nw/config.py b/nw/config.py index 675fad3d..65fd4d8a 100644 --- a/nw/config.py +++ b/nw/config.py @@ -107,44 +107,52 @@ class Config: self.hideHScroll = False # Hide horizontal scroll bars on main widgets ## Project - self.autoSaveProj = 60 - self.autoSaveDoc = 30 + self.autoSaveProj = 60 # Interval for auto-saving project in seconds + self.autoSaveDoc = 30 # Interval for auto-saving document in seconds ## Text Editor - self.textFont = None - self.textSize = 12 - self.textFixedW = True - self.textWidth = 600 - self.textMargin = 40 - self.tabWidth = 40 - self.focusWidth = 800 - self.hideFocusFooter = False - self.doJustify = False - self.autoSelect = True - self.doReplace = True - self.doReplaceSQuote = True - self.doReplaceDQuote = True - self.doReplaceDash = True - self.doReplaceDots = True - self.scrollPastEnd = True - self.scollWithCursor = False - self.scollToPoint = 40 + self.textFont = None # Editor font + self.textSize = 12 # Editor font size + self.textFixedW = True # Keep editor text fixed width + self.textWidth = 600 # Editor text width + self.textMargin = 40 # Editor/viewer text margin + self.tabWidth = 40 # Editor tabulator width - self.wordCountTimer = 5.0 - self.showTabsNSpaces = False - self.showLineEndings = False - self.bigDocLimit = 800 - self.showFullPath = True - self.highlightQuotes = True - self.highlightEmph = True + self.focusWidth = 800 # Focus Mode text width + self.hideFocusFooter = False # Hide document footer in Focus Mode + self.showFullPath = True # Show full document path in editor header + self.autoSelect = True # Auto-select word when applying format with no selection + self.doJustify = False # Justify text + self.showTabsNSpaces = False # Show tabs and spaces in edior + self.showLineEndings = False # Show line endings in editor + + self.doReplace = True # Enable auto-replace as you type + self.doReplaceSQuote = True # Smart single quotes + self.doReplaceDQuote = True # Smart double quotes + self.doReplaceDash = True # Replace multiple hyphens with dashes + self.doReplaceDots = True # Replace three dots with ellipsis + + self.scrollPastEnd = True # Allow scrolling past end of document + self.autoScroll = False # Typewriter-like scrolling + self.autoScrollPos = 30 # Start point for typewriter-like scrolling + + self.wordCountTimer = 5.0 # Interval for word count update in seconds + self.bigDocLimit = 800 # Size threshold for heavy editor features in kilobytes + + self.highlightQuotes = True # Highlight text in quotes + self.highlightEmph = True # Add colour to text emphasis + + ## User-Selected Symbols self.fmtApostrophe = nwUnicode.U_RSQUO self.fmtSingleQuotes = [nwUnicode.U_LSQUO, nwUnicode.U_RSQUO] self.fmtDoubleQuotes = [nwUnicode.U_LDQUO, nwUnicode.U_RDQUO] + ## Spell Checking self.spellTool = None self.spellLanguage = None + ## Search Bar Switches self.searchCase = False self.searchWord = False self.searchRegEx = False @@ -465,11 +473,11 @@ class Config: self.scrollPastEnd = self._parseLine( cnfParse, cnfSec, "scrollpastend", self.CNF_BOOL, self.scrollPastEnd ) - self.scollWithCursor = self._parseLine( - cnfParse, cnfSec, "scollwithcursor", self.CNF_BOOL, self.scollWithCursor + self.autoScroll = self._parseLine( + cnfParse, cnfSec, "autoscroll", self.CNF_BOOL, self.autoScroll ) - self.scollToPoint = self._parseLine( - cnfParse, cnfSec, "scolltopoint", self.CNF_INT, self.scollToPoint + self.autoScrollPos = self._parseLine( + cnfParse, cnfSec, "autoscrollpos", self.CNF_INT, self.autoScrollPos ) self.fmtSingleQuotes = self._parseLine( cnfParse, cnfSec, "fmtsinglequote", self.CNF_LIST, self.fmtSingleQuotes @@ -616,8 +624,8 @@ class Config: cnfParse.set(cnfSec, "repdash", str(self.doReplaceDash)) 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, "autoscroll", str(self.autoScroll)) + cnfParse.set(cnfSec, "autoscrollpos", str(self.autoScrollPos)) cnfParse.set(cnfSec, "fmtsinglequote", self._packList(self.fmtSingleQuotes)) cnfParse.set(cnfSec, "fmtdoublequote", self._packList(self.fmtDoubleQuotes)) cnfParse.set(cnfSec, "spelltool", str(self.spellTool)) @@ -911,16 +919,21 @@ class Config: """ if cnfParse.has_section(cnfSec): if cnfParse.has_option(cnfSec, cnfName): - if cnfType == self.CNF_STR: - return cnfParse.get(cnfSec, cnfName) - elif cnfType == self.CNF_INT: - return cnfParse.getint(cnfSec, cnfName) - elif cnfType == self.CNF_BOOL: - return cnfParse.getboolean(cnfSec, cnfName) - elif cnfType == self.CNF_LIST: - return self._unpackList( - cnfParse.get(cnfSec, cnfName), len(cnfDefault), cnfDefault - ) + try: + if cnfType == self.CNF_STR: + return cnfParse.get(cnfSec, cnfName) + elif cnfType == self.CNF_INT: + return cnfParse.getint(cnfSec, cnfName) + elif cnfType == self.CNF_BOOL: + return cnfParse.getboolean(cnfSec, cnfName) + elif cnfType == self.CNF_LIST: + return self._unpackList( + cnfParse.get(cnfSec, cnfName), len(cnfDefault), cnfDefault + ) + except ValueError as e: + logger.error("Failed to load value from config file.") + logger.error(str(e)) + return cnfDefault def _checkNone(self, checkVal): diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index 6d431319..9a2cb683 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -61,6 +61,11 @@ logger = logging.getLogger(__name__) class GuiDocEditor(QTextEdit): + MOVE_KEYS = ( + Qt.Key_Left, Qt.Key_Right, Qt.Key_Up, Qt.Key_Down, + Qt.Key_PageUp, Qt.Key_PageDown + ) + def __init__(self, theParent): QTextEdit.__init__(self, theParent) @@ -436,8 +441,7 @@ class GuiDocEditor(QTextEdit): if self.mainConf.scrollPastEnd: docFrame = self.qDocument.rootFrame().frameFormat() - docMargin = wH - uM - lM - 4*tB - 5*self.theTheme.fontPixelSize - docFrame.setBottomMargin(max(0, docMargin)) + docFrame.setBottomMargin(max(0, 0.6*(wH - uM - lM - 4*tB))) self.qDocument.rootFrame().setFrameFormat(docFrame) return @@ -765,33 +769,39 @@ class GuiDocEditor(QTextEdit): return elif keyEvent == QKeySequence.Redo: self.docAction(nwDocAction.REDO) + return elif keyEvent == QKeySequence.Undo: self.docAction(nwDocAction.UNDO) + return elif keyEvent == QKeySequence.SelectAll: self.docAction(nwDocAction.SEL_ALL) + return + + if self.mainConf.autoScroll: + + cOld = self.cursorRect().center().y() + QTextEdit.keyPressEvent(self, keyEvent) + + kMod = keyEvent.modifiers() + okMod = kMod == Qt.NoModifier or kMod == Qt.ShiftModifier + okKey = keyEvent.key() not in self.MOVE_KEYS + if okMod and okKey: + cNew = self.cursorRect().center().y() + cMov = cNew - cOld + mPos = self.mainConf.autoScrollPos * self.viewport().height() * 0.01 + if abs(cMov) > 0 and cOld > mPos: + # Move the scroll bar + vBar = self.verticalScrollBar() + doAnim = QPropertyAnimation(vBar, b"value", self) + doAnim.setDuration(120) + doAnim.setStartValue(vBar.value()) + doAnim.setEndValue(vBar.value() + cMov) + doAnim.start() + else: QTextEdit.keyPressEvent(self, keyEvent) - self.docFooter.updateLineCount() - if self.mainConf.scollWithCursor: - kMod = keyEvent.modifiers() - if kMod == Qt.NoModifier or kMod == Qt.ShiftModifier: - hWid = self.viewport().height() - cPos = self.cursorRect().center().y() - mPos = self.mainConf.scollToPoint * hWid - vBar = self.verticalScrollBar() - - # Compute the needed scroll and duration - pOld = vBar.value() - pNew = pOld + cPos - round(mPos*0.01) - aDur = 150 + round(min(abs(pNew - pOld)/hWid, 1.0)*500) - - if pNew >= 0: - doAnim = QPropertyAnimation(vBar, b"value", self) - doAnim.setDuration(aDur) - doAnim.setStartValue(pOld) - doAnim.setEndValue(pNew) - doAnim.start() + self.docFooter.updateLineCount() return diff --git a/nw/gui/preferences.py b/nw/gui/preferences.py index 7b8e0905..945e35f6 100644 --- a/nw/gui/preferences.py +++ b/nw/gui/preferences.py @@ -569,23 +569,23 @@ class GuiConfigEditLayoutTab(QWidget): ) ## Typewriter Scrolling - self.scollWithCursor = QSwitch() - self.scollWithCursor.setChecked(self.mainConf.scollWithCursor) + self.autoScroll = QSwitch() + self.autoScroll.setChecked(self.mainConf.autoScroll) self.mainForm.addRow( "Typewriter style scrolling when you type", - self.scollWithCursor, + self.autoScroll, "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.autoScrollPos = QSpinBox(self) + self.autoScrollPos.setMinimum(10) + self.autoScrollPos.setMaximum(90) + self.autoScrollPos.setSingleStep(1) + self.autoScrollPos.setValue(int(self.mainConf.autoScrollPos)) self.mainForm.addRow( - "Position in the editor to keep the cursor", - self.scollToPoint, + "Minimum position for Typewriter scrolling", + self.autoScrollPos, "In units of percentage of the editor height.", theUnit = "%" ) @@ -608,8 +608,8 @@ class GuiConfigEditLayoutTab(QWidget): textMargin = self.textMargin.value() tabWidth = self.tabWidth.value() scrollPastEnd = self.scrollPastEnd.isChecked() - scollWithCursor = self.scollWithCursor.isChecked() - scollToPoint = self.scollToPoint.value() + autoScroll = self.autoScroll.isChecked() + autoScrollPos = self.autoScrollPos.value() self.mainConf.textFont = textFont self.mainConf.textSize = textSize @@ -621,8 +621,8 @@ class GuiConfigEditLayoutTab(QWidget): self.mainConf.textMargin = textMargin self.mainConf.tabWidth = tabWidth self.mainConf.scrollPastEnd = scrollPastEnd - self.mainConf.scollWithCursor = scollWithCursor - self.mainConf.scollToPoint = scollToPoint + self.mainConf.autoScroll = autoScroll + self.mainConf.autoScrollPos = autoScrollPos self.mainConf.confChanged = True diff --git a/tests/reference/novelwriter.conf b/tests/reference/novelwriter.conf index bb141482..aef7459f 100644 --- a/tests/reference/novelwriter.conf +++ b/tests/reference/novelwriter.conf @@ -40,8 +40,8 @@ repdquotes = True repdash = True repdots = True scrollpastend = True -scollwithcursor = False -scolltopoint = 40 +autoscroll = False +autoscrollpos = 30 fmtsinglequote = ‘, ’ fmtdoublequote = “, ” spelltool = internal diff --git a/tests/reference/novelwriter_prefs.conf b/tests/reference/novelwriter_prefs.conf index ee591782..831bbf08 100644 --- a/tests/reference/novelwriter_prefs.conf +++ b/tests/reference/novelwriter_prefs.conf @@ -40,8 +40,8 @@ repdquotes = True repdash = True repdots = True scrollpastend = False -scollwithcursor = True -scolltopoint = 40 +autoscroll = True +autoscrollpos = 30 fmtsinglequote = ‘, ’ fmtdoublequote = “, ” spelltool = internal diff --git a/tests/test_dialogs.py b/tests/test_dialogs.py index ddc77738..abe14b64 100644 --- a/tests/test_dialogs.py +++ b/tests/test_dialogs.py @@ -1127,9 +1127,9 @@ def testPreferences(qtbot, monkeypatch, yesToAll, nwMinimal, nwTemp, nwRef, tmpC assert not tabLayout.scrollPastEnd.isChecked() qtbot.wait(keyDelay) - assert not tabLayout.scollWithCursor.isChecked() - qtbot.mouseClick(tabLayout.scollWithCursor, Qt.LeftButton) - assert tabLayout.scollWithCursor.isChecked() + assert not tabLayout.autoScroll.isChecked() + qtbot.mouseClick(tabLayout.autoScroll, Qt.LeftButton) + assert tabLayout.autoScroll.isChecked() # Editor Settings qtbot.wait(keyDelay) diff --git a/tests/test_gui.py b/tests/test_gui.py index ea7b54ec..0d9849d6 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -172,8 +172,8 @@ def testDocEditor(qtbot, yesToAll, nwFuncTemp, nwTempGUI, nwRef, nwTemp): nwGUI.mainConf.hideHScroll = True nwGUI.mainConf.hideVScroll = True nwGUI.mainConf.scrollPastEnd = True - nwGUI.mainConf.scollToPoint = 80 - nwGUI.mainConf.scollWithCursor = True + nwGUI.mainConf.autoScrollPos = 80 + nwGUI.mainConf.autoScroll = True # Add a Character File nwGUI.setFocus(1)