Update the design on the Project Settings dialog

This commit is contained in:
Veronica Berglyd Olsen
2024-01-26 19:50:17 +01:00
parent df4993c38e
commit ee21045ee2
4 changed files with 195 additions and 159 deletions
@@ -3,7 +3,8 @@ novelWriter GUI Project Settings
==================================
File History:
Created: 2018-09-29 [0.0.1] GuiProjectSettings
Created: 2018-09-29 [0.0.1] GuiProjectSettings
Rewritten: 2024-01-26 [2.3b1] GuiProjectSettings
This file is a part of novelWriter
Copyright 20182024, Veronica Berglyd Olsen
@@ -25,75 +26,102 @@ from __future__ import annotations
import logging
from typing import TYPE_CHECKING
from PyQt5.QtGui import QCloseEvent, QIcon, QPixmap, QColor
from PyQt5.QtGui import QCloseEvent, QColor, QIcon, QPixmap
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import (
QColorDialog, QComboBox, QDialogButtonBox, QHBoxLayout, QLabel, QLineEdit,
QPushButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget, qApp
QColorDialog, QComboBox, QDialog, QDialogButtonBox, QHBoxLayout, QLabel,
QLineEdit, QPushButton, QStackedWidget, QTreeWidget, QTreeWidgetItem,
QVBoxLayout, QWidget, qApp
)
from novelwriter import CONFIG, SHARED
from novelwriter.common import simplified
from novelwriter.extensions.switch import NSwitch
from novelwriter.extensions.pageddialog import NPagedDialog
from novelwriter.extensions.configlayout import NConfigLayout
if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain
from novelwriter.extensions.configlayout import NColourLabel, NScrollableForm
from novelwriter.extensions.pagedsidebar import NPagedSideBar
logger = logging.getLogger(__name__)
class GuiProjectSettings(NPagedDialog):
class GuiProjectSettings(QDialog):
TAB_MAIN = 0
TAB_STATUS = 1
TAB_IMPORT = 2
TAB_REPLACE = 3
PAGE_SETTINGS = 0
PAGE_STATUS = 1
PAGE_IMPORT = 2
PAGE_REPLACE = 3
newProjectSettingsReady = pyqtSignal()
newProjectSettingsReady = pyqtSignal(bool)
def __init__(self, mainGui: GuiMain, focusTab: int = TAB_MAIN) -> None:
super().__init__(parent=mainGui)
def __init__(self, parent: QWidget, gotoPage: int = PAGE_SETTINGS) -> None:
super().__init__(parent=parent)
logger.debug("Create: GuiProjectSettings")
self.setObjectName("GuiProjectSettings")
self.mainGui = mainGui
SHARED.project.countStatus()
self.setWindowTitle(self.tr("Project Settings"))
wW = CONFIG.pxInt(570)
wH = CONFIG.pxInt(375)
pOptions = SHARED.project.options
wW = CONFIG.pxInt(500)
wH = CONFIG.pxInt(400)
options = SHARED.project.options
self.setMinimumWidth(wW)
self.setMinimumHeight(wH)
self.setMinimumSize(wW, wH)
self.resize(
CONFIG.pxInt(pOptions.getInt("GuiProjectSettings", "winWidth", wW)),
CONFIG.pxInt(pOptions.getInt("GuiProjectSettings", "winHeight", wH))
CONFIG.pxInt(options.getInt("GuiProjectSettings", "winWidth", wW)),
CONFIG.pxInt(options.getInt("GuiProjectSettings", "winHeight", wH))
)
self.tabMain = GuiProjectEditMain(self)
self.tabStatus = GuiProjectEditStatus(self, True)
self.tabImport = GuiProjectEditStatus(self, False)
self.tabReplace = GuiProjectEditReplace(self)
# Title
self.titleLabel = NColourLabel(
self.tr("Project Settings"), SHARED.theme.helpText, parent=self, scale=1.25
)
self.titleLabel.setIndent(CONFIG.pxInt(4))
self.addTab(self.tabMain, self.tr("Settings"))
self.addTab(self.tabStatus, self.tr("Status"))
self.addTab(self.tabImport, self.tr("Importance"))
self.addTab(self.tabReplace, self.tr("Auto-Replace"))
# SideBar
self.sidebar = NPagedSideBar(self)
self.sidebar.setLabelColor(SHARED.theme.helpText)
self.sidebar.addButton(self.tr("Settings"), self.PAGE_SETTINGS)
self.sidebar.addButton(self.tr("Status"), self.PAGE_STATUS)
self.sidebar.addButton(self.tr("Importance"), self.PAGE_IMPORT)
self.sidebar.addButton(self.tr("Auto-Replace"), self.PAGE_REPLACE)
self.sidebar.setSelected(gotoPage)
self.sidebar.buttonClicked.connect(self._sidebarClicked)
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
# Buttons
self.buttonBox = QDialogButtonBox(
QDialogButtonBox.StandardButton.Save | QDialogButtonBox.StandardButton.Cancel
)
self.buttonBox.accepted.connect(self._doSave)
self.buttonBox.rejected.connect(self.close)
self.rejected.connect(self.close)
self.addControls(self.buttonBox)
# Focus Tab
self._focusTab(focusTab)
# Content
self.settingsPage = _SettingsPage(self)
self.statusPage = _StatusPage(self, True)
self.importPage = _StatusPage(self, False)
self.replacePage = _ReplacePage(self)
self.mainStack = QStackedWidget(self)
self.mainStack.addWidget(self.settingsPage)
self.mainStack.addWidget(self.statusPage)
self.mainStack.addWidget(self.importPage)
self.mainStack.addWidget(self.replacePage)
# Assemble
self.topBox = QHBoxLayout()
self.topBox.addWidget(self.titleLabel)
self.topBox.addStretch(1)
self.mainBox = QHBoxLayout()
self.mainBox.addWidget(self.sidebar)
self.mainBox.addWidget(self.mainStack)
self.mainBox.setContentsMargins(0, 0, 0, 0)
self.outerBox = QVBoxLayout()
self.outerBox.addLayout(self.topBox)
self.outerBox.addLayout(self.mainBox)
self.outerBox.addWidget(self.buttonBox)
self.outerBox.setSpacing(CONFIG.pxInt(8))
self.setLayout(self.outerBox)
self.setSizeGripEnabled(True)
logger.debug("Ready: GuiProjectSettings")
@@ -103,9 +131,13 @@ class GuiProjectSettings(NPagedDialog):
logger.debug("Delete: GuiProjectSettings")
return
##
# Events
##
def closeEvent(self, event: QCloseEvent) -> None:
"""Capture the close event and perform cleanup."""
self._saveGuiSettings()
"""Capture the user closing the window and save settings."""
self._saveSettings()
event.accept()
self.deleteLater()
return
@@ -114,40 +146,52 @@ class GuiProjectSettings(NPagedDialog):
# Private Slots
##
@pyqtSlot(int)
def _sidebarClicked(self, pageId: int) -> None:
"""Process a user request to switch page."""
if pageId == self.PAGE_SETTINGS:
self.mainStack.setCurrentWidget(self.settingsPage)
elif pageId == self.PAGE_STATUS:
self.mainStack.setCurrentWidget(self.statusPage)
elif pageId == self.PAGE_IMPORT:
self.mainStack.setCurrentWidget(self.importPage)
elif pageId == self.PAGE_REPLACE:
self.mainStack.setCurrentWidget(self.replacePage)
return
@pyqtSlot()
def _doSave(self) -> None:
"""Save settings and close dialog."""
project = SHARED.project
projName = self.tabMain.editName.text()
bookTitle = self.tabMain.editTitle.text()
bookAuthor = self.tabMain.editAuthor.text()
projLang = self.tabMain.projLang.currentData()
spellLang = self.tabMain.spellLang.currentData()
doBackup = not self.tabMain.doBackup.isChecked()
projName = self.settingsPage.projName.text()
projAuthor = self.settingsPage.projAuthor.text()
projLang = self.settingsPage.projLang.currentData()
spellLang = self.settingsPage.spellLang.currentData()
doBackup = not self.settingsPage.doBackup.isChecked()
project.data.setName(projName)
project.data.setTitle(bookTitle)
project.data.setAuthor(bookAuthor)
project.data.setAuthor(projAuthor)
project.data.setDoBackup(doBackup)
project.data.setSpellLang(spellLang)
project.setProjectLang(projLang)
if self.tabStatus.colChanged:
newList, delList = self.tabStatus.getNewList()
rebuildTrees = False
if self.statusPage.colChanged:
newList, delList = self.statusPage.getNewList()
project.setStatusColours(newList, delList)
rebuildTrees = True
if self.tabImport.colChanged:
newList, delList = self.tabImport.getNewList()
if self.importPage.colChanged:
newList, delList = self.importPage.getNewList()
project.setImportColours(newList, delList)
rebuildTrees = True
if self.tabStatus.colChanged or self.tabImport.colChanged:
self.mainGui.rebuildTrees()
if self.tabReplace.arChanged:
newList = self.tabReplace.getNewList()
if self.replacePage.arChanged:
newList = self.replacePage.getNewList()
project.data.setAutoReplace(newList)
self.newProjectSettingsReady.emit()
self.newProjectSettingsReady.emit(rebuildTrees)
qApp.processEvents()
self.close()
@@ -157,134 +201,104 @@ class GuiProjectSettings(NPagedDialog):
# Internal Functions
##
def _focusTab(self, tab: int) -> None:
"""Change which is the focused tab."""
if tab == self.TAB_MAIN:
self.setCurrentWidget(self.tabMain)
elif tab == self.TAB_STATUS:
self.setCurrentWidget(self.tabStatus)
elif tab == self.TAB_IMPORT:
self.setCurrentWidget(self.tabImport)
elif tab == self.TAB_REPLACE:
self.setCurrentWidget(self.tabReplace)
return
def _saveGuiSettings(self) -> None:
def _saveSettings(self) -> None:
"""Save GUI settings."""
winWidth = CONFIG.rpxInt(self.width())
winHeight = CONFIG.rpxInt(self.height())
replaceColW = CONFIG.rpxInt(self.tabReplace.listBox.columnWidth(0))
statusColW = CONFIG.rpxInt(self.tabStatus.listBox.columnWidth(0))
importColW = CONFIG.rpxInt(self.tabImport.listBox.columnWidth(0))
statusColW = CONFIG.rpxInt(self.statusPage.columnWidth())
importColW = CONFIG.rpxInt(self.importPage.columnWidth())
replaceColW = CONFIG.rpxInt(self.replacePage.columnWidth())
logger.debug("Saving State: GuiProjectSettings")
pOptions = SHARED.project.options
pOptions.setValue("GuiProjectSettings", "winWidth", winWidth)
pOptions.setValue("GuiProjectSettings", "winHeight", winHeight)
pOptions.setValue("GuiProjectSettings", "replaceColW", replaceColW)
pOptions.setValue("GuiProjectSettings", "statusColW", statusColW)
pOptions.setValue("GuiProjectSettings", "importColW", importColW)
options = SHARED.project.options
options.setValue("GuiProjectSettings", "winWidth", winWidth)
options.setValue("GuiProjectSettings", "winHeight", winHeight)
options.setValue("GuiProjectSettings", "statusColW", statusColW)
options.setValue("GuiProjectSettings", "importColW", importColW)
options.setValue("GuiProjectSettings", "replaceColW", replaceColW)
return
# END Class GuiProjectSettings
class GuiProjectEditMain(QWidget):
class _SettingsPage(NScrollableForm):
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
# The Form
self.mainForm = NConfigLayout()
self.mainForm.setHelpTextStyle(SHARED.theme.helpText)
self.setLayout(self.mainForm)
self.mainForm.addGroupLabel(self.tr("Project Settings"))
xW = CONFIG.pxInt(250)
pData = SHARED.project.data
data = SHARED.project.data
self.setHelpTextStyle(SHARED.theme.helpText)
self.setRowIndent(0)
self.editName = QLineEdit()
self.editName.setMaxLength(200)
self.editName.setMaximumWidth(xW)
self.editName.setText(pData.name)
self.mainForm.addRow(
# Project Name
self.projName = QLineEdit(self)
self.projName.setMaxLength(200)
self.projName.setMaximumWidth(xW)
self.projName.setText(data.name)
self.addRow(
self.tr("Project name"),
self.editName,
self.tr("Should be set only once.")
self.projName,
self.tr("Changing this will affect the backup path.")
)
self.editTitle = QLineEdit()
self.editTitle.setMaxLength(200)
self.editTitle.setMaximumWidth(xW)
self.editTitle.setText(pData.title)
self.mainForm.addRow(
self.tr("Novel title"),
self.editTitle,
self.tr("Change whenever you want!")
)
self.editAuthor = QLineEdit()
self.editAuthor.setMaxLength(200)
self.editAuthor.setMaximumWidth(xW)
self.editAuthor.setText(pData.author)
self.mainForm.addRow(
# Project Author
self.projAuthor = QLineEdit(self)
self.projAuthor.setMaxLength(200)
self.projAuthor.setMaximumWidth(xW)
self.projAuthor.setText(data.author)
self.addRow(
self.tr("Author(s)"),
self.editAuthor,
self.tr("Change whenever you want!")
self.projAuthor,
self.tr("Only used when building the manuscript.")
)
# Project Language
self.projLang = QComboBox(self)
self.projLang.setMaximumWidth(xW)
for tag, language in CONFIG.listLanguages(CONFIG.LANG_PROJ):
self.projLang.addItem(language, tag)
self.mainForm.addRow(
self.addRow(
self.tr("Project language"),
self.projLang,
self.tr("Used when building the manuscript.")
self.tr("Only used when building the manuscript.")
)
if (idx := self.projLang.findData(data.language)) != -1:
self.projLang.setCurrentIndex(idx)
langIdx = 0
if pData.language is not None:
langIdx = self.projLang.findData(pData.language)
if langIdx == -1:
langIdx = self.projLang.findData("en_GB")
if langIdx != -1:
self.projLang.setCurrentIndex(langIdx)
# Spell Check Language
self.spellLang = QComboBox(self)
self.spellLang.setMaximumWidth(xW)
self.spellLang.addItem(self.tr("Default"), "None")
if CONFIG.hasEnchant:
for tag, language in SHARED.spelling.listDictionaries():
self.spellLang.addItem(language, tag)
self.mainForm.addRow(
self.addRow(
self.tr("Spell check language"),
self.spellLang,
self.tr("Overrides main preferences.")
)
if (idx := self.spellLang.findData(data.spellLang)) != -1:
self.spellLang.setCurrentIndex(idx)
langIdx = 0
if pData.spellLang is not None:
langIdx = self.spellLang.findData(pData.spellLang)
if langIdx != -1:
self.spellLang.setCurrentIndex(langIdx)
# Backup on Close
self.doBackup = NSwitch(self)
self.doBackup.setChecked(not pData.doBackup)
self.mainForm.addRow(
self.tr("No backup on close"),
self.doBackup.setChecked(not data.doBackup)
self.addRow(
self.tr("Disable backup on close"),
self.doBackup,
self.tr("Overrides main preferences.")
)
self.finalise()
return
# END Class GuiProjectEditMain
# END Class _SettingsPage
class GuiProjectEditStatus(QWidget):
class _StatusPage(QWidget):
COL_LABEL = 0
COL_USAGE = 1
@@ -318,7 +332,7 @@ class GuiProjectEditStatus(QWidget):
# The List
# ========
self.listBox = QTreeWidget()
self.listBox = QTreeWidget(self)
self.listBox.setHeaderLabels([
self.tr("Label"), self.tr("Usage"),
])
@@ -332,16 +346,16 @@ class GuiProjectEditStatus(QWidget):
# List Controls
# =============
self.addButton = QPushButton(SHARED.theme.getIcon("add"), "")
self.addButton = QPushButton(SHARED.theme.getIcon("add"), "", self)
self.addButton.clicked.connect(self._newItem)
self.delButton = QPushButton(SHARED.theme.getIcon("remove"), "")
self.delButton = QPushButton(SHARED.theme.getIcon("remove"), "", self)
self.delButton.clicked.connect(self._delItem)
self.upButton = QPushButton(SHARED.theme.getIcon("up"), "")
self.upButton = QPushButton(SHARED.theme.getIcon("up"), "", self)
self.upButton.clicked.connect(lambda: self._moveItem(-1))
self.dnButton = QPushButton(SHARED.theme.getIcon("down"), "")
self.dnButton = QPushButton(SHARED.theme.getIcon("down"), "", self)
self.dnButton.clicked.connect(lambda: self._moveItem(1))
# Edit Form
@@ -389,6 +403,7 @@ class GuiProjectEditStatus(QWidget):
self.outerBox = QVBoxLayout()
self.outerBox.addWidget(QLabel("<b>%s</b>" % pageLabel))
self.outerBox.addLayout(self.innerBox)
self.outerBox.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.outerBox)
@@ -410,6 +425,10 @@ class GuiProjectEditStatus(QWidget):
return [], []
def columnWidth(self) -> int:
"""Return the size of the header column."""
return self.listBox.columnWidth(0)
##
# Private Slots
##
@@ -553,10 +572,10 @@ class GuiProjectEditStatus(QWidget):
else:
return self.tr("Used by {0} items").format(nUse)
# END Class GuiProjectEditStatus
# END Class _StatusPage
class GuiProjectEditReplace(QWidget):
class _ReplacePage(QWidget):
COL_KEY = 0
COL_REPL = 1
@@ -638,6 +657,7 @@ class GuiProjectEditReplace(QWidget):
self.outerBox = QVBoxLayout()
self.outerBox.addWidget(QLabel("<b>%s</b>" % pageLabel))
self.outerBox.addLayout(self.innerBox)
self.outerBox.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.outerBox)
@@ -655,6 +675,10 @@ class GuiProjectEditReplace(QWidget):
new[aKey] = aVal
return new
def columnWidth(self) -> int:
"""Return the size of the header column."""
return self.listBox.columnWidth(0)
##
# Internal Functions
##
@@ -725,4 +749,4 @@ class GuiProjectEditReplace(QWidget):
result += c
return result
# END Class GuiProjectEditReplace
# END Class _ReplacePage
+14 -4
View File
@@ -50,6 +50,7 @@ class NScrollableForm(QScrollArea):
self._helpCol = QColor(0, 0, 0)
self._fontScale = FONT_SCALE
self._first = True
self._indent = CONFIG.pxInt(12)
self._sections: dict[int, QLabel] = {}
self._editable: dict[str, NColourLabel] = {}
@@ -92,6 +93,16 @@ class NScrollableForm(QScrollArea):
qHelp.setText(text)
return
def setRowIndent(self, indent: int) -> None:
"""Set the indentation of each row."""
self._indent = max(indent, 0)
return
def setLayoutMargins(self, left: int, top: int, right: int, bottom: int) -> None:
"""Set the contents margins on the layout."""
self._layout.setContentsMargins(left, top, right, bottom)
return
##
# Methods
##
@@ -128,14 +139,13 @@ class NScrollableForm(QScrollArea):
row = QHBoxLayout()
row.setSpacing(CONFIG.pxInt(4))
mPx = CONFIG.pxInt(12)
qLabel = QLabel(label, self)
qLabel.setIndent(mPx)
qLabel.setIndent(self._indent)
qLabel.setBuddy(widget)
if helpText:
qHelp = NColourLabel(str(helpText), self._helpCol, scale=self._fontScale, wrap=True)
qHelp.setIndent(mPx)
qHelp.setIndent(self._indent)
labelBox = QVBoxLayout()
labelBox.addWidget(qLabel)
labelBox.addWidget(qHelp)
@@ -147,7 +157,7 @@ class NScrollableForm(QScrollArea):
else:
row.addWidget(qLabel)
row.addSpacing(mPx)
row.addSpacing(CONFIG.pxInt(12))
row.addWidget(widget)
if isinstance(unit, str):
+3 -3
View File
@@ -48,7 +48,7 @@ from novelwriter.core.coretools import DocDuplicator, DocMerger, DocSplitter
from novelwriter.dialogs.docmerge import GuiDocMerge
from novelwriter.dialogs.docsplit import GuiDocSplit
from novelwriter.dialogs.editlabel import GuiEditLabel
from novelwriter.dialogs.projsettings import GuiProjectSettings
from novelwriter.dialogs.projectsettings import GuiProjectSettings
from novelwriter.enum import (
nwDocMode, nwItemType, nwItemClass, nwItemLayout, nwWidget
)
@@ -1748,7 +1748,7 @@ class _TreeContextMenu(QMenu):
menu.addSeparator()
action = menu.addAction(self.tr("Manage Labels ..."))
action.triggered.connect(
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.TAB_STATUS)
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.PAGE_STATUS)
)
else:
menu = self.addMenu(self.tr("Set Importance to ..."))
@@ -1765,7 +1765,7 @@ class _TreeContextMenu(QMenu):
menu.addSeparator()
action = menu.addAction(self.tr("Manage Labels ..."))
action.triggered.connect(
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.TAB_IMPORT)
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.PAGE_IMPORT)
)
return
+7 -5
View File
@@ -53,7 +53,7 @@ from novelwriter.dialogs.about import GuiAbout
from novelwriter.dialogs.updates import GuiUpdates
from novelwriter.dialogs.wordlist import GuiWordList
from novelwriter.dialogs.preferences import GuiPreferences
from novelwriter.dialogs.projsettings import GuiProjectSettings
from novelwriter.dialogs.projectsettings import GuiProjectSettings
from novelwriter.tools.welcome import GuiWelcome
from novelwriter.tools.manuscript import GuiManuscript
from novelwriter.tools.dictionaries import GuiDictionaries
@@ -809,10 +809,10 @@ class GuiMain(QMainWindow):
@pyqtSlot()
@pyqtSlot(int)
def showProjectSettingsDialog(self, focusTab: int = GuiProjectSettings.TAB_MAIN) -> None:
def showProjectSettingsDialog(self, focusTab: int = GuiProjectSettings.PAGE_SETTINGS) -> None:
"""Open the project settings dialog."""
if SHARED.hasProject:
dialog = GuiProjectSettings(self, focusTab=focusTab)
dialog = GuiProjectSettings(self, gotoPage=focusTab)
dialog.newProjectSettingsReady.connect(self._processProjectSettingsChanges)
dialog.exec_()
return
@@ -1111,13 +1111,15 @@ class GuiMain(QMainWindow):
return
@pyqtSlot()
def _processProjectSettingsChanges(self) -> None:
@pyqtSlot(bool)
def _processProjectSettingsChanges(self, rebuildTrees: bool) -> None:
"""Refresh data dependent on project settings."""
logger.debug("Applying new project settings")
SHARED.updateSpellCheckLanguage()
self.itemDetails.refreshDetails()
self._updateWindowTitle(SHARED.project.data.name)
if rebuildTrees:
self.rebuildTrees()
return
@pyqtSlot()