Create a scrollable config layout and start adding it to preferences

This commit is contained in:
Veronica Berglyd Olsen
2024-01-08 19:12:39 +01:00
parent 5c99e2186a
commit 1482451e87
2 changed files with 264 additions and 42 deletions
+168 -38
View File
@@ -28,20 +28,23 @@ import logging
from PyQt5.QtGui import QCloseEvent, QFont from PyQt5.QtGui import QCloseEvent, QFont
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import ( 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 QLineEdit, QFileDialog, QFontDialog, QDoubleSpinBox, qApp
) )
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.dialogs.quotes import GuiQuoteSelect from novelwriter.dialogs.quotes import GuiQuoteSelect
from novelwriter.extensions.pagedsidebar import NPagedSideBar
from novelwriter.extensions.switch import NSwitch from novelwriter.extensions.switch import NSwitch
from novelwriter.extensions.pageddialog import NPagedDialog from novelwriter.extensions.pageddialog import NPagedDialog
from novelwriter.extensions.configlayout import NConfigLayout from novelwriter.extensions.configlayout import NConfigLayout, NScrollableForm
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class GuiPreferences(NPagedDialog): class GuiPreferences(QDialog):
NAV_APPEARANCE = 0
newPreferencesReady = pyqtSignal(bool, bool, bool, bool) newPreferencesReady = pyqtSignal(bool, bool, bool, bool)
@@ -50,32 +53,44 @@ class GuiPreferences(NPagedDialog):
logger.debug("Create: GuiPreferences") logger.debug("Create: GuiPreferences")
self.setObjectName("GuiPreferences") self.setObjectName("GuiPreferences")
self.setWindowTitle(self.tr("Preferences")) self.setWindowTitle(CONFIG.appName)
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)
mPx = CONFIG.pxInt(150)
self.resize(*CONFIG.preferencesWinSize) 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 # Settings
self._updateTheme = False self._updateTheme = False
self._updateSyntax = False self._updateSyntax = False
@@ -90,6 +105,16 @@ class GuiPreferences(NPagedDialog):
logger.debug("Delete: GuiPreferences") logger.debug("Delete: GuiPreferences")
return 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 # Events
## ##
@@ -106,22 +131,34 @@ class GuiPreferences(NPagedDialog):
# Private Slots # 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() @pyqtSlot()
def _doSave(self) -> None: def _doSave(self) -> None:
"""Trigger save functions in the tabs and emit ready signal.""" """Trigger save functions in the tabs and emit ready signal."""
self.tabGeneral.saveValues() # self.tabGeneral.saveValues()
self.tabProjects.saveValues() # self.tabProjects.saveValues()
self.tabDocs.saveValues() # self.tabDocs.saveValues()
self.tabEditor.saveValues() # self.tabEditor.saveValues()
self.tabSyntax.saveValues() # self.tabSyntax.saveValues()
self.tabAuto.saveValues() # self.tabAuto.saveValues()
self.tabQuote.saveValues() # self.tabQuote.saveValues()
CONFIG.saveConfig() # CONFIG.saveConfig()
self.newPreferencesReady.emit( # self.newPreferencesReady.emit(
self._needsRestart, self._refreshTree, self._updateTheme, self._updateSyntax # self._needsRestart, self._refreshTree, self._updateTheme, self._updateSyntax
) # )
qApp.processEvents() # qApp.processEvents()
self.close() self.close()
return return
@@ -135,6 +172,99 @@ class GuiPreferences(NPagedDialog):
CONFIG.setPreferencesWinSize(self.width(), self.height()) CONFIG.setPreferencesWinSize(self.width(), self.height())
return 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 # END Class GuiPreferences
+96 -4
View File
@@ -26,7 +26,7 @@ from __future__ import annotations
from PyQt5.QtGui import QColor, QPalette from PyQt5.QtGui import QColor, QPalette
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAbstractButton, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QSizePolicy, QAbstractButton, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QScrollArea, QSizePolicy,
QVBoxLayout, QWidget QVBoxLayout, QWidget
) )
@@ -37,6 +37,99 @@ RIGHT_TOP = Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop
LEFT_TOP = Qt.AlignmentFlag.AlignLeft | 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"<b>{label}</b>", 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): class NConfigLayout(QGridLayout):
def __init__(self) -> None: def __init__(self) -> None:
@@ -58,11 +151,10 @@ class NConfigLayout(QGridLayout):
# Getters and Setters # Getters and Setters
## ##
def setHelpTextStyle(self, color: QColor | list | tuple, def setHelpTextStyle(self, color: QColor | list | tuple, scale: float = FONT_SCALE) -> None:
fontScale: float = FONT_SCALE) -> None:
"""Set the text color for the help text.""" """Set the text color for the help text."""
self._helpCol = color if isinstance(color, QColor) else QColor(*color) self._helpCol = color if isinstance(color, QColor) else QColor(*color)
self._fontScale = fontScale self._fontScale = scale
return return
def setHelpText(self, row: int, text: str) -> None: def setHelpText(self, row: int, text: str) -> None: