From 1482451e8773e43ebf0a659d08636ec4f086f8cf Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 8 Jan 2024 19:12:39 +0100
Subject: [PATCH 01/13] Create a scrollable config layout and start adding it
to preferences
---
novelwriter/dialogs/preferences.py | 206 ++++++++++++++++++++-----
novelwriter/extensions/configlayout.py | 100 +++++++++++-
2 files changed, 264 insertions(+), 42 deletions(-)
diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py
index 4509d3b8..2e4617a9 100644
--- a/novelwriter/dialogs/preferences.py
+++ b/novelwriter/dialogs/preferences.py
@@ -28,20 +28,23 @@ import logging
from PyQt5.QtGui import QCloseEvent, QFont
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import (
- QDialog, QWidget, QComboBox, QSpinBox, QPushButton, QDialogButtonBox,
+ QAbstractButton, QDialog, QHBoxLayout, QLabel, QScrollArea, QVBoxLayout, QWidget, QComboBox, QSpinBox, QPushButton, QDialogButtonBox,
QLineEdit, QFileDialog, QFontDialog, QDoubleSpinBox, qApp
)
from novelwriter import CONFIG, SHARED
from novelwriter.dialogs.quotes import GuiQuoteSelect
+from novelwriter.extensions.pagedsidebar import NPagedSideBar
from novelwriter.extensions.switch import NSwitch
from novelwriter.extensions.pageddialog import NPagedDialog
-from novelwriter.extensions.configlayout import NConfigLayout
+from novelwriter.extensions.configlayout import NConfigLayout, NScrollableForm
logger = logging.getLogger(__name__)
-class GuiPreferences(NPagedDialog):
+class GuiPreferences(QDialog):
+
+ NAV_APPEARANCE = 0
newPreferencesReady = pyqtSignal(bool, bool, bool, bool)
@@ -50,32 +53,44 @@ class GuiPreferences(NPagedDialog):
logger.debug("Create: GuiPreferences")
self.setObjectName("GuiPreferences")
- self.setWindowTitle(self.tr("Preferences"))
-
- self.tabGeneral = GuiPreferencesGeneral(self)
- self.tabProjects = GuiPreferencesProjects(self)
- self.tabDocs = GuiPreferencesDocuments(self)
- self.tabEditor = GuiPreferencesEditor(self)
- self.tabSyntax = GuiPreferencesSyntax(self)
- self.tabAuto = GuiPreferencesAutomation(self)
- self.tabQuote = GuiPreferencesQuotes(self)
-
- self.addTab(self.tabGeneral, self.tr("General"))
- self.addTab(self.tabProjects, self.tr("Projects"))
- self.addTab(self.tabDocs, self.tr("Documents"))
- self.addTab(self.tabEditor, self.tr("Editor"))
- self.addTab(self.tabSyntax, self.tr("Highlighting"))
- self.addTab(self.tabAuto, self.tr("Automation"))
- self.addTab(self.tabQuote, self.tr("Quotes"))
-
- self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, self)
- self.buttonBox.accepted.connect(self._doSave)
- self.buttonBox.rejected.connect(self.close)
- self.rejected.connect(self.close)
- self.addControls(self.buttonBox)
+ self.setWindowTitle(CONFIG.appName)
+ mPx = CONFIG.pxInt(150)
self.resize(*CONFIG.preferencesWinSize)
+ self.minWidth = CONFIG.pxInt(200)
+
+ # SideBar
+ self.optSideBar = NPagedSideBar(self)
+ self.optSideBar.setMinimumWidth(mPx)
+ self.optSideBar.setMaximumWidth(mPx)
+ self.optSideBar.setLabelColor(SHARED.theme.helpText)
+ self.optSideBar.addLabel(self.tr("Settings"))
+
+ # Form
+ self.mainForm = NScrollableForm(self)
+ self.mainForm.setHelpTextStyle(SHARED.theme.helpText)
+
+ # Buttons
+ self.buttonBox = QDialogButtonBox(
+ QDialogButtonBox.Apply | QDialogButtonBox.Save | QDialogButtonBox.Close
+ )
+ self.buttonBox.clicked.connect(self._dialogButtonClicked)
+
+ # Assemble
+ self.mainBox = QHBoxLayout()
+ self.mainBox.addWidget(self.optSideBar)
+ self.mainBox.addWidget(self.mainForm)
+ self.mainBox.setContentsMargins(0, 0, 0, 0)
+
+ self.outerBox = QVBoxLayout()
+ self.outerBox.addLayout(self.mainBox)
+ self.outerBox.addWidget(self.buttonBox)
+ self.outerBox.setSpacing(CONFIG.pxInt(12))
+
+ self.setLayout(self.outerBox)
+ self.buildForm()
+
# Settings
self._updateTheme = False
self._updateSyntax = False
@@ -90,6 +105,16 @@ class GuiPreferences(NPagedDialog):
logger.debug("Delete: GuiPreferences")
return
+ def buildForm(self) -> None:
+ """"""
+ title = self.tr("Appearance")
+ self.optSideBar.addButton(title, self.NAV_APPEARANCE)
+ self.mainForm.addGroupLabel(title, self.NAV_APPEARANCE)
+ self._buildAppearance()
+
+ self.mainForm.finalise()
+ return
+
##
# Events
##
@@ -106,22 +131,34 @@ class GuiPreferences(NPagedDialog):
# Private Slots
##
+ @pyqtSlot("QAbstractButton*")
+ def _dialogButtonClicked(self, button: QAbstractButton) -> None:
+ """Handle button clicks from the dialog button box."""
+ role = self.buttonBox.buttonRole(button)
+ if role == QDialogButtonBox.ApplyRole:
+ pass
+ elif role == QDialogButtonBox.AcceptRole:
+ self.close()
+ elif role == QDialogButtonBox.RejectRole:
+ self.close()
+ return
+
@pyqtSlot()
def _doSave(self) -> None:
"""Trigger save functions in the tabs and emit ready signal."""
- self.tabGeneral.saveValues()
- self.tabProjects.saveValues()
- self.tabDocs.saveValues()
- self.tabEditor.saveValues()
- self.tabSyntax.saveValues()
- self.tabAuto.saveValues()
- self.tabQuote.saveValues()
+ # self.tabGeneral.saveValues()
+ # self.tabProjects.saveValues()
+ # self.tabDocs.saveValues()
+ # self.tabEditor.saveValues()
+ # self.tabSyntax.saveValues()
+ # self.tabAuto.saveValues()
+ # self.tabQuote.saveValues()
- CONFIG.saveConfig()
- self.newPreferencesReady.emit(
- self._needsRestart, self._refreshTree, self._updateTheme, self._updateSyntax
- )
- qApp.processEvents()
+ # CONFIG.saveConfig()
+ # self.newPreferencesReady.emit(
+ # self._needsRestart, self._refreshTree, self._updateTheme, self._updateSyntax
+ # )
+ # qApp.processEvents()
self.close()
return
@@ -135,6 +172,99 @@ class GuiPreferences(NPagedDialog):
CONFIG.setPreferencesWinSize(self.width(), self.height())
return
+ def _buildAppearance(self) -> None:
+ """Build the appearance section."""
+ # Select Locale
+ self.guiLocale = QComboBox(self)
+ self.guiLocale.setMinimumWidth(self.minWidth)
+ theLangs = CONFIG.listLanguages(CONFIG.LANG_NW)
+ for lang, langName in theLangs:
+ self.guiLocale.addItem(langName, lang)
+ langIdx = self.guiLocale.findData(CONFIG.guiLocale)
+ if langIdx < 0:
+ langIdx = self.guiLocale.findData("en_GB")
+ if langIdx != -1:
+ self.guiLocale.setCurrentIndex(langIdx)
+
+ self.mainForm.addRow(
+ self.tr("Main GUI language"),
+ self.guiLocale,
+ self.tr("Requires restart to take effect.")
+ )
+
+ # Select Theme
+ self.guiTheme = QComboBox(self)
+ self.guiTheme.setMinimumWidth(self.minWidth)
+ self.theThemes = SHARED.theme.listThemes()
+ for themeDir, themeName in self.theThemes:
+ self.guiTheme.addItem(themeName, themeDir)
+ themeIdx = self.guiTheme.findData(CONFIG.guiTheme)
+ if themeIdx != -1:
+ self.guiTheme.setCurrentIndex(themeIdx)
+
+ self.mainForm.addRow(
+ self.tr("Main GUI theme"),
+ self.guiTheme,
+ self.tr("General colour theme and icons.")
+ )
+
+ # Editor Theme
+ self.guiSyntax = QComboBox(self)
+ self.guiSyntax.setMinimumWidth(self.minWidth)
+ self.theSyntaxes = SHARED.theme.listSyntax()
+ for syntaxFile, syntaxName in self.theSyntaxes:
+ self.guiSyntax.addItem(syntaxName, syntaxFile)
+ syntaxIdx = self.guiSyntax.findData(CONFIG.guiSyntax)
+ if syntaxIdx != -1:
+ self.guiSyntax.setCurrentIndex(syntaxIdx)
+
+ self.mainForm.addRow(
+ self.tr("Editor theme"),
+ self.guiSyntax,
+ self.tr("Colour theme for the editor and viewer.")
+ )
+
+ # Font Family
+ self.guiFont = QLineEdit(self)
+ self.guiFont.setReadOnly(True)
+ self.guiFont.setFixedWidth(CONFIG.pxInt(162))
+ self.guiFont.setText(CONFIG.guiFont)
+ self.fontButton = QPushButton("...", self)
+ self.fontButton.setMaximumWidth(int(2.5*SHARED.theme.getTextWidth("...")))
+ # self.fontButton.clicked.connect(self._selectFont)
+ self.mainForm.addRow(
+ self.tr("Font family"),
+ self.guiFont,
+ self.tr("Requires restart to take effect."),
+ button=self.fontButton
+ )
+
+ # Font Size
+ self.guiFontSize = QSpinBox(self)
+ self.guiFontSize.setMinimum(8)
+ self.guiFontSize.setMaximum(60)
+ self.guiFontSize.setSingleStep(1)
+ self.guiFontSize.setValue(CONFIG.guiFontSize)
+ self.mainForm.addRow(
+ self.tr("Font size"),
+ self.guiFontSize,
+ self.tr("Requires restart to take effect."),
+ unit=self.tr("pt")
+ )
+
+ return
+ @pyqtSlot()
+ def _selectFont(self) -> None:
+ """Open the QFontDialog and set a font for the font style."""
+ currFont = QFont()
+ currFont.setFamily(CONFIG.guiFont)
+ currFont.setPointSize(CONFIG.guiFontSize)
+ theFont, theStatus = QFontDialog.getFont(currFont, self)
+ if theStatus:
+ self.guiFont.setText(theFont.family())
+ self.guiFontSize.setValue(theFont.pointSize())
+ return
+
# END Class GuiPreferences
diff --git a/novelwriter/extensions/configlayout.py b/novelwriter/extensions/configlayout.py
index 687dc715..3a541559 100644
--- a/novelwriter/extensions/configlayout.py
+++ b/novelwriter/extensions/configlayout.py
@@ -26,7 +26,7 @@ from __future__ import annotations
from PyQt5.QtGui import QColor, QPalette
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
- QAbstractButton, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QSizePolicy,
+ QAbstractButton, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QScrollArea, QSizePolicy,
QVBoxLayout, QWidget
)
@@ -37,6 +37,99 @@ RIGHT_TOP = Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop
LEFT_TOP = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop
+class NScrollableForm(QScrollArea):
+
+ def __init__(self, parent: QWidget) -> None:
+ super().__init__(parent=parent)
+ self._helpCol = QColor(0, 0, 0)
+ self._fontScale = FONT_SCALE
+ self._sections: dict[int, QLabel] = {}
+ self._editable: dict[str, NHelpLabel] = {}
+
+ self._layout = QVBoxLayout()
+ self._layout.setSpacing(CONFIG.pxInt(12))
+
+ self._widget = QWidget(self)
+ self._widget.setLayout(self._layout)
+
+ self.setWidget(self._widget)
+ self.setWidgetResizable(True)
+ self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
+ self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
+
+ return
+
+ def setHelpTextStyle(self, color: QColor | list | tuple, scale: float = FONT_SCALE) -> None:
+ """Set the text color for the help text."""
+ self._helpCol = color if isinstance(color, QColor) else QColor(*color)
+ self._fontScale = scale
+ return
+
+ def setHelpText(self, key: str, text: str) -> None:
+ """Set the text for the help label."""
+ if qHelp := self._editable.get(key):
+ qHelp.setText(text)
+ return
+
+ def finalise(self) -> None:
+ """Finalise the layout when the form is built."""
+ self._layout.addStretch(1)
+ return
+
+ def scrollToSection(self, identifier: int, offset: int = 50) -> None:
+ """Scroll to the requested section identifier."""
+ if identifier in self._sections:
+ yPos = self._sections[identifier].pos().y() - CONFIG.pxInt(8)
+ self.verticalScrollBar().setValue(yPos)
+ return
+
+ def addGroupLabel(self, label: str, identifier: int) -> None:
+ """Add a text label to separate groups of settings."""
+ hM = CONFIG.pxInt(4)
+ qLabel = QLabel(f"{label}", self)
+ qLabel.setContentsMargins(0, hM, 0, hM)
+ self._layout.addWidget(qLabel)
+ self._sections[identifier] = qLabel
+ return
+
+ def addRow(self, label: str, widget: QWidget, helpText: str = "", unit: str | None = None,
+ button: QWidget | None = None, editable: str | None = None) -> None:
+ """Add a label and a widget as a new row of the grid."""
+ row = QHBoxLayout()
+
+ wSp = CONFIG.pxInt(8)
+ qLabel = QLabel(label, self)
+ qLabel.setIndent(wSp)
+ qLabel.setBuddy(widget)
+
+ if helpText:
+ qHelp = NHelpLabel(str(helpText), self._helpCol, self._fontScale)
+ qHelp.setIndent(wSp)
+ labelBox = QVBoxLayout()
+ labelBox.addWidget(qLabel)
+ labelBox.addWidget(qHelp)
+ labelBox.setSpacing(0)
+ labelBox.addStretch(1)
+ row.addLayout(labelBox)
+ if editable:
+ self._editable[editable] = qHelp
+ else:
+ row.addWidget(qLabel)
+
+ row.addWidget(widget)
+
+ if isinstance(unit, str):
+ row.addWidget(QLabel(unit, self))
+ elif isinstance(button, QAbstractButton):
+ row.addWidget(button)
+
+ self._layout.addLayout(row)
+
+ return
+
+# END Class NScrollableForm
+
+
class NConfigLayout(QGridLayout):
def __init__(self) -> None:
@@ -58,11 +151,10 @@ class NConfigLayout(QGridLayout):
# Getters and Setters
##
- def setHelpTextStyle(self, color: QColor | list | tuple,
- fontScale: float = FONT_SCALE) -> None:
+ def setHelpTextStyle(self, color: QColor | list | tuple, scale: float = FONT_SCALE) -> None:
"""Set the text color for the help text."""
self._helpCol = color if isinstance(color, QColor) else QColor(*color)
- self._fontScale = fontScale
+ self._fontScale = scale
return
def setHelpText(self, row: int, text: str) -> None:
From 978a8145a8447ce9167bf33eafa6cb0bb7c59734 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 8 Jan 2024 19:13:58 +0100
Subject: [PATCH 02/13] Drop the setting to change word count interval
---
novelwriter/config.py | 6 +-----
novelwriter/gui/doceditor.py | 9 ++-------
2 files changed, 3 insertions(+), 12 deletions(-)
diff --git a/novelwriter/config.py b/novelwriter/config.py
index c8ff11ae..ad39b218 100644
--- a/novelwriter/config.py
+++ b/novelwriter/config.py
@@ -150,9 +150,6 @@ class Config:
self.autoScrollPos = 30 # Start point for typewriter-like scrolling
self.scrollPastEnd = True # Scroll past end of document, and centre cursor
- self.wordCountTimer = 5.0 # Interval for word count update in seconds
- self.incNotesWCount = True # The status bar word count includes notes
-
self.highlightQuotes = True # Highlight text in quotes
self.allowOpenSQuote = False # Allow open-ended single quotes
self.allowOpenDQuote = True # Allow open-ended double quotes
@@ -160,6 +157,7 @@ class Config:
self.stopWhenIdle = True # Stop the status bar clock when the user is idle
self.userIdleTime = 300 # Time of inactivity to consider user idle
+ self.incNotesWCount = True # The status bar word count includes notes
# User-Selected Symbol Settings
self.fmtApostrophe = nwUnicode.U_RSQUO
@@ -591,7 +589,6 @@ class Config:
self.showTabsNSpaces = conf.rdBool(sec, "showtabsnspaces", self.showTabsNSpaces)
self.showLineEndings = conf.rdBool(sec, "showlineendings", self.showLineEndings)
self.showMultiSpaces = conf.rdBool(sec, "showmultispaces", self.showMultiSpaces)
- self.wordCountTimer = conf.rdFlt(sec, "wordcounttimer", self.wordCountTimer)
self.incNotesWCount = conf.rdBool(sec, "incnoteswcount", self.incNotesWCount)
self.showFullPath = conf.rdBool(sec, "showfullpath", self.showFullPath)
self.highlightQuotes = conf.rdBool(sec, "highlightquotes", self.highlightQuotes)
@@ -698,7 +695,6 @@ class Config:
"showtabsnspaces": str(self.showTabsNSpaces),
"showlineendings": str(self.showLineEndings),
"showmultispaces": str(self.showMultiSpaces),
- "wordcounttimer": str(self.wordCountTimer),
"incnoteswcount": str(self.incNotesWCount),
"showfullpath": str(self.showFullPath),
"highlightquotes": str(self.highlightQuotes),
diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py
index 7cecb546..d7bedf8a 100644
--- a/novelwriter/gui/doceditor.py
+++ b/novelwriter/gui/doceditor.py
@@ -187,13 +187,12 @@ class GuiDocEditor(QPlainTextEdit):
# Set Up Document Word Counter
self.wcTimerDoc = QTimer()
self.wcTimerDoc.timeout.connect(self._runDocCounter)
+ self.wcTimerDoc.setInterval(5000)
self.wCounterDoc = BackgroundWordCounter(self)
self.wCounterDoc.setAutoDelete(False)
self.wCounterDoc.signals.countsReady.connect(self._updateDocCounts)
- self.wcInterval = CONFIG.wordCountTimer
-
# Set Up Selection Word Counter
self.wcTimerSel = QTimer()
self.wcTimerSel.timeout.connect(self._runSelCounter)
@@ -359,10 +358,6 @@ class GuiDocEditor(QPlainTextEdit):
# Refresh the tab stops
self.setTabStopDistance(CONFIG.getTabWidth())
- # Configure word count timer
- self.wcInterval = CONFIG.wordCountTimer
- self.wcTimerDoc.setInterval(int(self.wcInterval*1000))
-
# If we have a document open, we should reload it in case the
# font changed, otherwise we just clear the editor entirely,
# which makes it read only.
@@ -1190,7 +1185,7 @@ class GuiDocEditor(QPlainTextEdit):
logger.debug("Word counter is busy")
return
- if time() - self._lastEdit < 5.0 * self.wcInterval:
+ if time() - self._lastEdit < 25.0:
logger.debug("Running word counter")
SHARED.runInThreadPool(self.wCounterDoc)
From 1ed3cea4141c88333a8acc97c784cdf4ccb278de Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 8 Jan 2024 19:14:36 +0100
Subject: [PATCH 03/13] Simplify the custom switch widget
---
novelwriter/extensions/switch.py | 88 ++++++++++----------------------
1 file changed, 28 insertions(+), 60 deletions(-)
diff --git a/novelwriter/extensions/switch.py b/novelwriter/extensions/switch.py
index cb7177ce..54db8914 100644
--- a/novelwriter/extensions/switch.py
+++ b/novelwriter/extensions/switch.py
@@ -24,31 +24,22 @@ along with this program. If not, see .
from __future__ import annotations
from PyQt5.QtGui import QMouseEvent, QPainter, QPaintEvent, QResizeEvent
-from PyQt5.QtCore import QEvent, QPropertyAnimation, QRectF, Qt, pyqtProperty
+from PyQt5.QtCore import QEvent, QPropertyAnimation, Qt, pyqtProperty
from PyQt5.QtWidgets import QAbstractButton, QSizePolicy, QWidget
from novelwriter import CONFIG
-from novelwriter.constants import nwUnicode
class NSwitch(QAbstractButton):
- def __init__(self, parent: QWidget | None = None,
- width: int | None = None, height: int | None = None) -> None:
+ __slots__ = ("_xW", "_xH", "_xR", "_rB", "_rH", "_rR", "_offset")
+
+ def __init__(self, parent: QWidget | None = None, width: int = 0, height: int = 0) -> None:
super().__init__(parent=parent)
- if width is None:
- self._xW = CONFIG.pxInt(40)
- else:
- self._xW = width
-
- if height is None:
- self._xH = CONFIG.pxInt(20)
- else:
- self._xH = height
-
+ self._xW = width or CONFIG.pxInt(40)
+ self._xH = height or CONFIG.pxInt(20)
self._xR = int(self._xH*0.5)
- self._xT = int(self._xH*0.6)
self._rB = int(CONFIG.guiScale*2)
self._rH = self._xH - 2*self._rB
self._rR = self._xR - self._rB
@@ -82,10 +73,7 @@ class NSwitch(QAbstractButton):
def setChecked(self, checked: bool) -> None:
"""Overload setChecked to also alter the offset."""
super().setChecked(checked)
- if checked:
- self._offset = self._xW - self._xR
- else:
- self._offset = self._xR
+ self._offset = (self._xW - self._xR) if checked else self._xR
return
##
@@ -95,53 +83,36 @@ class NSwitch(QAbstractButton):
def resizeEvent(self, event: QResizeEvent) -> None:
"""Overload resize to ensure correct offset."""
super().resizeEvent(event)
- if self.isChecked():
- self._offset = self._xW - self._xR
- else:
- self._offset = self._xR
+ self._offset = (self._xW - self._xR) if self.isChecked() else self._xR
return
def paintEvent(self, event: QPaintEvent) -> None:
"""Drawing the switch itself."""
- qPaint = QPainter(self)
- qPaint.setRenderHint(QPainter.Antialiasing, True)
- qPaint.setPen(Qt.NoPen)
+ painter = QPainter(self)
+ painter.setRenderHint(QPainter.Antialiasing, True)
+ painter.setPen(Qt.NoPen)
- qPalette = self.palette()
+ palette = self.palette()
if self.isChecked():
- trackBrush = qPalette.highlight()
- thumbBrush = qPalette.highlightedText()
- textColor = qPalette.highlight().color()
- thumbText = nwUnicode.U_CHECK
+ trackBrush = palette.highlight()
+ thumbBrush = palette.highlightedText()
else:
- trackBrush = qPalette.dark()
- thumbBrush = qPalette.light()
- textColor = qPalette.dark().color()
- thumbText = nwUnicode.U_CROSS
+ trackBrush = palette.dark()
+ thumbBrush = palette.light()
if self.isEnabled():
trackOpacity = 1.0
else:
trackOpacity = 0.6
- trackBrush = qPalette.shadow()
- thumbBrush = qPalette.mid()
- textColor = qPalette.shadow().color()
+ trackBrush = palette.shadow()
+ thumbBrush = palette.mid()
- qPaint.setBrush(trackBrush)
- qPaint.setOpacity(trackOpacity)
- qPaint.drawRoundedRect(0, 0, self._xW, self._xH, self._xR, self._xR)
+ painter.setBrush(trackBrush)
+ painter.setOpacity(trackOpacity)
+ painter.drawRoundedRect(0, 0, self._xW, self._xH, self._xR, self._xR)
- qPaint.setBrush(thumbBrush)
- qPaint.drawEllipse(self._offset - self._rR, self._rB, self._rH, self._rH)
-
- font = qPaint.font()
- font.setPixelSize(self._xT)
- qPaint.setPen(textColor)
- qPaint.setFont(font)
- qPaint.drawText(
- QRectF(self._offset - self._rR, self._rB, self._rH, self._rH),
- Qt.AlignCenter, thumbText
- )
+ painter.setBrush(thumbBrush)
+ painter.drawEllipse(self._offset - self._rR, self._rB, self._rH, self._rH)
return
@@ -149,14 +120,11 @@ class NSwitch(QAbstractButton):
"""Animate the switch on mouse release."""
super().mouseReleaseEvent(event)
if event.button() == Qt.LeftButton:
- doAnim = QPropertyAnimation(self, b"offset", self)
- doAnim.setDuration(120)
- doAnim.setStartValue(self._offset)
- if self.isChecked():
- doAnim.setEndValue(self._xW - self._xR)
- else:
- doAnim.setEndValue(self._xR)
- doAnim.start()
+ anim = QPropertyAnimation(self, b"offset", self)
+ anim.setDuration(120)
+ anim.setStartValue(self._offset)
+ anim.setEndValue((self._xW - self._xR) if self.isChecked() else self._xR)
+ anim.start()
return
def enterEvent(self, event: QEvent) -> None:
From 1fbd50334ddc5c43da48b7cd7ffdc17e7f356fb1 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 8 Jan 2024 20:49:40 +0100
Subject: [PATCH 04/13] Complete the rewrite of the Preferences dialog
---
novelwriter/dialogs/preferences.py | 1129 +++++++++---------------
novelwriter/extensions/configlayout.py | 2 +-
novelwriter/extensions/pagedsidebar.py | 6 +-
3 files changed, 433 insertions(+), 704 deletions(-)
diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py
index 2e4617a9..5e532ffa 100644
--- a/novelwriter/dialogs/preferences.py
+++ b/novelwriter/dialogs/preferences.py
@@ -3,7 +3,8 @@ novelWriter – GUI Preferences
=============================
File History:
-Created: 2019-06-10 [0.1.5] GuiPreferences
+Created: 2019-06-10 [0.1.5] GuiPreferences
+Rewritten: 2024-01-08 [2.3b1] GuiPreferences
This file is a part of novelWriter
Copyright 2018–2024, Veronica Berglyd Olsen
@@ -28,16 +29,16 @@ import logging
from PyQt5.QtGui import QCloseEvent, QFont
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import (
- QAbstractButton, QDialog, QHBoxLayout, QLabel, QScrollArea, QVBoxLayout, QWidget, QComboBox, QSpinBox, QPushButton, QDialogButtonBox,
- QLineEdit, QFileDialog, QFontDialog, QDoubleSpinBox, qApp
+ QAbstractButton, QDialog, QHBoxLayout, QVBoxLayout, QWidget, QComboBox,
+ QSpinBox, QPushButton, QDialogButtonBox, QLineEdit, QFileDialog,
+ QFontDialog, QDoubleSpinBox, qApp
)
from novelwriter import CONFIG, SHARED
from novelwriter.dialogs.quotes import GuiQuoteSelect
-from novelwriter.extensions.pagedsidebar import NPagedSideBar
from novelwriter.extensions.switch import NSwitch
-from novelwriter.extensions.pageddialog import NPagedDialog
-from novelwriter.extensions.configlayout import NConfigLayout, NScrollableForm
+from novelwriter.extensions.configlayout import NScrollableForm
+from novelwriter.extensions.pagedsidebar import NPagedSideBar
logger = logging.getLogger(__name__)
@@ -54,18 +55,14 @@ class GuiPreferences(QDialog):
logger.debug("Create: GuiPreferences")
self.setObjectName("GuiPreferences")
self.setWindowTitle(CONFIG.appName)
-
- mPx = CONFIG.pxInt(150)
+ self.setMinimumSize(CONFIG.pxInt(600), CONFIG.pxInt(500))
self.resize(*CONFIG.preferencesWinSize)
- self.minWidth = CONFIG.pxInt(200)
-
# SideBar
- self.optSideBar = NPagedSideBar(self)
- self.optSideBar.setMinimumWidth(mPx)
- self.optSideBar.setMaximumWidth(mPx)
- self.optSideBar.setLabelColor(SHARED.theme.helpText)
- self.optSideBar.addLabel(self.tr("Settings"))
+ self.sidebar = NPagedSideBar(self)
+ self.sidebar.setLabelColor(SHARED.theme.helpText)
+ self.sidebar.addLabel(self.tr("Preferences"))
+ self.sidebar.buttonClicked.connect(self._sidebarClicked)
# Form
self.mainForm = NScrollableForm(self)
@@ -79,24 +76,19 @@ class GuiPreferences(QDialog):
# Assemble
self.mainBox = QHBoxLayout()
- self.mainBox.addWidget(self.optSideBar)
+ self.mainBox.addWidget(self.sidebar)
self.mainBox.addWidget(self.mainForm)
self.mainBox.setContentsMargins(0, 0, 0, 0)
self.outerBox = QVBoxLayout()
self.outerBox.addLayout(self.mainBox)
self.outerBox.addWidget(self.buttonBox)
- self.outerBox.setSpacing(CONFIG.pxInt(12))
+ self.outerBox.setSpacing(CONFIG.pxInt(8))
self.setLayout(self.outerBox)
+ self.setSizeGripEnabled(True)
self.buildForm()
- # Settings
- self._updateTheme = False
- self._updateSyntax = False
- self._needsRestart = False
- self._refreshTree = False
-
logger.debug("Ready: GuiPreferences")
return
@@ -106,284 +98,76 @@ class GuiPreferences(QDialog):
return
def buildForm(self) -> None:
- """"""
- title = self.tr("Appearance")
- self.optSideBar.addButton(title, self.NAV_APPEARANCE)
- self.mainForm.addGroupLabel(title, self.NAV_APPEARANCE)
- self._buildAppearance()
-
- self.mainForm.finalise()
- return
-
- ##
- # Events
- ##
-
- def closeEvent(self, event: QCloseEvent) -> None:
- """Capture the close event and perform cleanup."""
- logger.debug("Close: GuiPreferences")
- self._saveWindowSize()
- event.accept()
- self.deleteLater()
- return
-
- ##
- # Private Slots
- ##
-
- @pyqtSlot("QAbstractButton*")
- def _dialogButtonClicked(self, button: QAbstractButton) -> None:
- """Handle button clicks from the dialog button box."""
- role = self.buttonBox.buttonRole(button)
- if role == QDialogButtonBox.ApplyRole:
- pass
- elif role == QDialogButtonBox.AcceptRole:
- self.close()
- elif role == QDialogButtonBox.RejectRole:
- self.close()
- return
-
- @pyqtSlot()
- def _doSave(self) -> None:
- """Trigger save functions in the tabs and emit ready signal."""
- # self.tabGeneral.saveValues()
- # self.tabProjects.saveValues()
- # self.tabDocs.saveValues()
- # self.tabEditor.saveValues()
- # self.tabSyntax.saveValues()
- # self.tabAuto.saveValues()
- # self.tabQuote.saveValues()
-
- # CONFIG.saveConfig()
- # self.newPreferencesReady.emit(
- # self._needsRestart, self._refreshTree, self._updateTheme, self._updateSyntax
- # )
- # qApp.processEvents()
- self.close()
-
- return
-
- ##
- # Internal Functions
- ##
-
- def _saveWindowSize(self) -> None:
- """Save the dialog window size."""
- CONFIG.setPreferencesWinSize(self.width(), self.height())
- return
-
- def _buildAppearance(self) -> None:
- """Build the appearance section."""
- # Select Locale
- self.guiLocale = QComboBox(self)
- self.guiLocale.setMinimumWidth(self.minWidth)
- theLangs = CONFIG.listLanguages(CONFIG.LANG_NW)
- for lang, langName in theLangs:
- self.guiLocale.addItem(langName, lang)
- langIdx = self.guiLocale.findData(CONFIG.guiLocale)
- if langIdx < 0:
- langIdx = self.guiLocale.findData("en_GB")
- if langIdx != -1:
- self.guiLocale.setCurrentIndex(langIdx)
-
- self.mainForm.addRow(
- self.tr("Main GUI language"),
- self.guiLocale,
- self.tr("Requires restart to take effect.")
- )
-
- # Select Theme
- self.guiTheme = QComboBox(self)
- self.guiTheme.setMinimumWidth(self.minWidth)
- self.theThemes = SHARED.theme.listThemes()
- for themeDir, themeName in self.theThemes:
- self.guiTheme.addItem(themeName, themeDir)
- themeIdx = self.guiTheme.findData(CONFIG.guiTheme)
- if themeIdx != -1:
- self.guiTheme.setCurrentIndex(themeIdx)
-
- self.mainForm.addRow(
- self.tr("Main GUI theme"),
- self.guiTheme,
- self.tr("General colour theme and icons.")
- )
-
- # Editor Theme
- self.guiSyntax = QComboBox(self)
- self.guiSyntax.setMinimumWidth(self.minWidth)
- self.theSyntaxes = SHARED.theme.listSyntax()
- for syntaxFile, syntaxName in self.theSyntaxes:
- self.guiSyntax.addItem(syntaxName, syntaxFile)
- syntaxIdx = self.guiSyntax.findData(CONFIG.guiSyntax)
- if syntaxIdx != -1:
- self.guiSyntax.setCurrentIndex(syntaxIdx)
-
- self.mainForm.addRow(
- self.tr("Editor theme"),
- self.guiSyntax,
- self.tr("Colour theme for the editor and viewer.")
- )
-
- # Font Family
- self.guiFont = QLineEdit(self)
- self.guiFont.setReadOnly(True)
- self.guiFont.setFixedWidth(CONFIG.pxInt(162))
- self.guiFont.setText(CONFIG.guiFont)
- self.fontButton = QPushButton("...", self)
- self.fontButton.setMaximumWidth(int(2.5*SHARED.theme.getTextWidth("...")))
- # self.fontButton.clicked.connect(self._selectFont)
- self.mainForm.addRow(
- self.tr("Font family"),
- self.guiFont,
- self.tr("Requires restart to take effect."),
- button=self.fontButton
- )
-
- # Font Size
- self.guiFontSize = QSpinBox(self)
- self.guiFontSize.setMinimum(8)
- self.guiFontSize.setMaximum(60)
- self.guiFontSize.setSingleStep(1)
- self.guiFontSize.setValue(CONFIG.guiFontSize)
- self.mainForm.addRow(
- self.tr("Font size"),
- self.guiFontSize,
- self.tr("Requires restart to take effect."),
- unit=self.tr("pt")
- )
-
- return
- @pyqtSlot()
- def _selectFont(self) -> None:
- """Open the QFontDialog and set a font for the font style."""
- currFont = QFont()
- currFont.setFamily(CONFIG.guiFont)
- currFont.setPointSize(CONFIG.guiFontSize)
- theFont, theStatus = QFontDialog.getFont(currFont, self)
- if theStatus:
- self.guiFont.setText(theFont.family())
- self.guiFontSize.setValue(theFont.pointSize())
- return
-
-# END Class GuiPreferences
-
-
-class GuiPreferencesGeneral(QWidget):
-
- def __init__(self, prefsGui: GuiPreferences) -> None:
- super().__init__(parent=prefsGui)
-
- self.prefsGui = prefsGui
-
- # The Form
- self.mainForm = NConfigLayout()
- self.mainForm.setHelpTextStyle(SHARED.theme.helpText)
- self.setLayout(self.mainForm)
-
- # Look and Feel
- # =============
- self.mainForm.addGroupLabel(self.tr("Look and Feel"))
+ """Build the settings form."""
+ section = 0
minWidth = CONFIG.pxInt(200)
- # Select Locale
+ # Appearance
+ # ==========
+
+ title = self.tr("Appearance")
+ section += 1
+ self.sidebar.addButton(title, section)
+ self.mainForm.addGroupLabel(title, section)
+
+ # Display Language
self.guiLocale = QComboBox(self)
self.guiLocale.setMinimumWidth(minWidth)
- theLangs = CONFIG.listLanguages(CONFIG.LANG_NW)
- for lang, langName in theLangs:
- self.guiLocale.addItem(langName, lang)
- langIdx = self.guiLocale.findData(CONFIG.guiLocale)
- if langIdx < 0:
- langIdx = self.guiLocale.findData("en_GB")
- if langIdx != -1:
- self.guiLocale.setCurrentIndex(langIdx)
+ for lang, name in CONFIG.listLanguages(CONFIG.LANG_NW):
+ self.guiLocale.addItem(name, lang)
+ if (idx := self.guiLocale.findData(CONFIG.guiLocale)) != -1:
+ self.guiLocale.setCurrentIndex(idx)
self.mainForm.addRow(
- self.tr("Main GUI language"),
+ self.tr("Display language"),
self.guiLocale,
self.tr("Requires restart to take effect.")
)
- # Select Theme
+ # Colour Theme
self.guiTheme = QComboBox(self)
self.guiTheme.setMinimumWidth(minWidth)
- self.theThemes = SHARED.theme.listThemes()
- for themeDir, themeName in self.theThemes:
- self.guiTheme.addItem(themeName, themeDir)
- themeIdx = self.guiTheme.findData(CONFIG.guiTheme)
- if themeIdx != -1:
- self.guiTheme.setCurrentIndex(themeIdx)
+ for theme, name in SHARED.theme.listThemes():
+ self.guiTheme.addItem(name, theme)
+ if (idx := self.guiTheme.findData(CONFIG.guiTheme)) != -1:
+ self.guiTheme.setCurrentIndex(idx)
self.mainForm.addRow(
- self.tr("Main GUI theme"),
+ self.tr("Colour theme"),
self.guiTheme,
self.tr("General colour theme and icons.")
)
- # Editor Theme
- self.guiSyntax = QComboBox(self)
- self.guiSyntax.setMinimumWidth(CONFIG.pxInt(200))
- self.theSyntaxes = SHARED.theme.listSyntax()
- for syntaxFile, syntaxName in self.theSyntaxes:
- self.guiSyntax.addItem(syntaxName, syntaxFile)
- syntaxIdx = self.guiSyntax.findData(CONFIG.guiSyntax)
- if syntaxIdx != -1:
- self.guiSyntax.setCurrentIndex(syntaxIdx)
-
- self.mainForm.addRow(
- self.tr("Editor theme"),
- self.guiSyntax,
- self.tr("Colour theme for the editor and viewer.")
- )
-
- # Font Family
+ # Application Font Family
self.guiFont = QLineEdit(self)
self.guiFont.setReadOnly(True)
self.guiFont.setFixedWidth(CONFIG.pxInt(162))
self.guiFont.setText(CONFIG.guiFont)
- self.fontButton = QPushButton("...", self)
- self.fontButton.setMaximumWidth(int(2.5*SHARED.theme.getTextWidth("...")))
- self.fontButton.clicked.connect(self._selectFont)
+ self.guiFontButton = QPushButton("...", self)
+ self.guiFontButton.setMaximumWidth(int(2.5*SHARED.theme.getTextWidth("...")))
+ self.guiFontButton.clicked.connect(self._selectGuiFont)
self.mainForm.addRow(
- self.tr("Font family"),
+ self.tr("Application font family"),
self.guiFont,
self.tr("Requires restart to take effect."),
- button=self.fontButton
+ button=self.guiFontButton
)
- # Font Size
+ # Application Font Size
self.guiFontSize = QSpinBox(self)
self.guiFontSize.setMinimum(8)
self.guiFontSize.setMaximum(60)
self.guiFontSize.setSingleStep(1)
self.guiFontSize.setValue(CONFIG.guiFontSize)
self.mainForm.addRow(
- self.tr("Font size"),
+ self.tr("Application font size"),
self.guiFontSize,
self.tr("Requires restart to take effect."),
unit=self.tr("pt")
)
- # GUI Settings
- # ============
- self.mainForm.addGroupLabel(self.tr("GUI Settings"))
-
- self.emphLabels = NSwitch()
- self.emphLabels.setChecked(CONFIG.emphLabels)
- self.mainForm.addRow(
- self.tr("Emphasise partition and chapter labels"),
- self.emphLabels,
- self.tr("Makes them stand out in the project tree."),
- )
-
- self.showFullPath = NSwitch()
- self.showFullPath.setChecked(CONFIG.showFullPath)
- self.mainForm.addRow(
- self.tr("Show full path in document header"),
- self.showFullPath,
- self.tr("Add the parent folder names to the header.")
- )
-
- self.hideVScroll = NSwitch()
+ # Vertical Scrollbars
+ self.hideVScroll = NSwitch(self)
self.hideVScroll.setChecked(CONFIG.hideVScroll)
self.mainForm.addRow(
self.tr("Hide vertical scroll bars in main windows"),
@@ -391,7 +175,8 @@ class GuiPreferencesGeneral(QWidget):
self.tr("Scrolling available with mouse wheel and keys only.")
)
- self.hideHScroll = NSwitch()
+ # Horizontal Scrollbars
+ self.hideHScroll = NSwitch(self)
self.hideHScroll.setChecked(CONFIG.hideHScroll)
self.mainForm.addRow(
self.tr("Hide horizontal scroll bars in main windows"),
@@ -399,69 +184,89 @@ class GuiPreferencesGeneral(QWidget):
self.tr("Scrolling available with mouse wheel and keys only.")
)
- return
-
- def saveValues(self) -> None:
- """Save the values set for this tab."""
- guiLocale = self.guiLocale.currentData()
- guiTheme = self.guiTheme.currentData()
- guiSyntax = self.guiSyntax.currentData()
- guiFont = self.guiFont.text()
- guiFontSize = self.guiFontSize.value()
- emphLabels = self.emphLabels.isChecked()
-
- # Update Flags
- self.prefsGui._updateTheme |= CONFIG.guiTheme != guiTheme
- self.prefsGui._updateSyntax |= CONFIG.guiSyntax != guiSyntax
- self.prefsGui._needsRestart |= CONFIG.guiLocale != guiLocale
- self.prefsGui._needsRestart |= CONFIG.guiFont != guiFont
- self.prefsGui._needsRestart |= CONFIG.guiFontSize != guiFontSize
- self.prefsGui._refreshTree |= CONFIG.emphLabels != emphLabels
-
- CONFIG.guiLocale = guiLocale
- CONFIG.guiTheme = guiTheme
- CONFIG.guiSyntax = guiSyntax
- CONFIG.guiFont = guiFont
- CONFIG.guiFontSize = guiFontSize
- CONFIG.emphLabels = emphLabels
- CONFIG.showFullPath = self.showFullPath.isChecked()
- CONFIG.hideVScroll = self.hideVScroll.isChecked()
- CONFIG.hideHScroll = self.hideHScroll.isChecked()
-
- return
-
- ##
- # Private Slots
- ##
-
- @pyqtSlot()
- def _selectFont(self) -> None:
- """Open the QFontDialog and set a font for the font style."""
- currFont = QFont()
- currFont.setFamily(CONFIG.guiFont)
- currFont.setPointSize(CONFIG.guiFontSize)
- theFont, theStatus = QFontDialog.getFont(currFont, self)
- if theStatus:
- self.guiFont.setText(theFont.family())
- self.guiFontSize.setValue(theFont.pointSize())
- return
-
-# END Class GuiPreferencesGeneral
-
-
-class GuiPreferencesProjects(QWidget):
-
- def __init__(self, prefsGui: GuiPreferences) -> None:
- super().__init__(parent=prefsGui)
-
- # The Form
- self.mainForm = NConfigLayout()
- self.mainForm.setHelpTextStyle(SHARED.theme.helpText)
- self.setLayout(self.mainForm)
-
- # Automatic Save
+ # Document Style
# ==============
- self.mainForm.addGroupLabel(self.tr("Automatic Save"))
+
+ title = self.tr("Document Style")
+ section += 1
+ self.sidebar.addButton(title, section)
+ self.mainForm.addGroupLabel(title, section)
+
+ # Document Colour Theme
+ self.guiSyntax = QComboBox(self)
+ self.guiSyntax.setMinimumWidth(CONFIG.pxInt(200))
+ for syntax, name in SHARED.theme.listSyntax():
+ self.guiSyntax.addItem(name, syntax)
+ if (idx := self.guiSyntax.findData(CONFIG.guiSyntax)) != -1:
+ self.guiSyntax.setCurrentIndex(idx)
+
+ self.mainForm.addRow(
+ self.tr("Document colour theme"),
+ self.guiSyntax,
+ self.tr("Colour theme for the editor and viewer.")
+ )
+
+ # Document Font Family
+ self.textFont = QLineEdit(self)
+ self.textFont.setReadOnly(True)
+ self.textFont.setFixedWidth(CONFIG.pxInt(162))
+ self.textFont.setText(CONFIG.textFont)
+ self.textFontButton = QPushButton("...", self)
+ self.textFontButton.setMaximumWidth(int(2.5*SHARED.theme.getTextWidth("...")))
+ self.textFontButton.clicked.connect(self._selectTextFont)
+ self.mainForm.addRow(
+ self.tr("Document font family"),
+ self.textFont,
+ self.tr("Applies to both document editor and viewer."),
+ button=self.textFontButton
+ )
+
+ # Document Font Size
+ self.textSize = QSpinBox(self)
+ self.textSize.setMinimum(8)
+ self.textSize.setMaximum(60)
+ self.textSize.setSingleStep(1)
+ self.textSize.setValue(CONFIG.textSize)
+ self.mainForm.addRow(
+ self.tr("Document font size"),
+ self.textSize,
+ self.tr("Applies to both document editor and viewer."),
+ unit=self.tr("pt")
+ )
+
+ # Emphasise Labels
+ self.emphLabels = NSwitch(self)
+ self.emphLabels.setChecked(CONFIG.emphLabels)
+ self.mainForm.addRow(
+ self.tr("Emphasise partition and chapter labels"),
+ self.emphLabels,
+ self.tr("Makes them stand out in the project tree."),
+ )
+
+ # Document Path
+ self.showFullPath = NSwitch(self)
+ self.showFullPath.setChecked(CONFIG.showFullPath)
+ self.mainForm.addRow(
+ self.tr("Show full path in document header"),
+ self.showFullPath,
+ self.tr("Add the parent folder names to the header.")
+ )
+
+ # Include Notes in Word Count
+ self.incNotesWCount = NSwitch(self)
+ self.incNotesWCount.setChecked(CONFIG.incNotesWCount)
+ self.mainForm.addRow(
+ self.tr("Include project notes in status bar word count"),
+ self.incNotesWCount
+ )
+
+ # Auto Save
+ # =========
+
+ title = self.tr("Auto Save")
+ section += 1
+ self.sidebar.addButton(title, section)
+ self.mainForm.addGroupLabel(title, section)
# Document Save Timer
self.autoSaveDoc = QSpinBox(self)
@@ -491,20 +296,25 @@ class GuiPreferencesProjects(QWidget):
# Project Backup
# ==============
- self.mainForm.addGroupLabel(self.tr("Project Backup"))
+
+ title = self.tr("Project Backup")
+ section += 1
+ self.sidebar.addButton(title, section)
+ self.mainForm.addGroupLabel(title, section)
# Backup Path
self.backupPath = CONFIG.backupPath()
- self.backupGetPath = QPushButton(self.tr("Browse"), self)
+ self.backupGetPath = QPushButton(SHARED.theme.getIcon("browse"), self.tr("Browse"), self)
self.backupGetPath.clicked.connect(self._backupFolder)
- self.backupPathRow = self.mainForm.addRow(
+ self.mainForm.addRow(
self.tr("Backup storage location"),
self.backupGetPath,
- self.tr("Path: {0}").format(self.backupPath)
+ self.tr("Path: {0}").format(self.backupPath),
+ editable="backupPath"
)
- # Run when closing
- self.backupOnClose = NSwitch()
+ # Run When Closing
+ self.backupOnClose = NSwitch(self)
self.backupOnClose.setChecked(CONFIG.backupOnClose)
self.backupOnClose.toggled.connect(self._toggledBackupOnClose)
self.mainForm.addRow(
@@ -513,9 +323,9 @@ class GuiPreferencesProjects(QWidget):
self.tr("Can be overridden for individual projects in Project Settings.")
)
- # Ask before backup
+ # Ask Before Backup
# Only enabled when "Run when closing" is checked
- self.askBeforeBackup = NSwitch()
+ self.askBeforeBackup = NSwitch(self)
self.askBeforeBackup.setChecked(CONFIG.askBeforeBackup)
self.askBeforeBackup.setEnabled(CONFIG.backupOnClose)
self.mainForm.addRow(
@@ -526,10 +336,14 @@ class GuiPreferencesProjects(QWidget):
# Session Timer
# =============
- self.mainForm.addGroupLabel(self.tr("Session Timer"))
- # Pause when idle
- self.stopWhenIdle = NSwitch()
+ title = self.tr("Session Timer")
+ section += 1
+ self.sidebar.addButton(title, section)
+ self.mainForm.addGroupLabel(title, section)
+
+ # Pause When Idle
+ self.stopWhenIdle = NSwitch(self)
self.stopWhenIdle.setChecked(CONFIG.stopWhenIdle)
self.mainForm.addRow(
self.tr("Pause the session timer when not writing"),
@@ -537,7 +351,7 @@ class GuiPreferencesProjects(QWidget):
self.tr("Also pauses when the application window does not have focus.")
)
- # Inactive time for idle
+ # Inactive Time for Idle
self.userIdleTime = QDoubleSpinBox(self)
self.userIdleTime.setMinimum(0.5)
self.userIdleTime.setMaximum(600.0)
@@ -551,98 +365,13 @@ class GuiPreferencesProjects(QWidget):
unit=self.tr("minutes")
)
- return
-
- def saveValues(self) -> None:
- """Save the values set for this tab."""
- # Automatic Save
- CONFIG.autoSaveDoc = self.autoSaveDoc.value()
- CONFIG.autoSaveProj = self.autoSaveProj.value()
-
- # Project Backup
- CONFIG.setBackupPath(self.backupPath)
- CONFIG.backupOnClose = self.backupOnClose.isChecked()
- CONFIG.askBeforeBackup = self.askBeforeBackup.isChecked()
-
- # Session Timer
- CONFIG.stopWhenIdle = self.stopWhenIdle.isChecked()
- CONFIG.userIdleTime = round(self.userIdleTime.value() * 60)
-
- return
-
- ##
- # Private Slots
- ##
-
- @pyqtSlot()
- def _backupFolder(self) -> None:
- """Open a dialog to select the backup folder."""
- currDir = self.backupPath or ""
- newDir = QFileDialog.getExistingDirectory(
- self, self.tr("Backup Directory"), str(currDir), options=QFileDialog.ShowDirsOnly
- )
- if newDir:
- self.backupPath = newDir
- self.mainForm.setHelpText(
- self.backupPathRow, self.tr("Path: {0}").format(self.backupPath)
- )
- return
- return
-
- @pyqtSlot(bool)
- def _toggledBackupOnClose(self, state: bool) -> None:
- """Toggle switch that depends on the backup on close switch."""
- self.askBeforeBackup.setEnabled(state)
- return
-
-# END Class GuiPreferencesProjects
-
-
-class GuiPreferencesDocuments(QWidget):
-
- def __init__(self, prefsGui: GuiPreferences) -> None:
- super().__init__(parent=prefsGui)
-
- # The Form
- self.mainForm = NConfigLayout()
- self.mainForm.setHelpTextStyle(SHARED.theme.helpText)
- self.setLayout(self.mainForm)
-
- # Text Style
- # ==========
- self.mainForm.addGroupLabel(self.tr("Text Style"))
-
- # Font Family
- self.textFont = QLineEdit(self)
- self.textFont.setReadOnly(True)
- self.textFont.setFixedWidth(CONFIG.pxInt(162))
- self.textFont.setText(CONFIG.textFont)
- self.fontButton = QPushButton("...", self)
- self.fontButton.setMaximumWidth(int(2.5*SHARED.theme.getTextWidth("...")))
- self.fontButton.clicked.connect(self._selectFont)
- self.mainForm.addRow(
- self.tr("Font family"),
- self.textFont,
- self.tr("Applies to both document editor and viewer."),
- button=self.fontButton
- )
-
- # Font Size
- self.textSize = QSpinBox(self)
- self.textSize.setMinimum(8)
- self.textSize.setMaximum(60)
- self.textSize.setSingleStep(1)
- self.textSize.setValue(CONFIG.textSize)
- self.mainForm.addRow(
- self.tr("Font size"),
- self.textSize,
- self.tr("Applies to both document editor and viewer."),
- unit=self.tr("pt")
- )
-
# Text Flow
# =========
- self.mainForm.addGroupLabel(self.tr("Text Flow"))
+
+ title = self.tr("Text Flow")
+ section += 1
+ self.sidebar.addButton(title, section)
+ self.mainForm.addGroupLabel(title, section)
# Max Text Width in Normal Mode
self.textWidth = QSpinBox(self)
@@ -671,7 +400,7 @@ class GuiPreferencesDocuments(QWidget):
)
# Focus Mode Footer
- self.hideFocusFooter = NSwitch()
+ self.hideFocusFooter = NSwitch(self)
self.hideFocusFooter.setChecked(CONFIG.hideFocusFooter)
self.mainForm.addRow(
self.tr("Hide document footer in \"Focus Mode\""),
@@ -680,7 +409,7 @@ class GuiPreferencesDocuments(QWidget):
)
# Justify Text
- self.doJustify = NSwitch()
+ self.doJustify = NSwitch(self)
self.doJustify.setChecked(CONFIG.doJustify)
self.mainForm.addRow(
self.tr("Justify the text margins"),
@@ -714,62 +443,17 @@ class GuiPreferencesDocuments(QWidget):
unit=self.tr("px")
)
- return
+ # Text Editing
+ # ============
- def saveValues(self) -> None:
- """Save the values set for this tab."""
- # Text Style
- CONFIG.setTextFont(self.textFont.text(), self.textSize.value())
-
- # Text Flow
- CONFIG.textWidth = self.textWidth.value()
- CONFIG.focusWidth = self.focusWidth.value()
- CONFIG.hideFocusFooter = self.hideFocusFooter.isChecked()
- CONFIG.doJustify = self.doJustify.isChecked()
- CONFIG.textMargin = self.textMargin.value()
- CONFIG.tabWidth = self.tabWidth.value()
-
- return
-
- ##
- # Private Slots
- ##
-
- @pyqtSlot()
- def _selectFont(self):
- """Open the QFontDialog and set a font for the font style."""
- currFont = QFont()
- currFont.setFamily(CONFIG.textFont)
- currFont.setPointSize(CONFIG.textSize)
- theFont, theStatus = QFontDialog.getFont(currFont, self)
- if theStatus:
- self.textFont.setText(theFont.family())
- self.textSize.setValue(theFont.pointSize())
-
- return
-
-# END Class GuiPreferencesDocuments
-
-
-class GuiPreferencesEditor(QWidget):
-
- def __init__(self, prefsGui: GuiPreferences) -> None:
- super().__init__(parent=prefsGui)
-
- # The Form
- self.mainForm = NConfigLayout()
- self.mainForm.setHelpTextStyle(SHARED.theme.helpText)
- self.setLayout(self.mainForm)
-
- mW = CONFIG.pxInt(250)
+ title = self.tr("Text Editing")
+ section += 1
+ self.sidebar.addButton(title, section)
+ self.mainForm.addGroupLabel(title, section)
# Spell Checking
- # ==============
- self.mainForm.addGroupLabel(self.tr("Spell Checking"))
-
- # Spell Check Provider and Language
self.spellLanguage = QComboBox(self)
- self.spellLanguage.setMaximumWidth(mW)
+ self.spellLanguage.setMinimumWidth(minWidth)
if CONFIG.hasEnchant:
for tag, language in SHARED.spelling.listDictionaries():
@@ -778,9 +462,8 @@ class GuiPreferencesEditor(QWidget):
self.spellLanguage.addItem(self.tr("None"), "")
self.spellLanguage.setEnabled(False)
- spellIdx = self.spellLanguage.findData(CONFIG.spellLanguage)
- if spellIdx != -1:
- self.spellLanguage.setCurrentIndex(spellIdx)
+ if (idx := self.spellLanguage.findData(CONFIG.spellLanguage)) != -1:
+ self.spellLanguage.setCurrentIndex(idx)
self.mainForm.addRow(
self.tr("Spell check language"),
@@ -788,37 +471,17 @@ class GuiPreferencesEditor(QWidget):
self.tr("Available languages are determined by your system.")
)
- # Word Count
- # ==========
- self.mainForm.addGroupLabel(self.tr("Word Count"))
-
- # Word Count Timer
- self.wordCountTimer = QDoubleSpinBox(self)
- self.wordCountTimer.setDecimals(1)
- self.wordCountTimer.setMinimum(2.0)
- self.wordCountTimer.setMaximum(600.0)
- self.wordCountTimer.setSingleStep(0.1)
- self.wordCountTimer.setValue(CONFIG.wordCountTimer)
+ # Auto-Select Word Under Cursor
+ self.autoSelect = NSwitch(self)
+ self.autoSelect.setChecked(CONFIG.autoSelect)
self.mainForm.addRow(
- self.tr("Word count interval"),
- self.wordCountTimer,
- unit=self.tr("seconds")
+ self.tr("Auto-select word under cursor"),
+ self.autoSelect,
+ self.tr("Apply formatting to word under cursor if no selection is made.")
)
- # Include Notes in Word Count
- self.incNotesWCount = NSwitch()
- self.incNotesWCount.setChecked(CONFIG.incNotesWCount)
- self.mainForm.addRow(
- self.tr("Include project notes in status bar word count"),
- self.incNotesWCount
- )
-
- # Writing Guides
- # ==============
- self.mainForm.addGroupLabel(self.tr("Writing Guides"))
-
# Show Tabs and Spaces
- self.showTabsNSpaces = NSwitch()
+ self.showTabsNSpaces = NSwitch(self)
self.showTabsNSpaces.setChecked(CONFIG.showTabsNSpaces)
self.mainForm.addRow(
self.tr("Show tabs and spaces"),
@@ -826,19 +489,23 @@ class GuiPreferencesEditor(QWidget):
)
# Show Line Endings
- self.showLineEndings = NSwitch()
+ self.showLineEndings = NSwitch(self)
self.showLineEndings.setChecked(CONFIG.showLineEndings)
self.mainForm.addRow(
self.tr("Show line endings"),
self.showLineEndings
)
- # Scroll Behaviour
+ # Editor Scrolling
# ================
- self.mainForm.addGroupLabel(self.tr("Scroll Behaviour"))
+
+ title = self.tr("Editor Scrolling")
+ section += 1
+ self.sidebar.addButton(title, section)
+ self.mainForm.addGroupLabel(title, section)
# Scroll Past End
- self.scrollPastEnd = NSwitch()
+ self.scrollPastEnd = NSwitch(self)
self.scrollPastEnd.setChecked(CONFIG.scrollPastEnd)
self.mainForm.addRow(
self.tr("Scroll past end of the document"),
@@ -847,7 +514,7 @@ class GuiPreferencesEditor(QWidget):
)
# Typewriter Scrolling
- self.autoScroll = NSwitch()
+ self.autoScroll = NSwitch(self)
self.autoScroll.setChecked(CONFIG.autoScroll)
self.mainForm.addRow(
self.tr("Typewriter style scrolling when you type"),
@@ -868,48 +535,15 @@ class GuiPreferencesEditor(QWidget):
unit="%"
)
- return
-
- def saveValues(self) -> None:
- """Save the values set for this tab."""
- # Spell Checking
- CONFIG.spellLanguage = self.spellLanguage.currentData()
-
- # Word Count
- CONFIG.wordCountTimer = self.wordCountTimer.value()
- CONFIG.incNotesWCount = self.incNotesWCount.isChecked()
-
- # Writing Guides
- CONFIG.showTabsNSpaces = self.showTabsNSpaces.isChecked()
- CONFIG.showLineEndings = self.showLineEndings.isChecked()
-
- # Scroll Behaviour
- CONFIG.autoScroll = self.autoScroll.isChecked()
- CONFIG.autoScrollPos = self.autoScrollPos.value()
- CONFIG.scrollPastEnd = self.scrollPastEnd.isChecked()
-
- return
-
-# END Class GuiPreferencesEditor
-
-
-class GuiPreferencesSyntax(QWidget):
-
- def __init__(self, prefsGui: GuiPreferences) -> None:
- super().__init__(parent=prefsGui)
-
- self.prefsGui = prefsGui
-
- # The Form
- self.mainForm = NConfigLayout()
- self.mainForm.setHelpTextStyle(SHARED.theme.helpText)
- self.setLayout(self.mainForm)
-
- # Quotes & Dialogue
+ # Text Highlighting
# =================
- self.mainForm.addGroupLabel(self.tr("Quotes & Dialogue"))
- self.highlightQuotes = NSwitch()
+ title = self.tr("Text Highlighting")
+ section += 1
+ self.sidebar.addButton(title, section)
+ self.mainForm.addGroupLabel(title, section)
+
+ self.highlightQuotes = NSwitch(self)
self.highlightQuotes.setChecked(CONFIG.highlightQuotes)
self.highlightQuotes.toggled.connect(self._toggleHighlightQuotes)
self.mainForm.addRow(
@@ -918,7 +552,7 @@ class GuiPreferencesSyntax(QWidget):
self.tr("Applies to the document editor only.")
)
- self.allowOpenSQuote = NSwitch()
+ self.allowOpenSQuote = NSwitch(self)
self.allowOpenSQuote.setChecked(CONFIG.allowOpenSQuote)
self.mainForm.addRow(
self.tr("Allow open-ended single quotes"),
@@ -926,7 +560,7 @@ class GuiPreferencesSyntax(QWidget):
self.tr("Highlight single-quoted line with no closing quote.")
)
- self.allowOpenDQuote = NSwitch()
+ self.allowOpenDQuote = NSwitch(self)
self.allowOpenDQuote.setChecked(CONFIG.allowOpenDQuote)
self.mainForm.addRow(
self.tr("Allow open-ended double quotes"),
@@ -934,11 +568,7 @@ class GuiPreferencesSyntax(QWidget):
self.tr("Highlight double-quoted line with no closing quote.")
)
- # Text Emphasis
- # =============
- self.mainForm.addGroupLabel(self.tr("Text Emphasis"))
-
- self.highlightEmph = NSwitch()
+ self.highlightEmph = NSwitch(self)
self.highlightEmph.setChecked(CONFIG.highlightEmph)
self.mainForm.addRow(
self.tr("Add highlight colour to emphasised text"),
@@ -946,12 +576,7 @@ class GuiPreferencesSyntax(QWidget):
self.tr("Applies to the document editor only.")
)
- # Text Errors
- # ===========
-
- self.mainForm.addGroupLabel(self.tr("Text Errors"))
-
- self.showMultiSpaces = NSwitch()
+ self.showMultiSpaces = NSwitch(self)
self.showMultiSpaces.setChecked(CONFIG.showMultiSpaces)
self.mainForm.addRow(
self.tr("Highlight multiple or trailing spaces"),
@@ -959,67 +584,18 @@ class GuiPreferencesSyntax(QWidget):
self.tr("Applies to the document editor only.")
)
- return
+ # Text Automation
+ # ===============
- def saveValues(self) -> None:
- """Save the values set for this tab."""
- highlightQuotes = self.highlightQuotes.isChecked()
- allowOpenSQuote = self.allowOpenSQuote.isChecked()
- allowOpenDQuote = self.allowOpenDQuote.isChecked()
- highlightEmph = self.highlightEmph.isChecked()
- showMultiSpaces = self.showMultiSpaces.isChecked()
+ title = self.tr("Text Automation")
+ section += 1
+ self.sidebar.addButton(title, section)
+ self.mainForm.addGroupLabel(title, section)
- self.prefsGui._updateSyntax |= CONFIG.highlightQuotes != highlightQuotes
- self.prefsGui._updateSyntax |= CONFIG.highlightEmph != highlightEmph
- self.prefsGui._updateSyntax |= CONFIG.showMultiSpaces != showMultiSpaces
-
- CONFIG.highlightQuotes = highlightQuotes
- CONFIG.allowOpenSQuote = allowOpenSQuote
- CONFIG.allowOpenDQuote = allowOpenDQuote
- CONFIG.highlightEmph = highlightEmph
- CONFIG.showMultiSpaces = showMultiSpaces
-
- return
-
- ##
- # Private Slots
- ##
-
- @pyqtSlot(bool)
- def _toggleHighlightQuotes(self, state: bool) -> None:
- """Toggle switches controlled by the highlight quotes switch."""
- self.allowOpenSQuote.setEnabled(state)
- self.allowOpenDQuote.setEnabled(state)
- return
-
-# END Class GuiPreferencesSyntax
-
-
-class GuiPreferencesAutomation(QWidget):
-
- def __init__(self, prefsGui: GuiPreferences) -> None:
- super().__init__(parent=prefsGui)
-
- # The Form
- self.mainForm = NConfigLayout()
- self.mainForm.setHelpTextStyle(SHARED.theme.helpText)
- self.setLayout(self.mainForm)
-
- # Automatic Features
- # ==================
- self.mainForm.addGroupLabel(self.tr("Automatic Features"))
-
- # Auto-Select Word Under Cursor
- self.autoSelect = NSwitch()
- self.autoSelect.setChecked(CONFIG.autoSelect)
- self.mainForm.addRow(
- self.tr("Auto-select word under cursor"),
- self.autoSelect,
- self.tr("Apply formatting to word under cursor if no selection is made.")
- )
+ boxWidth = CONFIG.pxInt(150)
# Auto-Replace as You Type Main Switch
- self.doReplace = NSwitch()
+ self.doReplace = NSwitch(self)
self.doReplace.setChecked(CONFIG.doReplace)
self.doReplace.toggled.connect(self._toggleAutoReplaceMain)
self.mainForm.addRow(
@@ -1028,12 +604,8 @@ class GuiPreferencesAutomation(QWidget):
self.tr("Allow the editor to replace symbols as you type.")
)
- # Replace as You Type
- # ===================
- self.mainForm.addGroupLabel(self.tr("Replace as You Type"))
-
# Auto-Replace Single Quotes
- self.doReplaceSQuote = NSwitch()
+ self.doReplaceSQuote = NSwitch(self)
self.doReplaceSQuote.setChecked(CONFIG.doReplaceSQuote)
self.doReplaceSQuote.setEnabled(CONFIG.doReplace)
self.mainForm.addRow(
@@ -1043,7 +615,7 @@ class GuiPreferencesAutomation(QWidget):
)
# Auto-Replace Double Quotes
- self.doReplaceDQuote = NSwitch()
+ self.doReplaceDQuote = NSwitch(self)
self.doReplaceDQuote.setChecked(CONFIG.doReplaceDQuote)
self.doReplaceDQuote.setEnabled(CONFIG.doReplace)
self.mainForm.addRow(
@@ -1053,7 +625,7 @@ class GuiPreferencesAutomation(QWidget):
)
# Auto-Replace Hyphens
- self.doReplaceDash = NSwitch()
+ self.doReplaceDash = NSwitch(self)
self.doReplaceDash.setChecked(CONFIG.doReplaceDash)
self.doReplaceDash.setEnabled(CONFIG.doReplace)
self.mainForm.addRow(
@@ -1063,7 +635,7 @@ class GuiPreferencesAutomation(QWidget):
)
# Auto-Replace Dots
- self.doReplaceDots = NSwitch()
+ self.doReplaceDots = NSwitch(self)
self.doReplaceDots.setChecked(CONFIG.doReplaceDots)
self.doReplaceDots.setEnabled(CONFIG.doReplace)
self.mainForm.addRow(
@@ -1072,13 +644,10 @@ class GuiPreferencesAutomation(QWidget):
self.tr("Three consecutive dots become ellipsis.")
)
- # Automatic Padding
- # =================
- self.mainForm.addGroupLabel(self.tr("Automatic Padding"))
-
# Pad Before
self.fmtPadBefore = QLineEdit(self)
self.fmtPadBefore.setMaxLength(32)
+ self.fmtPadBefore.setMaximumWidth(boxWidth)
self.fmtPadBefore.setText(CONFIG.fmtPadBefore)
self.mainForm.addRow(
self.tr("Insert non-breaking space before"),
@@ -1089,6 +658,7 @@ class GuiPreferencesAutomation(QWidget):
# Pad After
self.fmtPadAfter = QLineEdit(self)
self.fmtPadAfter.setMaxLength(32)
+ self.fmtPadAfter.setMaximumWidth(boxWidth)
self.fmtPadAfter.setText(CONFIG.fmtPadAfter)
self.mainForm.addRow(
self.tr("Insert non-breaking space after"),
@@ -1097,7 +667,7 @@ class GuiPreferencesAutomation(QWidget):
)
# Use Thin Space
- self.fmtPadThin = NSwitch()
+ self.fmtPadThin = NSwitch(self)
self.fmtPadThin.setChecked(CONFIG.fmtPadThin)
self.fmtPadThin.setEnabled(CONFIG.doReplace)
self.mainForm.addRow(
@@ -1106,57 +676,13 @@ class GuiPreferencesAutomation(QWidget):
self.tr("Inserts a thin space instead of a regular space.")
)
- return
-
- def saveValues(self) -> None:
- """Save the values set for this tab."""
- # Automatic Features
- CONFIG.autoSelect = self.autoSelect.isChecked()
- CONFIG.doReplace = self.doReplace.isChecked()
-
- # Replace as You Type
- CONFIG.doReplaceSQuote = self.doReplaceSQuote.isChecked()
- CONFIG.doReplaceDQuote = self.doReplaceDQuote.isChecked()
- CONFIG.doReplaceDash = self.doReplaceDash.isChecked()
- CONFIG.doReplaceDots = self.doReplaceDots.isChecked()
-
- # Automatic Padding
- CONFIG.fmtPadBefore = self.fmtPadBefore.text().strip()
- CONFIG.fmtPadAfter = self.fmtPadAfter.text().strip()
- CONFIG.fmtPadThin = self.fmtPadThin.isChecked()
-
- return
-
- ##
- # Private Slots
- ##
-
- @pyqtSlot(bool)
- def _toggleAutoReplaceMain(self, state: bool) -> None:
- """Toggle switches controlled by the auto replace switch."""
- self.doReplaceSQuote.setEnabled(state)
- self.doReplaceDQuote.setEnabled(state)
- self.doReplaceDash.setEnabled(state)
- self.doReplaceDots.setEnabled(state)
- self.fmtPadThin.setEnabled(state)
- return
-
-# END Class GuiPreferencesAutomation
-
-
-class GuiPreferencesQuotes(QWidget):
-
- def __init__(self, prefsGui: GuiPreferences) -> None:
- super().__init__(parent=prefsGui)
-
- # The Form
- self.mainForm = NConfigLayout()
- self.mainForm.setHelpTextStyle(SHARED.theme.helpText)
- self.setLayout(self.mainForm)
-
# Quotation Style
# ===============
- self.mainForm.addGroupLabel(self.tr("Quotation Style"))
+
+ title = self.tr("Quotation Style")
+ section += 1
+ self.sidebar.addButton(title, section)
+ self.mainForm.addGroupLabel(title, section)
qWidth = CONFIG.pxInt(40)
bWidth = int(2.5*SHARED.theme.getTextWidth("..."))
@@ -1228,27 +754,226 @@ class GuiPreferencesQuotes(QWidget):
button=self.btnDoubleStyleC
)
+ self.mainForm.finalise()
+ self.sidebar.setSelected(1)
+
return
- def saveValues(self) -> None:
- """Save the values set for this tab."""
- # Quotation Style
- CONFIG.fmtSQuoteOpen = self.quoteSym["SO"].text()
- CONFIG.fmtSQuoteClose = self.quoteSym["SC"].text()
- CONFIG.fmtDQuoteOpen = self.quoteSym["DO"].text()
- CONFIG.fmtDQuoteClose = self.quoteSym["DC"].text()
+ ##
+ # Events
+ ##
+
+ def closeEvent(self, event: QCloseEvent) -> None:
+ """Capture the close event and perform cleanup."""
+ logger.debug("Close: GuiPreferences")
+ self._saveWindowSize()
+ event.accept()
+ self.deleteLater()
+ return
+
+ ##
+ # Private Slots
+ ##
+
+ @pyqtSlot("QAbstractButton*")
+ def _dialogButtonClicked(self, button: QAbstractButton) -> None:
+ """Handle button clicks from the dialog button box."""
+ role = self.buttonBox.buttonRole(button)
+ if role == QDialogButtonBox.ApplyRole:
+ self._saveValues()
+ elif role == QDialogButtonBox.AcceptRole:
+ self._saveValues()
+ self.close()
+ elif role == QDialogButtonBox.RejectRole:
+ self.close()
+ return
+
+ @pyqtSlot(int)
+ def _sidebarClicked(self, section: int) -> None:
+ """Process a user request to switch page."""
+ self.mainForm.scrollToSection(section)
+ return
+
+ @pyqtSlot()
+ def _selectGuiFont(self) -> None:
+ """Open the QFontDialog and set a font for the font style."""
+ current = QFont()
+ current.setFamily(CONFIG.guiFont)
+ current.setPointSize(CONFIG.guiFontSize)
+ font, status = QFontDialog.getFont(current, self)
+ if status:
+ self.guiFont.setText(font.family())
+ self.guiFontSize.setValue(font.pointSize())
+ return
+
+ @pyqtSlot()
+ def _selectTextFont(self):
+ """Open the QFontDialog and set a font for the font style."""
+ current = QFont()
+ current.setFamily(CONFIG.textFont)
+ current.setPointSize(CONFIG.textSize)
+ font, status = QFontDialog.getFont(current, self)
+ if status:
+ self.textFont.setText(font.family())
+ self.textSize.setValue(font.pointSize())
+ return
+
+ @pyqtSlot()
+ def _backupFolder(self) -> None:
+ """Open a dialog to select the backup folder."""
+ if path := QFileDialog.getExistingDirectory(
+ self, self.tr("Backup Directory"), str(self.backupPath or ""),
+ options=QFileDialog.ShowDirsOnly
+ ):
+ self.backupPath = path
+ self.mainForm.setHelpText("backupPath", self.tr("Path: {0}").format(path))
+ return
+
+ @pyqtSlot(bool)
+ def _toggledBackupOnClose(self, state: bool) -> None:
+ """Toggle switch that depends on the backup on close switch."""
+ self.askBeforeBackup.setEnabled(state)
+ return
+
+ @pyqtSlot(bool)
+ def _toggleHighlightQuotes(self, state: bool) -> None:
+ """Toggle switches controlled by the highlight quotes switch."""
+ self.allowOpenSQuote.setEnabled(state)
+ self.allowOpenDQuote.setEnabled(state)
+ return
+
+ @pyqtSlot(bool)
+ def _toggleAutoReplaceMain(self, state: bool) -> None:
+ """Toggle switches controlled by the auto replace switch."""
+ self.doReplaceSQuote.setEnabled(state)
+ self.doReplaceDQuote.setEnabled(state)
+ self.doReplaceDash.setEnabled(state)
+ self.doReplaceDots.setEnabled(state)
+ self.fmtPadThin.setEnabled(state)
+ return
+
+ def _getQuote(self, qType: str) -> None:
+ """Dialog for single quote open."""
+ quote = GuiQuoteSelect(self, currentQuote=self.quoteSym[qType].text())
+ if quote.exec_() == QDialog.Accepted:
+ self.quoteSym[qType].setText(quote.selectedQuote)
return
##
# Internal Functions
##
- def _getQuote(self, qType: str) -> None:
- """Dialog for single quote open."""
- qtBox = GuiQuoteSelect(self, currentQuote=self.quoteSym[qType].text())
- if qtBox.exec_() == QDialog.Accepted:
- self.quoteSym[qType].setText(qtBox.selectedQuote)
+ def _saveWindowSize(self) -> None:
+ """Save the dialog window size."""
+ CONFIG.setPreferencesWinSize(self.width(), self.height())
+ return
+
+ def _saveValues(self) -> None:
+ """Save the values set in the form."""
+ updateTheme = False
+ needsRestart = False
+ updateSyntax = False
+ refreshTree = False
+
+ # Appearance
+ guiLocale = self.guiLocale.currentData()
+ guiTheme = self.guiTheme.currentData()
+ guiFont = self.guiFont.text()
+ guiFontSize = self.guiFontSize.value()
+
+ updateTheme |= CONFIG.guiTheme != guiTheme
+ needsRestart |= CONFIG.guiLocale != guiLocale
+ needsRestart |= CONFIG.guiFont != guiFont
+ needsRestart |= CONFIG.guiFontSize != guiFontSize
+
+ CONFIG.guiLocale = guiLocale
+ CONFIG.guiTheme = guiTheme
+ CONFIG.guiFont = guiFont
+ CONFIG.guiFontSize = guiFontSize
+ CONFIG.hideVScroll = self.hideVScroll.isChecked()
+ CONFIG.hideHScroll = self.hideHScroll.isChecked()
+
+ # Document Style
+ guiSyntax = self.guiSyntax.currentData()
+ emphLabels = self.emphLabels.isChecked()
+
+ updateSyntax |= CONFIG.guiSyntax != guiSyntax
+ refreshTree |= CONFIG.emphLabels != emphLabels
+
+ CONFIG.guiSyntax = guiSyntax
+ CONFIG.emphLabels = emphLabels
+ CONFIG.showFullPath = self.showFullPath.isChecked()
+ CONFIG.incNotesWCount = self.incNotesWCount.isChecked()
+ CONFIG.setTextFont(self.textFont.text(), self.textSize.value())
+
+ # Auto Save
+ CONFIG.autoSaveDoc = self.autoSaveDoc.value()
+ CONFIG.autoSaveProj = self.autoSaveProj.value()
+
+ # Project Backup
+ CONFIG.setBackupPath(self.backupPath)
+ CONFIG.backupOnClose = self.backupOnClose.isChecked()
+ CONFIG.askBeforeBackup = self.askBeforeBackup.isChecked()
+
+ # Session Timer
+ CONFIG.stopWhenIdle = self.stopWhenIdle.isChecked()
+ CONFIG.userIdleTime = round(self.userIdleTime.value() * 60)
+
+ # Text Flow
+ CONFIG.textWidth = self.textWidth.value()
+ CONFIG.focusWidth = self.focusWidth.value()
+ CONFIG.hideFocusFooter = self.hideFocusFooter.isChecked()
+ CONFIG.doJustify = self.doJustify.isChecked()
+ CONFIG.textMargin = self.textMargin.value()
+ CONFIG.tabWidth = self.tabWidth.value()
+
+ # Text Editing
+ CONFIG.spellLanguage = self.spellLanguage.currentData()
+ CONFIG.autoSelect = self.autoSelect.isChecked()
+ CONFIG.showTabsNSpaces = self.showTabsNSpaces.isChecked()
+ CONFIG.showLineEndings = self.showLineEndings.isChecked()
+
+ # Editor Scrolling
+ CONFIG.autoScroll = self.autoScroll.isChecked()
+ CONFIG.autoScrollPos = self.autoScrollPos.value()
+ CONFIG.scrollPastEnd = self.scrollPastEnd.isChecked()
+
+ # Text Highlighting
+ highlightQuotes = self.highlightQuotes.isChecked()
+ highlightEmph = self.highlightEmph.isChecked()
+ showMultiSpaces = self.showMultiSpaces.isChecked()
+
+ updateSyntax |= CONFIG.highlightQuotes != highlightQuotes
+ updateSyntax |= CONFIG.highlightEmph != highlightEmph
+ updateSyntax |= CONFIG.showMultiSpaces != showMultiSpaces
+
+ CONFIG.highlightQuotes = highlightQuotes
+ CONFIG.highlightEmph = highlightEmph
+ CONFIG.showMultiSpaces = showMultiSpaces
+ CONFIG.allowOpenSQuote = self.allowOpenSQuote.isChecked()
+ CONFIG.allowOpenDQuote = self.allowOpenDQuote.isChecked()
+
+ # Text Automation
+ CONFIG.doReplace = self.doReplace.isChecked()
+ CONFIG.doReplaceSQuote = self.doReplaceSQuote.isChecked()
+ CONFIG.doReplaceDQuote = self.doReplaceDQuote.isChecked()
+ CONFIG.doReplaceDash = self.doReplaceDash.isChecked()
+ CONFIG.doReplaceDots = self.doReplaceDots.isChecked()
+ CONFIG.fmtPadBefore = self.fmtPadBefore.text().strip()
+ CONFIG.fmtPadAfter = self.fmtPadAfter.text().strip()
+ CONFIG.fmtPadThin = self.fmtPadThin.isChecked()
+
+ # Quotation Style
+ CONFIG.fmtSQuoteOpen = self.quoteSym["SO"].text()
+ CONFIG.fmtSQuoteClose = self.quoteSym["SC"].text()
+ CONFIG.fmtDQuoteOpen = self.quoteSym["DO"].text()
+ CONFIG.fmtDQuoteClose = self.quoteSym["DC"].text()
+
+ # Finalise
+ CONFIG.saveConfig()
+ self.newPreferencesReady.emit(needsRestart, refreshTree, updateTheme, updateSyntax)
+ qApp.processEvents()
return
-# END Class GuiPreferencesQuotes
+# END Class GuiPreferences
diff --git a/novelwriter/extensions/configlayout.py b/novelwriter/extensions/configlayout.py
index 3a541559..83a2715c 100644
--- a/novelwriter/extensions/configlayout.py
+++ b/novelwriter/extensions/configlayout.py
@@ -94,7 +94,7 @@ class NScrollableForm(QScrollArea):
def addRow(self, label: str, widget: QWidget, helpText: str = "", unit: str | None = None,
button: QWidget | None = None, editable: str | None = None) -> None:
- """Add a label and a widget as a new row of the grid."""
+ """Add a label and a widget as a new row of the form."""
row = QHBoxLayout()
wSp = CONFIG.pxInt(8)
diff --git a/novelwriter/extensions/pagedsidebar.py b/novelwriter/extensions/pagedsidebar.py
index 0141d90a..853fc7de 100644
--- a/novelwriter/extensions/pagedsidebar.py
+++ b/novelwriter/extensions/pagedsidebar.py
@@ -26,7 +26,7 @@ along with this program. If not, see .
from __future__ import annotations
from PyQt5.QtGui import QColor, QPaintEvent, QPainter, QPolygon
-from PyQt5.QtCore import QPoint, QRectF, Qt, pyqtSignal, pyqtSlot
+from PyQt5.QtCore import QPoint, QRectF, QSize, Qt, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import (
QAbstractButton, QAction, QButtonGroup, QLabel, QSizePolicy, QStyle,
QStyleOptionToolButton, QToolBar, QToolButton, QWidget
@@ -136,6 +136,10 @@ class _NPagedToolButton(QToolButton):
return
+ def sizeHint(self) -> QSize:
+ """Return a size hint that includes the arrow."""
+ return super().sizeHint() + QSize(4*self._aH, 0)
+
def paintEvent(self, event: QPaintEvent) -> None:
"""Overload the paint event to draw a simple, left aligned text
label, with a highlight when selected and a transparent base
From 671d7130e1750ca19d233b4942f66c9b76473cbd Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 8 Jan 2024 21:08:33 +0100
Subject: [PATCH 05/13] Add search bar to preferences
---
novelwriter/dialogs/preferences.py | 70 +++++++++++++++++++++++---
novelwriter/extensions/configlayout.py | 39 +++++++++++---
2 files changed, 96 insertions(+), 13 deletions(-)
diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py
index 5e532ffa..6207a468 100644
--- a/novelwriter/dialogs/preferences.py
+++ b/novelwriter/dialogs/preferences.py
@@ -26,12 +26,12 @@ from __future__ import annotations
import logging
-from PyQt5.QtGui import QCloseEvent, QFont
+from PyQt5.QtGui import QCloseEvent, QColor, QFont, QKeyEvent, QKeySequence, QPalette
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import (
- QAbstractButton, QDialog, QHBoxLayout, QVBoxLayout, QWidget, QComboBox,
- QSpinBox, QPushButton, QDialogButtonBox, QLineEdit, QFileDialog,
- QFontDialog, QDoubleSpinBox, qApp
+ QAbstractButton, QComboBox, QCompleter, QDialog, QDialogButtonBox,
+ QDoubleSpinBox, QFileDialog, QFontDialog, QHBoxLayout, QLabel, QLineEdit,
+ QPushButton, QSpinBox, QVBoxLayout, QWidget, qApp
)
from novelwriter import CONFIG, SHARED
@@ -58,37 +58,80 @@ class GuiPreferences(QDialog):
self.setMinimumSize(CONFIG.pxInt(600), CONFIG.pxInt(500))
self.resize(*CONFIG.preferencesWinSize)
+ # Title
+ font = self.font()
+ font.setPointSizeF(1.5*SHARED.theme.fontPointSize)
+
+ palette = self.palette()
+ palette.setColor(QPalette.ColorRole.WindowText, QColor(*SHARED.theme.helpText))
+
+ self.titleLabel = QLabel(self.tr("Preferences"), self)
+ self.titleLabel.setFont(font)
+ self.titleLabel.setPalette(palette)
+ self.titleLabel.setIndent(CONFIG.pxInt(4))
+
# SideBar
self.sidebar = NPagedSideBar(self)
self.sidebar.setLabelColor(SHARED.theme.helpText)
- self.sidebar.addLabel(self.tr("Preferences"))
self.sidebar.buttonClicked.connect(self._sidebarClicked)
+ # Search Box
+ self.searchText = QLineEdit(self)
+ self.searchText.setPlaceholderText(self.tr("Search"))
+ self.searchText.setMinimumWidth(CONFIG.pxInt(200))
+ self.searchAction = self.searchText.addAction(
+ SHARED.theme.getIcon("search"), QLineEdit.ActionPosition.TrailingPosition
+ )
+ self.searchAction.triggered.connect(self._gotoSearch)
+
+ self.searchBox = QHBoxLayout()
+ self.searchBox.addWidget(self.titleLabel)
+ self.searchBox.addStretch(1)
+ self.searchBox.addWidget(self.searchText, 1)
+
# Form
self.mainForm = NScrollableForm(self)
self.mainForm.setHelpTextStyle(SHARED.theme.helpText)
# Buttons
self.buttonBox = QDialogButtonBox(
- QDialogButtonBox.Apply | QDialogButtonBox.Save | QDialogButtonBox.Close
+ QDialogButtonBox.StandardButton.Apply
+ | QDialogButtonBox.StandardButton.Save
+ | QDialogButtonBox.StandardButton.Close
)
self.buttonBox.clicked.connect(self._dialogButtonClicked)
# Assemble
+ self.mainSearch = QVBoxLayout()
+ self.mainSearch.addItem(self.searchBox)
+ self.mainSearch.addWidget(self.mainForm)
+
self.mainBox = QHBoxLayout()
self.mainBox.addWidget(self.sidebar)
+ # self.mainBox.addLayout(self.mainSearch)
self.mainBox.addWidget(self.mainForm)
self.mainBox.setContentsMargins(0, 0, 0, 0)
self.outerBox = QVBoxLayout()
+ self.outerBox.addLayout(self.searchBox)
self.outerBox.addLayout(self.mainBox)
self.outerBox.addWidget(self.buttonBox)
self.outerBox.setSpacing(CONFIG.pxInt(8))
self.setLayout(self.outerBox)
self.setSizeGripEnabled(True)
+
+ # Build Form
self.buildForm()
+ # Populate Search
+ self.searchCompleter = QCompleter(self.mainForm.labels, self)
+ self.searchCompleter.setCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
+ self.searchCompleter.setFilterMode(Qt.MatchFlag.MatchContains)
+ self.searchCompleter.activated.connect(self._gotoSearch)
+
+ self.searchText.setCompleter(self.searchCompleter)
+
logger.debug("Ready: GuiPreferences")
return
@@ -768,9 +811,17 @@ class GuiPreferences(QDialog):
logger.debug("Close: GuiPreferences")
self._saveWindowSize()
event.accept()
+ qApp.processEvents()
self.deleteLater()
return
+ def keyPressEvent(self, event: QKeyEvent) -> None:
+ """Overload keyPressEvent to block enter key to save."""
+ if event.matches(QKeySequence.Cancel):
+ self.reject()
+ event.ignore()
+ return
+
##
# Private Slots
##
@@ -794,6 +845,12 @@ class GuiPreferences(QDialog):
self.mainForm.scrollToSection(section)
return
+ @pyqtSlot()
+ def _gotoSearch(self) -> None:
+ """Go to the setting indicated by the search text."""
+ self.mainForm.scrollToLabel(self.searchText.text().strip())
+ return
+
@pyqtSlot()
def _selectGuiFont(self) -> None:
"""Open the QFontDialog and set a font for the font style."""
@@ -972,7 +1029,6 @@ class GuiPreferences(QDialog):
# Finalise
CONFIG.saveConfig()
self.newPreferencesReady.emit(needsRestart, refreshTree, updateTheme, updateSyntax)
- qApp.processEvents()
return
diff --git a/novelwriter/extensions/configlayout.py b/novelwriter/extensions/configlayout.py
index 83a2715c..676d055b 100644
--- a/novelwriter/extensions/configlayout.py
+++ b/novelwriter/extensions/configlayout.py
@@ -3,7 +3,9 @@ novelWriter – Custom Widget: Config Layout
==========================================
File History:
-Created: 2020-05-03 [0.4.5]
+Created: 2020-05-03 [0.4.5] NConfigLayout, NHelpLabel
+Created: 2023-05-23 [2.1b1] NSimpleLayout
+Created: 2024-01-08 [2.3b1] NScrollableForm
This file is a part of novelWriter
Copyright 2018–2024, Veronica Berglyd Olsen
@@ -45,6 +47,7 @@ class NScrollableForm(QScrollArea):
self._fontScale = FONT_SCALE
self._sections: dict[int, QLabel] = {}
self._editable: dict[str, NHelpLabel] = {}
+ self._index: dict[str, QWidget] = {}
self._layout = QVBoxLayout()
self._layout.setSpacing(CONFIG.pxInt(12))
@@ -59,6 +62,18 @@ class NScrollableForm(QScrollArea):
return
+ ##
+ # Properties
+ ##
+
+ @property
+ def labels(self) -> list[str]:
+ return list(self._index.keys())
+
+ ##
+ # Setters
+ ##
+
def setHelpTextStyle(self, color: QColor | list | tuple, scale: float = FONT_SCALE) -> None:
"""Set the text color for the help text."""
self._helpCol = color if isinstance(color, QColor) else QColor(*color)
@@ -71,18 +86,24 @@ class NScrollableForm(QScrollArea):
qHelp.setText(text)
return
- def finalise(self) -> None:
- """Finalise the layout when the form is built."""
- self._layout.addStretch(1)
- return
+ ##
+ # Methods
+ ##
- def scrollToSection(self, identifier: int, offset: int = 50) -> None:
+ def scrollToSection(self, identifier: int) -> None:
"""Scroll to the requested section identifier."""
if identifier in self._sections:
yPos = self._sections[identifier].pos().y() - CONFIG.pxInt(8)
self.verticalScrollBar().setValue(yPos)
return
+ def scrollToLabel(self, label: str) -> None:
+ """Scroll to the requested label."""
+ if label in self._index:
+ yPos = self._index[label].pos().y() - CONFIG.pxInt(8)
+ self.verticalScrollBar().setValue(yPos)
+ return
+
def addGroupLabel(self, label: str, identifier: int) -> None:
"""Add a text label to separate groups of settings."""
hM = CONFIG.pxInt(4)
@@ -124,9 +145,15 @@ class NScrollableForm(QScrollArea):
row.addWidget(button)
self._layout.addLayout(row)
+ self._index[label.strip()] = widget
return
+ def finalise(self) -> None:
+ """Finalise the layout when the form is built."""
+ self._layout.addStretch(1)
+ return
+
# END Class NScrollableForm
From 34fcbdd35022de97756673d24114ae586649398b Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 8 Jan 2024 21:48:51 +0100
Subject: [PATCH 06/13] Fix a couple of bugs on the preference dialog
---
novelwriter/dialogs/preferences.py | 9 ++-------
1 file changed, 2 insertions(+), 7 deletions(-)
diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py
index 6207a468..633253c0 100644
--- a/novelwriter/dialogs/preferences.py
+++ b/novelwriter/dialogs/preferences.py
@@ -60,7 +60,7 @@ class GuiPreferences(QDialog):
# Title
font = self.font()
- font.setPointSizeF(1.5*SHARED.theme.fontPointSize)
+ font.setPointSizeF(1.25*SHARED.theme.fontPointSize)
palette = self.palette()
palette.setColor(QPalette.ColorRole.WindowText, QColor(*SHARED.theme.helpText))
@@ -102,13 +102,8 @@ class GuiPreferences(QDialog):
self.buttonBox.clicked.connect(self._dialogButtonClicked)
# Assemble
- self.mainSearch = QVBoxLayout()
- self.mainSearch.addItem(self.searchBox)
- self.mainSearch.addWidget(self.mainForm)
-
self.mainBox = QHBoxLayout()
self.mainBox.addWidget(self.sidebar)
- # self.mainBox.addLayout(self.mainSearch)
self.mainBox.addWidget(self.mainForm)
self.mainBox.setContentsMargins(0, 0, 0, 0)
@@ -818,7 +813,7 @@ class GuiPreferences(QDialog):
def keyPressEvent(self, event: QKeyEvent) -> None:
"""Overload keyPressEvent to block enter key to save."""
if event.matches(QKeySequence.Cancel):
- self.reject()
+ self.close()
event.ignore()
return
From 2504965f8d767a02a79b52c7656a6a18e018a28d Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 8 Jan 2024 22:06:04 +0100
Subject: [PATCH 07/13] Add more spacing in preferences layout
---
novelwriter/extensions/configlayout.py | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/novelwriter/extensions/configlayout.py b/novelwriter/extensions/configlayout.py
index 676d055b..9cb932cd 100644
--- a/novelwriter/extensions/configlayout.py
+++ b/novelwriter/extensions/configlayout.py
@@ -45,6 +45,8 @@ class NScrollableForm(QScrollArea):
super().__init__(parent=parent)
self._helpCol = QColor(0, 0, 0)
self._fontScale = FONT_SCALE
+ self._first = True
+
self._sections: dict[int, QLabel] = {}
self._editable: dict[str, NHelpLabel] = {}
self._index: dict[str, QWidget] = {}
@@ -109,8 +111,11 @@ class NScrollableForm(QScrollArea):
hM = CONFIG.pxInt(4)
qLabel = QLabel(f"{label}", self)
qLabel.setContentsMargins(0, hM, 0, hM)
+ if not self._first:
+ self._layout.addSpacing(5*hM)
self._layout.addWidget(qLabel)
self._sections[identifier] = qLabel
+ self._first = False
return
def addRow(self, label: str, widget: QWidget, helpText: str = "", unit: str | None = None,
@@ -118,7 +123,7 @@ class NScrollableForm(QScrollArea):
"""Add a label and a widget as a new row of the form."""
row = QHBoxLayout()
- wSp = CONFIG.pxInt(8)
+ wSp = CONFIG.pxInt(12)
qLabel = QLabel(label, self)
qLabel.setIndent(wSp)
qLabel.setBuddy(widget)
@@ -146,11 +151,13 @@ class NScrollableForm(QScrollArea):
self._layout.addLayout(row)
self._index[label.strip()] = widget
+ self._first = False
return
def finalise(self) -> None:
"""Finalise the layout when the form is built."""
+ self._layout.addSpacing(CONFIG.pxInt(20))
self._layout.addStretch(1)
return
From 944e2455d500d8269aed2891e98d1e0d86e8cb14 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 8 Jan 2024 22:13:33 +0100
Subject: [PATCH 08/13] Fix a bug with editor init on preferences update
---
novelwriter/gui/doceditor.py | 17 +++++++++--------
novelwriter/gui/docviewer.py | 14 +++++++-------
2 files changed, 16 insertions(+), 15 deletions(-)
diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py
index d7bedf8a..c7554591 100644
--- a/novelwriter/gui/doceditor.py
+++ b/novelwriter/gui/doceditor.py
@@ -319,10 +319,10 @@ class GuiDocEditor(QPlainTextEdit):
SHARED.updateSpellCheckLanguage()
# Set font
- textFont = QFont()
- textFont.setFamily(CONFIG.textFont)
- textFont.setPointSize(CONFIG.textSize)
- self.setFont(textFont)
+ font = QFont()
+ font.setFamily(CONFIG.textFont)
+ font.setPointSize(CONFIG.textSize)
+ self.setFont(font)
# Set default text margins
# Due to cursor visibility, a part of the margin must be
@@ -358,13 +358,14 @@ class GuiDocEditor(QPlainTextEdit):
# Refresh the tab stops
self.setTabStopDistance(CONFIG.getTabWidth())
- # If we have a document open, we should reload it in case the
+ # If we have a document open, we should refresh it in case the
# font changed, otherwise we just clear the editor entirely,
# which makes it read only.
- if self._docHandle is None:
- self.clearEditor()
- else:
+ if self._docHandle:
self._qDocument.syntaxHighlighter.rehighlight()
+ self.docHeader.setTitleFromHandle(self._docHandle)
+ else:
+ self.clearEditor()
return
diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py
index dc0562d3..5a066ccc 100644
--- a/novelwriter/gui/docviewer.py
+++ b/novelwriter/gui/docviewer.py
@@ -142,10 +142,10 @@ class GuiDocViewer(QTextBrowser):
self._makeStyleSheet()
# Set Font
- textFont = QFont()
- textFont.setFamily(CONFIG.textFont)
- textFont.setPointSize(CONFIG.textSize)
- self.setFont(textFont)
+ font = QFont()
+ font.setFamily(CONFIG.textFont)
+ font.setPointSize(CONFIG.textSize)
+ self.setFont(font)
# Set the widget colours to match syntax theme
mainPalette = self.palette()
@@ -164,10 +164,10 @@ class GuiDocViewer(QTextBrowser):
# Set default text margins
self.document().setDocumentMargin(0)
- theOpt = QTextOption()
+ options = QTextOption()
if CONFIG.doJustify:
- theOpt.setAlignment(Qt.AlignmentFlag.AlignJustify)
- self.document().setDefaultTextOption(theOpt)
+ options.setAlignment(Qt.AlignmentFlag.AlignJustify)
+ self.document().setDefaultTextOption(options)
# Scroll bars
if CONFIG.hideVScroll:
From 4afbd98d6ff2bf9ce7121b83b5cbddfa51e16e80 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Thu, 11 Jan 2024 09:17:46 +0100
Subject: [PATCH 09/13] Add icons to preferences buttons
---
.../assets/icons/typicons_dark/icons.conf | 1 +
.../icons/typicons_dark/typ_th-dot-more.svg | 4 +++
.../assets/icons/typicons_light/icons.conf | 1 +
.../icons/typicons_light/typ_th-dot-more.svg | 4 +++
novelwriter/dialogs/preferences.py | 32 +++++++++----------
novelwriter/extensions/configlayout.py | 8 +++--
novelwriter/gui/theme.py | 2 +-
7 files changed, 32 insertions(+), 20 deletions(-)
create mode 100644 novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg
create mode 100644 novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg
diff --git a/novelwriter/assets/icons/typicons_dark/icons.conf b/novelwriter/assets/icons/typicons_dark/icons.conf
index 44317317..f66874ee 100644
--- a/novelwriter/assets/icons/typicons_dark/icons.conf
+++ b/novelwriter/assets/icons/typicons_dark/icons.conf
@@ -61,6 +61,7 @@ forward = typ_chevron-right.svg
maximise = typ_arrow-maximise.svg
menu = typ_th-dot-menu.svg
minimise = typ_arrow-minimise.svg
+more = typ_th-dot-more.svg
noncheckable = mixed_input-none.svg
panel = nw_panel.svg
proj_chapter = mixed_document-chapter.svg
diff --git a/novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg b/novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg
new file mode 100644
index 00000000..782996dc
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_light/icons.conf b/novelwriter/assets/icons/typicons_light/icons.conf
index 7579bb70..3a6837d9 100644
--- a/novelwriter/assets/icons/typicons_light/icons.conf
+++ b/novelwriter/assets/icons/typicons_light/icons.conf
@@ -61,6 +61,7 @@ forward = typ_chevron-right.svg
maximise = typ_arrow-maximise.svg
menu = typ_th-dot-menu.svg
minimise = typ_arrow-minimise.svg
+more = typ_th-dot-more.svg
noncheckable = mixed_input-none.svg
panel = nw_panel.svg
proj_chapter = mixed_document-chapter.svg
diff --git a/novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg b/novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg
new file mode 100644
index 00000000..779bc23f
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py
index 633253c0..34349c91 100644
--- a/novelwriter/dialogs/preferences.py
+++ b/novelwriter/dialogs/preferences.py
@@ -31,7 +31,7 @@ from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import (
QAbstractButton, QComboBox, QCompleter, QDialog, QDialogButtonBox,
QDoubleSpinBox, QFileDialog, QFontDialog, QHBoxLayout, QLabel, QLineEdit,
- QPushButton, QSpinBox, QVBoxLayout, QWidget, qApp
+ QPushButton, QSpinBox, QToolButton, QVBoxLayout, QWidget, qApp
)
from novelwriter import CONFIG, SHARED
@@ -54,7 +54,7 @@ class GuiPreferences(QDialog):
logger.debug("Create: GuiPreferences")
self.setObjectName("GuiPreferences")
- self.setWindowTitle(CONFIG.appName)
+ self.setWindowTitle(self.tr("Preferences"))
self.setMinimumSize(CONFIG.pxInt(600), CONFIG.pxInt(500))
self.resize(*CONFIG.preferencesWinSize)
@@ -139,6 +139,7 @@ class GuiPreferences(QDialog):
"""Build the settings form."""
section = 0
minWidth = CONFIG.pxInt(200)
+ mIcon = SHARED.theme.getIcon("more")
# Appearance
# ==========
@@ -181,8 +182,8 @@ class GuiPreferences(QDialog):
self.guiFont.setReadOnly(True)
self.guiFont.setFixedWidth(CONFIG.pxInt(162))
self.guiFont.setText(CONFIG.guiFont)
- self.guiFontButton = QPushButton("...", self)
- self.guiFontButton.setMaximumWidth(int(2.5*SHARED.theme.getTextWidth("...")))
+ self.guiFontButton = QToolButton(self)
+ self.guiFontButton.setIcon(mIcon)
self.guiFontButton.clicked.connect(self._selectGuiFont)
self.mainForm.addRow(
self.tr("Application font family"),
@@ -249,8 +250,8 @@ class GuiPreferences(QDialog):
self.textFont.setReadOnly(True)
self.textFont.setFixedWidth(CONFIG.pxInt(162))
self.textFont.setText(CONFIG.textFont)
- self.textFontButton = QPushButton("...", self)
- self.textFontButton.setMaximumWidth(int(2.5*SHARED.theme.getTextWidth("...")))
+ self.textFontButton = QToolButton(self)
+ self.textFontButton.setIcon(mIcon)
self.textFontButton.clicked.connect(self._selectTextFont)
self.mainForm.addRow(
self.tr("Document font family"),
@@ -722,9 +723,8 @@ class GuiPreferences(QDialog):
self.sidebar.addButton(title, section)
self.mainForm.addGroupLabel(title, section)
- qWidth = CONFIG.pxInt(40)
- bWidth = int(2.5*SHARED.theme.getTextWidth("..."))
self.quoteSym = {}
+ qWidth = CONFIG.pxInt(40)
# Single Quote Style
self.quoteSym["SO"] = QLineEdit(self)
@@ -733,8 +733,8 @@ class GuiPreferences(QDialog):
self.quoteSym["SO"].setFixedWidth(qWidth)
self.quoteSym["SO"].setAlignment(Qt.AlignCenter)
self.quoteSym["SO"].setText(CONFIG.fmtSQuoteOpen)
- self.btnSingleStyleO = QPushButton("...", self)
- self.btnSingleStyleO.setMaximumWidth(bWidth)
+ self.btnSingleStyleO = QToolButton(self)
+ self.btnSingleStyleO.setIcon(mIcon)
self.btnSingleStyleO.clicked.connect(lambda: self._getQuote("SO"))
self.mainForm.addRow(
self.tr("Single quote open style"),
@@ -749,8 +749,8 @@ class GuiPreferences(QDialog):
self.quoteSym["SC"].setFixedWidth(qWidth)
self.quoteSym["SC"].setAlignment(Qt.AlignCenter)
self.quoteSym["SC"].setText(CONFIG.fmtSQuoteClose)
- self.btnSingleStyleC = QPushButton("...", self)
- self.btnSingleStyleC.setMaximumWidth(bWidth)
+ self.btnSingleStyleC = QToolButton(self)
+ self.btnSingleStyleC.setIcon(mIcon)
self.btnSingleStyleC.clicked.connect(lambda: self._getQuote("SC"))
self.mainForm.addRow(
self.tr("Single quote close style"),
@@ -766,8 +766,8 @@ class GuiPreferences(QDialog):
self.quoteSym["DO"].setFixedWidth(qWidth)
self.quoteSym["DO"].setAlignment(Qt.AlignCenter)
self.quoteSym["DO"].setText(CONFIG.fmtDQuoteOpen)
- self.btnDoubleStyleO = QPushButton("...", self)
- self.btnDoubleStyleO.setMaximumWidth(bWidth)
+ self.btnDoubleStyleO = QToolButton(self)
+ self.btnDoubleStyleO.setIcon(mIcon)
self.btnDoubleStyleO.clicked.connect(lambda: self._getQuote("DO"))
self.mainForm.addRow(
self.tr("Double quote open style"),
@@ -782,8 +782,8 @@ class GuiPreferences(QDialog):
self.quoteSym["DC"].setFixedWidth(qWidth)
self.quoteSym["DC"].setAlignment(Qt.AlignCenter)
self.quoteSym["DC"].setText(CONFIG.fmtDQuoteClose)
- self.btnDoubleStyleC = QPushButton("...", self)
- self.btnDoubleStyleC.setMaximumWidth(bWidth)
+ self.btnDoubleStyleC = QToolButton(self)
+ self.btnDoubleStyleC.setIcon(mIcon)
self.btnDoubleStyleC.clicked.connect(lambda: self._getQuote("DC"))
self.mainForm.addRow(
self.tr("Double quote close style"),
diff --git a/novelwriter/extensions/configlayout.py b/novelwriter/extensions/configlayout.py
index 9cb932cd..d714cf00 100644
--- a/novelwriter/extensions/configlayout.py
+++ b/novelwriter/extensions/configlayout.py
@@ -122,15 +122,16 @@ class NScrollableForm(QScrollArea):
button: QWidget | None = None, editable: str | None = None) -> None:
"""Add a label and a widget as a new row of the form."""
row = QHBoxLayout()
+ row.setSpacing(CONFIG.pxInt(4))
- wSp = CONFIG.pxInt(12)
+ mPx = CONFIG.pxInt(12)
qLabel = QLabel(label, self)
- qLabel.setIndent(wSp)
+ qLabel.setIndent(mPx)
qLabel.setBuddy(widget)
if helpText:
qHelp = NHelpLabel(str(helpText), self._helpCol, self._fontScale)
- qHelp.setIndent(wSp)
+ qHelp.setIndent(mPx)
labelBox = QVBoxLayout()
labelBox.addWidget(qLabel)
labelBox.addWidget(qHelp)
@@ -142,6 +143,7 @@ class NScrollableForm(QScrollArea):
else:
row.addWidget(qLabel)
+ row.addSpacing(mPx)
row.addWidget(widget)
if isinstance(unit, str):
diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py
index 65642ea4..2e3e8b94 100644
--- a/novelwriter/gui/theme.py
+++ b/novelwriter/gui/theme.py
@@ -466,7 +466,7 @@ class GuiIcons:
# General Button Icons
"add", "add_document", "backward", "bookmark", "browse", "checked", "close", "cross",
- "document", "down", "edit", "export", "forward", "maximise", "menu", "minimise",
+ "document", "down", "edit", "export", "forward", "maximise", "menu", "minimise", "more",
"noncheckable", "panel", "refresh", "remove", "revert", "search_replace", "search",
"settings", "star", "unchecked", "up", "view",
From 7b5b82136fc1ad87d805a24ac0866a47035518a5 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sat, 13 Jan 2024 18:28:41 +0100
Subject: [PATCH 10/13] Update preferences dialog and quotes selection dialog
---
novelwriter/constants.py | 3 ++
novelwriter/dialogs/preferences.py | 20 ++++++------
novelwriter/dialogs/quotes.py | 42 ++++++++++++++------------
novelwriter/extensions/pagedsidebar.py | 4 +++
4 files changed, 41 insertions(+), 28 deletions(-)
diff --git a/novelwriter/constants.py b/novelwriter/constants.py
index ce12ced8..b133fcb9 100644
--- a/novelwriter/constants.py
+++ b/novelwriter/constants.py
@@ -56,6 +56,9 @@ class nwConst:
# Gui Settings
STATUS_MSG_TIMEOUT = 15000 # milliseconds
+ # Dialogs
+ DLG_FINISHED = 2
+
# END Class nwConst
diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py
index 34349c91..635f0496 100644
--- a/novelwriter/dialogs/preferences.py
+++ b/novelwriter/dialogs/preferences.py
@@ -35,6 +35,7 @@ from PyQt5.QtWidgets import (
)
from novelwriter import CONFIG, SHARED
+from novelwriter.constants import nwConst, nwUnicode
from novelwriter.dialogs.quotes import GuiQuoteSelect
from novelwriter.extensions.switch import NSwitch
from novelwriter.extensions.configlayout import NScrollableForm
@@ -498,7 +499,7 @@ class GuiPreferences(QDialog):
for tag, language in SHARED.spelling.listDictionaries():
self.spellLanguage.addItem(language, tag)
else:
- self.spellLanguage.addItem(self.tr("None"), "")
+ self.spellLanguage.addItem(nwUnicode.U_EMDASH, "")
self.spellLanguage.setEnabled(False)
if (idx := self.spellLanguage.findData(CONFIG.spellLanguage)) != -1:
@@ -807,12 +808,13 @@ class GuiPreferences(QDialog):
self._saveWindowSize()
event.accept()
qApp.processEvents()
+ self.done(nwConst.DLG_FINISHED)
self.deleteLater()
return
def keyPressEvent(self, event: QKeyEvent) -> None:
"""Overload keyPressEvent to block enter key to save."""
- if event.matches(QKeySequence.Cancel):
+ if event.matches(QKeySequence.StandardKey.Cancel):
self.close()
event.ignore()
return
@@ -825,12 +827,12 @@ class GuiPreferences(QDialog):
def _dialogButtonClicked(self, button: QAbstractButton) -> None:
"""Handle button clicks from the dialog button box."""
role = self.buttonBox.buttonRole(button)
- if role == QDialogButtonBox.ApplyRole:
+ if role == QDialogButtonBox.ButtonRole.ApplyRole:
self._saveValues()
- elif role == QDialogButtonBox.AcceptRole:
+ elif role == QDialogButtonBox.ButtonRole.AcceptRole:
self._saveValues()
self.close()
- elif role == QDialogButtonBox.RejectRole:
+ elif role == QDialogButtonBox.ButtonRole.RejectRole:
self.close()
return
@@ -874,7 +876,7 @@ class GuiPreferences(QDialog):
def _backupFolder(self) -> None:
"""Open a dialog to select the backup folder."""
if path := QFileDialog.getExistingDirectory(
- self, self.tr("Backup Directory"), str(self.backupPath or ""),
+ self, self.tr("Backup Directory"), str(self.backupPath) or "",
options=QFileDialog.ShowDirsOnly
):
self.backupPath = path
@@ -906,9 +908,9 @@ class GuiPreferences(QDialog):
def _getQuote(self, qType: str) -> None:
"""Dialog for single quote open."""
- quote = GuiQuoteSelect(self, currentQuote=self.quoteSym[qType].text())
- if quote.exec_() == QDialog.Accepted:
- self.quoteSym[qType].setText(quote.selectedQuote)
+ quote, status = GuiQuoteSelect.getQuote(self, current=self.quoteSym[qType].text())
+ if status:
+ self.quoteSym[qType].setText(quote)
return
##
diff --git a/novelwriter/dialogs/quotes.py b/novelwriter/dialogs/quotes.py
index db32cc23..4fd81c59 100644
--- a/novelwriter/dialogs/quotes.py
+++ b/novelwriter/dialogs/quotes.py
@@ -25,7 +25,7 @@ from __future__ import annotations
import logging
-from PyQt5.QtGui import QCloseEvent, QFontMetrics
+from PyQt5.QtGui import QFontMetrics
from PyQt5.QtCore import QSize, Qt, pyqtSlot
from PyQt5.QtWidgets import (
QDialog, QDialogButtonBox, QFrame, QHBoxLayout, QLabel, QListWidget,
@@ -40,11 +40,11 @@ logger = logging.getLogger(__name__)
class GuiQuoteSelect(QDialog):
- selectedQuote = ""
+ _selected = ""
D_KEY = Qt.ItemDataRole.UserRole
- def __init__(self, parent: QWidget, currentQuote: str = '"') -> None:
+ def __init__(self, parent: QWidget, current: str = '"') -> None:
super().__init__(parent=parent)
logger.debug("Create: GuiQuoteSelect")
@@ -54,7 +54,7 @@ class GuiQuoteSelect(QDialog):
self.innerBox = QHBoxLayout()
self.labelBox = QVBoxLayout()
- self.selectedQuote = currentQuote
+ self._selected = current
qMetrics = QFontMetrics(self.font())
pxW = 7*qMetrics.boundingRectChar("M").width()
@@ -65,7 +65,7 @@ class GuiQuoteSelect(QDialog):
lblFont.setPointSizeF(4*lblFont.pointSizeF())
# Preview Label
- self.previewLabel = QLabel(currentQuote)
+ self.previewLabel = QLabel(current)
self.previewLabel.setFont(lblFont)
self.previewLabel.setFixedSize(QSize(pxW, pxH))
self.previewLabel.setAlignment(Qt.AlignCenter)
@@ -82,7 +82,7 @@ class GuiQuoteSelect(QDialog):
qtItem = QListWidgetItem(theText)
qtItem.setData(self.D_KEY, sKey)
self.listBox.addItem(qtItem)
- if sKey == currentQuote:
+ if sKey == current:
self.listBox.setCurrentItem(qtItem)
self.listBox.setMinimumWidth(minSize + CONFIG.pxInt(40))
@@ -113,15 +113,20 @@ class GuiQuoteSelect(QDialog):
logger.debug("Delete: GuiQuoteSelect")
return
- ##
- # Events
- ##
+ @property
+ def selectedQuote(self) -> str:
+ """Return the selected quote symbol."""
+ return self._selected
- def closeEvent(self, event: QCloseEvent) -> None:
- """Capture the close event and perform cleanup."""
- event.accept()
- self.deleteLater()
- return
+ @classmethod
+ def getQuote(cls, parent: QWidget, current: str = "") -> tuple[str, bool]:
+ """Pop the dialog and return the result."""
+ cls = GuiQuoteSelect(parent, current=current)
+ cls.exec_()
+ quote = cls._selected
+ accepted = cls.result() == QDialog.DialogCode.Accepted
+ cls.deleteLater()
+ return quote, accepted
##
# Private Slots
@@ -130,11 +135,10 @@ class GuiQuoteSelect(QDialog):
@pyqtSlot()
def _selectedSymbol(self) -> None:
"""Update the preview label and the selected quote style."""
- selItems = self.listBox.selectedItems()
- if selItems:
- theSymbol = selItems[0].data(self.D_KEY)
- self.previewLabel.setText(theSymbol)
- self.selectedQuote = theSymbol
+ if items := self.listBox.selectedItems():
+ quote = items[0].data(self.D_KEY)
+ self.previewLabel.setText(quote)
+ self._selected = quote
return
# END Class GuiQuoteSelect
diff --git a/novelwriter/extensions/pagedsidebar.py b/novelwriter/extensions/pagedsidebar.py
index 853fc7de..2f417b44 100644
--- a/novelwriter/extensions/pagedsidebar.py
+++ b/novelwriter/extensions/pagedsidebar.py
@@ -63,6 +63,10 @@ class NPagedSideBar(QToolBar):
return
+ def button(self, buttonId: int) -> _NPagedToolButton:
+ """Return a specific button."""
+ return self._buttons[buttonId]
+
def setLabelColor(self, color: list | QColor) -> None:
"""Set the text color for the labels."""
self._labelCol = color if isinstance(color, QColor) else QColor(*color)
From 2cb772847e26673b8a3062e985078edc05c6bf6f Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sat, 13 Jan 2024 18:29:06 +0100
Subject: [PATCH 11/13] Update tests
---
tests/reference/baseConfig_novelwriter.conf | 1 -
tests/test_dialogs/test_dlg_dialogs.py | 17 ++++++++++++++---
2 files changed, 14 insertions(+), 4 deletions(-)
diff --git a/tests/reference/baseConfig_novelwriter.conf b/tests/reference/baseConfig_novelwriter.conf
index 6f064701..32a9883e 100644
--- a/tests/reference/baseConfig_novelwriter.conf
+++ b/tests/reference/baseConfig_novelwriter.conf
@@ -57,7 +57,6 @@ spellcheck = en
showtabsnspaces = False
showlineendings = False
showmultispaces = True
-wordcounttimer = 5.0
incnoteswcount = True
showfullpath = True
highlightquotes = True
diff --git a/tests/test_dialogs/test_dlg_dialogs.py b/tests/test_dialogs/test_dlg_dialogs.py
index 0aa43e39..94d488ed 100644
--- a/tests/test_dialogs/test_dlg_dialogs.py
+++ b/tests/test_dialogs/test_dlg_dialogs.py
@@ -31,8 +31,10 @@ from novelwriter.dialogs.editlabel import GuiEditLabel
@pytest.mark.gui
-def testDlgOther_QuoteSelect(qtbot, nwGUI):
+def testDlgOther_QuoteSelect(qtbot, monkeypatch, nwGUI):
"""Test the quote symbols dialog."""
+ monkeypatch.setattr(GuiQuoteSelect, "exec_", lambda *a: None)
+
nwQuot = GuiQuoteSelect(nwGUI)
nwQuot.show()
@@ -41,16 +43,25 @@ def testDlgOther_QuoteSelect(qtbot, nwGUI):
anItem = nwQuot.listBox.item(i)
assert isinstance(anItem, QListWidgetItem)
nwQuot.listBox.clearSelection()
- nwQuot.listBox.setCurrentItem(anItem, QItemSelectionModel.Select)
+ nwQuot.listBox.setCurrentItem(anItem, QItemSelectionModel.SelectionFlag.Select)
lastItem = anItem.text()[2]
assert nwQuot.previewLabel.text() == lastItem
nwQuot.accept()
assert nwQuot.result() == QDialog.Accepted
assert nwQuot.selectedQuote == lastItem
+ nwQuot.close()
+
+ # Test Class Method
+ with monkeypatch.context() as mp:
+ mp.setattr(GuiQuoteSelect, "result", lambda *a: QDialog.DialogCode.Accepted)
+ assert GuiQuoteSelect.getQuote(nwGUI, current="X") == ("X", True)
+
+ with monkeypatch.context() as mp:
+ mp.setattr(GuiQuoteSelect, "result", lambda *a: QDialog.DialogCode.Rejected)
+ assert GuiQuoteSelect.getQuote(nwGUI, current="X") == ("X", False)
# qtbot.stop()
- nwQuot.close()
# END Test testDlgOther_QuoteSelect
From 34eb39e10dac9b9d0a92af23f882724d4a0419b9 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sat, 13 Jan 2024 18:29:21 +0100
Subject: [PATCH 12/13] Add new test for preferences dialog
---
.../reference/guiPreferences_novelwriter.conf | 82 ---
tests/test_dialogs/test_dlg_preferences.py | 526 ++++++++++++------
2 files changed, 361 insertions(+), 247 deletions(-)
delete mode 100644 tests/reference/guiPreferences_novelwriter.conf
diff --git a/tests/reference/guiPreferences_novelwriter.conf b/tests/reference/guiPreferences_novelwriter.conf
deleted file mode 100644
index b291bf4a..00000000
--- a/tests/reference/guiPreferences_novelwriter.conf
+++ /dev/null
@@ -1,82 +0,0 @@
-[Meta]
-timestamp = 2023-12-29 14:09:13
-
-[Main]
-theme = default
-syntax = default_light
-font = Cantarell
-fontsize = 12
-localisation = en_GB
-hidevscroll = True
-hidehscroll = True
-lastnotes = 0x0
-lastpath =
-
-[Sizes]
-mainwindow = 1200, 650
-welcome = 800, 500
-preferences = 713, 614
-mainpane = 300, 800
-viewpane = 500, 150
-outlinepane = 500, 150
-
-[Project]
-autosaveproject = 40
-autosavedoc = 20
-emphlabels = True
-backuppath = some/dir
-backuponclose = True
-askbeforebackup = True
-
-[Editor]
-textfont = None
-textsize = 13
-width = 700
-margin = 45
-tabwidth = 45
-focuswidth = 900
-hidefocusfooter = True
-justify = True
-autoselect = False
-autoreplace = False
-repsquotes = True
-repdquotes = True
-repdash = True
-repdots = True
-autoscroll = True
-autoscrollpos = 30
-scrollpastend = True
-fmtsquoteopen = ‘
-fmtsquoteclose = ’
-fmtdquoteopen = “
-fmtdquoteclose = ”
-fmtpadbefore =
-fmtpadafter =
-fmtpadthin = False
-spellcheck = en
-showtabsnspaces = True
-showlineendings = True
-showmultispaces = True
-wordcounttimer = 5.0
-incnoteswcount = True
-showfullpath = False
-highlightquotes = False
-allowopensquote = False
-allowopendquote = True
-highlightemph = False
-stopwhenidle = True
-useridletime = 300
-
-[State]
-showviewerpanel = True
-showedittoolbar = False
-useshortcodes = False
-viewcomments = True
-viewsynopsis = True
-searchcase = False
-searchword = False
-searchregex = False
-searchloop = False
-searchnextfile = False
-searchmatchcap = False
-
diff --git a/tests/test_dialogs/test_dlg_preferences.py b/tests/test_dialogs/test_dlg_preferences.py
index a56f4978..fea38412 100644
--- a/tests/test_dialogs/test_dlg_preferences.py
+++ b/tests/test_dialogs/test_dlg_preferences.py
@@ -22,191 +22,387 @@ from __future__ import annotations
import pytest
-from shutil import copyfile
-
-from tools import cmpFiles, getGuiItem
-
-from PyQt5.QtCore import Qt
-from PyQt5.QtWidgets import (
- QDialogButtonBox, QDialog, QAction, QFileDialog, QFontDialog
-)
+from PyQt5.QtGui import QFontDatabase, QKeyEvent
+from PyQt5.QtCore import QEvent, Qt
+from PyQt5.QtWidgets import QDialogButtonBox, QFileDialog, QFontDialog
from novelwriter import CONFIG, SHARED
-from novelwriter.dialogs.quotes import GuiQuoteSelect
+from novelwriter.constants import nwConst, nwUnicode
from novelwriter.dialogs.preferences import GuiPreferences
+from novelwriter.dialogs.quotes import GuiQuoteSelect
KEY_DELAY = 1
@pytest.mark.gui
def testDlgPreferences_Main(qtbot, monkeypatch, nwGUI, tstPaths):
- """Test the preferences dialog."""
- monkeypatch.setattr(GuiPreferences, "exec_", lambda *a: None)
- monkeypatch.setattr(GuiPreferences, "result", lambda *a: QDialog.Accepted)
+ """Test the preferences dialog loading."""
monkeypatch.setattr(SHARED._spelling, "listDictionaries", lambda: [("en", "English [en]")])
- nwGUI.mainMenu.aPreferences.activate(QAction.Trigger)
- qtbot.waitUntil(lambda: getGuiItem("GuiPreferences") is not None, timeout=1000)
- nwPrefs = getGuiItem("GuiPreferences")
- assert isinstance(nwPrefs, GuiPreferences)
- nwPrefs.show()
+ # Load GUI with standard values
+ prefs = GuiPreferences(nwGUI)
+ prefs.show()
- # General Settings
- qtbot.wait(KEY_DELAY)
- tabGeneral = nwPrefs.tabGeneral
- nwPrefs._tabBox.setCurrentWidget(tabGeneral)
+ # Check Languages
+ languages = [prefs.guiLocale.itemData(i) for i in range(prefs.guiLocale.count())]
+ assert len(languages) > 0
+ assert "en_GB" in languages
- qtbot.wait(KEY_DELAY)
- assert tabGeneral.showFullPath.isChecked()
- qtbot.mouseClick(tabGeneral.showFullPath, Qt.LeftButton)
- assert not tabGeneral.showFullPath.isChecked()
+ # Check GUI Themes
+ themes = [prefs.guiTheme.itemData(i) for i in range(prefs.guiTheme.count())]
+ assert len(themes) >= 5
+ assert "default" in themes
- qtbot.wait(KEY_DELAY)
- assert not tabGeneral.hideVScroll.isChecked()
- qtbot.mouseClick(tabGeneral.hideVScroll, Qt.LeftButton)
- assert tabGeneral.hideVScroll.isChecked()
+ # Check GUI Syntax
+ syntax = [prefs.guiSyntax.itemData(i) for i in range(prefs.guiSyntax.count())]
+ assert len(syntax) >= 10
+ assert "default_dark" in syntax
+ assert "default_light" in syntax
- qtbot.wait(KEY_DELAY)
- assert not tabGeneral.hideHScroll.isChecked()
- qtbot.mouseClick(tabGeneral.hideHScroll, Qt.LeftButton)
- assert tabGeneral.hideHScroll.isChecked()
+ # Check Spell Checking
+ spelling = [prefs.spellLanguage.itemData(i) for i in range(prefs.spellLanguage.count())]
+ assert len(spelling) == 1
+ assert spelling == ["en"]
- # Check font button
- monkeypatch.setattr(QFontDialog, "getFont", lambda font, obj: (font, True))
- qtbot.mouseClick(tabGeneral.fontButton, Qt.LeftButton)
+ prefs.close()
- qtbot.wait(KEY_DELAY)
- tabGeneral.guiFontSize.setValue(12)
+ # Check Fallback Values
+ with monkeypatch.context() as mp:
+ mp.setattr(CONFIG, "hasEnchant", False)
+ prefs = GuiPreferences(nwGUI)
+ prefs.show()
- # Projects Settings
- qtbot.wait(KEY_DELAY)
- tabProjects = nwPrefs.tabProjects
- nwPrefs._tabBox.setCurrentWidget(tabProjects)
- tabProjects.backupPath = "no/where"
+ # Check Spell Checking
+ spelling = [prefs.spellLanguage.itemData(i) for i in range(prefs.spellLanguage.count())]
+ assert len(spelling) == 1
+ assert spelling == [""]
- qtbot.wait(KEY_DELAY)
- assert not tabProjects.backupOnClose.isChecked()
- qtbot.mouseClick(tabProjects.backupOnClose, Qt.LeftButton)
- assert tabProjects.backupOnClose.isChecked()
-
- # Check Browse button
- monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *a, **k: "")
- assert not tabProjects._backupFolder()
- monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *a, **k: "some/dir")
- qtbot.mouseClick(tabProjects.backupGetPath, Qt.LeftButton)
-
- qtbot.wait(KEY_DELAY)
- tabProjects.autoSaveDoc.setValue(20)
- tabProjects.autoSaveProj.setValue(40)
-
- # Document Settings
- qtbot.wait(KEY_DELAY)
- tabDocs = nwPrefs.tabDocs
- nwPrefs._tabBox.setCurrentWidget(tabDocs)
-
- qtbot.wait(KEY_DELAY)
- qtbot.mouseClick(tabDocs.fontButton, Qt.LeftButton)
-
- qtbot.wait(KEY_DELAY)
- tabDocs.textSize.setValue(13)
- tabDocs.textWidth.setValue(700)
- tabDocs.focusWidth.setValue(900)
- tabDocs.textMargin.setValue(45)
- tabDocs.tabWidth.setValue(45)
-
- qtbot.wait(KEY_DELAY)
- assert not tabDocs.hideFocusFooter.isChecked()
- qtbot.mouseClick(tabDocs.hideFocusFooter, Qt.LeftButton)
- assert tabDocs.hideFocusFooter.isChecked()
-
- qtbot.wait(KEY_DELAY)
- assert not tabDocs.doJustify.isChecked()
- qtbot.mouseClick(tabDocs.doJustify, Qt.LeftButton)
- assert tabDocs.doJustify.isChecked()
-
- # Editor Settings
- qtbot.wait(KEY_DELAY)
- tabEditor = nwPrefs.tabEditor
- nwPrefs._tabBox.setCurrentWidget(tabEditor)
-
- qtbot.wait(KEY_DELAY)
- assert not tabEditor.showTabsNSpaces.isChecked()
- qtbot.mouseClick(tabEditor.showTabsNSpaces, Qt.LeftButton)
- assert tabEditor.showTabsNSpaces.isChecked()
-
- qtbot.wait(KEY_DELAY)
- assert not tabEditor.showLineEndings.isChecked()
- qtbot.mouseClick(tabEditor.showLineEndings, Qt.LeftButton)
- assert tabEditor.showLineEndings.isChecked()
-
- qtbot.wait(KEY_DELAY)
- assert not tabEditor.autoScroll.isChecked()
- qtbot.mouseClick(tabEditor.autoScroll, Qt.LeftButton)
- assert tabEditor.autoScroll.isChecked()
-
- # Syntax Settings
- qtbot.wait(KEY_DELAY)
- tabSyntax = nwPrefs.tabSyntax
- nwPrefs._tabBox.setCurrentWidget(tabSyntax)
-
- qtbot.wait(KEY_DELAY)
- assert tabSyntax.highlightQuotes.isChecked()
- qtbot.mouseClick(tabSyntax.highlightQuotes, Qt.LeftButton)
- assert not tabSyntax.highlightQuotes.isChecked()
-
- qtbot.wait(KEY_DELAY)
- assert tabSyntax.highlightEmph.isChecked()
- qtbot.mouseClick(tabSyntax.highlightEmph, Qt.LeftButton)
- assert not tabSyntax.highlightEmph.isChecked()
-
- # Automation Settings
- qtbot.wait(KEY_DELAY)
- tabAuto = nwPrefs.tabAuto
- nwPrefs._tabBox.setCurrentWidget(tabAuto)
-
- qtbot.wait(KEY_DELAY)
- assert tabAuto.autoSelect.isChecked()
- qtbot.mouseClick(tabAuto.autoSelect, Qt.LeftButton)
- assert not tabAuto.autoSelect.isChecked()
-
- qtbot.wait(KEY_DELAY)
- assert tabAuto.doReplace.isChecked()
- qtbot.mouseClick(tabAuto.doReplace, Qt.LeftButton)
- assert not tabAuto.doReplace.isChecked()
-
- qtbot.wait(KEY_DELAY)
- assert not tabAuto.doReplaceSQuote.isEnabled()
- assert not tabAuto.doReplaceDQuote.isEnabled()
- assert not tabAuto.doReplaceDash.isEnabled()
- assert not tabAuto.doReplaceDots.isEnabled()
-
- # Quotation Style
- qtbot.wait(KEY_DELAY)
- tabQuote = nwPrefs.tabQuote
- nwPrefs._tabBox.setCurrentWidget(tabQuote)
-
- monkeypatch.setattr(GuiQuoteSelect, "selectedQuote", "'")
- monkeypatch.setattr(GuiQuoteSelect, "exec_", lambda *a: QDialog.Accepted)
- qtbot.mouseClick(tabQuote.btnDoubleStyleC, Qt.LeftButton)
-
- # Save and Check Config
- qtbot.mouseClick(nwPrefs.buttonBox.button(QDialogButtonBox.Ok), Qt.LeftButton)
-
- assert CONFIG.saveConfig()
- projFile = tstPaths.cnfDir / "novelwriter.conf"
- testFile = tstPaths.outDir / "guiPreferences_novelwriter.conf"
- compFile = tstPaths.refDir / "guiPreferences_novelwriter.conf"
- copyfile(projFile, testFile)
- ignTuple = (
- "timestamp", "font", "lastnotes", "localisation", "geometry",
- "preferences", "projcols", "mainpane", "docpane", "viewpane",
- "outlinepane", "textfont", "textsize", "lastpath", "backuppath"
- )
- assert cmpFiles(testFile, compFile, ignoreStart=ignTuple)
-
- # Clean up
- nwGUI.closeMain()
+ prefs.close()
# qtbot.stop()
# END Test testDlgPreferences_Main
+
+
+@pytest.mark.gui
+def testDlgPreferences_Actions(qtbot, monkeypatch, nwGUI):
+ """Test the preferences dialog actions."""
+ monkeypatch.setattr(SHARED._spelling, "listDictionaries", lambda: [("en", "English [en]")])
+ monkeypatch.setattr(GuiPreferences, "deleteLater", lambda *a: None)
+ prefs = GuiPreferences(nwGUI)
+ prefs.show()
+
+ # Check Navigation
+ vBar = prefs.mainForm.verticalScrollBar()
+ old = -1
+ with qtbot.waitSignal(vBar.valueChanged) as value:
+ prefs.sidebar.button(0).click()
+ assert value.args[0] > old
+ old = value.args[0]
+ with qtbot.waitSignal(vBar.valueChanged) as value:
+ prefs.sidebar.button(1).click()
+ assert value.args[0] > old
+ old = value.args[0]
+ with qtbot.waitSignal(vBar.valueChanged) as value:
+ prefs.sidebar.button(2).click()
+ assert value.args[0] > old
+ old = value.args[0]
+
+ # Check Search
+ prefs.searchText.setText("Display language")
+ with qtbot.waitSignal(vBar.valueChanged) as value:
+ prefs._gotoSearch()
+ assert value.args[0] < old
+
+ # Check Apply Button
+ prefs.show()
+ with qtbot.waitSignal(prefs.newPreferencesReady) as signal:
+ prefs.buttonBox.button(QDialogButtonBox.StandardButton.Apply).click()
+ assert signal.args == [False, False, False, False]
+
+ # Check Save Button
+ prefs.show()
+ with qtbot.waitSignal(prefs.newPreferencesReady) as signal:
+ with qtbot.waitSignal(prefs.finished) as status:
+ prefs.buttonBox.button(QDialogButtonBox.StandardButton.Save).click()
+ assert signal.args == [False, False, False, False]
+ assert status.args == [nwConst.DLG_FINISHED]
+
+ # Check Close Button
+ prefs.show()
+ with qtbot.waitSignal(prefs.finished) as status:
+ prefs.buttonBox.button(QDialogButtonBox.StandardButton.Close).click()
+ assert status.args == [nwConst.DLG_FINISHED]
+
+ # Close Using Escape Key
+ prefs.show()
+ with qtbot.waitSignal(prefs.finished) as status:
+ event = QKeyEvent(QEvent.Type.KeyPress, Qt.Key.Key_Escape, Qt.KeyboardModifier.NoModifier)
+ prefs.keyPressEvent(event)
+ assert status.args == [nwConst.DLG_FINISHED]
+
+ # qtbot.stop()
+
+# END Test testDlgPreferences_Actions
+
+
+@pytest.mark.gui
+def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, tstPaths):
+ """Test the preferences dialog settings."""
+ spelling = [("en", "English [en]"), ("de", "Deutch [de]")]
+
+ monkeypatch.setattr(SHARED._spelling, "listDictionaries", lambda: spelling)
+ monkeypatch.setattr(GuiPreferences, "deleteLater", lambda *a: None)
+
+ prefs = GuiPreferences(nwGUI)
+ prefs.show()
+
+ # Mock Font
+ class MockFont:
+
+ def family(self):
+ return "TestFont"
+
+ def pointSize(self):
+ return 42
+
+ # Appearance
+ prefs.guiLocale.setCurrentIndex(prefs.guiLocale.findData("en_US"))
+ prefs.guiTheme.setCurrentIndex(prefs.guiTheme.findData("default_dark"))
+ with monkeypatch.context() as mp:
+ mp.setattr(QFontDialog, "getFont", lambda *a: (MockFont(), True))
+ prefs.guiFontButton.click()
+ prefs.guiFontSize.stepDown() # Should change it to 41
+ prefs.hideVScroll.setChecked(True)
+ prefs.hideHScroll.setChecked(True)
+
+ assert CONFIG.guiLocale != "en_US"
+ assert CONFIG.guiTheme != "default_dark"
+ assert CONFIG.guiFont != "TestFont"
+ assert CONFIG.guiFontSize < 42
+ assert CONFIG.hideVScroll is False
+ assert CONFIG.hideHScroll is False
+
+ # Document Style
+ prefs.guiSyntax.setCurrentIndex(prefs.guiSyntax.findData("default_dark"))
+ with monkeypatch.context() as mp:
+ mp.setattr(QFontDialog, "getFont", lambda *a: (MockFont(), True))
+ prefs.textFontButton.click()
+ prefs.textSize.stepDown() # Should change it to 41
+ prefs.emphLabels.setChecked(False)
+ prefs.showFullPath.setChecked(False)
+ prefs.incNotesWCount.setChecked(False)
+
+ assert CONFIG.guiSyntax != "default_dark"
+ assert CONFIG.textFont != "testFont"
+ assert CONFIG.textSize < 42
+ assert CONFIG.emphLabels is True
+ assert CONFIG.showFullPath is True
+ assert CONFIG.incNotesWCount is True
+
+ # Auto Save
+ prefs.autoSaveDoc.stepUp()
+ prefs.autoSaveProj.stepUp()
+
+ assert CONFIG.autoSaveDoc == 30
+ assert CONFIG.autoSaveProj == 60
+
+ # Project Backup
+ with monkeypatch.context() as mp:
+ mp.setattr(QFileDialog, "getExistingDirectory", lambda *a, **k: str(tstPaths.testDir))
+ prefs.backupGetPath.click()
+ assert prefs.backupPath == str(tstPaths.testDir)
+ prefs.backupOnClose.setChecked(True)
+ assert prefs.askBeforeBackup.isEnabled() is True
+ prefs.askBeforeBackup.setChecked(False)
+
+ assert CONFIG._backupPath != tstPaths.testDir
+ assert CONFIG.backupOnClose is False
+ assert CONFIG.askBeforeBackup is True
+
+ # Session Timer
+ prefs.stopWhenIdle.setChecked(False)
+ prefs.userIdleTime.stepUp()
+
+ assert CONFIG.stopWhenIdle is True
+ assert CONFIG.userIdleTime == 300
+
+ # Text Flow
+ prefs.textWidth.stepDown()
+ prefs.focusWidth.stepDown()
+ prefs.hideFocusFooter.setChecked(True)
+ prefs.doJustify.setChecked(True)
+ prefs.textMargin.stepUp()
+ prefs.tabWidth.stepUp()
+
+ assert CONFIG.textWidth == 700
+ assert CONFIG.focusWidth == 800
+ assert CONFIG.hideFocusFooter is False
+ assert CONFIG.doJustify is False
+ assert CONFIG.textMargin == 40
+ assert CONFIG.tabWidth == 40
+
+ # Text Editing
+ prefs.spellLanguage.setCurrentIndex(prefs.spellLanguage.findData("de"))
+ prefs.autoSelect.setChecked(False)
+ prefs.showTabsNSpaces.setChecked(True)
+ prefs.showLineEndings.setChecked(True)
+
+ assert CONFIG.spellLanguage != "de"
+ assert CONFIG.autoSelect is True
+ assert CONFIG.showTabsNSpaces is False
+ assert CONFIG.showLineEndings is False
+
+ # Editor Scrolling
+ prefs.scrollPastEnd.setChecked(False)
+ prefs.autoScroll.setChecked(True)
+ prefs.autoScrollPos.stepUp()
+
+ assert CONFIG.scrollPastEnd is True
+ assert CONFIG.autoScroll is False
+ assert CONFIG.autoScrollPos == 30
+
+ # Text Highlighting
+ prefs.allowOpenSQuote.setChecked(True)
+ prefs.allowOpenDQuote.setChecked(False)
+ prefs.highlightQuotes.setChecked(False)
+ prefs.highlightEmph.setChecked(False)
+ prefs.showMultiSpaces.setChecked(False)
+
+ assert prefs.allowOpenSQuote.isEnabled() is False
+ assert prefs.allowOpenDQuote.isEnabled() is False
+
+ assert CONFIG.highlightQuotes is True
+ assert CONFIG.allowOpenSQuote is False
+ assert CONFIG.allowOpenDQuote is True
+ assert CONFIG.highlightEmph is True
+ assert CONFIG.showMultiSpaces is True
+
+ # Text Automation
+ prefs.doReplaceSQuote.setChecked(False)
+ prefs.doReplaceDQuote.setChecked(False)
+ prefs.doReplaceDash.setChecked(False)
+ prefs.doReplaceDots.setChecked(False)
+ prefs.doReplace.setChecked(False)
+ prefs.fmtPadBefore.setText("!?:")
+ prefs.fmtPadAfter.setText("¡¿")
+ prefs.fmtPadThin.setChecked(True)
+
+ assert prefs.doReplaceSQuote.isEnabled() is False
+ assert prefs.doReplaceDQuote.isEnabled() is False
+ assert prefs.doReplaceDash.isEnabled() is False
+ assert prefs.doReplaceDots.isEnabled() is False
+ assert prefs.fmtPadThin.isEnabled() is False
+
+ assert CONFIG.doReplace is True
+ assert CONFIG.doReplaceSQuote is True
+ assert CONFIG.doReplaceDQuote is True
+ assert CONFIG.doReplaceDash is True
+ assert CONFIG.doReplaceDots is True
+ assert CONFIG.fmtPadBefore == ""
+ assert CONFIG.fmtPadAfter == ""
+ assert CONFIG.fmtPadThin is False
+
+ # Quotation Style
+ with monkeypatch.context() as mp:
+ mp.setattr(GuiQuoteSelect, "getQuote", lambda *a, **k: (nwUnicode.U_LSAQUO, True))
+ prefs.btnSingleStyleO.click()
+ with monkeypatch.context() as mp:
+ mp.setattr(GuiQuoteSelect, "getQuote", lambda *a, **k: (nwUnicode.U_RSAQUO, True))
+ prefs.btnSingleStyleC.click()
+ with monkeypatch.context() as mp:
+ mp.setattr(GuiQuoteSelect, "getQuote", lambda *a, **k: (nwUnicode.U_LAQUO, True))
+ prefs.btnDoubleStyleO.click()
+ with monkeypatch.context() as mp:
+ mp.setattr(GuiQuoteSelect, "getQuote", lambda *a, **k: (nwUnicode.U_RAQUO, True))
+ prefs.btnDoubleStyleC.click()
+
+ assert CONFIG.fmtSQuoteOpen == nwUnicode.U_LSQUO
+ assert CONFIG.fmtSQuoteClose == nwUnicode.U_RSQUO
+ assert CONFIG.fmtDQuoteOpen == nwUnicode.U_LDQUO
+ assert CONFIG.fmtDQuoteClose == nwUnicode.U_RDQUO
+
+ # Save Settings
+ with monkeypatch.context() as mp:
+ mp.setattr(QFontDatabase, "families", lambda *a: ["TestFont"])
+ with qtbot.waitSignal(prefs.newPreferencesReady) as signal:
+ prefs.buttonBox.button(QDialogButtonBox.StandardButton.Apply).click()
+ assert signal.args == [True, True, True, True]
+
+ # Check Settings
+ # ==============
+
+ # Appearance
+ assert CONFIG.guiLocale == "en_US"
+ assert CONFIG.guiTheme == "default_dark"
+ assert CONFIG.guiFont == "TestFont"
+ assert CONFIG.guiFontSize == 41
+ assert CONFIG.hideVScroll is True
+ assert CONFIG.hideHScroll is True
+
+ # Document Style
+ assert CONFIG.guiSyntax == "default_dark"
+ assert CONFIG.textFont == "TestFont"
+ assert CONFIG.textSize == 41
+ assert CONFIG.emphLabels is False
+ assert CONFIG.showFullPath is False
+ assert CONFIG.incNotesWCount is False
+
+ # Auto Save
+ assert CONFIG.autoSaveDoc == 31
+ assert CONFIG.autoSaveProj == 61
+
+ # Project Backup
+ assert CONFIG._backupPath == tstPaths.testDir
+ assert CONFIG.backupOnClose is True
+ assert CONFIG.askBeforeBackup is False
+
+ # Session Timer
+ assert CONFIG.stopWhenIdle is False
+ assert CONFIG.userIdleTime == 330
+
+ # Text Flow
+ assert CONFIG.textWidth == 690
+ assert CONFIG.focusWidth == 790
+ assert CONFIG.hideFocusFooter is True
+ assert CONFIG.doJustify is True
+ assert CONFIG.textMargin == 41
+ assert CONFIG.tabWidth == 41
+
+ # Text Editing
+ assert CONFIG.spellLanguage == "de"
+ assert CONFIG.autoSelect is False
+ assert CONFIG.showTabsNSpaces is True
+ assert CONFIG.showLineEndings is True
+
+ # Editor Scrolling
+ assert CONFIG.scrollPastEnd is False
+ assert CONFIG.autoScroll is True
+ assert CONFIG.autoScrollPos == 31
+
+ # Text Highlighting
+ assert CONFIG.highlightQuotes is False
+ assert CONFIG.allowOpenSQuote is True
+ assert CONFIG.allowOpenDQuote is False
+ assert CONFIG.highlightEmph is False
+ assert CONFIG.showMultiSpaces is False
+
+ # Text Automation
+ assert CONFIG.doReplace is False
+ assert CONFIG.doReplaceSQuote is False
+ assert CONFIG.doReplaceDQuote is False
+ assert CONFIG.doReplaceDash is False
+ assert CONFIG.doReplaceDots is False
+ assert CONFIG.fmtPadBefore == "!?:"
+ assert CONFIG.fmtPadAfter == "¡¿"
+ assert CONFIG.fmtPadThin is True
+
+ # Quotation Style
+ assert CONFIG.fmtSQuoteOpen == nwUnicode.U_LSAQUO
+ assert CONFIG.fmtSQuoteClose == nwUnicode.U_RSAQUO
+ assert CONFIG.fmtDQuoteOpen == nwUnicode.U_LAQUO
+ assert CONFIG.fmtDQuoteClose == nwUnicode.U_RAQUO
+
+ # qtbot.stop()
+
+# END Test testDlgPreferences_Settings
From 16461279cfc94005bf6fc16c39da7b393a6a1b85 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sat, 13 Jan 2024 18:38:34 +0100
Subject: [PATCH 13/13] Remove dependency of i18n files in preferences test
---
tests/test_dialogs/test_dlg_preferences.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/tests/test_dialogs/test_dlg_preferences.py b/tests/test_dialogs/test_dlg_preferences.py
index fea38412..c4c2e590 100644
--- a/tests/test_dialogs/test_dlg_preferences.py
+++ b/tests/test_dialogs/test_dlg_preferences.py
@@ -150,8 +150,10 @@ def testDlgPreferences_Actions(qtbot, monkeypatch, nwGUI):
def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, tstPaths):
"""Test the preferences dialog settings."""
spelling = [("en", "English [en]"), ("de", "Deutch [de]")]
+ languages = [("en_GB", "British English"), ("en_US", "US English")]
monkeypatch.setattr(SHARED._spelling, "listDictionaries", lambda: spelling)
+ monkeypatch.setattr(CONFIG, "listLanguages", lambda *a: languages)
monkeypatch.setattr(GuiPreferences, "deleteLater", lambda *a: None)
prefs = GuiPreferences(nwGUI)