Update Project Settings dialog (#1668)

This commit is contained in:
Veronica Berglyd Olsen
2024-01-26 22:40:04 +01:00
committed by GitHub
9 changed files with 545 additions and 627 deletions
+1 -1
View File
@@ -131,7 +131,7 @@ def main(sysArgs: list | None = None):
elif inOpt == "--debug": elif inOpt == "--debug":
CONFIG.isDebug = True CONFIG.isDebug = True
logLevel = logging.DEBUG logLevel = logging.DEBUG
logFormat = "[{asctime:}] {filename:>17}:{lineno:<4d} {levelname:8} {message:}" logFormat = "[{asctime:}] {filename:>18}:{lineno:<4d} {levelname:8} {message:}"
elif inOpt == "--style": elif inOpt == "--style":
qtStyle = inArg qtStyle = inArg
elif inOpt == "--config": elif inOpt == "--config":
+2 -2
View File
@@ -59,9 +59,9 @@ class GuiPreferences(QDialog):
# Title # Title
self.titleLabel = NColourLabel( self.titleLabel = NColourLabel(
self.tr("Preferences"), SHARED.theme.helpText, parent=self, scale=1.25 self.tr("Preferences"), SHARED.theme.helpText,
parent=self, scale=NColourLabel.HEADER_SCALE, indent=CONFIG.pxInt(4)
) )
self.titleLabel.setIndent(CONFIG.pxInt(4))
# Search Box # Search Box
self.searchText = QLineEdit(self) self.searchText = QLineEdit(self)
@@ -3,7 +3,8 @@ novelWriter GUI Project Settings
================================== ==================================
File History: 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 This file is a part of novelWriter
Copyright 20182024, Veronica Berglyd Olsen Copyright 20182024, Veronica Berglyd Olsen
@@ -25,75 +26,101 @@ from __future__ import annotations
import logging import logging
from typing import TYPE_CHECKING from PyQt5.QtGui import QCloseEvent, QColor, QIcon, QPixmap
from PyQt5.QtGui import QCloseEvent, QIcon, QPixmap, QColor
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QColorDialog, QComboBox, QDialogButtonBox, QHBoxLayout, QLabel, QLineEdit, QColorDialog, QComboBox, QDialog, QDialogButtonBox, QHBoxLayout, QLineEdit,
QPushButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget, qApp QPushButton, QStackedWidget, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
QWidget, qApp
) )
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.common import simplified from novelwriter.common import simplified
from novelwriter.extensions.switch import NSwitch from novelwriter.extensions.switch import NSwitch
from novelwriter.extensions.pageddialog import NPagedDialog from novelwriter.extensions.configlayout import NColourLabel, NFixedPage, NScrollableForm
from novelwriter.extensions.configlayout import NConfigLayout from novelwriter.extensions.pagedsidebar import NPagedSideBar
if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class GuiProjectSettings(NPagedDialog): class GuiProjectSettings(QDialog):
TAB_MAIN = 0 PAGE_SETTINGS = 0
TAB_STATUS = 1 PAGE_STATUS = 1
TAB_IMPORT = 2 PAGE_IMPORT = 2
TAB_REPLACE = 3 PAGE_REPLACE = 3
newProjectSettingsReady = pyqtSignal() newProjectSettingsReady = pyqtSignal(bool)
def __init__(self, mainGui: GuiMain, focusTab: int = TAB_MAIN) -> None: def __init__(self, parent: QWidget, gotoPage: int = PAGE_SETTINGS) -> None:
super().__init__(parent=mainGui) super().__init__(parent=parent)
logger.debug("Create: GuiProjectSettings") logger.debug("Create: GuiProjectSettings")
self.setObjectName("GuiProjectSettings") self.setObjectName("GuiProjectSettings")
self.mainGui = mainGui
SHARED.project.countStatus()
self.setWindowTitle(self.tr("Project Settings")) self.setWindowTitle(self.tr("Project Settings"))
wW = CONFIG.pxInt(570) options = SHARED.project.options
wH = CONFIG.pxInt(375) self.setMinimumSize(CONFIG.pxInt(500), CONFIG.pxInt(400))
pOptions = SHARED.project.options
self.setMinimumWidth(wW)
self.setMinimumHeight(wH)
self.resize( self.resize(
CONFIG.pxInt(pOptions.getInt("GuiProjectSettings", "winWidth", wW)), CONFIG.pxInt(options.getInt("GuiProjectSettings", "winWidth", CONFIG.pxInt(650))),
CONFIG.pxInt(pOptions.getInt("GuiProjectSettings", "winHeight", wH)) CONFIG.pxInt(options.getInt("GuiProjectSettings", "winHeight", CONFIG.pxInt(500)))
) )
self.tabMain = GuiProjectEditMain(self) # Title
self.tabStatus = GuiProjectEditStatus(self, True) self.titleLabel = NColourLabel(
self.tabImport = GuiProjectEditStatus(self, False) self.tr("Project Settings"), SHARED.theme.helpText,
self.tabReplace = GuiProjectEditReplace(self) parent=self, scale=NColourLabel.HEADER_SCALE, indent=CONFIG.pxInt(4)
)
self.addTab(self.tabMain, self.tr("Settings")) # SideBar
self.addTab(self.tabStatus, self.tr("Status")) self.sidebar = NPagedSideBar(self)
self.addTab(self.tabImport, self.tr("Importance")) self.sidebar.setLabelColor(SHARED.theme.helpText)
self.addTab(self.tabReplace, self.tr("Auto-Replace")) 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.accepted.connect(self._doSave)
self.buttonBox.rejected.connect(self.close) self.buttonBox.rejected.connect(self.close)
self.rejected.connect(self.close)
self.addControls(self.buttonBox)
# Focus Tab # Content
self._focusTab(focusTab) SHARED.project.countStatus()
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") logger.debug("Ready: GuiProjectSettings")
@@ -103,9 +130,13 @@ class GuiProjectSettings(NPagedDialog):
logger.debug("Delete: GuiProjectSettings") logger.debug("Delete: GuiProjectSettings")
return return
##
# Events
##
def closeEvent(self, event: QCloseEvent) -> None: def closeEvent(self, event: QCloseEvent) -> None:
"""Capture the close event and perform cleanup.""" """Capture the user closing the window and save settings."""
self._saveGuiSettings() self._saveSettings()
event.accept() event.accept()
self.deleteLater() self.deleteLater()
return return
@@ -114,40 +145,52 @@ class GuiProjectSettings(NPagedDialog):
# Private Slots # 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() @pyqtSlot()
def _doSave(self) -> None: def _doSave(self) -> None:
"""Save settings and close dialog.""" """Save settings and close dialog."""
project = SHARED.project project = SHARED.project
projName = self.tabMain.editName.text() projName = self.settingsPage.projName.text()
bookTitle = self.tabMain.editTitle.text() projAuthor = self.settingsPage.projAuthor.text()
bookAuthor = self.tabMain.editAuthor.text() projLang = self.settingsPage.projLang.currentData()
projLang = self.tabMain.projLang.currentData() spellLang = self.settingsPage.spellLang.currentData()
spellLang = self.tabMain.spellLang.currentData() doBackup = not self.settingsPage.doBackup.isChecked()
doBackup = not self.tabMain.doBackup.isChecked()
project.data.setName(projName) project.data.setName(projName)
project.data.setTitle(bookTitle) project.data.setAuthor(projAuthor)
project.data.setAuthor(bookAuthor)
project.data.setDoBackup(doBackup) project.data.setDoBackup(doBackup)
project.data.setSpellLang(spellLang) project.data.setSpellLang(spellLang)
project.setProjectLang(projLang) project.setProjectLang(projLang)
if self.tabStatus.colChanged: rebuildTrees = False
newList, delList = self.tabStatus.getNewList()
if self.statusPage.wasChanged:
newList, delList = self.statusPage.getNewList()
project.setStatusColours(newList, delList) project.setStatusColours(newList, delList)
rebuildTrees = True
if self.tabImport.colChanged: if self.importPage.wasChanged:
newList, delList = self.tabImport.getNewList() newList, delList = self.importPage.getNewList()
project.setImportColours(newList, delList) project.setImportColours(newList, delList)
rebuildTrees = True
if self.tabStatus.colChanged or self.tabImport.colChanged: if self.replacePage.wasChanged:
self.mainGui.rebuildTrees() newList = self.replacePage.getNewList()
if self.tabReplace.arChanged:
newList = self.tabReplace.getNewList()
project.data.setAutoReplace(newList) project.data.setAutoReplace(newList)
self.newProjectSettingsReady.emit() self.newProjectSettingsReady.emit(rebuildTrees)
qApp.processEvents() qApp.processEvents()
self.close() self.close()
@@ -157,134 +200,103 @@ class GuiProjectSettings(NPagedDialog):
# Internal Functions # Internal Functions
## ##
def _focusTab(self, tab: int) -> None: def _saveSettings(self) -> 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:
"""Save GUI settings.""" """Save GUI settings."""
winWidth = CONFIG.rpxInt(self.width()) winWidth = CONFIG.rpxInt(self.width())
winHeight = CONFIG.rpxInt(self.height()) winHeight = CONFIG.rpxInt(self.height())
replaceColW = CONFIG.rpxInt(self.tabReplace.listBox.columnWidth(0)) statusColW = CONFIG.rpxInt(self.statusPage.columnWidth())
statusColW = CONFIG.rpxInt(self.tabStatus.listBox.columnWidth(0)) importColW = CONFIG.rpxInt(self.importPage.columnWidth())
importColW = CONFIG.rpxInt(self.tabImport.listBox.columnWidth(0)) replaceColW = CONFIG.rpxInt(self.replacePage.columnWidth())
logger.debug("Saving State: GuiProjectSettings") logger.debug("Saving State: GuiProjectSettings")
pOptions = SHARED.project.options options = SHARED.project.options
pOptions.setValue("GuiProjectSettings", "winWidth", winWidth) options.setValue("GuiProjectSettings", "winWidth", winWidth)
pOptions.setValue("GuiProjectSettings", "winHeight", winHeight) options.setValue("GuiProjectSettings", "winHeight", winHeight)
pOptions.setValue("GuiProjectSettings", "replaceColW", replaceColW) options.setValue("GuiProjectSettings", "statusColW", statusColW)
pOptions.setValue("GuiProjectSettings", "statusColW", statusColW) options.setValue("GuiProjectSettings", "importColW", importColW)
pOptions.setValue("GuiProjectSettings", "importColW", importColW) options.setValue("GuiProjectSettings", "replaceColW", replaceColW)
return return
# END Class GuiProjectSettings # END Class GuiProjectSettings
class GuiProjectEditMain(QWidget): class _SettingsPage(NScrollableForm):
def __init__(self, parent: QWidget) -> None: def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
# The Form xW = CONFIG.pxInt(200)
self.mainForm = NConfigLayout() data = SHARED.project.data
self.mainForm.setHelpTextStyle(SHARED.theme.helpText) self.setHelpTextStyle(SHARED.theme.helpText)
self.setLayout(self.mainForm) self.setRowIndent(0)
self.mainForm.addGroupLabel(self.tr("Project Settings")) # Project Name
self.projName = QLineEdit(self)
xW = CONFIG.pxInt(250) self.projName.setMaxLength(200)
pData = SHARED.project.data self.projName.setMinimumWidth(xW)
self.projName.setText(data.name)
self.editName = QLineEdit() self.addRow(
self.editName.setMaxLength(200) self.tr("Project name"), self.projName,
self.editName.setMaximumWidth(xW) self.tr("Changing this will affect the backup path."),
self.editName.setText(pData.name) stretch=(3, 2)
self.mainForm.addRow(
self.tr("Project name"),
self.editName,
self.tr("Should be set only once.")
) )
self.editTitle = QLineEdit() # Project Author
self.editTitle.setMaxLength(200) self.projAuthor = QLineEdit(self)
self.editTitle.setMaximumWidth(xW) self.projAuthor.setMaxLength(200)
self.editTitle.setText(pData.title) self.projAuthor.setMinimumWidth(xW)
self.mainForm.addRow( self.projAuthor.setText(data.author)
self.tr("Novel title"), self.addRow(
self.editTitle, self.tr("Author(s)"), self.projAuthor,
self.tr("Change whenever you want!") self.tr("Only used when building the manuscript."),
) stretch=(3, 2)
self.editAuthor = QLineEdit()
self.editAuthor.setMaxLength(200)
self.editAuthor.setMaximumWidth(xW)
self.editAuthor.setText(pData.author)
self.mainForm.addRow(
self.tr("Author(s)"),
self.editAuthor,
self.tr("Change whenever you want!")
) )
# Project Language
self.projLang = QComboBox(self) self.projLang = QComboBox(self)
self.projLang.setMaximumWidth(xW) self.projLang.setMinimumWidth(xW)
for tag, language in CONFIG.listLanguages(CONFIG.LANG_PROJ): for tag, language in CONFIG.listLanguages(CONFIG.LANG_PROJ):
self.projLang.addItem(language, tag) self.projLang.addItem(language, tag)
self.mainForm.addRow( self.addRow(
self.tr("Project language"), self.tr("Project language"), self.projLang,
self.projLang, self.tr("Only used when building the manuscript."),
self.tr("Used when building the manuscript.") stretch=(3, 2)
) )
if (idx := self.projLang.findData(data.language)) != -1:
self.projLang.setCurrentIndex(idx)
langIdx = 0 # Spell Check Language
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)
self.spellLang = QComboBox(self) self.spellLang = QComboBox(self)
self.spellLang.setMaximumWidth(xW) self.spellLang.setMinimumWidth(xW)
self.spellLang.addItem(self.tr("Default"), "None") self.spellLang.addItem(self.tr("Default"), "None")
if CONFIG.hasEnchant: if CONFIG.hasEnchant:
for tag, language in SHARED.spelling.listDictionaries(): for tag, language in SHARED.spelling.listDictionaries():
self.spellLang.addItem(language, tag) self.spellLang.addItem(language, tag)
self.mainForm.addRow( self.addRow(
self.tr("Spell check language"), self.tr("Spell check language"), self.spellLang,
self.spellLang, self.tr("Overrides main preferences."),
self.tr("Overrides main preferences.") stretch=(3, 2)
) )
if (idx := self.spellLang.findData(data.spellLang)) != -1:
self.spellLang.setCurrentIndex(idx)
langIdx = 0 # Backup on Close
if pData.spellLang is not None:
langIdx = self.spellLang.findData(pData.spellLang)
if langIdx != -1:
self.spellLang.setCurrentIndex(langIdx)
self.doBackup = NSwitch(self) self.doBackup = NSwitch(self)
self.doBackup.setChecked(not pData.doBackup) self.doBackup.setChecked(not data.doBackup)
self.mainForm.addRow( self.addRow(
self.tr("No backup on close"), self.tr("Disable backup on close"), self.doBackup,
self.doBackup,
self.tr("Overrides main preferences.") self.tr("Overrides main preferences.")
) )
self.finalise()
return return
# END Class GuiProjectEditMain # END Class _SettingsPage
class GuiProjectEditStatus(QWidget): class _StatusPage(NFixedPage):
COL_LABEL = 0 COL_LABEL = 0
COL_USAGE = 1 COL_USAGE = 1
@@ -297,56 +309,54 @@ class GuiProjectEditStatus(QWidget):
super().__init__(parent=parent) super().__init__(parent=parent)
if isStatus: if isStatus:
self.theStatus = SHARED.project.data.itemStatus status = SHARED.project.data.itemStatus
pageLabel = self.tr("Novel File Status Levels") pageLabel = self.tr("Novel Document Status Levels")
colSetting = "statusColW" colSetting = "statusColW"
else: else:
self.theStatus = SHARED.project.data.itemImport status = SHARED.project.data.itemImport
pageLabel = self.tr("Note File Importance Levels") pageLabel = self.tr("Project Note Importance Levels")
colSetting = "importColW" colSetting = "importColW"
wCol0 = CONFIG.pxInt( wCol0 = CONFIG.pxInt(
SHARED.project.options.getInt("GuiProjectSettings", colSetting, 130) SHARED.project.options.getInt("GuiProjectSettings", colSetting, 130)
) )
self.colDeleted = [] self._changed = False
self.colChanged = False self._colDeleted = []
self.selColour = QColor(100, 100, 100) self._selColour = QColor(100, 100, 100)
self.iPx = SHARED.theme.baseIconSize self.iPx = SHARED.theme.baseIconSize
# The List # Title
# ======== self.pageTitle = NColourLabel(
pageLabel, SHARED.theme.helpText, parent=self,
scale=NColourLabel.HEADER_SCALE
)
self.listBox = QTreeWidget() # List Box
self.listBox.setHeaderLabels([ self.listBox = QTreeWidget(self)
self.tr("Label"), self.tr("Usage"), self.listBox.setHeaderLabels([self.tr("Label"), self.tr("Usage")])
])
self.listBox.itemSelectionChanged.connect(self._selectedItem) self.listBox.itemSelectionChanged.connect(self._selectedItem)
self.listBox.setColumnWidth(self.COL_LABEL, wCol0) self.listBox.setColumnWidth(self.COL_LABEL, wCol0)
self.listBox.setIndentation(0) self.listBox.setIndentation(0)
for key, entry in self.theStatus.items(): for key, entry in status.items():
self._addItem(key, entry["name"], entry["cols"], entry["count"]) self._addItem(key, entry["name"], entry["cols"], entry["count"])
# List Controls # List Controls
# ============= self.addButton = QPushButton(SHARED.theme.getIcon("add"), "", self)
self.addButton = QPushButton(SHARED.theme.getIcon("add"), "")
self.addButton.clicked.connect(self._newItem) 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.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.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)) self.dnButton.clicked.connect(lambda: self._moveItem(1))
# Edit Form # Edit Form
# =========
self.editName = QLineEdit() self.editName = QLineEdit()
self.editName.setMaxLength(40) self.editName.setMaxLength(40)
self.editName.setPlaceholderText(self.tr("Select item to edit")) self.editName.setPlaceholderText(self.tr("Select item to edit"))
@@ -364,8 +374,6 @@ class GuiProjectEditStatus(QWidget):
self.saveButton.clicked.connect(self._saveItem) self.saveButton.clicked.connect(self._saveItem)
# Assemble # Assemble
# ========
self.listControls = QVBoxLayout() self.listControls = QVBoxLayout()
self.listControls.addWidget(self.addButton) self.listControls.addWidget(self.addButton)
self.listControls.addWidget(self.delButton) self.listControls.addWidget(self.delButton)
@@ -387,16 +395,25 @@ class GuiProjectEditStatus(QWidget):
self.innerBox.addLayout(self.listControls) self.innerBox.addLayout(self.listControls)
self.outerBox = QVBoxLayout() self.outerBox = QVBoxLayout()
self.outerBox.addWidget(QLabel("<b>%s</b>" % pageLabel)) self.outerBox.addWidget(self.pageTitle)
self.outerBox.addLayout(self.innerBox) self.outerBox.addLayout(self.innerBox)
self.setLayout(self.outerBox) self.setCentralLayout(self.outerBox)
return return
@property
def wasChanged(self) -> bool:
"""The user changed these settings."""
return self._changed
##
# Methods
##
def getNewList(self) -> tuple[list, list]: def getNewList(self) -> tuple[list, list]:
"""Return list of entries.""" """Return list of entries."""
if self.colChanged: if self._changed:
newList = [] newList = []
for n in range(self.listBox.topLevelItemCount()): for n in range(self.listBox.topLevelItemCount()):
item = self.listBox.topLevelItem(n) item = self.listBox.topLevelItem(n)
@@ -406,10 +423,13 @@ class GuiProjectEditStatus(QWidget):
"name": item.text(self.COL_LABEL), "name": item.text(self.COL_LABEL),
"cols": item.data(self.COL_LABEL, self.COL_ROLE), "cols": item.data(self.COL_LABEL, self.COL_ROLE),
}) })
return newList, self.colDeleted return newList, self._colDeleted
return [], [] return [], []
def columnWidth(self) -> int:
"""Return the size of the header column."""
return self.listBox.columnWidth(0)
## ##
# Private Slots # Private Slots
## ##
@@ -417,12 +437,12 @@ class GuiProjectEditStatus(QWidget):
@pyqtSlot() @pyqtSlot()
def _selectColour(self) -> None: def _selectColour(self) -> None:
"""Open a dialog to select the status icon colour.""" """Open a dialog to select the status icon colour."""
if self.selColour is not None: if self._selColour is not None:
newCol = QColorDialog.getColor( newCol = QColorDialog.getColor(
self.selColour, self, self.tr("Select Colour") self._selColour, self, self.tr("Select Colour")
) )
if newCol.isValid(): if newCol.isValid():
self.selColour = newCol self._selColour = newCol
pixmap = QPixmap(self.iPx, self.iPx) pixmap = QPixmap(self.iPx, self.iPx)
pixmap.fill(newCol) pixmap.fill(newCol)
self.colButton.setIcon(QIcon(pixmap)) self.colButton.setIcon(QIcon(pixmap))
@@ -433,7 +453,7 @@ class GuiProjectEditStatus(QWidget):
def _newItem(self) -> None: def _newItem(self) -> None:
"""Create a new status item.""" """Create a new status item."""
self._addItem(None, self.tr("New Item"), (100, 100, 100), 0) self._addItem(None, self.tr("New Item"), (100, 100, 100), 0)
self.colChanged = True self._changed = True
return return
@pyqtSlot() @pyqtSlot()
@@ -446,8 +466,8 @@ class GuiProjectEditStatus(QWidget):
SHARED.error(self.tr("Cannot delete a status item that is in use.")) SHARED.error(self.tr("Cannot delete a status item that is in use."))
else: else:
self.listBox.takeTopLevelItem(iRow) self.listBox.takeTopLevelItem(iRow)
self.colDeleted.append(selItem.data(self.COL_LABEL, self.KEY_ROLE)) self._colDeleted.append(selItem.data(self.COL_LABEL, self.KEY_ROLE))
self.colChanged = True self._changed = True
return return
@pyqtSlot() @pyqtSlot()
@@ -458,9 +478,9 @@ class GuiProjectEditStatus(QWidget):
selItem.setText(self.COL_LABEL, simplified(self.editName.text())) selItem.setText(self.COL_LABEL, simplified(self.editName.text()))
selItem.setIcon(self.COL_LABEL, self.colButton.icon()) selItem.setIcon(self.COL_LABEL, self.colButton.icon())
selItem.setData(self.COL_LABEL, self.COL_ROLE, ( selItem.setData(self.COL_LABEL, self.COL_ROLE, (
self.selColour.red(), self.selColour.green(), self.selColour.blue() self._selColour.red(), self._selColour.green(), self._selColour.blue()
)) ))
self.colChanged = True self._changed = True
return return
@pyqtSlot() @pyqtSlot()
@@ -474,7 +494,7 @@ class GuiProjectEditStatus(QWidget):
name = selItem.text(self.COL_LABEL) name = selItem.text(self.COL_LABEL)
pixmap = QPixmap(self.iPx, self.iPx) pixmap = QPixmap(self.iPx, self.iPx)
pixmap.fill(QColor(*cols)) pixmap.fill(QColor(*cols))
self.selColour = QColor(*cols) self._selColour = QColor(*cols)
self.editName.setText(name) self.editName.setText(name)
self.colButton.setIcon(QIcon(pixmap)) self.colButton.setIcon(QIcon(pixmap))
self.editName.selectAll() self.editName.selectAll()
@@ -485,7 +505,7 @@ class GuiProjectEditStatus(QWidget):
else: else:
pixmap = QPixmap(self.iPx, self.iPx) pixmap = QPixmap(self.iPx, self.iPx)
pixmap.fill(QColor(100, 100, 100)) pixmap.fill(QColor(100, 100, 100))
self.selColour = QColor(100, 100, 100) self._selColour = QColor(100, 100, 100)
self.editName.setText("") self.editName.setText("")
self.colButton.setIcon(QIcon(pixmap)) self.colButton.setIcon(QIcon(pixmap))
self.editName.setEnabled(False) self.editName.setEnabled(False)
@@ -498,16 +518,16 @@ class GuiProjectEditStatus(QWidget):
## ##
def _addItem(self, key: str | None, name: str, def _addItem(self, key: str | None, name: str,
cols: tuple[int, int, int], count: int) -> None: colour: tuple[int, int, int], count: int) -> None:
"""Add a status item to the list.""" """Add a status item to the list."""
pixmap = QPixmap(self.iPx, self.iPx) pixmap = QPixmap(self.iPx, self.iPx)
pixmap.fill(QColor(*cols)) pixmap.fill(QColor(*colour))
item = QTreeWidgetItem() item = QTreeWidgetItem()
item.setText(self.COL_LABEL, name) item.setText(self.COL_LABEL, name)
item.setIcon(self.COL_LABEL, QIcon(pixmap)) item.setIcon(self.COL_LABEL, QIcon(pixmap))
item.setData(self.COL_LABEL, self.KEY_ROLE, key) item.setData(self.COL_LABEL, self.KEY_ROLE, key)
item.setData(self.COL_LABEL, self.COL_ROLE, cols) item.setData(self.COL_LABEL, self.COL_ROLE, colour)
item.setData(self.COL_LABEL, self.NUM_ROLE, count) item.setData(self.COL_LABEL, self.NUM_ROLE, count)
item.setText(self.COL_USAGE, self._usageString(count)) item.setText(self.COL_USAGE, self._usageString(count))
@@ -533,30 +553,29 @@ class GuiProjectEditStatus(QWidget):
if cItem is not None: if cItem is not None:
cItem.setSelected(True) cItem.setSelected(True)
self.colChanged = True self._changed = True
return return
def _getSelectedItem(self) -> QTreeWidgetItem | None: def _getSelectedItem(self) -> QTreeWidgetItem | None:
"""Get the currently selected item.""" """Get the currently selected item."""
selItem = self.listBox.selectedItems() if items := self.listBox.selectedItems():
if len(selItem) > 0: return items[0]
return selItem[0]
return None return None
def _usageString(self, nUse: int) -> str: def _usageString(self, count: int) -> str:
"""Generate usage string.""" """Generate usage string."""
if nUse == 0: if count == 0:
return self.tr("Not in use") return self.tr("Not in use")
elif nUse == 1: elif count == 1:
return self.tr("Used once") return self.tr("Used once")
else: else:
return self.tr("Used by {0} items").format(nUse) return self.tr("Used by {0} items").format(count)
# END Class GuiProjectEditStatus # END Class _StatusPage
class GuiProjectEditReplace(QWidget): class _ReplacePage(NFixedPage):
COL_KEY = 0 COL_KEY = 0
COL_REPL = 1 COL_REPL = 1
@@ -564,24 +583,24 @@ class GuiProjectEditReplace(QWidget):
def __init__(self, parent: QWidget) -> None: def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
self.arChanged = False self._changed = False
wCol0 = CONFIG.pxInt( wCol0 = CONFIG.pxInt(
SHARED.project.options.getInt("GuiProjectSettings", "replaceColW", 130) SHARED.project.options.getInt("GuiProjectSettings", "replaceColW", 130)
) )
pageLabel = self.tr("Text Replace List for Preview and Export")
# Title
self.pageTitle = NColourLabel(
self.tr("Text Auto-Replace for Preview and Build"),
SHARED.theme.helpText, parent=self, scale=NColourLabel.HEADER_SCALE
)
# List Box # List Box
# ========
self.listBox = QTreeWidget() self.listBox = QTreeWidget()
self.listBox.setHeaderLabels([ self.listBox.setHeaderLabels([self.tr("Keyword"), self.tr("Replace With")])
self.tr("Keyword"),
self.tr("Replace With"),
])
self.listBox.itemSelectionChanged.connect(self._selectedItem)
self.listBox.setColumnWidth(self.COL_KEY, wCol0) self.listBox.setColumnWidth(self.COL_KEY, wCol0)
self.listBox.setIndentation(0) self.listBox.setIndentation(0)
self.listBox.itemSelectionChanged.connect(self._selectedItem)
for aKey, aVal in SHARED.project.data.autoReplace.items(): for aKey, aVal in SHARED.project.data.autoReplace.items():
newItem = QTreeWidgetItem(["<%s>" % aKey, aVal]) newItem = QTreeWidgetItem(["<%s>" % aKey, aVal])
@@ -591,8 +610,6 @@ class GuiProjectEditReplace(QWidget):
self.listBox.setSortingEnabled(True) self.listBox.setSortingEnabled(True)
# List Controls # List Controls
# =============
self.addButton = QPushButton(SHARED.theme.getIcon("add"), "") self.addButton = QPushButton(SHARED.theme.getIcon("add"), "")
self.addButton.clicked.connect(self._addEntry) self.addButton.clicked.connect(self._addEntry)
@@ -600,8 +617,6 @@ class GuiProjectEditReplace(QWidget):
self.delButton.clicked.connect(self._delEntry) self.delButton.clicked.connect(self._delEntry)
# Edit Form # Edit Form
# =========
self.editKey = QLineEdit() self.editKey = QLineEdit()
self.editKey.setPlaceholderText(self.tr("Select item to edit")) self.editKey.setPlaceholderText(self.tr("Select item to edit"))
self.editKey.setEnabled(False) self.editKey.setEnabled(False)
@@ -615,8 +630,6 @@ class GuiProjectEditReplace(QWidget):
self.saveButton.clicked.connect(self._saveEntry) self.saveButton.clicked.connect(self._saveEntry)
# Assemble # Assemble
# ========
self.listControls = QVBoxLayout() self.listControls = QVBoxLayout()
self.listControls.addWidget(self.addButton) self.listControls.addWidget(self.addButton)
self.listControls.addWidget(self.delButton) self.listControls.addWidget(self.delButton)
@@ -636,50 +649,61 @@ class GuiProjectEditReplace(QWidget):
self.innerBox.addLayout(self.listControls) self.innerBox.addLayout(self.listControls)
self.outerBox = QVBoxLayout() self.outerBox = QVBoxLayout()
self.outerBox.addWidget(QLabel("<b>%s</b>" % pageLabel)) self.outerBox.addWidget(self.pageTitle)
self.outerBox.addLayout(self.innerBox) self.outerBox.addLayout(self.innerBox)
self.setLayout(self.outerBox) self.setCentralLayout(self.outerBox)
return return
@property
def wasChanged(self) -> bool:
"""The user changed these settings."""
return self._changed
##
# Methods
##
def getNewList(self) -> dict: def getNewList(self) -> dict:
"""Extract the list from the widget.""" """Extract the list from the widget."""
new = {} new = {}
for n in range(self.listBox.topLevelItemCount()): for n in range(self.listBox.topLevelItemCount()):
tItem = self.listBox.topLevelItem(n) if tItem := self.listBox.topLevelItem(n):
if tItem is not None:
aKey = self._stripNotAllowed(tItem.text(0)) aKey = self._stripNotAllowed(tItem.text(0))
aVal = tItem.text(1) aVal = tItem.text(1)
if len(aKey) > 0: if len(aKey) > 0:
new[aKey] = aVal new[aKey] = aVal
return new return new
def columnWidth(self) -> int:
"""Return the size of the header column."""
return self.listBox.columnWidth(0)
## ##
# Internal Functions # Private Slots
## ##
def _selectedItem(self) -> bool: @pyqtSlot()
def _selectedItem(self) -> None:
"""Extract the details from the selected item and populate the """Extract the details from the selected item and populate the
edit form. edit form.
""" """
selItem = self._getSelectedItem() if selItem := self._getSelectedItem():
if selItem is None: editKey = self._stripNotAllowed(selItem.text(0))
return False editVal = selItem.text(1)
editKey = self._stripNotAllowed(selItem.text(0)) self.editKey.setText(editKey)
editVal = selItem.text(1) self.editValue.setText(editVal)
self.editKey.setText(editKey) self.editKey.setEnabled(True)
self.editValue.setText(editVal) self.editValue.setEnabled(True)
self.editKey.setEnabled(True) self.editKey.selectAll()
self.editValue.setEnabled(True) self.editKey.setFocus()
self.editKey.selectAll() return
self.editKey.setFocus()
return True
@pyqtSlot()
def _saveEntry(self) -> None: def _saveEntry(self) -> None:
"""Save the form data into the list widget.""" """Save the form data into the list widget."""
selItem = self._getSelectedItem() if selItem := self._getSelectedItem():
if selItem:
newKey = self.editKey.text() newKey = self.editKey.text()
newVal = self.editValue.text() newVal = self.editValue.text()
saveKey = self._stripNotAllowed(newKey) saveKey = self._stripNotAllowed(newKey)
@@ -691,38 +715,36 @@ class GuiProjectEditReplace(QWidget):
self.editKey.setEnabled(False) self.editKey.setEnabled(False)
self.editValue.setEnabled(False) self.editValue.setEnabled(False)
self.listBox.clearSelection() self.listBox.clearSelection()
self.arChanged = True self._changed = True
return return
@pyqtSlot()
def _addEntry(self) -> None: def _addEntry(self) -> None:
"""Add a new list entry.""" """Add a new list entry."""
saveKey = "<keyword%d>" % (self.listBox.topLevelItemCount() + 1) saveKey = "<keyword%d>" % (self.listBox.topLevelItemCount() + 1)
newVal = "" self.listBox.addTopLevelItem(QTreeWidgetItem([saveKey, ""]))
newItem = QTreeWidgetItem([saveKey, newVal])
self.listBox.addTopLevelItem(newItem)
return return
@pyqtSlot()
def _delEntry(self) -> None: def _delEntry(self) -> None:
"""Delete the selected entry.""" """Delete the selected entry."""
selItem = self._getSelectedItem() if selItem := self._getSelectedItem():
if selItem:
self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(selItem)) self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(selItem))
self.arChanged = True self._changed = True
return return
##
# Internal Functions
##
def _getSelectedItem(self) -> QTreeWidgetItem | None: def _getSelectedItem(self) -> QTreeWidgetItem | None:
"""Extract the currently selected item.""" """Extract the currently selected item."""
selItem = self.listBox.selectedItems() if items := self.listBox.selectedItems():
if len(selItem) == 0: return items[0]
return None return None
return selItem[0]
def _stripNotAllowed(self, key: str) -> str: def _stripNotAllowed(self, key: str) -> str:
"""Clean up the replace key string.""" """Clean up the replace key string."""
result = "" return "".join(c for c in key if c.isalnum())
for c in key:
if c.isalnum():
result += c
return result
# END Class GuiProjectEditReplace # END Class _ReplacePage
+93 -56
View File
@@ -6,6 +6,8 @@ File History:
Created: 2020-05-03 [0.4.5] NConfigLayout, NColourLabel Created: 2020-05-03 [0.4.5] NConfigLayout, NColourLabel
Created: 2023-05-23 [2.1b1] NSimpleLayout Created: 2023-05-23 [2.1b1] NSimpleLayout
Created: 2024-01-08 [2.3b1] NScrollableForm Created: 2024-01-08 [2.3b1] NScrollableForm
Created: 2024-01-26 [2.3b1] NScrollablePage
Created: 2024-01-26 [2.3b1] NFixedPage
This file is a part of novelWriter This file is a part of novelWriter
Copyright 20182024, Veronica Berglyd Olsen Copyright 20182024, Veronica Berglyd Olsen
@@ -28,17 +30,59 @@ 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, QScrollArea, QSizePolicy, QAbstractButton, QFrame, QGridLayout, QHBoxLayout, QLabel, QLayout,
QVBoxLayout, QWidget QScrollArea, QSizePolicy, QVBoxLayout, QWidget
) )
from novelwriter import CONFIG from novelwriter import CONFIG
FONT_SCALE = 0.9 DEFAULT_SCALE = 0.9
RIGHT_TOP = Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop 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 NFixedPage(QFrame):
"""Extension: Fixed Page Widget
A custom widget that holds a layout. This is just a wrapper around a
QFrame that sets the same frame style as the other Page widgets.
"""
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
self.setFrameShadow(QFrame.Shadow.Sunken)
self.setFrameShape(QFrame.Shape.StyledPanel)
self.setCentralLayout = self.setLayout
return
# END Class NFixedPage
class NScrollablePage(QScrollArea):
"""Extension: Scrollable Page Widget
A custom widget that holds a layout within a scrollable area.
"""
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
self._widget = QWidget(self)
self.setWidget(self._widget)
self.setWidgetResizable(True)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
self.setFrameShadow(QFrame.Shadow.Sunken)
self.setFrameShape(QFrame.Shape.StyledPanel)
return
def setCentralLayout(self, layout: QLayout) -> None:
"""Set the central layout of the scroll page."""
self._widget.setLayout(layout)
return
# END Class NScrollablePage
class NScrollableForm(QScrollArea): class NScrollableForm(QScrollArea):
"""Extension: Scrollable Form Widget """Extension: Scrollable Form Widget
@@ -48,8 +92,9 @@ class NScrollableForm(QScrollArea):
def __init__(self, parent: QWidget) -> None: def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
self._helpCol = QColor(0, 0, 0) self._helpCol = QColor(0, 0, 0)
self._fontScale = FONT_SCALE self._fontScale = DEFAULT_SCALE
self._first = True self._first = True
self._indent = CONFIG.pxInt(12)
self._sections: dict[int, QLabel] = {} self._sections: dict[int, QLabel] = {}
self._editable: dict[str, NColourLabel] = {} self._editable: dict[str, NColourLabel] = {}
@@ -65,6 +110,8 @@ class NScrollableForm(QScrollArea):
self.setWidgetResizable(True) self.setWidgetResizable(True)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
self.setFrameShadow(QFrame.Shadow.Sunken)
self.setFrameShape(QFrame.Shape.StyledPanel)
return return
@@ -80,7 +127,7 @@ class NScrollableForm(QScrollArea):
# Setters # Setters
## ##
def setHelpTextStyle(self, color: QColor, scale: float = FONT_SCALE) -> None: def setHelpTextStyle(self, color: QColor, scale: float = DEFAULT_SCALE) -> None:
"""Set the text color for the help text.""" """Set the text color for the help text."""
self._helpCol = color self._helpCol = color
self._fontScale = scale self._fontScale = scale
@@ -92,6 +139,11 @@ class NScrollableForm(QScrollArea):
qHelp.setText(text) qHelp.setText(text)
return return
def setRowIndent(self, indent: int) -> None:
"""Set the indentation of each row."""
self._indent = max(indent, 0)
return
## ##
# Methods # Methods
## ##
@@ -123,37 +175,43 @@ class NScrollableForm(QScrollArea):
return return
def addRow(self, label: str, widget: QWidget, helpText: str = "", unit: str | None = None, def addRow(self, label: str, widget: QWidget, helpText: str = "", unit: str | None = None,
button: QWidget | None = None, editable: str | None = None) -> None: button: QWidget | None = None, editable: str | None = None,
stretch: tuple[int, int] = (0, 0)) -> None:
"""Add a label and a widget as a new row of the form.""" """Add a label and a widget as a new row of the form."""
row = QHBoxLayout() row = QHBoxLayout()
row.setSpacing(CONFIG.pxInt(4)) row.setSpacing(CONFIG.pxInt(12))
mPx = CONFIG.pxInt(12)
qLabel = QLabel(label, self) qLabel = QLabel(label, self)
qLabel.setIndent(mPx) qLabel.setIndent(self._indent)
qLabel.setBuddy(widget) qLabel.setBuddy(widget)
if helpText: if helpText:
qHelp = NColourLabel(str(helpText), self._helpCol, scale=self._fontScale, wrap=True) qHelp = NColourLabel(
qHelp.setIndent(mPx) str(helpText), self._helpCol, parent=self,
scale=self._fontScale, wrap=True, indent=self._indent
)
labelBox = QVBoxLayout() labelBox = QVBoxLayout()
labelBox.addWidget(qLabel) labelBox.addWidget(qLabel)
labelBox.addWidget(qHelp) labelBox.addWidget(qHelp)
labelBox.setSpacing(0) labelBox.setSpacing(0)
labelBox.addStretch(1) row.addLayout(labelBox, stretch[0])
row.addLayout(labelBox)
if editable: if editable:
self._editable[editable] = qHelp self._editable[editable] = qHelp
else: else:
row.addWidget(qLabel) row.addWidget(qLabel, stretch[0])
row.addSpacing(mPx)
row.addWidget(widget)
if isinstance(unit, str): if isinstance(unit, str):
row.addWidget(QLabel(unit, self)) box = QHBoxLayout()
box.addWidget(widget)
box.addWidget(QLabel(unit, self))
row.addLayout(box, stretch[1])
elif isinstance(button, QAbstractButton): elif isinstance(button, QAbstractButton):
row.addWidget(button) box = QHBoxLayout()
box.addWidget(widget)
box.addWidget(button)
row.addLayout(box, stretch[1])
else:
row.addWidget(widget, stretch[1])
self._layout.addLayout(row) self._layout.addLayout(row)
self._index[label.strip()] = widget self._index[label.strip()] = widget
@@ -177,7 +235,7 @@ class NConfigLayout(QGridLayout):
self._nextRow = 0 self._nextRow = 0
self._helpCol = QColor(0, 0, 0) self._helpCol = QColor(0, 0, 0)
self._fontScale = FONT_SCALE self._fontScale = DEFAULT_SCALE
self._itemMap = {} self._itemMap = {}
wSp = CONFIG.pxInt(8) wSp = CONFIG.pxInt(8)
@@ -187,16 +245,6 @@ class NConfigLayout(QGridLayout):
return return
##
# Getters and Setters
##
def setHelpTextStyle(self, color: QColor, 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
## ##
# Class Methods # Class Methods
## ##
@@ -212,8 +260,8 @@ class NConfigLayout(QGridLayout):
self._nextRow += 1 self._nextRow += 1
return return
def addRow(self, label: str, widget: QWidget, helpText: str | None = None, def addRow(self, label: str, widget: QWidget, unit: str | None = None,
unit: str | None = None, button: QWidget | None = None) -> int: button: QWidget | None = None) -> int:
"""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 grid."""
wSp = CONFIG.pxInt(8) wSp = CONFIG.pxInt(8)
qLabel = QLabel(label) qLabel = QLabel(label)
@@ -221,17 +269,7 @@ class NConfigLayout(QGridLayout):
qLabel.setBuddy(widget) qLabel.setBuddy(widget)
qHelp = None qHelp = None
if helpText is not None: self.addWidget(qLabel, self._nextRow, 0, 1, 1, LEFT_TOP)
qHelp = NColourLabel(str(helpText), self._helpCol, scale=self._fontScale, wrap=True)
qHelp.setIndent(wSp)
labelBox = QVBoxLayout()
labelBox.addWidget(qLabel)
labelBox.addWidget(qHelp)
labelBox.setSpacing(0)
labelBox.addStretch(1)
self.addLayout(labelBox, self._nextRow, 0, 1, 1, LEFT_TOP)
else:
self.addWidget(qLabel, self._nextRow, 0, 1, 1, LEFT_TOP)
if isinstance(unit, str): if isinstance(unit, str):
controlBox = QHBoxLayout() controlBox = QHBoxLayout()
@@ -248,12 +286,7 @@ class NConfigLayout(QGridLayout):
self.addLayout(controlBox, self._nextRow, 1, 1, 1, RIGHT_TOP) self.addLayout(controlBox, self._nextRow, 1, 1, 1, RIGHT_TOP)
else: else:
if isinstance(widget, QLineEdit): self.addWidget(widget, self._nextRow, 1, 1, 1, RIGHT_TOP)
qLayout = QHBoxLayout()
qLayout.addWidget(widget)
self.addLayout(qLayout, self._nextRow, 1, 1, 1, RIGHT_TOP)
else:
self.addWidget(widget, self._nextRow, 1, 1, 1, RIGHT_TOP)
self.setRowStretch(self._nextRow, 0) self.setRowStretch(self._nextRow, 0)
self.setRowStretch(self._nextRow+1, 1) self.setRowStretch(self._nextRow+1, 1)
@@ -321,17 +354,21 @@ class NColourLabel(QLabel):
optionally at a specific size, and word wrapped. optionally at a specific size, and word wrapped.
""" """
HELP_SCALE = DEFAULT_SCALE
HEADER_SCALE = 1.25
def __init__(self, text: str, color: QColor, parent: QWidget | None = None, def __init__(self, text: str, color: QColor, parent: QWidget | None = None,
scale: float = FONT_SCALE, wrap: bool = False) -> None: scale: float = HELP_SCALE, wrap: bool = False, indent: int = 0) -> None:
super().__init__(text, parent=parent) super().__init__(text, parent=parent)
lblCol = self.palette() font = self.font()
lblCol.setColor(QPalette.WindowText, color) font.setPointSizeF(scale*font.pointSizeF())
self.setPalette(lblCol) colour = self.palette()
colour.setColor(QPalette.WindowText, color)
lblFont = self.font() self.setPalette(colour)
lblFont.setPointSizeF(scale*lblFont.pointSizeF()) self.setFont(font)
self.setFont(lblFont) self.setIndent(indent)
if wrap: if wrap:
self.setWordWrap(True) self.setWordWrap(True)
-130
View File
@@ -1,130 +0,0 @@
"""
novelWriter Custom Widget: Paged Dialog
=========================================
File History:
Created: 2020-05-17 [0.5.1]
This file is a part of novelWriter
Copyright 20182024, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations
from PyQt5.QtGui import QPaintEvent
from PyQt5.QtCore import QRect, QPoint, QSize
from PyQt5.QtWidgets import (
QDialog, QHBoxLayout, QStyle, QStyleOptionTab, QStylePainter, QTabBar,
QTabWidget, QVBoxLayout, QWidget
)
from novelwriter import CONFIG
class NPagedDialog(QDialog):
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
self._tabBar = NVerticalTabBar(self)
self._tabBar.setExpanding(False)
self._tabBox = QTabWidget(self)
self._tabBox.setTabBar(self._tabBar)
self._tabBox.setTabPosition(QTabWidget.West)
self._buttonBox = QHBoxLayout()
self._outerBox = QVBoxLayout()
self._outerBox.addWidget(self._tabBox)
self._outerBox.addLayout(self._buttonBox)
# Default Margins
thisStyle = self.style()
mL = thisStyle.pixelMetric(QStyle.PM_LayoutLeftMargin)
mR = thisStyle.pixelMetric(QStyle.PM_LayoutRightMargin)
mT = thisStyle.pixelMetric(QStyle.PM_LayoutLeftMargin)
mB = thisStyle.pixelMetric(QStyle.PM_LayoutBottomMargin)
# Set Margins
self.setContentsMargins(0, 0, 0, 0)
self._outerBox.setContentsMargins(0, 0, 0, mB)
self._buttonBox.setContentsMargins(mL, 0, mR, 0)
self._outerBox.setSpacing(mT)
self.setLayout(self._outerBox)
return
def addTab(self, widget: QWidget, label: str) -> None:
"""Forward the adding of tabs to the QTabWidget."""
self._tabBox.addTab(widget, label)
return
def addControls(self, buttonBar: QWidget) -> None:
"""Add a button bar to the dialog."""
self._buttonBox.addWidget(buttonBar)
return
def setCurrentWidget(self, widget: QWidget) -> None:
"""Forward the changing of tab to the QTabWidget."""
self._tabBox.setCurrentWidget(widget)
return
# END Class NPagedDialog
class NVerticalTabBar(QTabBar):
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
self._mW = CONFIG.pxInt(150)
return
def tabSizeHint(self, index: int) -> QSize:
"""Return a transposed size hint for the rotated bar."""
tSize = super().tabSizeHint(index)
tSize.transpose()
tSize.setWidth(min(tSize.width(), self._mW))
return tSize
def paintEvent(self, event: QPaintEvent) -> None:
"""Custom implementation of the label painter that rotates the
label 90 degrees.
"""
pObj = QStylePainter(self)
oObj = QStyleOptionTab()
for i in range(self.count()):
self.initStyleOption(oObj, i)
pObj.drawControl(QStyle.CE_TabBarTabShape, oObj)
pObj.save()
oSize = oObj.rect.size()
oSize.transpose()
oRect = QRect(QPoint(), oSize)
oRect.moveCenter(oObj.rect.center())
oObj.rect = oRect
oCenter = self.tabRect(i).center()
pObj.translate(oCenter)
pObj.rotate(90)
pObj.translate(-oCenter)
pObj.drawControl(QStyle.CE_TabBarTabLabel, oObj)
pObj.restore()
return
# END Class NVerticalTabBar
+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.docmerge import GuiDocMerge
from novelwriter.dialogs.docsplit import GuiDocSplit from novelwriter.dialogs.docsplit import GuiDocSplit
from novelwriter.dialogs.editlabel import GuiEditLabel from novelwriter.dialogs.editlabel import GuiEditLabel
from novelwriter.dialogs.projsettings import GuiProjectSettings from novelwriter.dialogs.projectsettings import GuiProjectSettings
from novelwriter.enum import ( from novelwriter.enum import (
nwDocMode, nwItemType, nwItemClass, nwItemLayout, nwWidget nwDocMode, nwItemType, nwItemClass, nwItemLayout, nwWidget
) )
@@ -1748,7 +1748,7 @@ class _TreeContextMenu(QMenu):
menu.addSeparator() menu.addSeparator()
action = menu.addAction(self.tr("Manage Labels ...")) action = menu.addAction(self.tr("Manage Labels ..."))
action.triggered.connect( action.triggered.connect(
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.TAB_STATUS) lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.PAGE_STATUS)
) )
else: else:
menu = self.addMenu(self.tr("Set Importance to ...")) menu = self.addMenu(self.tr("Set Importance to ..."))
@@ -1765,7 +1765,7 @@ class _TreeContextMenu(QMenu):
menu.addSeparator() menu.addSeparator()
action = menu.addAction(self.tr("Manage Labels ...")) action = menu.addAction(self.tr("Manage Labels ..."))
action.triggered.connect( action.triggered.connect(
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.TAB_IMPORT) lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.PAGE_IMPORT)
) )
return return
+7 -5
View File
@@ -53,7 +53,7 @@ from novelwriter.dialogs.about import GuiAbout
from novelwriter.dialogs.updates import GuiUpdates from novelwriter.dialogs.updates import GuiUpdates
from novelwriter.dialogs.wordlist import GuiWordList from novelwriter.dialogs.wordlist import GuiWordList
from novelwriter.dialogs.preferences import GuiPreferences 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.welcome import GuiWelcome
from novelwriter.tools.manuscript import GuiManuscript from novelwriter.tools.manuscript import GuiManuscript
from novelwriter.tools.dictionaries import GuiDictionaries from novelwriter.tools.dictionaries import GuiDictionaries
@@ -809,10 +809,10 @@ class GuiMain(QMainWindow):
@pyqtSlot() @pyqtSlot()
@pyqtSlot(int) @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.""" """Open the project settings dialog."""
if SHARED.hasProject: if SHARED.hasProject:
dialog = GuiProjectSettings(self, focusTab=focusTab) dialog = GuiProjectSettings(self, gotoPage=focusTab)
dialog.newProjectSettingsReady.connect(self._processProjectSettingsChanges) dialog.newProjectSettingsReady.connect(self._processProjectSettingsChanges)
dialog.exec_() dialog.exec_()
return return
@@ -1111,13 +1111,15 @@ class GuiMain(QMainWindow):
return return
@pyqtSlot() @pyqtSlot(bool)
def _processProjectSettingsChanges(self) -> None: def _processProjectSettingsChanges(self, rebuildTrees: bool) -> None:
"""Refresh data dependent on project settings.""" """Refresh data dependent on project settings."""
logger.debug("Applying new project settings") logger.debug("Applying new project settings")
SHARED.updateSpellCheckLanguage() SHARED.updateSpellCheckLanguage()
self.itemDetails.refreshDetails() self.itemDetails.refreshDetails()
self._updateWindowTitle(SHARED.project.data.name) self._updateWindowTitle(SHARED.project.data.name)
if rebuildTrees:
self.rebuildTrees()
return return
@pyqtSlot() @pyqtSlot()
+27 -25
View File
@@ -38,14 +38,12 @@ from novelwriter import CONFIG, SHARED
from novelwriter.common import formatTime, numberToRoman from novelwriter.common import formatTime, numberToRoman
from novelwriter.constants import nwUnicode from novelwriter.constants import nwUnicode
from novelwriter.extensions.switch import NSwitch from novelwriter.extensions.switch import NSwitch
from novelwriter.extensions.configlayout import NColourLabel from novelwriter.extensions.configlayout import NColourLabel, NFixedPage, NScrollablePage
from novelwriter.extensions.pagedsidebar import NPagedSideBar from novelwriter.extensions.pagedsidebar import NPagedSideBar
from novelwriter.extensions.novelselector import NovelSelector from novelwriter.extensions.novelselector import NovelSelector
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
HEADER_SIZE = 1.4
class GuiNovelDetails(QDialog): class GuiNovelDetails(QDialog):
@@ -59,21 +57,18 @@ class GuiNovelDetails(QDialog):
self.setObjectName("GuiNovelDetails") self.setObjectName("GuiNovelDetails")
self.setWindowTitle(self.tr("Novel Details")) self.setWindowTitle(self.tr("Novel Details"))
wW = CONFIG.pxInt(500)
wH = CONFIG.pxInt(400)
options = SHARED.project.options options = SHARED.project.options
self.setMinimumSize(CONFIG.pxInt(500), CONFIG.pxInt(400))
self.setMinimumSize(wW, wH)
self.resize( self.resize(
CONFIG.pxInt(options.getInt("GuiNovelDetails", "winWidth", wW)), CONFIG.pxInt(options.getInt("GuiNovelDetails", "winWidth", CONFIG.pxInt(650))),
CONFIG.pxInt(options.getInt("GuiNovelDetails", "winHeight", wH)) CONFIG.pxInt(options.getInt("GuiNovelDetails", "winHeight", CONFIG.pxInt(500)))
) )
# Title # Title
self.titleLabel = NColourLabel( self.titleLabel = NColourLabel(
self.tr("Novel Details"), SHARED.theme.helpText, parent=self, scale=1.25 self.tr("Novel Details"), SHARED.theme.helpText,
parent=self, scale=NColourLabel.HEADER_SCALE, indent=CONFIG.pxInt(4)
) )
self.titleLabel.setIndent(CONFIG.pxInt(4))
# Novel Selector # Novel Selector
self.novelSelector = NovelSelector(self) self.novelSelector = NovelSelector(self)
@@ -192,7 +187,7 @@ class GuiNovelDetails(QDialog):
# END Class GuiNovelDetails # END Class GuiNovelDetails
class _OverviewPage(QWidget): class _OverviewPage(NScrollablePage):
def __init__(self, parent: QWidget) -> None: def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
@@ -204,7 +199,8 @@ class _OverviewPage(QWidget):
# Project Info # Project Info
self.projLabel = NColourLabel( self.projLabel = NColourLabel(
self.tr("Project"), SHARED.theme.helpText, parent=self, scale=HEADER_SIZE self.tr("Project"), SHARED.theme.helpText,
parent=self, scale=NColourLabel.HEADER_SCALE
) )
self.projName = QLabel("", self) self.projName = QLabel("", self)
@@ -227,7 +223,8 @@ class _OverviewPage(QWidget):
# Novel Info # Novel Info
self.novelLabel = NColourLabel( self.novelLabel = NColourLabel(
self.tr("Selected Novel"), SHARED.theme.helpText, parent=self, scale=HEADER_SIZE self.tr("Selected Novel"), SHARED.theme.helpText,
parent=self, scale=NColourLabel.HEADER_SCALE
) )
self.novelName = QLabel("", self) self.novelName = QLabel("", self)
@@ -250,11 +247,10 @@ class _OverviewPage(QWidget):
self.outerBox.addLayout(self.projForm) self.outerBox.addLayout(self.projForm)
self.outerBox.addWidget(self.novelLabel) self.outerBox.addWidget(self.novelLabel)
self.outerBox.addLayout(self.novelForm) self.outerBox.addLayout(self.novelForm)
self.outerBox.setContentsMargins(0, 0, 0, 0)
self.outerBox.setSpacing(sPx) self.outerBox.setSpacing(sPx)
self.outerBox.addStretch(1) self.outerBox.addStretch(1)
self.setLayout(self.outerBox) self.setCentralLayout(self.outerBox)
return return
@@ -299,7 +295,7 @@ class _OverviewPage(QWidget):
# END Class _OverviewPage # END Class _OverviewPage
class _ContentsPage(QWidget): class _ContentsPage(NFixedPage):
C_TITLE = 0 C_TITLE = 0
C_WORDS = 1 C_WORDS = 1
@@ -320,7 +316,8 @@ class _ContentsPage(QWidget):
# Title # Title
self.contentLabel = NColourLabel( self.contentLabel = NColourLabel(
self.tr("Table of Contents"), SHARED.theme.helpText, parent=self, scale=HEADER_SIZE self.tr("Table of Contents"), SHARED.theme.helpText,
parent=self, scale=NColourLabel.HEADER_SCALE
) )
# Contents Tree # Contents Tree
@@ -407,20 +404,25 @@ class _ContentsPage(QWidget):
self.outerBox.addWidget(self.contentLabel) self.outerBox.addWidget(self.contentLabel)
self.outerBox.addWidget(self.tocTree) self.outerBox.addWidget(self.tocTree)
self.outerBox.addLayout(self.optionsBox) self.outerBox.addLayout(self.optionsBox)
self.outerBox.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.outerBox) self.setCentralLayout(self.outerBox)
return return
def saveSettings(self) -> None: def saveSettings(self) -> None:
"""Save the user GUI settings.""" """Save the user GUI settings."""
widthCol0 = CONFIG.rpxInt(self.tocTree.columnWidth(0))
widthCol1 = CONFIG.rpxInt(self.tocTree.columnWidth(1))
widthCol2 = CONFIG.rpxInt(self.tocTree.columnWidth(2))
widthCol3 = CONFIG.rpxInt(self.tocTree.columnWidth(3))
widthCol4 = CONFIG.rpxInt(self.tocTree.columnWidth(4))
options = SHARED.project.options options = SHARED.project.options
options.setValue("GuiNovelDetails", "widthCol0", self.tocTree.columnWidth(0)) options.setValue("GuiNovelDetails", "widthCol0", widthCol0)
options.setValue("GuiNovelDetails", "widthCol1", self.tocTree.columnWidth(1)) options.setValue("GuiNovelDetails", "widthCol1", widthCol1)
options.setValue("GuiNovelDetails", "widthCol2", self.tocTree.columnWidth(2)) options.setValue("GuiNovelDetails", "widthCol2", widthCol2)
options.setValue("GuiNovelDetails", "widthCol3", self.tocTree.columnWidth(3)) options.setValue("GuiNovelDetails", "widthCol3", widthCol3)
options.setValue("GuiNovelDetails", "widthCol4", self.tocTree.columnWidth(4)) options.setValue("GuiNovelDetails", "widthCol4", widthCol4)
options.setValue("GuiNovelDetails", "wordsPerPage", self.wpValue.value()) options.setValue("GuiNovelDetails", "wordsPerPage", self.wpValue.value())
options.setValue("GuiNovelDetails", "countFrom", self.poValue.value()) options.setValue("GuiNovelDetails", "countFrom", self.poValue.value())
options.setValue("GuiNovelDetails", "clearDouble", self.dblValue.isChecked()) options.setValue("GuiNovelDetails", "clearDouble", self.dblValue.isChecked())
@@ -31,7 +31,7 @@ from PyQt5.QtWidgets import QDialog, QAction, QColorDialog
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwItemType from novelwriter.enum import nwItemType
from novelwriter.dialogs.editlabel import GuiEditLabel from novelwriter.dialogs.editlabel import GuiEditLabel
from novelwriter.dialogs.projsettings import GuiProjectSettings from novelwriter.dialogs.projectsettings import GuiProjectSettings
KEY_DELAY = 1 KEY_DELAY = 1
@@ -63,17 +63,17 @@ def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI):
qtbot.addWidget(projSettings) qtbot.addWidget(projSettings)
# Switch Tabs # Switch Tabs
projSettings._focusTab(GuiProjectSettings.TAB_REPLACE) projSettings.sidebar.button(GuiProjectSettings.PAGE_SETTINGS).click()
assert projSettings._tabBox.currentWidget() == projSettings.tabReplace assert projSettings.mainStack.currentWidget() == projSettings.settingsPage
projSettings._focusTab(GuiProjectSettings.TAB_IMPORT) projSettings.sidebar.button(GuiProjectSettings.PAGE_STATUS).click()
assert projSettings._tabBox.currentWidget() == projSettings.tabImport assert projSettings.mainStack.currentWidget() == projSettings.statusPage
projSettings._focusTab(GuiProjectSettings.TAB_STATUS) projSettings.sidebar.button(GuiProjectSettings.PAGE_IMPORT).click()
assert projSettings._tabBox.currentWidget() == projSettings.tabStatus assert projSettings.mainStack.currentWidget() == projSettings.importPage
projSettings._focusTab(GuiProjectSettings.TAB_MAIN) projSettings.sidebar.button(GuiProjectSettings.PAGE_REPLACE).click()
assert projSettings._tabBox.currentWidget() == projSettings.tabMain assert projSettings.mainStack.currentWidget() == projSettings.replacePage
# Clean Up # Clean Up
projSettings.close() projSettings.close()
@@ -83,9 +83,11 @@ def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI):
@pytest.mark.gui @pytest.mark.gui
def testDlgProjSettings_Main(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd): def testDlgProjSettings_SettingsPage(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd):
"""Test the main tab of the project settings dialog.""" """Test the settings page of the dialog."""
monkeypatch.setattr(SHARED._spelling, "listDictionaries", lambda: [("en", "English [en]")]) languages = [("en", "English"), ("de", "German")]
monkeypatch.setattr(SHARED._spelling, "listDictionaries", lambda *a: languages)
monkeypatch.setattr(CONFIG, "listLanguages", lambda *a: languages)
# Create new project # Create new project
buildTestProject(nwGUI, projPath) buildTestProject(nwGUI, projPath)
@@ -94,72 +96,57 @@ def testDlgProjSettings_Main(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockR
# Set some values # Set some values
project = SHARED.project project = SHARED.project
project.data.setLanguage("en")
project.data.setSpellLang("en") project.data.setSpellLang("en")
project.data.setAuthor("Jane Smith") project.data.setAuthor("Jane Smith")
project.data.setAutoReplace({"A": "B", "C": "D"}) project.data.setAutoReplace({"A": "B", "C": "D"})
# Create Dialog # Create Dialog
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.TAB_MAIN) projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.PAGE_SETTINGS)
projSettings.show() projSettings.show()
qtbot.addWidget(projSettings) qtbot.addWidget(projSettings)
# Settings Tab # Settings Tab
# ============ settings = projSettings.settingsPage
tabMain = projSettings.tabMain assert settings.projName.text() == "New Project"
assert settings.projAuthor.text() == "Jane Smith"
assert settings.projLang.currentData() == "en"
assert settings.spellLang.currentData() == "en"
assert settings.doBackup.isChecked() is False
assert tabMain.editName.text() == "New Project" settings.projName.setText("Project Name")
assert tabMain.editTitle.text() == "New Novel" settings.projAuthor.setText("Jane Doe")
assert tabMain.editAuthor.text() == "Jane Smith" settings.projLang.setCurrentIndex(settings.projLang.findData("de"))
assert tabMain.spellLang.currentData() == "en" settings.spellLang.setCurrentIndex(settings.spellLang.findData("de"))
assert tabMain.doBackup.isChecked() is False settings.doBackup.setChecked(True)
tabMain.editName.setText("")
for c in "Project Name":
qtbot.keyClick(tabMain.editName, c, delay=KEY_DELAY)
tabMain.editTitle.setText("")
for c in "Project Title":
qtbot.keyClick(tabMain.editTitle, c, delay=KEY_DELAY)
tabMain.editAuthor.clear()
for c in "Jane Doe":
qtbot.keyClick(tabMain.editAuthor, c, delay=KEY_DELAY)
assert tabMain.editName.text() == "Project Name"
assert tabMain.editTitle.text() == "Project Title"
assert tabMain.editAuthor.text() == "Jane Doe"
projSettings._doSave() projSettings._doSave()
assert project.data.name == "Project Name" assert project.data.name == "Project Name"
assert project.data.title == "Project Title"
assert project.data.author == "Jane Doe" assert project.data.author == "Jane Doe"
assert project.data.language == "de"
assert project.data.spellLang == "de"
assert project.data.doBackup is False
nwGUI._processProjectSettingsChanges() nwGUI._processProjectSettingsChanges(False)
assert nwGUI.windowTitle() == "novelWriter - Project Name" assert nwGUI.windowTitle() == "novelWriter - Project Name"
# qtbot.stop() # qtbot.stop()
# END Test testDlgProjSettings_Main # END Test testDlgProjSettings_SettingsPage
@pytest.mark.gui @pytest.mark.gui
def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd): def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
"""Test the status and importance tabs of the project settings """Test the status and importance pages of the dialog."""
dialog.
"""
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True)) monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
monkeypatch.setattr(SHARED._spelling, "listDictionaries", lambda: [("en", "English [en]")])
# Create new project
mockRnd.reset()
buildTestProject(nwGUI, projPath) buildTestProject(nwGUI, projPath)
CONFIG.setBackupPath(fncPath)
# Set some values # Set some values
theProject = SHARED.project project = SHARED.project
theProject.tree[C.hTitlePage].setStatus(C.sFinished) # type: ignore project.tree[C.hTitlePage].setStatus(C.sFinished) # type: ignore
theProject.tree[C.hChapterDoc].setStatus(C.sDraft) # type: ignore project.tree[C.hChapterDoc].setStatus(C.sDraft) # type: ignore
theProject.tree[C.hSceneDoc].setStatus(C.sDraft) # type: ignore project.tree[C.hSceneDoc].setStatus(C.sDraft) # type: ignore
nwGUI.projView.projTree.setSelectedHandle(C.hPlotRoot) nwGUI.projView.projTree.setSelectedHandle(C.hPlotRoot)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, hLevel=1, isNote=True) nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, hLevel=1, isNote=True)
@@ -172,51 +159,54 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncPath, projPat
hCharNote = "0000000000011" hCharNote = "0000000000011"
hWorldNote = "0000000000012" hWorldNote = "0000000000012"
theProject.tree[hPlotNote].setImport(C.iMajor) # type: ignore project.tree[hPlotNote].setImport(C.iMajor) # type: ignore
theProject.tree[hCharNote].setImport(C.iMajor) # type: ignore project.tree[hCharNote].setImport(C.iMajor) # type: ignore
theProject.tree[hWorldNote].setImport(C.iMain) # type: ignore project.tree[hWorldNote].setImport(C.iMain) # type: ignore
nwGUI.rebuildTrees()
project.countStatus()
assert [e["count"] for _, e in project.data.itemStatus.items()] == [2, 0, 2, 1]
assert [e["count"] for _, e in project.data.itemImport.items()] == [3, 0, 2, 1]
# Create Dialog # Create Dialog
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.TAB_STATUS) projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.PAGE_STATUS)
projSettings.show() projSettings.show()
qtbot.addWidget(projSettings) qtbot.addWidget(projSettings)
# Status Tab # Status Tab
# ========== # ==========
tabStatus = projSettings.tabStatus status = projSettings.statusPage
assert tabStatus.colChanged is False assert status.wasChanged is False
assert tabStatus.getNewList() == ([], []) assert status.getNewList() == ([], [])
assert tabStatus.listBox.topLevelItemCount() == 4 assert status.listBox.topLevelItemCount() == 4
# Can't delete the first item (it's in use) # Can't delete the first item (it's in use)
tabStatus.listBox.clearSelection() status.listBox.clearSelection()
tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(0)) status.listBox.setCurrentItem(status.listBox.topLevelItem(0))
qtbot.mouseClick(tabStatus.delButton, Qt.LeftButton) qtbot.mouseClick(status.delButton, Qt.LeftButton)
assert tabStatus.listBox.topLevelItemCount() == 4 assert status.listBox.topLevelItemCount() == 4
# Can delete the second item # Can delete the second item
tabStatus.listBox.clearSelection() status.listBox.clearSelection()
tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(1)) status.listBox.setCurrentItem(status.listBox.topLevelItem(1))
qtbot.mouseClick(tabStatus.delButton, Qt.LeftButton) qtbot.mouseClick(status.delButton, Qt.LeftButton)
assert tabStatus.listBox.topLevelItemCount() == 3 assert status.listBox.topLevelItemCount() == 3
# Add a new item # Add a new item
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(QColorDialog, "getColor", lambda *a: QColor(20, 30, 40)) mp.setattr(QColorDialog, "getColor", lambda *a: QColor(20, 30, 40))
qtbot.mouseClick(tabStatus.addButton, Qt.LeftButton) status.addButton.click()
tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(3)) status.listBox.setCurrentItem(status.listBox.topLevelItem(3))
for _ in range(8): status.editName.setText("Final")
qtbot.keyClick(tabStatus.editName, Qt.Key_Backspace, delay=KEY_DELAY) status.colButton.click()
for c in "Final": status.saveButton.click()
qtbot.keyClick(tabStatus.editName, c, delay=KEY_DELAY) assert status.listBox.topLevelItemCount() == 4
qtbot.mouseClick(tabStatus.colButton, Qt.LeftButton)
qtbot.mouseClick(tabStatus.saveButton, Qt.LeftButton)
assert tabStatus.listBox.topLevelItemCount() == 4
assert tabStatus.colChanged is True assert status.wasChanged is True
assert tabStatus.getNewList() == ( assert status.getNewList() == (
[ [
{ {
"key": C.sNew, "key": C.sNew,
@@ -241,62 +231,62 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncPath, projPat
) )
# Move items, none selected -> no change # Move items, none selected -> no change
tabStatus.listBox.clearSelection() status.listBox.clearSelection()
tabStatus._moveItem(1) status._moveItem(1)
assert [x["key"] for x in tabStatus.getNewList()[0]] == [ assert [x["key"] for x in status.getNewList()[0]] == [
C.sNew, C.sDraft, C.sFinished, None C.sNew, C.sDraft, C.sFinished, None
] ]
# Move items, first selected, move up -> no change # Move items, first selected, move up -> no change
tabStatus.listBox.clearSelection() status.listBox.clearSelection()
tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(0)) status.listBox.setCurrentItem(status.listBox.topLevelItem(0))
tabStatus._moveItem(-1) status._moveItem(-1)
assert [x["key"] for x in tabStatus.getNewList()[0]] == [ assert [x["key"] for x in status.getNewList()[0]] == [
C.sNew, C.sDraft, C.sFinished, None C.sNew, C.sDraft, C.sFinished, None
] ]
# Move items, last selected, move up -> allowed # Move items, last selected, move up -> allowed
tabStatus.listBox.clearSelection() status.listBox.clearSelection()
tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(3)) status.listBox.setCurrentItem(status.listBox.topLevelItem(3))
tabStatus._moveItem(-1) status._moveItem(-1)
assert [x["key"] for x in tabStatus.getNewList()[0]] == [ assert [x["key"] for x in status.getNewList()[0]] == [
C.sNew, C.sDraft, None, C.sFinished C.sNew, C.sDraft, None, C.sFinished
] ]
# Move items, same selected, move down -> allowed # Move items, same selected, move down -> allowed
tabStatus._moveItem(1) status._moveItem(1)
assert [x["key"] for x in tabStatus.getNewList()[0]] == [ assert [x["key"] for x in status.getNewList()[0]] == [
C.sNew, C.sDraft, C.sFinished, None C.sNew, C.sDraft, C.sFinished, None
] ]
# Importance Tab # Importance Tab
# ============== # ==============
tabImport = projSettings.tabImport importance = projSettings.importPage
projSettings._focusTab(GuiProjectSettings.TAB_IMPORT) projSettings._sidebarClicked(GuiProjectSettings.PAGE_IMPORT)
# Delete unused entry # Delete unused entry
tabImport.listBox.clearSelection() importance.listBox.clearSelection()
tabImport.listBox.setCurrentItem(tabImport.listBox.topLevelItem(1)) importance.listBox.setCurrentItem(importance.listBox.topLevelItem(1))
qtbot.mouseClick(tabImport.delButton, Qt.LeftButton) qtbot.mouseClick(importance.delButton, Qt.LeftButton)
assert tabImport.listBox.topLevelItemCount() == 3 assert importance.listBox.topLevelItemCount() == 3
# Add a new entry # Add a new entry
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(QColorDialog, "getColor", lambda *a: QColor(20, 30, 40)) mp.setattr(QColorDialog, "getColor", lambda *a: QColor(20, 30, 40))
qtbot.mouseClick(tabImport.addButton, Qt.LeftButton) qtbot.mouseClick(importance.addButton, Qt.LeftButton)
tabImport.listBox.clearSelection() importance.listBox.clearSelection()
tabImport.listBox.setCurrentItem(tabImport.listBox.topLevelItem(3)) importance.listBox.setCurrentItem(importance.listBox.topLevelItem(3))
for _ in range(8): for _ in range(8):
qtbot.keyClick(tabImport.editName, Qt.Key_Backspace, delay=KEY_DELAY) qtbot.keyClick(importance.editName, Qt.Key_Backspace, delay=KEY_DELAY)
for c in "Final": for c in "Final":
qtbot.keyClick(tabImport.editName, c, delay=KEY_DELAY) qtbot.keyClick(importance.editName, c, delay=KEY_DELAY)
qtbot.mouseClick(tabImport.colButton, Qt.LeftButton) qtbot.mouseClick(importance.colButton, Qt.LeftButton)
qtbot.mouseClick(tabImport.saveButton, Qt.LeftButton) qtbot.mouseClick(importance.saveButton, Qt.LeftButton)
assert tabImport.listBox.topLevelItemCount() == 4 assert importance.listBox.topLevelItemCount() == 4
assert tabImport.colChanged is True assert importance.wasChanged is True
assert tabImport.getNewList() == ( assert importance.getNewList() == (
[ [
{ {
"key": C.iNew, "key": C.iNew,
@@ -323,13 +313,13 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncPath, projPat
# Check Project # Check Project
projSettings._doSave() projSettings._doSave()
statusItems = dict(theProject.data.itemStatus.items()) statusItems = dict(project.data.itemStatus.items())
assert statusItems[C.sNew]["name"] == "New" assert statusItems[C.sNew]["name"] == "New"
assert statusItems[C.sDraft]["name"] == "Draft" assert statusItems[C.sDraft]["name"] == "Draft"
assert statusItems[C.sFinished]["name"] == "Finished" assert statusItems[C.sFinished]["name"] == "Finished"
assert statusItems["s000013"]["name"] == "Final" assert statusItems["s000013"]["name"] == "Final"
importItems = dict(theProject.data.itemImport.items()) importItems = dict(project.data.itemImport.items())
assert importItems[C.iNew]["name"] == "New" assert importItems[C.iNew]["name"] == "New"
assert importItems[C.iMajor]["name"] == "Major" assert importItems[C.iMajor]["name"] == "Major"
assert importItems[C.iMain]["name"] == "Main" assert importItems[C.iMain]["name"] == "Main"
@@ -341,83 +331,78 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncPath, projPat
@pytest.mark.gui @pytest.mark.gui
def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd): def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
"""Test the auto-replace tab of the project settings dialog.""" """Test the auto-replace page of the dialog."""
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True)) monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
monkeypatch.setattr(SHARED._spelling, "listDictionaries", lambda: [("en", "English [en]")])
# Create new project
mockRnd.reset()
buildTestProject(nwGUI, projPath) buildTestProject(nwGUI, projPath)
CONFIG.setBackupPath(fncPath)
# Set some values # Set some values
theProject = SHARED.project project = SHARED.project
theProject.data.setAutoReplace({ project.data.setAutoReplace({
"A": "B", "C": "D" "A": "B", "C": "D"
}) })
# Create Dialog # Create Dialog
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.TAB_REPLACE) projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.PAGE_REPLACE)
projSettings.show() projSettings.show()
qtbot.addWidget(projSettings) qtbot.addWidget(projSettings)
# Auto-Replace Tab # Auto-Replace Tab
# ================ # ================
tabReplace = projSettings.tabReplace replace = projSettings.replacePage
assert tabReplace.listBox.topLevelItem(0).text(0) == "<A>" # type: ignore assert replace.listBox.topLevelItem(0).text(0) == "<A>" # type: ignore
assert tabReplace.listBox.topLevelItem(0).text(1) == "B" # type: ignore assert replace.listBox.topLevelItem(0).text(1) == "B" # type: ignore
assert tabReplace.listBox.topLevelItem(1).text(0) == "<C>" # type: ignore assert replace.listBox.topLevelItem(1).text(0) == "<C>" # type: ignore
assert tabReplace.listBox.topLevelItem(1).text(1) == "D" # type: ignore assert replace.listBox.topLevelItem(1).text(1) == "D" # type: ignore
assert tabReplace.listBox.topLevelItemCount() == 2 assert replace.listBox.topLevelItemCount() == 2
# Nothing to save or delete # Nothing to save or delete
tabReplace.listBox.clearSelection() replace.listBox.clearSelection()
tabReplace._saveEntry() replace._saveEntry()
tabReplace._delEntry() replace._delEntry()
assert tabReplace.listBox.topLevelItemCount() == 2 assert replace.listBox.topLevelItemCount() == 2
# Create a new entry # Create a new entry
qtbot.mouseClick(tabReplace.addButton, Qt.LeftButton) qtbot.mouseClick(replace.addButton, Qt.LeftButton)
assert tabReplace.listBox.topLevelItemCount() == 3 assert replace.listBox.topLevelItemCount() == 3
assert tabReplace.listBox.topLevelItem(2).text(0) == "<keyword3>" # type: ignore assert replace.listBox.topLevelItem(2).text(0) == "<keyword3>" # type: ignore
assert tabReplace.listBox.topLevelItem(2).text(1) == "" # type: ignore assert replace.listBox.topLevelItem(2).text(1) == "" # type: ignore
# Edit the entry # Edit the entry
tabReplace.listBox.setCurrentItem(tabReplace.listBox.topLevelItem(2)) replace.listBox.setCurrentItem(replace.listBox.topLevelItem(2))
tabReplace.editKey.setText("") replace.editKey.setText("")
for c in "Th is ": for c in "Th is ":
qtbot.keyClick(tabReplace.editKey, c, delay=KEY_DELAY) qtbot.keyClick(replace.editKey, c, delay=KEY_DELAY)
tabReplace.editValue.setText("") replace.editValue.setText("")
for c in "With This Stuff ": for c in "With This Stuff ":
qtbot.keyClick(tabReplace.editValue, c, delay=KEY_DELAY) qtbot.keyClick(replace.editValue, c, delay=KEY_DELAY)
qtbot.mouseClick(tabReplace.saveButton, Qt.LeftButton) qtbot.mouseClick(replace.saveButton, Qt.LeftButton)
assert tabReplace.listBox.topLevelItem(2).text(0) == "<This>" # type: ignore assert replace.listBox.topLevelItem(2).text(0) == "<This>" # type: ignore
assert tabReplace.listBox.topLevelItem(2).text(1) == "With This Stuff " # type: ignore assert replace.listBox.topLevelItem(2).text(1) == "With This Stuff " # type: ignore
# Create a new entry again # Create a new entry again
tabReplace.listBox.clearSelection() replace.listBox.clearSelection()
qtbot.mouseClick(tabReplace.addButton, Qt.LeftButton) qtbot.mouseClick(replace.addButton, Qt.LeftButton)
assert tabReplace.listBox.topLevelItemCount() == 4 assert replace.listBox.topLevelItemCount() == 4
# The list is sorted, so we must find it # The list is sorted, so we must find it
newIdx = -1 newIdx = -1
for i in range(tabReplace.listBox.topLevelItemCount()): for i in range(replace.listBox.topLevelItemCount()):
if tabReplace.listBox.topLevelItem(i).text(0) == "<keyword4>": # type: ignore if replace.listBox.topLevelItem(i).text(0) == "<keyword4>": # type: ignore
newIdx = i newIdx = i
break break
assert newIdx >= 0 assert newIdx >= 0
# Then delete the new item # Then delete the new item
tabReplace.listBox.setCurrentItem(tabReplace.listBox.topLevelItem(newIdx)) replace.listBox.setCurrentItem(replace.listBox.topLevelItem(newIdx))
qtbot.mouseClick(tabReplace.delButton, Qt.LeftButton) qtbot.mouseClick(replace.delButton, Qt.LeftButton)
assert tabReplace.listBox.topLevelItemCount() == 3 assert replace.listBox.topLevelItemCount() == 3
# Check Project # Check Project
projSettings._doSave() projSettings._doSave()
assert theProject.data.autoReplace == { assert project.data.autoReplace == {
"A": "B", "C": "D", "This": "With This Stuff" "A": "B", "C": "D", "This": "With This Stuff"
} }