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":
CONFIG.isDebug = True
logLevel = logging.DEBUG
logFormat = "[{asctime:}] {filename:>17}:{lineno:<4d} {levelname:8} {message:}"
logFormat = "[{asctime:}] {filename:>18}:{lineno:<4d} {levelname:8} {message:}"
elif inOpt == "--style":
qtStyle = inArg
elif inOpt == "--config":
+2 -2
View File
@@ -59,9 +59,9 @@ class GuiPreferences(QDialog):
# Title
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
self.searchText = QLineEdit(self)
@@ -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,101 @@ 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, 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, NFixedPage, 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
self.setMinimumWidth(wW)
self.setMinimumHeight(wH)
options = SHARED.project.options
self.setMinimumSize(CONFIG.pxInt(500), CONFIG.pxInt(400))
self.resize(
CONFIG.pxInt(pOptions.getInt("GuiProjectSettings", "winWidth", wW)),
CONFIG.pxInt(pOptions.getInt("GuiProjectSettings", "winHeight", wH))
CONFIG.pxInt(options.getInt("GuiProjectSettings", "winWidth", CONFIG.pxInt(650))),
CONFIG.pxInt(options.getInt("GuiProjectSettings", "winHeight", CONFIG.pxInt(500)))
)
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=NColourLabel.HEADER_SCALE, indent=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
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")
@@ -103,9 +130,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 +145,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.wasChanged:
newList, delList = self.statusPage.getNewList()
project.setStatusColours(newList, delList)
rebuildTrees = True
if self.tabImport.colChanged:
newList, delList = self.tabImport.getNewList()
if self.importPage.wasChanged:
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.wasChanged:
newList = self.replacePage.getNewList()
project.data.setAutoReplace(newList)
self.newProjectSettingsReady.emit()
self.newProjectSettingsReady.emit(rebuildTrees)
qApp.processEvents()
self.close()
@@ -157,134 +200,103 @@ 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)
xW = CONFIG.pxInt(200)
data = SHARED.project.data
self.setHelpTextStyle(SHARED.theme.helpText)
self.setRowIndent(0)
self.mainForm.addGroupLabel(self.tr("Project Settings"))
xW = CONFIG.pxInt(250)
pData = SHARED.project.data
self.editName = QLineEdit()
self.editName.setMaxLength(200)
self.editName.setMaximumWidth(xW)
self.editName.setText(pData.name)
self.mainForm.addRow(
self.tr("Project name"),
self.editName,
self.tr("Should be set only once.")
# Project Name
self.projName = QLineEdit(self)
self.projName.setMaxLength(200)
self.projName.setMinimumWidth(xW)
self.projName.setText(data.name)
self.addRow(
self.tr("Project name"), self.projName,
self.tr("Changing this will affect the backup path."),
stretch=(3, 2)
)
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(
self.tr("Author(s)"),
self.editAuthor,
self.tr("Change whenever you want!")
# Project Author
self.projAuthor = QLineEdit(self)
self.projAuthor.setMaxLength(200)
self.projAuthor.setMinimumWidth(xW)
self.projAuthor.setText(data.author)
self.addRow(
self.tr("Author(s)"), self.projAuthor,
self.tr("Only used when building the manuscript."),
stretch=(3, 2)
)
# Project Language
self.projLang = QComboBox(self)
self.projLang.setMaximumWidth(xW)
self.projLang.setMinimumWidth(xW)
for tag, language in CONFIG.listLanguages(CONFIG.LANG_PROJ):
self.projLang.addItem(language, tag)
self.mainForm.addRow(
self.tr("Project language"),
self.projLang,
self.tr("Used when building the manuscript.")
self.addRow(
self.tr("Project language"), self.projLang,
self.tr("Only used when building the manuscript."),
stretch=(3, 2)
)
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.setMinimumWidth(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.tr("Spell check language"),
self.spellLang,
self.tr("Overrides main preferences.")
self.addRow(
self.tr("Spell check language"), self.spellLang,
self.tr("Overrides main preferences."),
stretch=(3, 2)
)
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,
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(NFixedPage):
COL_LABEL = 0
COL_USAGE = 1
@@ -297,56 +309,54 @@ class GuiProjectEditStatus(QWidget):
super().__init__(parent=parent)
if isStatus:
self.theStatus = SHARED.project.data.itemStatus
pageLabel = self.tr("Novel File Status Levels")
status = SHARED.project.data.itemStatus
pageLabel = self.tr("Novel Document Status Levels")
colSetting = "statusColW"
else:
self.theStatus = SHARED.project.data.itemImport
pageLabel = self.tr("Note File Importance Levels")
status = SHARED.project.data.itemImport
pageLabel = self.tr("Project Note Importance Levels")
colSetting = "importColW"
wCol0 = CONFIG.pxInt(
SHARED.project.options.getInt("GuiProjectSettings", colSetting, 130)
)
self.colDeleted = []
self.colChanged = False
self.selColour = QColor(100, 100, 100)
self._changed = False
self._colDeleted = []
self._selColour = QColor(100, 100, 100)
self.iPx = SHARED.theme.baseIconSize
# The List
# ========
# Title
self.pageTitle = NColourLabel(
pageLabel, SHARED.theme.helpText, parent=self,
scale=NColourLabel.HEADER_SCALE
)
self.listBox = QTreeWidget()
self.listBox.setHeaderLabels([
self.tr("Label"), self.tr("Usage"),
])
# List Box
self.listBox = QTreeWidget(self)
self.listBox.setHeaderLabels([self.tr("Label"), self.tr("Usage")])
self.listBox.itemSelectionChanged.connect(self._selectedItem)
self.listBox.setColumnWidth(self.COL_LABEL, wCol0)
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"])
# 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
# =========
self.editName = QLineEdit()
self.editName.setMaxLength(40)
self.editName.setPlaceholderText(self.tr("Select item to edit"))
@@ -364,8 +374,6 @@ class GuiProjectEditStatus(QWidget):
self.saveButton.clicked.connect(self._saveItem)
# Assemble
# ========
self.listControls = QVBoxLayout()
self.listControls.addWidget(self.addButton)
self.listControls.addWidget(self.delButton)
@@ -387,16 +395,25 @@ class GuiProjectEditStatus(QWidget):
self.innerBox.addLayout(self.listControls)
self.outerBox = QVBoxLayout()
self.outerBox.addWidget(QLabel("<b>%s</b>" % pageLabel))
self.outerBox.addWidget(self.pageTitle)
self.outerBox.addLayout(self.innerBox)
self.setLayout(self.outerBox)
self.setCentralLayout(self.outerBox)
return
@property
def wasChanged(self) -> bool:
"""The user changed these settings."""
return self._changed
##
# Methods
##
def getNewList(self) -> tuple[list, list]:
"""Return list of entries."""
if self.colChanged:
if self._changed:
newList = []
for n in range(self.listBox.topLevelItemCount()):
item = self.listBox.topLevelItem(n)
@@ -406,10 +423,13 @@ class GuiProjectEditStatus(QWidget):
"name": item.text(self.COL_LABEL),
"cols": item.data(self.COL_LABEL, self.COL_ROLE),
})
return newList, self.colDeleted
return newList, self._colDeleted
return [], []
def columnWidth(self) -> int:
"""Return the size of the header column."""
return self.listBox.columnWidth(0)
##
# Private Slots
##
@@ -417,12 +437,12 @@ class GuiProjectEditStatus(QWidget):
@pyqtSlot()
def _selectColour(self) -> None:
"""Open a dialog to select the status icon colour."""
if self.selColour is not None:
if self._selColour is not None:
newCol = QColorDialog.getColor(
self.selColour, self, self.tr("Select Colour")
self._selColour, self, self.tr("Select Colour")
)
if newCol.isValid():
self.selColour = newCol
self._selColour = newCol
pixmap = QPixmap(self.iPx, self.iPx)
pixmap.fill(newCol)
self.colButton.setIcon(QIcon(pixmap))
@@ -433,7 +453,7 @@ class GuiProjectEditStatus(QWidget):
def _newItem(self) -> None:
"""Create a new status item."""
self._addItem(None, self.tr("New Item"), (100, 100, 100), 0)
self.colChanged = True
self._changed = True
return
@pyqtSlot()
@@ -446,8 +466,8 @@ class GuiProjectEditStatus(QWidget):
SHARED.error(self.tr("Cannot delete a status item that is in use."))
else:
self.listBox.takeTopLevelItem(iRow)
self.colDeleted.append(selItem.data(self.COL_LABEL, self.KEY_ROLE))
self.colChanged = True
self._colDeleted.append(selItem.data(self.COL_LABEL, self.KEY_ROLE))
self._changed = True
return
@pyqtSlot()
@@ -458,9 +478,9 @@ class GuiProjectEditStatus(QWidget):
selItem.setText(self.COL_LABEL, simplified(self.editName.text()))
selItem.setIcon(self.COL_LABEL, self.colButton.icon())
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
@pyqtSlot()
@@ -474,7 +494,7 @@ class GuiProjectEditStatus(QWidget):
name = selItem.text(self.COL_LABEL)
pixmap = QPixmap(self.iPx, self.iPx)
pixmap.fill(QColor(*cols))
self.selColour = QColor(*cols)
self._selColour = QColor(*cols)
self.editName.setText(name)
self.colButton.setIcon(QIcon(pixmap))
self.editName.selectAll()
@@ -485,7 +505,7 @@ class GuiProjectEditStatus(QWidget):
else:
pixmap = QPixmap(self.iPx, self.iPx)
pixmap.fill(QColor(100, 100, 100))
self.selColour = QColor(100, 100, 100)
self._selColour = QColor(100, 100, 100)
self.editName.setText("")
self.colButton.setIcon(QIcon(pixmap))
self.editName.setEnabled(False)
@@ -498,16 +518,16 @@ class GuiProjectEditStatus(QWidget):
##
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."""
pixmap = QPixmap(self.iPx, self.iPx)
pixmap.fill(QColor(*cols))
pixmap.fill(QColor(*colour))
item = QTreeWidgetItem()
item.setText(self.COL_LABEL, name)
item.setIcon(self.COL_LABEL, QIcon(pixmap))
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.setText(self.COL_USAGE, self._usageString(count))
@@ -533,30 +553,29 @@ class GuiProjectEditStatus(QWidget):
if cItem is not None:
cItem.setSelected(True)
self.colChanged = True
self._changed = True
return
def _getSelectedItem(self) -> QTreeWidgetItem | None:
"""Get the currently selected item."""
selItem = self.listBox.selectedItems()
if len(selItem) > 0:
return selItem[0]
if items := self.listBox.selectedItems():
return items[0]
return None
def _usageString(self, nUse: int) -> str:
def _usageString(self, count: int) -> str:
"""Generate usage string."""
if nUse == 0:
if count == 0:
return self.tr("Not in use")
elif nUse == 1:
elif count == 1:
return self.tr("Used once")
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_REPL = 1
@@ -564,24 +583,24 @@ class GuiProjectEditReplace(QWidget):
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
self.arChanged = False
self._changed = False
wCol0 = CONFIG.pxInt(
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
# ========
self.listBox = QTreeWidget()
self.listBox.setHeaderLabels([
self.tr("Keyword"),
self.tr("Replace With"),
])
self.listBox.itemSelectionChanged.connect(self._selectedItem)
self.listBox.setHeaderLabels([self.tr("Keyword"), self.tr("Replace With")])
self.listBox.setColumnWidth(self.COL_KEY, wCol0)
self.listBox.setIndentation(0)
self.listBox.itemSelectionChanged.connect(self._selectedItem)
for aKey, aVal in SHARED.project.data.autoReplace.items():
newItem = QTreeWidgetItem(["<%s>" % aKey, aVal])
@@ -591,8 +610,6 @@ class GuiProjectEditReplace(QWidget):
self.listBox.setSortingEnabled(True)
# List Controls
# =============
self.addButton = QPushButton(SHARED.theme.getIcon("add"), "")
self.addButton.clicked.connect(self._addEntry)
@@ -600,8 +617,6 @@ class GuiProjectEditReplace(QWidget):
self.delButton.clicked.connect(self._delEntry)
# Edit Form
# =========
self.editKey = QLineEdit()
self.editKey.setPlaceholderText(self.tr("Select item to edit"))
self.editKey.setEnabled(False)
@@ -615,8 +630,6 @@ class GuiProjectEditReplace(QWidget):
self.saveButton.clicked.connect(self._saveEntry)
# Assemble
# ========
self.listControls = QVBoxLayout()
self.listControls.addWidget(self.addButton)
self.listControls.addWidget(self.delButton)
@@ -636,50 +649,61 @@ class GuiProjectEditReplace(QWidget):
self.innerBox.addLayout(self.listControls)
self.outerBox = QVBoxLayout()
self.outerBox.addWidget(QLabel("<b>%s</b>" % pageLabel))
self.outerBox.addWidget(self.pageTitle)
self.outerBox.addLayout(self.innerBox)
self.setLayout(self.outerBox)
self.setCentralLayout(self.outerBox)
return
@property
def wasChanged(self) -> bool:
"""The user changed these settings."""
return self._changed
##
# Methods
##
def getNewList(self) -> dict:
"""Extract the list from the widget."""
new = {}
for n in range(self.listBox.topLevelItemCount()):
tItem = self.listBox.topLevelItem(n)
if tItem is not None:
if tItem := self.listBox.topLevelItem(n):
aKey = self._stripNotAllowed(tItem.text(0))
aVal = tItem.text(1)
if len(aKey) > 0:
new[aKey] = aVal
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
edit form.
"""
selItem = self._getSelectedItem()
if selItem is None:
return False
editKey = self._stripNotAllowed(selItem.text(0))
editVal = selItem.text(1)
self.editKey.setText(editKey)
self.editValue.setText(editVal)
self.editKey.setEnabled(True)
self.editValue.setEnabled(True)
self.editKey.selectAll()
self.editKey.setFocus()
return True
if selItem := self._getSelectedItem():
editKey = self._stripNotAllowed(selItem.text(0))
editVal = selItem.text(1)
self.editKey.setText(editKey)
self.editValue.setText(editVal)
self.editKey.setEnabled(True)
self.editValue.setEnabled(True)
self.editKey.selectAll()
self.editKey.setFocus()
return
@pyqtSlot()
def _saveEntry(self) -> None:
"""Save the form data into the list widget."""
selItem = self._getSelectedItem()
if selItem:
if selItem := self._getSelectedItem():
newKey = self.editKey.text()
newVal = self.editValue.text()
saveKey = self._stripNotAllowed(newKey)
@@ -691,38 +715,36 @@ class GuiProjectEditReplace(QWidget):
self.editKey.setEnabled(False)
self.editValue.setEnabled(False)
self.listBox.clearSelection()
self.arChanged = True
self._changed = True
return
@pyqtSlot()
def _addEntry(self) -> None:
"""Add a new list entry."""
saveKey = "<keyword%d>" % (self.listBox.topLevelItemCount() + 1)
newVal = ""
newItem = QTreeWidgetItem([saveKey, newVal])
self.listBox.addTopLevelItem(newItem)
self.listBox.addTopLevelItem(QTreeWidgetItem([saveKey, ""]))
return
@pyqtSlot()
def _delEntry(self) -> None:
"""Delete the selected entry."""
selItem = self._getSelectedItem()
if selItem:
if selItem := self._getSelectedItem():
self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(selItem))
self.arChanged = True
self._changed = True
return
##
# Internal Functions
##
def _getSelectedItem(self) -> QTreeWidgetItem | None:
"""Extract the currently selected item."""
selItem = self.listBox.selectedItems()
if len(selItem) == 0:
return None
return selItem[0]
if items := self.listBox.selectedItems():
return items[0]
return None
def _stripNotAllowed(self, key: str) -> str:
"""Clean up the replace key string."""
result = ""
for c in key:
if c.isalnum():
result += c
return result
return "".join(c for c in key if c.isalnum())
# 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: 2023-05-23 [2.1b1] NSimpleLayout
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
Copyright 20182024, Veronica Berglyd Olsen
@@ -28,17 +30,59 @@ from __future__ import annotations
from PyQt5.QtGui import QColor, QPalette
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
QAbstractButton, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QScrollArea, QSizePolicy,
QVBoxLayout, QWidget
QAbstractButton, QFrame, QGridLayout, QHBoxLayout, QLabel, QLayout,
QScrollArea, QSizePolicy, QVBoxLayout, QWidget
)
from novelwriter import CONFIG
FONT_SCALE = 0.9
DEFAULT_SCALE = 0.9
RIGHT_TOP = Qt.AlignmentFlag.AlignRight | 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):
"""Extension: Scrollable Form Widget
@@ -48,8 +92,9 @@ class NScrollableForm(QScrollArea):
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
self._helpCol = QColor(0, 0, 0)
self._fontScale = FONT_SCALE
self._fontScale = DEFAULT_SCALE
self._first = True
self._indent = CONFIG.pxInt(12)
self._sections: dict[int, QLabel] = {}
self._editable: dict[str, NColourLabel] = {}
@@ -65,6 +110,8 @@ class NScrollableForm(QScrollArea):
self.setWidgetResizable(True)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
self.setFrameShadow(QFrame.Shadow.Sunken)
self.setFrameShape(QFrame.Shape.StyledPanel)
return
@@ -80,7 +127,7 @@ class NScrollableForm(QScrollArea):
# 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."""
self._helpCol = color
self._fontScale = scale
@@ -92,6 +139,11 @@ 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
##
# Methods
##
@@ -123,37 +175,43 @@ class NScrollableForm(QScrollArea):
return
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."""
row = QHBoxLayout()
row.setSpacing(CONFIG.pxInt(4))
row.setSpacing(CONFIG.pxInt(12))
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 = NColourLabel(
str(helpText), self._helpCol, parent=self,
scale=self._fontScale, wrap=True, indent=self._indent
)
labelBox = QVBoxLayout()
labelBox.addWidget(qLabel)
labelBox.addWidget(qHelp)
labelBox.setSpacing(0)
labelBox.addStretch(1)
row.addLayout(labelBox)
row.addLayout(labelBox, stretch[0])
if editable:
self._editable[editable] = qHelp
else:
row.addWidget(qLabel)
row.addSpacing(mPx)
row.addWidget(widget)
row.addWidget(qLabel, stretch[0])
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):
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._index[label.strip()] = widget
@@ -177,7 +235,7 @@ class NConfigLayout(QGridLayout):
self._nextRow = 0
self._helpCol = QColor(0, 0, 0)
self._fontScale = FONT_SCALE
self._fontScale = DEFAULT_SCALE
self._itemMap = {}
wSp = CONFIG.pxInt(8)
@@ -187,16 +245,6 @@ class NConfigLayout(QGridLayout):
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
##
@@ -212,8 +260,8 @@ class NConfigLayout(QGridLayout):
self._nextRow += 1
return
def addRow(self, label: str, widget: QWidget, helpText: str | None = None,
unit: str | None = None, button: QWidget | None = None) -> int:
def addRow(self, label: str, widget: QWidget, unit: str | None = None,
button: QWidget | None = None) -> int:
"""Add a label and a widget as a new row of the grid."""
wSp = CONFIG.pxInt(8)
qLabel = QLabel(label)
@@ -221,17 +269,7 @@ class NConfigLayout(QGridLayout):
qLabel.setBuddy(widget)
qHelp = None
if helpText is not None:
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)
self.addWidget(qLabel, self._nextRow, 0, 1, 1, LEFT_TOP)
if isinstance(unit, str):
controlBox = QHBoxLayout()
@@ -248,12 +286,7 @@ class NConfigLayout(QGridLayout):
self.addLayout(controlBox, self._nextRow, 1, 1, 1, RIGHT_TOP)
else:
if isinstance(widget, QLineEdit):
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.addWidget(widget, self._nextRow, 1, 1, 1, RIGHT_TOP)
self.setRowStretch(self._nextRow, 0)
self.setRowStretch(self._nextRow+1, 1)
@@ -321,17 +354,21 @@ class NColourLabel(QLabel):
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,
scale: float = FONT_SCALE, wrap: bool = False) -> None:
scale: float = HELP_SCALE, wrap: bool = False, indent: int = 0) -> None:
super().__init__(text, parent=parent)
lblCol = self.palette()
lblCol.setColor(QPalette.WindowText, color)
self.setPalette(lblCol)
font = self.font()
font.setPointSizeF(scale*font.pointSizeF())
colour = self.palette()
colour.setColor(QPalette.WindowText, color)
lblFont = self.font()
lblFont.setPointSizeF(scale*lblFont.pointSizeF())
self.setFont(lblFont)
self.setPalette(colour)
self.setFont(font)
self.setIndent(indent)
if wrap:
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.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()
+27 -25
View File
@@ -38,14 +38,12 @@ from novelwriter import CONFIG, SHARED
from novelwriter.common import formatTime, numberToRoman
from novelwriter.constants import nwUnicode
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.novelselector import NovelSelector
logger = logging.getLogger(__name__)
HEADER_SIZE = 1.4
class GuiNovelDetails(QDialog):
@@ -59,21 +57,18 @@ class GuiNovelDetails(QDialog):
self.setObjectName("GuiNovelDetails")
self.setWindowTitle(self.tr("Novel Details"))
wW = CONFIG.pxInt(500)
wH = CONFIG.pxInt(400)
options = SHARED.project.options
self.setMinimumSize(wW, wH)
self.setMinimumSize(CONFIG.pxInt(500), CONFIG.pxInt(400))
self.resize(
CONFIG.pxInt(options.getInt("GuiNovelDetails", "winWidth", wW)),
CONFIG.pxInt(options.getInt("GuiNovelDetails", "winHeight", wH))
CONFIG.pxInt(options.getInt("GuiNovelDetails", "winWidth", CONFIG.pxInt(650))),
CONFIG.pxInt(options.getInt("GuiNovelDetails", "winHeight", CONFIG.pxInt(500)))
)
# Title
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
self.novelSelector = NovelSelector(self)
@@ -192,7 +187,7 @@ class GuiNovelDetails(QDialog):
# END Class GuiNovelDetails
class _OverviewPage(QWidget):
class _OverviewPage(NScrollablePage):
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
@@ -204,7 +199,8 @@ class _OverviewPage(QWidget):
# Project Info
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)
@@ -227,7 +223,8 @@ class _OverviewPage(QWidget):
# Novel Info
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)
@@ -250,11 +247,10 @@ class _OverviewPage(QWidget):
self.outerBox.addLayout(self.projForm)
self.outerBox.addWidget(self.novelLabel)
self.outerBox.addLayout(self.novelForm)
self.outerBox.setContentsMargins(0, 0, 0, 0)
self.outerBox.setSpacing(sPx)
self.outerBox.addStretch(1)
self.setLayout(self.outerBox)
self.setCentralLayout(self.outerBox)
return
@@ -299,7 +295,7 @@ class _OverviewPage(QWidget):
# END Class _OverviewPage
class _ContentsPage(QWidget):
class _ContentsPage(NFixedPage):
C_TITLE = 0
C_WORDS = 1
@@ -320,7 +316,8 @@ class _ContentsPage(QWidget):
# Title
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
@@ -407,20 +404,25 @@ class _ContentsPage(QWidget):
self.outerBox.addWidget(self.contentLabel)
self.outerBox.addWidget(self.tocTree)
self.outerBox.addLayout(self.optionsBox)
self.outerBox.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.outerBox)
self.setCentralLayout(self.outerBox)
return
def saveSettings(self) -> None:
"""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.setValue("GuiNovelDetails", "widthCol0", self.tocTree.columnWidth(0))
options.setValue("GuiNovelDetails", "widthCol1", self.tocTree.columnWidth(1))
options.setValue("GuiNovelDetails", "widthCol2", self.tocTree.columnWidth(2))
options.setValue("GuiNovelDetails", "widthCol3", self.tocTree.columnWidth(3))
options.setValue("GuiNovelDetails", "widthCol4", self.tocTree.columnWidth(4))
options.setValue("GuiNovelDetails", "widthCol0", widthCol0)
options.setValue("GuiNovelDetails", "widthCol1", widthCol1)
options.setValue("GuiNovelDetails", "widthCol2", widthCol2)
options.setValue("GuiNovelDetails", "widthCol3", widthCol3)
options.setValue("GuiNovelDetails", "widthCol4", widthCol4)
options.setValue("GuiNovelDetails", "wordsPerPage", self.wpValue.value())
options.setValue("GuiNovelDetails", "countFrom", self.poValue.value())
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.enum import nwItemType
from novelwriter.dialogs.editlabel import GuiEditLabel
from novelwriter.dialogs.projsettings import GuiProjectSettings
from novelwriter.dialogs.projectsettings import GuiProjectSettings
KEY_DELAY = 1
@@ -63,17 +63,17 @@ def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI):
qtbot.addWidget(projSettings)
# Switch Tabs
projSettings._focusTab(GuiProjectSettings.TAB_REPLACE)
assert projSettings._tabBox.currentWidget() == projSettings.tabReplace
projSettings.sidebar.button(GuiProjectSettings.PAGE_SETTINGS).click()
assert projSettings.mainStack.currentWidget() == projSettings.settingsPage
projSettings._focusTab(GuiProjectSettings.TAB_IMPORT)
assert projSettings._tabBox.currentWidget() == projSettings.tabImport
projSettings.sidebar.button(GuiProjectSettings.PAGE_STATUS).click()
assert projSettings.mainStack.currentWidget() == projSettings.statusPage
projSettings._focusTab(GuiProjectSettings.TAB_STATUS)
assert projSettings._tabBox.currentWidget() == projSettings.tabStatus
projSettings.sidebar.button(GuiProjectSettings.PAGE_IMPORT).click()
assert projSettings.mainStack.currentWidget() == projSettings.importPage
projSettings._focusTab(GuiProjectSettings.TAB_MAIN)
assert projSettings._tabBox.currentWidget() == projSettings.tabMain
projSettings.sidebar.button(GuiProjectSettings.PAGE_REPLACE).click()
assert projSettings.mainStack.currentWidget() == projSettings.replacePage
# Clean Up
projSettings.close()
@@ -83,9 +83,11 @@ def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI):
@pytest.mark.gui
def testDlgProjSettings_Main(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd):
"""Test the main tab of the project settings dialog."""
monkeypatch.setattr(SHARED._spelling, "listDictionaries", lambda: [("en", "English [en]")])
def testDlgProjSettings_SettingsPage(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd):
"""Test the settings page of the dialog."""
languages = [("en", "English"), ("de", "German")]
monkeypatch.setattr(SHARED._spelling, "listDictionaries", lambda *a: languages)
monkeypatch.setattr(CONFIG, "listLanguages", lambda *a: languages)
# Create new project
buildTestProject(nwGUI, projPath)
@@ -94,72 +96,57 @@ def testDlgProjSettings_Main(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockR
# Set some values
project = SHARED.project
project.data.setLanguage("en")
project.data.setSpellLang("en")
project.data.setAuthor("Jane Smith")
project.data.setAutoReplace({"A": "B", "C": "D"})
# Create Dialog
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.TAB_MAIN)
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.PAGE_SETTINGS)
projSettings.show()
qtbot.addWidget(projSettings)
# 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"
assert tabMain.editTitle.text() == "New Novel"
assert tabMain.editAuthor.text() == "Jane Smith"
assert tabMain.spellLang.currentData() == "en"
assert tabMain.doBackup.isChecked() is False
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"
settings.projName.setText("Project Name")
settings.projAuthor.setText("Jane Doe")
settings.projLang.setCurrentIndex(settings.projLang.findData("de"))
settings.spellLang.setCurrentIndex(settings.spellLang.findData("de"))
settings.doBackup.setChecked(True)
projSettings._doSave()
assert project.data.name == "Project Name"
assert project.data.title == "Project Title"
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"
# qtbot.stop()
# END Test testDlgProjSettings_Main
# END Test testDlgProjSettings_SettingsPage
@pytest.mark.gui
def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd):
"""Test the status and importance tabs of the project settings
dialog.
"""
def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
"""Test the status and importance pages of the dialog."""
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)
CONFIG.setBackupPath(fncPath)
# Set some values
theProject = SHARED.project
theProject.tree[C.hTitlePage].setStatus(C.sFinished) # type: ignore
theProject.tree[C.hChapterDoc].setStatus(C.sDraft) # type: ignore
theProject.tree[C.hSceneDoc].setStatus(C.sDraft) # type: ignore
project = SHARED.project
project.tree[C.hTitlePage].setStatus(C.sFinished) # type: ignore
project.tree[C.hChapterDoc].setStatus(C.sDraft) # type: ignore
project.tree[C.hSceneDoc].setStatus(C.sDraft) # type: ignore
nwGUI.projView.projTree.setSelectedHandle(C.hPlotRoot)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, hLevel=1, isNote=True)
@@ -172,51 +159,54 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncPath, projPat
hCharNote = "0000000000011"
hWorldNote = "0000000000012"
theProject.tree[hPlotNote].setImport(C.iMajor) # type: ignore
theProject.tree[hCharNote].setImport(C.iMajor) # type: ignore
theProject.tree[hWorldNote].setImport(C.iMain) # type: ignore
project.tree[hPlotNote].setImport(C.iMajor) # type: ignore
project.tree[hCharNote].setImport(C.iMajor) # 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
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.TAB_STATUS)
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.PAGE_STATUS)
projSettings.show()
qtbot.addWidget(projSettings)
# Status Tab
# ==========
tabStatus = projSettings.tabStatus
status = projSettings.statusPage
assert tabStatus.colChanged is False
assert tabStatus.getNewList() == ([], [])
assert tabStatus.listBox.topLevelItemCount() == 4
assert status.wasChanged is False
assert status.getNewList() == ([], [])
assert status.listBox.topLevelItemCount() == 4
# Can't delete the first item (it's in use)
tabStatus.listBox.clearSelection()
tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(0))
qtbot.mouseClick(tabStatus.delButton, Qt.LeftButton)
assert tabStatus.listBox.topLevelItemCount() == 4
status.listBox.clearSelection()
status.listBox.setCurrentItem(status.listBox.topLevelItem(0))
qtbot.mouseClick(status.delButton, Qt.LeftButton)
assert status.listBox.topLevelItemCount() == 4
# Can delete the second item
tabStatus.listBox.clearSelection()
tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(1))
qtbot.mouseClick(tabStatus.delButton, Qt.LeftButton)
assert tabStatus.listBox.topLevelItemCount() == 3
status.listBox.clearSelection()
status.listBox.setCurrentItem(status.listBox.topLevelItem(1))
qtbot.mouseClick(status.delButton, Qt.LeftButton)
assert status.listBox.topLevelItemCount() == 3
# Add a new item
with monkeypatch.context() as mp:
mp.setattr(QColorDialog, "getColor", lambda *a: QColor(20, 30, 40))
qtbot.mouseClick(tabStatus.addButton, Qt.LeftButton)
tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(3))
for _ in range(8):
qtbot.keyClick(tabStatus.editName, Qt.Key_Backspace, delay=KEY_DELAY)
for c in "Final":
qtbot.keyClick(tabStatus.editName, c, delay=KEY_DELAY)
qtbot.mouseClick(tabStatus.colButton, Qt.LeftButton)
qtbot.mouseClick(tabStatus.saveButton, Qt.LeftButton)
assert tabStatus.listBox.topLevelItemCount() == 4
status.addButton.click()
status.listBox.setCurrentItem(status.listBox.topLevelItem(3))
status.editName.setText("Final")
status.colButton.click()
status.saveButton.click()
assert status.listBox.topLevelItemCount() == 4
assert tabStatus.colChanged is True
assert tabStatus.getNewList() == (
assert status.wasChanged is True
assert status.getNewList() == (
[
{
"key": C.sNew,
@@ -241,62 +231,62 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncPath, projPat
)
# Move items, none selected -> no change
tabStatus.listBox.clearSelection()
tabStatus._moveItem(1)
assert [x["key"] for x in tabStatus.getNewList()[0]] == [
status.listBox.clearSelection()
status._moveItem(1)
assert [x["key"] for x in status.getNewList()[0]] == [
C.sNew, C.sDraft, C.sFinished, None
]
# Move items, first selected, move up -> no change
tabStatus.listBox.clearSelection()
tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(0))
tabStatus._moveItem(-1)
assert [x["key"] for x in tabStatus.getNewList()[0]] == [
status.listBox.clearSelection()
status.listBox.setCurrentItem(status.listBox.topLevelItem(0))
status._moveItem(-1)
assert [x["key"] for x in status.getNewList()[0]] == [
C.sNew, C.sDraft, C.sFinished, None
]
# Move items, last selected, move up -> allowed
tabStatus.listBox.clearSelection()
tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(3))
tabStatus._moveItem(-1)
assert [x["key"] for x in tabStatus.getNewList()[0]] == [
status.listBox.clearSelection()
status.listBox.setCurrentItem(status.listBox.topLevelItem(3))
status._moveItem(-1)
assert [x["key"] for x in status.getNewList()[0]] == [
C.sNew, C.sDraft, None, C.sFinished
]
# Move items, same selected, move down -> allowed
tabStatus._moveItem(1)
assert [x["key"] for x in tabStatus.getNewList()[0]] == [
status._moveItem(1)
assert [x["key"] for x in status.getNewList()[0]] == [
C.sNew, C.sDraft, C.sFinished, None
]
# Importance Tab
# ==============
tabImport = projSettings.tabImport
projSettings._focusTab(GuiProjectSettings.TAB_IMPORT)
importance = projSettings.importPage
projSettings._sidebarClicked(GuiProjectSettings.PAGE_IMPORT)
# Delete unused entry
tabImport.listBox.clearSelection()
tabImport.listBox.setCurrentItem(tabImport.listBox.topLevelItem(1))
qtbot.mouseClick(tabImport.delButton, Qt.LeftButton)
assert tabImport.listBox.topLevelItemCount() == 3
importance.listBox.clearSelection()
importance.listBox.setCurrentItem(importance.listBox.topLevelItem(1))
qtbot.mouseClick(importance.delButton, Qt.LeftButton)
assert importance.listBox.topLevelItemCount() == 3
# Add a new entry
with monkeypatch.context() as mp:
mp.setattr(QColorDialog, "getColor", lambda *a: QColor(20, 30, 40))
qtbot.mouseClick(tabImport.addButton, Qt.LeftButton)
tabImport.listBox.clearSelection()
tabImport.listBox.setCurrentItem(tabImport.listBox.topLevelItem(3))
qtbot.mouseClick(importance.addButton, Qt.LeftButton)
importance.listBox.clearSelection()
importance.listBox.setCurrentItem(importance.listBox.topLevelItem(3))
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":
qtbot.keyClick(tabImport.editName, c, delay=KEY_DELAY)
qtbot.mouseClick(tabImport.colButton, Qt.LeftButton)
qtbot.mouseClick(tabImport.saveButton, Qt.LeftButton)
assert tabImport.listBox.topLevelItemCount() == 4
qtbot.keyClick(importance.editName, c, delay=KEY_DELAY)
qtbot.mouseClick(importance.colButton, Qt.LeftButton)
qtbot.mouseClick(importance.saveButton, Qt.LeftButton)
assert importance.listBox.topLevelItemCount() == 4
assert tabImport.colChanged is True
assert tabImport.getNewList() == (
assert importance.wasChanged is True
assert importance.getNewList() == (
[
{
"key": C.iNew,
@@ -323,13 +313,13 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncPath, projPat
# Check Project
projSettings._doSave()
statusItems = dict(theProject.data.itemStatus.items())
statusItems = dict(project.data.itemStatus.items())
assert statusItems[C.sNew]["name"] == "New"
assert statusItems[C.sDraft]["name"] == "Draft"
assert statusItems[C.sFinished]["name"] == "Finished"
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.iMajor]["name"] == "Major"
assert importItems[C.iMain]["name"] == "Main"
@@ -341,83 +331,78 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncPath, projPat
@pytest.mark.gui
def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd):
"""Test the auto-replace tab of the project settings dialog."""
def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
"""Test the auto-replace page of the dialog."""
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)
CONFIG.setBackupPath(fncPath)
# Set some values
theProject = SHARED.project
theProject.data.setAutoReplace({
project = SHARED.project
project.data.setAutoReplace({
"A": "B", "C": "D"
})
# Create Dialog
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.TAB_REPLACE)
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.PAGE_REPLACE)
projSettings.show()
qtbot.addWidget(projSettings)
# Auto-Replace Tab
# ================
tabReplace = projSettings.tabReplace
replace = projSettings.replacePage
assert tabReplace.listBox.topLevelItem(0).text(0) == "<A>" # type: ignore
assert tabReplace.listBox.topLevelItem(0).text(1) == "B" # type: ignore
assert tabReplace.listBox.topLevelItem(1).text(0) == "<C>" # type: ignore
assert tabReplace.listBox.topLevelItem(1).text(1) == "D" # type: ignore
assert tabReplace.listBox.topLevelItemCount() == 2
assert replace.listBox.topLevelItem(0).text(0) == "<A>" # type: ignore
assert replace.listBox.topLevelItem(0).text(1) == "B" # type: ignore
assert replace.listBox.topLevelItem(1).text(0) == "<C>" # type: ignore
assert replace.listBox.topLevelItem(1).text(1) == "D" # type: ignore
assert replace.listBox.topLevelItemCount() == 2
# Nothing to save or delete
tabReplace.listBox.clearSelection()
tabReplace._saveEntry()
tabReplace._delEntry()
assert tabReplace.listBox.topLevelItemCount() == 2
replace.listBox.clearSelection()
replace._saveEntry()
replace._delEntry()
assert replace.listBox.topLevelItemCount() == 2
# Create a new entry
qtbot.mouseClick(tabReplace.addButton, Qt.LeftButton)
assert tabReplace.listBox.topLevelItemCount() == 3
assert tabReplace.listBox.topLevelItem(2).text(0) == "<keyword3>" # type: ignore
assert tabReplace.listBox.topLevelItem(2).text(1) == "" # type: ignore
qtbot.mouseClick(replace.addButton, Qt.LeftButton)
assert replace.listBox.topLevelItemCount() == 3
assert replace.listBox.topLevelItem(2).text(0) == "<keyword3>" # type: ignore
assert replace.listBox.topLevelItem(2).text(1) == "" # type: ignore
# Edit the entry
tabReplace.listBox.setCurrentItem(tabReplace.listBox.topLevelItem(2))
tabReplace.editKey.setText("")
replace.listBox.setCurrentItem(replace.listBox.topLevelItem(2))
replace.editKey.setText("")
for c in "Th is ":
qtbot.keyClick(tabReplace.editKey, c, delay=KEY_DELAY)
tabReplace.editValue.setText("")
qtbot.keyClick(replace.editKey, c, delay=KEY_DELAY)
replace.editValue.setText("")
for c in "With This Stuff ":
qtbot.keyClick(tabReplace.editValue, c, delay=KEY_DELAY)
qtbot.mouseClick(tabReplace.saveButton, Qt.LeftButton)
assert tabReplace.listBox.topLevelItem(2).text(0) == "<This>" # type: ignore
assert tabReplace.listBox.topLevelItem(2).text(1) == "With This Stuff " # type: ignore
qtbot.keyClick(replace.editValue, c, delay=KEY_DELAY)
qtbot.mouseClick(replace.saveButton, Qt.LeftButton)
assert replace.listBox.topLevelItem(2).text(0) == "<This>" # type: ignore
assert replace.listBox.topLevelItem(2).text(1) == "With This Stuff " # type: ignore
# Create a new entry again
tabReplace.listBox.clearSelection()
qtbot.mouseClick(tabReplace.addButton, Qt.LeftButton)
assert tabReplace.listBox.topLevelItemCount() == 4
replace.listBox.clearSelection()
qtbot.mouseClick(replace.addButton, Qt.LeftButton)
assert replace.listBox.topLevelItemCount() == 4
# The list is sorted, so we must find it
newIdx = -1
for i in range(tabReplace.listBox.topLevelItemCount()):
if tabReplace.listBox.topLevelItem(i).text(0) == "<keyword4>": # type: ignore
for i in range(replace.listBox.topLevelItemCount()):
if replace.listBox.topLevelItem(i).text(0) == "<keyword4>": # type: ignore
newIdx = i
break
assert newIdx >= 0
# Then delete the new item
tabReplace.listBox.setCurrentItem(tabReplace.listBox.topLevelItem(newIdx))
qtbot.mouseClick(tabReplace.delButton, Qt.LeftButton)
assert tabReplace.listBox.topLevelItemCount() == 3
replace.listBox.setCurrentItem(replace.listBox.topLevelItem(newIdx))
qtbot.mouseClick(replace.delButton, Qt.LeftButton)
assert replace.listBox.topLevelItemCount() == 3
# Check Project
projSettings._doSave()
assert theProject.data.autoReplace == {
assert project.data.autoReplace == {
"A": "B", "C": "D", "This": "With This Stuff"
}