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.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
+96 -4
View File
@@ -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"<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):
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: