Add deleteLater to temporary widgets (#1629)

This commit is contained in:
Veronica Berglyd Olsen
2023-11-28 23:27:36 +01:00
committed by GitHub
39 changed files with 622 additions and 576 deletions
+5
View File
@@ -76,6 +76,7 @@ def main(sysArgs: list | None = None):
"config=", "config=",
"data=", "data=",
"testmode", "testmode",
"meminfo"
] ]
helpMsg = ( helpMsg = (
@@ -92,6 +93,7 @@ def main(sysArgs: list | None = None):
" -v, --version Print program version and exit.\n" " -v, --version Print program version and exit.\n"
" --info Print additional runtime information.\n" " --info Print additional runtime information.\n"
" --debug Print debug output. Includes --info.\n" " --debug Print debug output. Includes --info.\n"
" --meminfo Show memory usage information in the status bar.\n"
" --style= Sets Qt5 style flag. Defaults to 'Fusion'.\n" " --style= Sets Qt5 style flag. Defaults to 'Fusion'.\n"
" --config= Alternative config file.\n" " --config= Alternative config file.\n"
" --data= Alternative user data path.\n" " --data= Alternative user data path.\n"
@@ -127,6 +129,7 @@ def main(sysArgs: list | None = None):
elif inOpt == "--info": elif inOpt == "--info":
logLevel = logging.INFO logLevel = logging.INFO
elif inOpt == "--debug": elif inOpt == "--debug":
CONFIG.isDebug = True
logLevel = logging.DEBUG logLevel = logging.DEBUG
logFormat = "[{asctime:}] {filename:>17}:{lineno:<4d} {levelname:8} {message:}" logFormat = "[{asctime:}] {filename:>17}:{lineno:<4d} {levelname:8} {message:}"
elif inOpt == "--style": elif inOpt == "--style":
@@ -137,6 +140,8 @@ def main(sysArgs: list | None = None):
dataPath = inArg dataPath = inArg
elif inOpt == "--testmode": elif inOpt == "--testmode":
testMode = True testMode = True
elif inOpt == "--meminfo":
CONFIG.memInfo = True
# Setup Logging # Setup Logging
pkgLogger = logging.getLogger(__package__) pkgLogger = logging.getLogger(__package__)
+2 -2
View File
@@ -225,7 +225,8 @@ class Config:
# Other System Info # Other System Info
self.hostName = QSysInfo.machineHostName() self.hostName = QSysInfo.machineHostName()
self.kernelVer = QSysInfo.kernelVersion() self.kernelVer = QSysInfo.kernelVersion()
self.isDebug = False self.isDebug = False # True if running in debug mode
self.memInfo = False # True if displaying mem info in status bar
# Packages # Packages
self.hasEnchant = False # The pyenchant package self.hasEnchant = False # The pyenchant package
@@ -485,7 +486,6 @@ class Config:
self._recentObj.loadCache() self._recentObj.loadCache()
self._checkOptionalPackages() self._checkOptionalPackages()
self.isDebug = logger.getEffectiveLevel() == logging.DEBUG
logger.debug("Config instance initialised") logger.debug("Config instance initialised")
+1 -1
View File
@@ -551,7 +551,7 @@ class NWIndex:
return 0 return 0
def getTableOfContents( def getTableOfContents(
self, rHandle: str, maxDepth: int, skipExcl: bool = True self, rHandle: str | None, maxDepth: int, skipExcl: bool = True
) -> list[tuple[str, int, str, int]]: ) -> list[tuple[str, int, str, int]]:
"""Generate a table of contents up to a maximum depth.""" """Generate a table of contents up to a maximum depth."""
tOrder = [] tOrder = []
+28 -29
View File
@@ -28,7 +28,7 @@ import novelwriter
from datetime import datetime from datetime import datetime
from PyQt5.QtGui import QCursor from PyQt5.QtGui import QCloseEvent, QCursor
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
qApp, QDialog, QDialogButtonBox, QHBoxLayout, QLabel, QTabWidget, qApp, QDialog, QDialogButtonBox, QHBoxLayout, QLabel, QTabWidget,
@@ -44,7 +44,7 @@ logger = logging.getLogger(__name__)
class GuiAbout(QDialog): class GuiAbout(QDialog):
def __init__(self, parent: QWidget): def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
logger.debug("Create: GuiAbout") logger.debug("Create: GuiAbout")
@@ -101,7 +101,7 @@ class GuiAbout(QDialog):
# OK Button # OK Button
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok) self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok)
self.buttonBox.accepted.connect(self._doClose) self.buttonBox.accepted.connect(self.close)
self.outerBox.addLayout(self.innerBox) self.outerBox.addLayout(self.innerBox)
self.outerBox.addWidget(self.buttonBox) self.outerBox.addWidget(self.buttonBox)
@@ -111,13 +111,12 @@ class GuiAbout(QDialog):
return return
def __del__(self): # pragma: no cover def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: GuiAbout") logger.debug("Delete: GuiAbout")
return return
def populateGUI(self): def populateGUI(self) -> None:
"""Populate tabs with text. """Populate tabs with text."""
"""
qApp.setOverrideCursor(QCursor(Qt.WaitCursor)) qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
self._setStyleSheet() self._setStyleSheet()
self._fillAboutPage() self._fillAboutPage()
@@ -127,19 +126,27 @@ class GuiAbout(QDialog):
qApp.restoreOverrideCursor() qApp.restoreOverrideCursor()
return return
def showReleaseNotes(self): def showReleaseNotes(self) -> None:
"""Show the release notes. """Show the release notes."""
"""
self.tabBox.setCurrentWidget(self.pageNotes) self.tabBox.setCurrentWidget(self.pageNotes)
return return
##
# Events
##
def closeEvent(self, event: QCloseEvent) -> None:
"""Capture the close event and perform cleanup."""
event.accept()
self.deleteLater()
return
## ##
# Internal Functions # Internal Functions
## ##
def _fillAboutPage(self): def _fillAboutPage(self) -> None:
"""Generate the content for the About page. """Generate the content for the About page."""
"""
aboutMsg = ( aboutMsg = (
"<h2>{title1}</h2>" "<h2>{title1}</h2>"
"<p>{copy}</p>" "<p>{copy}</p>"
@@ -181,9 +188,8 @@ class GuiAbout(QDialog):
return return
def _fillNotesPage(self): def _fillNotesPage(self) -> None:
"""Load the content for the Release Notes page. """Load the content for the Release Notes page."""
"""
docPath = CONFIG.assetPath("text") / "release_notes.htm" docPath = CONFIG.assetPath("text") / "release_notes.htm"
docText = readTextFile(docPath) docText = readTextFile(docPath)
if docText: if docText:
@@ -192,9 +198,8 @@ class GuiAbout(QDialog):
self.pageNotes.setHtml("Error loading release notes text ...") self.pageNotes.setHtml("Error loading release notes text ...")
return return
def _fillCreditsPage(self): def _fillCreditsPage(self) -> None:
"""Load the content for the Credits page. """Load the content for the Credits page."""
"""
docPath = CONFIG.assetPath("text") / "credits_en.htm" docPath = CONFIG.assetPath("text") / "credits_en.htm"
docText = readTextFile(docPath) docText = readTextFile(docPath)
if docText: if docText:
@@ -203,9 +208,8 @@ class GuiAbout(QDialog):
self.pageCredits.setHtml("Error loading credits text ...") self.pageCredits.setHtml("Error loading credits text ...")
return return
def _fillLicensePage(self): def _fillLicensePage(self) -> None:
"""Load the content for the Licence page. """Load the content for the Licence page."""
"""
docPath = CONFIG.assetPath("text") / "gplv3_en.htm" docPath = CONFIG.assetPath("text") / "gplv3_en.htm"
docText = readTextFile(docPath) docText = readTextFile(docPath)
if docText: if docText:
@@ -214,9 +218,8 @@ class GuiAbout(QDialog):
self.pageLicense.setHtml("Error loading licence text ...") self.pageLicense.setHtml("Error loading licence text ...")
return return
def _setStyleSheet(self): def _setStyleSheet(self) -> None:
"""Set stylesheet for all browser tabs """Set stylesheet for all browser tabs."""
"""
styleSheet = ( styleSheet = (
"h1, h2, h3, h4 {{" "h1, h2, h3, h4 {{"
" color: rgb({hColR},{hColG},{hColB});" " color: rgb({hColR},{hColG},{hColB});"
@@ -242,8 +245,4 @@ class GuiAbout(QDialog):
return return
def _doClose(self):
self.close()
return
# END Class GuiAbout # END Class GuiAbout
+21 -12
View File
@@ -26,7 +26,8 @@ from __future__ import annotations
import logging import logging
from PyQt5.QtCore import Qt, QSize from PyQt5.QtGui import QCloseEvent
from PyQt5.QtCore import Qt, QSize, pyqtSlot
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAbstractItemView, QDialog, QDialogButtonBox, QGridLayout, QLabel, QAbstractItemView, QDialog, QDialogButtonBox, QGridLayout, QLabel,
QListWidget, QListWidgetItem, QVBoxLayout, QWidget QListWidget, QListWidgetItem, QVBoxLayout, QWidget
@@ -108,13 +109,12 @@ class GuiDocMerge(QDialog):
return return
def __del__(self): # pragma: no cover def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: GuiDocMerge") logger.debug("Delete: GuiDocMerge")
return return
def getData(self): def getData(self) -> dict:
"""Return the user's choices. """Return the user's choices."""
"""
finalItems = [] finalItems = []
for i in range(self.listBox.count()): for i in range(self.listBox.count()):
item = self.listBox.item(i) item = self.listBox.item(i)
@@ -127,12 +127,22 @@ class GuiDocMerge(QDialog):
return self._data return self._data
## ##
# Slots # Events
## ##
def _resetList(self): def closeEvent(self, event: QCloseEvent) -> None:
"""Reset the content of the list box to its original state. """Capture the close event and perform cleanup."""
""" event.accept()
self.deleteLater()
return
##
# Private Slots
##
@pyqtSlot()
def _resetList(self) -> None:
"""Reset the content of the list box to its original state."""
logger.debug("Resetting list box content") logger.debug("Resetting list box content")
sHandle = self._data.get("sHandle", None) sHandle = self._data.get("sHandle", None)
itemList = self._data.get("origItems", []) itemList = self._data.get("origItems", [])
@@ -143,9 +153,8 @@ class GuiDocMerge(QDialog):
# Internal Functions # Internal Functions
## ##
def _loadContent(self, sHandle, itemList): def _loadContent(self, sHandle: str, itemList: list[str]) -> None:
"""Load content from a given list of items. """Load content from a given list of items."""
"""
self._data = {} self._data = {}
self._data["sHandle"] = sHandle self._data["sHandle"] = sHandle
self._data["origItems"] = itemList self._data["origItems"] = itemList
+24 -13
View File
@@ -26,10 +26,11 @@ from __future__ import annotations
import logging import logging
from PyQt5.QtCore import Qt from PyQt5.QtGui import QCloseEvent
from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QComboBox, QListWidget, QAbstractItemView, QAbstractItemView, QComboBox, QDialog, QDialogButtonBox, QGridLayout,
QListWidgetItem, QDialogButtonBox, QLabel, QGridLayout QLabel, QListWidget, QListWidgetItem, QVBoxLayout, QWidget
) )
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
@@ -45,7 +46,7 @@ class GuiDocSplit(QDialog):
LEVEL_ROLE = Qt.ItemDataRole.UserRole + 1 LEVEL_ROLE = Qt.ItemDataRole.UserRole + 1
LABEL_ROLE = Qt.ItemDataRole.UserRole + 2 LABEL_ROLE = Qt.ItemDataRole.UserRole + 2
def __init__(self, parent, sHandle): def __init__(self, parent: QWidget, sHandle: str) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
logger.debug("Create: GuiDocSplit") logger.debug("Create: GuiDocSplit")
@@ -138,11 +139,11 @@ class GuiDocSplit(QDialog):
return return
def __del__(self): # pragma: no cover def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: GuiDocSplit") logger.debug("Delete: GuiDocSplit")
return return
def getData(self): def getData(self) -> tuple[dict, list]:
"""Return the user's choices. Also save the users options for """Return the user's choices. Also save the users options for
the next time the dialog is used. the next time the dialog is used.
""" """
@@ -167,6 +168,7 @@ class GuiDocSplit(QDialog):
self._data["docHierarchy"] = docHierarchy self._data["docHierarchy"] = docHierarchy
self._data["moveToTrash"] = moveToTrash self._data["moveToTrash"] = moveToTrash
logger.debug("Saving State: GuiDocSplit")
pOptions = SHARED.project.options pOptions = SHARED.project.options
pOptions.setValue("GuiDocSplit", "spLevel", spLevel) pOptions.setValue("GuiDocSplit", "spLevel", spLevel)
pOptions.setValue("GuiDocSplit", "intoFolder", intoFolder) pOptions.setValue("GuiDocSplit", "intoFolder", intoFolder)
@@ -175,12 +177,22 @@ class GuiDocSplit(QDialog):
return self._data, self._text return self._data, self._text
## ##
# Slots # Events
## ##
def _reloadList(self): def closeEvent(self, event: QCloseEvent) -> None:
"""Reload the content of the list box. """Capture the close event and perform cleanup."""
""" event.accept()
self.deleteLater()
return
##
# Private Slots
##
@pyqtSlot()
def _reloadList(self) -> None:
"""Reload the content of the list box."""
sHandle = self._data.get("sHandle", None) sHandle = self._data.get("sHandle", None)
self._loadContent(sHandle) self._loadContent(sHandle)
return return
@@ -189,9 +201,8 @@ class GuiDocSplit(QDialog):
# Internal Functions # Internal Functions
## ##
def _loadContent(self, sHandle): def _loadContent(self, sHandle: str) -> None:
"""Load content from a given source item. """Load content from a given source item."""
"""
self._data = {} self._data = {}
self._data["sHandle"] = sHandle self._data["sHandle"] = sHandle
+17 -5
View File
@@ -26,7 +26,8 @@ from __future__ import annotations
import logging import logging
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QLineEdit, QLabel, QDialogButtonBox, QHBoxLayout QDialog, QDialogButtonBox, QHBoxLayout, QLabel, QLineEdit, QVBoxLayout,
QWidget
) )
from novelwriter import CONFIG from novelwriter import CONFIG
@@ -36,9 +37,10 @@ logger = logging.getLogger(__name__)
class GuiEditLabel(QDialog): class GuiEditLabel(QDialog):
def __init__(self, parent, text=""): def __init__(self, parent: QWidget, text: str = "") -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
logger.debug("Create: GuiEditLabel")
self.setObjectName("GuiEditLabel") self.setObjectName("GuiEditLabel")
self.setWindowTitle(self.tr("Item Label")) self.setWindowTitle(self.tr("Item Label"))
@@ -70,16 +72,26 @@ class GuiEditLabel(QDialog):
self.setLayout(self.outerBox) self.setLayout(self.outerBox)
logger.debug("Ready: GuiEditLabel")
return
def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: GuiEditLabel")
return return
@property @property
def itemLabel(self): def itemLabel(self) -> str:
return self.labelValue.text() return self.labelValue.text()
@classmethod @classmethod
def getLabel(cls, parent, text): def getLabel(cls, parent: QWidget, text: str) -> tuple[str, bool]:
"""Pop the dialog and return the result."""
cls = GuiEditLabel(parent, text=text) cls = GuiEditLabel(parent, text=text)
cls.exec_() cls.exec_()
return cls.itemLabel, cls.result() == QDialog.Accepted label = cls.itemLabel
accepted = cls.result() == QDialog.Accepted
cls.deleteLater()
return label, accepted
# END Class GuiEditLabel # END Class GuiEditLabel
+42 -55
View File
@@ -25,11 +25,11 @@ from __future__ import annotations
import logging import logging
from PyQt5.QtGui import QFont from PyQt5.QtGui import QCloseEvent, QFont
from PyQt5.QtCore import Qt, pyqtSlot from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QDialog, QWidget, QComboBox, QSpinBox, QPushButton, QDialogButtonBox, QDialog, QWidget, QComboBox, QSpinBox, QPushButton, QDialogButtonBox,
QLineEdit, QFileDialog, QFontDialog, QDoubleSpinBox QLineEdit, QFileDialog, QFontDialog, QDoubleSpinBox, qApp
) )
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
@@ -43,6 +43,8 @@ logger = logging.getLogger(__name__)
class GuiPreferences(NPagedDialog): class GuiPreferences(NPagedDialog):
newPreferencesReady = pyqtSignal(bool, bool, bool, bool)
def __init__(self, parent: QWidget) -> None: def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
@@ -66,9 +68,10 @@ class GuiPreferences(NPagedDialog):
self.addTab(self.tabAuto, self.tr("Automation")) self.addTab(self.tabAuto, self.tr("Automation"))
self.addTab(self.tabQuote, self.tr("Quotes")) self.addTab(self.tabQuote, self.tr("Quotes"))
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, self)
self.buttonBox.accepted.connect(self._doSave) self.buttonBox.accepted.connect(self._doSave)
self.buttonBox.rejected.connect(self._doClose) self.buttonBox.rejected.connect(self.close)
self.rejected.connect(self.close)
self.addControls(self.buttonBox) self.addControls(self.buttonBox)
self.resize(*CONFIG.preferencesWinSize) self.resize(*CONFIG.preferencesWinSize)
@@ -83,29 +86,21 @@ class GuiPreferences(NPagedDialog):
return return
def __del__(self): # pragma: no cover def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: GuiPreferences") logger.debug("Delete: GuiPreferences")
return return
## ##
# Properties # Events
## ##
@property def closeEvent(self, event: QCloseEvent) -> None:
def updateTheme(self) -> bool: """Capture the close event and perform cleanup."""
return self._updateTheme logger.debug("Close: GuiPreferences")
self._saveWindowSize()
@property event.accept()
def updateSyntax(self) -> bool: self.deleteLater()
return self._updateSyntax return
@property
def needsRestart(self) -> bool:
return self._needsRestart
@property
def refreshTree(self) -> bool:
return self._refreshTree
## ##
# Private Slots # Private Slots
@@ -113,11 +108,7 @@ class GuiPreferences(NPagedDialog):
@pyqtSlot() @pyqtSlot()
def _doSave(self) -> None: def _doSave(self) -> None:
"""Trigger all the save functions in the tabs, and collect the """Trigger save functions in the tabs and emit ready signal."""
status of the saves.
"""
logger.debug("Saving new preferences")
self.tabGeneral.saveValues() self.tabGeneral.saveValues()
self.tabProjects.saveValues() self.tabProjects.saveValues()
self.tabDocs.saveValues() self.tabDocs.saveValues()
@@ -126,19 +117,15 @@ class GuiPreferences(NPagedDialog):
self.tabAuto.saveValues() self.tabAuto.saveValues()
self.tabQuote.saveValues() self.tabQuote.saveValues()
self._saveWindowSize()
CONFIG.saveConfig() CONFIG.saveConfig()
self.accept() self.newPreferencesReady.emit(
self._needsRestart, self._refreshTree, self._updateTheme, self._updateSyntax
)
qApp.processEvents()
self.close()
return return
@pyqtSlot()
def _doClose(self) -> None:
"""Close the preferences without saving the changes."""
self._saveWindowSize()
self.reject()
return
## ##
# Internal Functions # Internal Functions
## ##
@@ -169,7 +156,7 @@ class GuiPreferencesGeneral(QWidget):
minWidth = CONFIG.pxInt(200) minWidth = CONFIG.pxInt(200)
# Select Locale # Select Locale
self.guiLocale = QComboBox() self.guiLocale = QComboBox(self)
self.guiLocale.setMinimumWidth(minWidth) self.guiLocale.setMinimumWidth(minWidth)
theLangs = CONFIG.listLanguages(CONFIG.LANG_NW) theLangs = CONFIG.listLanguages(CONFIG.LANG_NW)
for lang, langName in theLangs: for lang, langName in theLangs:
@@ -187,7 +174,7 @@ class GuiPreferencesGeneral(QWidget):
) )
# Select Theme # Select Theme
self.guiTheme = QComboBox() self.guiTheme = QComboBox(self)
self.guiTheme.setMinimumWidth(minWidth) self.guiTheme.setMinimumWidth(minWidth)
self.theThemes = SHARED.theme.listThemes() self.theThemes = SHARED.theme.listThemes()
for themeDir, themeName in self.theThemes: for themeDir, themeName in self.theThemes:
@@ -203,7 +190,7 @@ class GuiPreferencesGeneral(QWidget):
) )
# Editor Theme # Editor Theme
self.guiSyntax = QComboBox() self.guiSyntax = QComboBox(self)
self.guiSyntax.setMinimumWidth(CONFIG.pxInt(200)) self.guiSyntax.setMinimumWidth(CONFIG.pxInt(200))
self.theSyntaxes = SHARED.theme.listSyntax() self.theSyntaxes = SHARED.theme.listSyntax()
for syntaxFile, syntaxName in self.theSyntaxes: for syntaxFile, syntaxName in self.theSyntaxes:
@@ -219,11 +206,11 @@ class GuiPreferencesGeneral(QWidget):
) )
# Font Family # Font Family
self.guiFont = QLineEdit() self.guiFont = QLineEdit(self)
self.guiFont.setReadOnly(True) self.guiFont.setReadOnly(True)
self.guiFont.setFixedWidth(CONFIG.pxInt(162)) self.guiFont.setFixedWidth(CONFIG.pxInt(162))
self.guiFont.setText(CONFIG.guiFont) self.guiFont.setText(CONFIG.guiFont)
self.fontButton = QPushButton("...") self.fontButton = QPushButton("...", self)
self.fontButton.setMaximumWidth(int(2.5*SHARED.theme.getTextWidth("..."))) self.fontButton.setMaximumWidth(int(2.5*SHARED.theme.getTextWidth("...")))
self.fontButton.clicked.connect(self._selectFont) self.fontButton.clicked.connect(self._selectFont)
self.mainForm.addRow( self.mainForm.addRow(
@@ -378,7 +365,7 @@ class GuiPreferencesProjects(QWidget):
# Backup Path # Backup Path
self.backupPath = CONFIG.backupPath() self.backupPath = CONFIG.backupPath()
self.backupGetPath = QPushButton(self.tr("Browse")) self.backupGetPath = QPushButton(self.tr("Browse"), self)
self.backupGetPath.clicked.connect(self._backupFolder) self.backupGetPath.clicked.connect(self._backupFolder)
self.backupPathRow = self.mainForm.addRow( self.backupPathRow = self.mainForm.addRow(
self.tr("Backup storage location"), self.tr("Backup storage location"),
@@ -421,7 +408,7 @@ class GuiPreferencesProjects(QWidget):
) )
# Inactive time for idle # Inactive time for idle
self.userIdleTime = QDoubleSpinBox() self.userIdleTime = QDoubleSpinBox(self)
self.userIdleTime.setMinimum(0.5) self.userIdleTime.setMinimum(0.5)
self.userIdleTime.setMaximum(600.0) self.userIdleTime.setMaximum(600.0)
self.userIdleTime.setSingleStep(0.5) self.userIdleTime.setSingleStep(0.5)
@@ -496,11 +483,11 @@ class GuiPreferencesDocuments(QWidget):
self.mainForm.addGroupLabel(self.tr("Text Style")) self.mainForm.addGroupLabel(self.tr("Text Style"))
# Font Family # Font Family
self.textFont = QLineEdit() self.textFont = QLineEdit(self)
self.textFont.setReadOnly(True) self.textFont.setReadOnly(True)
self.textFont.setFixedWidth(CONFIG.pxInt(162)) self.textFont.setFixedWidth(CONFIG.pxInt(162))
self.textFont.setText(CONFIG.textFont) self.textFont.setText(CONFIG.textFont)
self.fontButton = QPushButton("...") self.fontButton = QPushButton("...", self)
self.fontButton.setMaximumWidth(int(2.5*SHARED.theme.getTextWidth("..."))) self.fontButton.setMaximumWidth(int(2.5*SHARED.theme.getTextWidth("...")))
self.fontButton.clicked.connect(self._selectFont) self.fontButton.clicked.connect(self._selectFont)
self.mainForm.addRow( self.mainForm.addRow(
@@ -960,7 +947,7 @@ class GuiPreferencesAutomation(QWidget):
self.mainForm.addGroupLabel(self.tr("Automatic Padding")) self.mainForm.addGroupLabel(self.tr("Automatic Padding"))
# Pad Before # Pad Before
self.fmtPadBefore = QLineEdit() self.fmtPadBefore = QLineEdit(self)
self.fmtPadBefore.setMaxLength(32) self.fmtPadBefore.setMaxLength(32)
self.fmtPadBefore.setText(CONFIG.fmtPadBefore) self.fmtPadBefore.setText(CONFIG.fmtPadBefore)
self.mainForm.addRow( self.mainForm.addRow(
@@ -970,7 +957,7 @@ class GuiPreferencesAutomation(QWidget):
) )
# Pad After # Pad After
self.fmtPadAfter = QLineEdit() self.fmtPadAfter = QLineEdit(self)
self.fmtPadAfter.setMaxLength(32) self.fmtPadAfter.setMaxLength(32)
self.fmtPadAfter.setText(CONFIG.fmtPadAfter) self.fmtPadAfter.setText(CONFIG.fmtPadAfter)
self.mainForm.addRow( self.mainForm.addRow(
@@ -1046,13 +1033,13 @@ class GuiPreferencesQuotes(QWidget):
self.quoteSym = {} self.quoteSym = {}
# Single Quote Style # Single Quote Style
self.quoteSym["SO"] = QLineEdit() self.quoteSym["SO"] = QLineEdit(self)
self.quoteSym["SO"].setMaxLength(1) self.quoteSym["SO"].setMaxLength(1)
self.quoteSym["SO"].setReadOnly(True) self.quoteSym["SO"].setReadOnly(True)
self.quoteSym["SO"].setFixedWidth(qWidth) self.quoteSym["SO"].setFixedWidth(qWidth)
self.quoteSym["SO"].setAlignment(Qt.AlignCenter) self.quoteSym["SO"].setAlignment(Qt.AlignCenter)
self.quoteSym["SO"].setText(CONFIG.fmtSQuoteOpen) self.quoteSym["SO"].setText(CONFIG.fmtSQuoteOpen)
self.btnSingleStyleO = QPushButton("...") self.btnSingleStyleO = QPushButton("...", self)
self.btnSingleStyleO.setMaximumWidth(bWidth) self.btnSingleStyleO.setMaximumWidth(bWidth)
self.btnSingleStyleO.clicked.connect(lambda: self._getQuote("SO")) self.btnSingleStyleO.clicked.connect(lambda: self._getQuote("SO"))
self.mainForm.addRow( self.mainForm.addRow(
@@ -1062,13 +1049,13 @@ class GuiPreferencesQuotes(QWidget):
button=self.btnSingleStyleO button=self.btnSingleStyleO
) )
self.quoteSym["SC"] = QLineEdit() self.quoteSym["SC"] = QLineEdit(self)
self.quoteSym["SC"].setMaxLength(1) self.quoteSym["SC"].setMaxLength(1)
self.quoteSym["SC"].setReadOnly(True) self.quoteSym["SC"].setReadOnly(True)
self.quoteSym["SC"].setFixedWidth(qWidth) self.quoteSym["SC"].setFixedWidth(qWidth)
self.quoteSym["SC"].setAlignment(Qt.AlignCenter) self.quoteSym["SC"].setAlignment(Qt.AlignCenter)
self.quoteSym["SC"].setText(CONFIG.fmtSQuoteClose) self.quoteSym["SC"].setText(CONFIG.fmtSQuoteClose)
self.btnSingleStyleC = QPushButton("...") self.btnSingleStyleC = QPushButton("...", self)
self.btnSingleStyleC.setMaximumWidth(bWidth) self.btnSingleStyleC.setMaximumWidth(bWidth)
self.btnSingleStyleC.clicked.connect(lambda: self._getQuote("SC")) self.btnSingleStyleC.clicked.connect(lambda: self._getQuote("SC"))
self.mainForm.addRow( self.mainForm.addRow(
@@ -1079,13 +1066,13 @@ class GuiPreferencesQuotes(QWidget):
) )
# Double Quote Style # Double Quote Style
self.quoteSym["DO"] = QLineEdit() self.quoteSym["DO"] = QLineEdit(self)
self.quoteSym["DO"].setMaxLength(1) self.quoteSym["DO"].setMaxLength(1)
self.quoteSym["DO"].setReadOnly(True) self.quoteSym["DO"].setReadOnly(True)
self.quoteSym["DO"].setFixedWidth(qWidth) self.quoteSym["DO"].setFixedWidth(qWidth)
self.quoteSym["DO"].setAlignment(Qt.AlignCenter) self.quoteSym["DO"].setAlignment(Qt.AlignCenter)
self.quoteSym["DO"].setText(CONFIG.fmtDQuoteOpen) self.quoteSym["DO"].setText(CONFIG.fmtDQuoteOpen)
self.btnDoubleStyleO = QPushButton("...") self.btnDoubleStyleO = QPushButton("...", self)
self.btnDoubleStyleO.setMaximumWidth(bWidth) self.btnDoubleStyleO.setMaximumWidth(bWidth)
self.btnDoubleStyleO.clicked.connect(lambda: self._getQuote("DO")) self.btnDoubleStyleO.clicked.connect(lambda: self._getQuote("DO"))
self.mainForm.addRow( self.mainForm.addRow(
@@ -1095,13 +1082,13 @@ class GuiPreferencesQuotes(QWidget):
button=self.btnDoubleStyleO button=self.btnDoubleStyleO
) )
self.quoteSym["DC"] = QLineEdit() self.quoteSym["DC"] = QLineEdit(self)
self.quoteSym["DC"].setMaxLength(1) self.quoteSym["DC"].setMaxLength(1)
self.quoteSym["DC"].setReadOnly(True) self.quoteSym["DC"].setReadOnly(True)
self.quoteSym["DC"].setFixedWidth(qWidth) self.quoteSym["DC"].setFixedWidth(qWidth)
self.quoteSym["DC"].setAlignment(Qt.AlignCenter) self.quoteSym["DC"].setAlignment(Qt.AlignCenter)
self.quoteSym["DC"].setText(CONFIG.fmtDQuoteClose) self.quoteSym["DC"].setText(CONFIG.fmtDQuoteClose)
self.btnDoubleStyleC = QPushButton("...") self.btnDoubleStyleC = QPushButton("...", self)
self.btnDoubleStyleC.setMaximumWidth(bWidth) self.btnDoubleStyleC.setMaximumWidth(bWidth)
self.btnDoubleStyleC.clicked.connect(lambda: self._getQuote("DC")) self.btnDoubleStyleC.clicked.connect(lambda: self._getQuote("DC"))
self.mainForm.addRow( self.mainForm.addRow(
+27 -34
View File
@@ -26,8 +26,8 @@ from __future__ import annotations
import math import math
import logging import logging
from PyQt5.QtGui import QCloseEvent, QFont
from PyQt5.QtCore import Qt, QSize, pyqtSlot from PyQt5.QtCore import Qt, QSize, pyqtSlot
from PyQt5.QtGui import QFont
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAbstractItemView, QDialogButtonBox, QGridLayout, QHBoxLayout, QLabel, QAbstractItemView, QDialogButtonBox, QGridLayout, QHBoxLayout, QLabel,
QLineEdit, QSpinBox, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget QLineEdit, QSpinBox, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
@@ -45,7 +45,7 @@ logger = logging.getLogger(__name__)
class GuiProjectDetails(NPagedDialog): class GuiProjectDetails(NPagedDialog):
def __init__(self, parent): def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
logger.debug("Create: GuiProjectDetails") logger.debug("Create: GuiProjectDetails")
@@ -71,43 +71,41 @@ class GuiProjectDetails(NPagedDialog):
self.addTab(self.tabContents, self.tr("Contents")) self.addTab(self.tabContents, self.tr("Contents"))
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close) self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close)
self.buttonBox.button(QDialogButtonBox.Close) self.buttonBox.rejected.connect(self.close)
self.buttonBox.rejected.connect(self._doClose) self.rejected.connect(self.close)
self.addControls(self.buttonBox) self.addControls(self.buttonBox)
logger.debug("Ready: GuiProjectDetails") logger.debug("Ready: GuiProjectDetails")
return return
def __del__(self): # pragma: no cover def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: GuiProjectDetails") logger.debug("Delete: GuiProjectDetails")
return return
def updateValues(self): def updateValues(self) -> None:
"""Set all the values of the pages. """Set all the values of the pages."""
"""
self.tabMain.updateValues() self.tabMain.updateValues()
self.tabContents.updateValues() self.tabContents.updateValues()
return return
## ##
# Slots # Events
## ##
def _doClose(self): def closeEvent(self, event: QCloseEvent) -> None:
"""Save settings and close the dialog. """Capture the close event and perform cleanup."""
"""
self._saveGuiSettings() self._saveGuiSettings()
self.close() event.accept()
self.deleteLater()
return return
## ##
# Internal Functions # Internal Functions
## ##
def _saveGuiSettings(self): 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())
@@ -122,6 +120,7 @@ class GuiProjectDetails(NPagedDialog):
countFrom = self.tabContents.poValue.value() countFrom = self.tabContents.poValue.value()
clearDouble = self.tabContents.dblValue.isChecked() clearDouble = self.tabContents.dblValue.isChecked()
logger.debug("Saving State: GuiProjectDetails")
pOptions = SHARED.project.options pOptions = SHARED.project.options
pOptions.setValue("GuiProjectDetails", "winWidth", winWidth) pOptions.setValue("GuiProjectDetails", "winWidth", winWidth)
pOptions.setValue("GuiProjectDetails", "winHeight", winHeight) pOptions.setValue("GuiProjectDetails", "winHeight", winHeight)
@@ -141,7 +140,7 @@ class GuiProjectDetails(NPagedDialog):
class GuiProjectDetailsMain(QWidget): class GuiProjectDetailsMain(QWidget):
def __init__(self, parent): def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
fPx = SHARED.theme.fontPixelSize fPx = SHARED.theme.fontPixelSize
@@ -270,7 +269,7 @@ class GuiProjectDetailsContents(QWidget):
C_PAGE = 3 C_PAGE = 3
C_PROG = 4 C_PROG = 4
def __init__(self, parent): def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
# Internal # Internal
@@ -406,9 +405,8 @@ class GuiProjectDetailsContents(QWidget):
return return
def getColumnSizes(self): def getColumnSizes(self) -> list[int]:
"""Return the column widths for the tree columns. """Return the column widths for the tree columns."""
"""
retVals = [ retVals = [
self.tocTree.columnWidth(0), self.tocTree.columnWidth(0),
self.tocTree.columnWidth(1), self.tocTree.columnWidth(1),
@@ -418,24 +416,21 @@ class GuiProjectDetailsContents(QWidget):
] ]
return retVals return retVals
def updateValues(self): def updateValues(self) -> None:
"""Populate the tree. """Populate the tree."""
"""
self._currentRoot = None self._currentRoot = None
self.novelValue.updateList() self.novelValue.updateList()
self.novelValue.setHandle(self.novelValue.firstHandle) self.novelValue.setHandle(self.novelValue.firstHandle)
self._prepareData(self.novelValue.firstHandle) self._prepareData(self.novelValue.firstHandle)
self._populateTree() self._populateTree()
return return
## ##
# Internal Functions # Internal Functions
## ##
def _prepareData(self, rootHandle): def _prepareData(self, rootHandle: str | None) -> None:
"""Extract the information from the project index. """Extract the information from the project index."""
"""
logger.debug("Populating ToC from handle '%s'", rootHandle) logger.debug("Populating ToC from handle '%s'", rootHandle)
self._theToC = SHARED.project.index.getTableOfContents(rootHandle, 2) self._theToC = SHARED.project.index.getTableOfContents(rootHandle, 2)
self._theToC.append(("", 0, self.tr("END"), 0)) self._theToC.append(("", 0, self.tr("END"), 0))
@@ -446,9 +441,8 @@ class GuiProjectDetailsContents(QWidget):
## ##
@pyqtSlot(str) @pyqtSlot(str)
def _novelValueChanged(self, tHandle): def _novelValueChanged(self, tHandle: str) -> None:
"""Refresh the tree with another root item. """Refresh the tree with another root item."""
"""
if tHandle != self._currentRoot: if tHandle != self._currentRoot:
self._prepareData(tHandle) self._prepareData(tHandle)
self._populateTree() self._populateTree()
@@ -456,9 +450,8 @@ class GuiProjectDetailsContents(QWidget):
return return
@pyqtSlot() @pyqtSlot()
def _populateTree(self): def _populateTree(self) -> None:
"""Set the content of the chapter/page tree. """Set the content of the chapter/page tree."""
"""
dblPages = self.dblValue.isChecked() dblPages = self.dblValue.isChecked()
wpPage = self.wpValue.value() wpPage = self.wpValue.value()
fstPage = self.poValue.value() - 1 fstPage = self.poValue.value() - 1
+1 -1
View File
@@ -153,7 +153,7 @@ class GuiProjectLoad(QDialog):
return return
def __del__(self): # pragma: no cover def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: GuiProjectLoad") logger.debug("Delete: GuiProjectLoad")
return return
+18 -13
View File
@@ -27,11 +27,11 @@ import logging
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtGui import QIcon, QPixmap, QColor from PyQt5.QtGui import QCloseEvent, QIcon, QPixmap, QColor
from PyQt5.QtCore import Qt, pyqtSlot from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QColorDialog, QComboBox, QDialogButtonBox, QHBoxLayout, QLabel, QLineEdit, QColorDialog, QComboBox, QDialogButtonBox, QHBoxLayout, QLabel, QLineEdit,
QPushButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget QPushButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget, qApp
) )
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
@@ -53,6 +53,8 @@ class GuiProjectSettings(NPagedDialog):
TAB_IMPORT = 2 TAB_IMPORT = 2
TAB_REPLACE = 3 TAB_REPLACE = 3
newProjectSettingsReady = pyqtSignal()
def __init__(self, mainGui: GuiMain, focusTab: int = TAB_MAIN) -> None: def __init__(self, mainGui: GuiMain, focusTab: int = TAB_MAIN) -> None:
super().__init__(parent=mainGui) super().__init__(parent=mainGui)
@@ -86,7 +88,8 @@ class GuiProjectSettings(NPagedDialog):
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.buttonBox.accepted.connect(self._doSave) self.buttonBox.accepted.connect(self._doSave)
self.buttonBox.rejected.connect(self._doClose) self.buttonBox.rejected.connect(self.close)
self.rejected.connect(self.close)
self.addControls(self.buttonBox) self.addControls(self.buttonBox)
# Focus Tab # Focus Tab
@@ -100,6 +103,13 @@ class GuiProjectSettings(NPagedDialog):
logger.debug("Delete: GuiProjectSettings") logger.debug("Delete: GuiProjectSettings")
return return
def closeEvent(self, event: QCloseEvent) -> None:
"""Capture the close event and perform cleanup."""
self._saveGuiSettings()
event.accept()
self.deleteLater()
return
## ##
# Private Slots # Private Slots
## ##
@@ -137,18 +147,12 @@ class GuiProjectSettings(NPagedDialog):
newList = self.tabReplace.getNewList() newList = self.tabReplace.getNewList()
project.data.setAutoReplace(newList) project.data.setAutoReplace(newList)
self._saveGuiSettings() self.newProjectSettingsReady.emit()
self.accept() qApp.processEvents()
self.close()
return return
@pyqtSlot()
def _doClose(self) -> None:
"""Save settings and close the dialog."""
self._saveGuiSettings()
self.reject()
return
## ##
# Internal Functions # Internal Functions
## ##
@@ -173,6 +177,7 @@ class GuiProjectSettings(NPagedDialog):
statusColW = CONFIG.rpxInt(self.tabStatus.listBox.columnWidth(0)) statusColW = CONFIG.rpxInt(self.tabStatus.listBox.columnWidth(0))
importColW = CONFIG.rpxInt(self.tabImport.listBox.columnWidth(0)) importColW = CONFIG.rpxInt(self.tabImport.listBox.columnWidth(0))
logger.debug("Saving State: GuiProjectSettings")
pOptions = SHARED.project.options pOptions = SHARED.project.options
pOptions.setValue("GuiProjectSettings", "winWidth", winWidth) pOptions.setValue("GuiProjectSettings", "winWidth", winWidth)
pOptions.setValue("GuiProjectSettings", "winHeight", winHeight) pOptions.setValue("GuiProjectSettings", "winHeight", winHeight)
+30 -23
View File
@@ -25,11 +25,11 @@ from __future__ import annotations
import logging import logging
from PyQt5.QtGui import QFontMetrics from PyQt5.QtGui import QCloseEvent, QFontMetrics
from PyQt5.QtCore import Qt, QSize from PyQt5.QtCore import QSize, Qt, pyqtSlot
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QLabel, QVBoxLayout, QHBoxLayout, QDialog, QDialogButtonBox, QDialog, QDialogButtonBox, QFrame, QHBoxLayout, QLabel, QListWidget,
QListWidget, QListWidgetItem, QFrame QListWidgetItem, QVBoxLayout, QWidget
) )
from novelwriter import CONFIG from novelwriter import CONFIG
@@ -44,9 +44,12 @@ class GuiQuoteSelect(QDialog):
D_KEY = Qt.ItemDataRole.UserRole D_KEY = Qt.ItemDataRole.UserRole
def __init__(self, parent=None, currentQuote='"'): def __init__(self, parent: QWidget, currentQuote: str = '"') -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
logger.debug("Create: GuiQuoteSelect")
self.setObjectName("GuiQuoteSelect")
self.outerBox = QVBoxLayout() self.outerBox = QVBoxLayout()
self.innerBox = QHBoxLayout() self.innerBox = QHBoxLayout()
self.labelBox = QVBoxLayout() self.labelBox = QVBoxLayout()
@@ -87,8 +90,8 @@ class GuiQuoteSelect(QDialog):
# Buttons # Buttons
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.buttonBox.accepted.connect(self._doAccept) self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self._doReject) self.buttonBox.rejected.connect(self.reject)
# Assemble # Assemble
self.labelBox.addWidget(self.previewLabel, 0, Qt.AlignTop) self.labelBox.addWidget(self.previewLabel, 0, Qt.AlignTop)
@@ -102,15 +105,31 @@ class GuiQuoteSelect(QDialog):
self.setLayout(self.outerBox) self.setLayout(self.outerBox)
logger.debug("Ready: GuiQuoteSelect")
return
def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: GuiQuoteSelect")
return return
## ##
# Slots # Events
## ##
def _selectedSymbol(self): def closeEvent(self, event: QCloseEvent) -> None:
"""Update the preview label and the selected quote style. """Capture the close event and perform cleanup."""
""" event.accept()
self.deleteLater()
return
##
# Private Slots
##
@pyqtSlot()
def _selectedSymbol(self) -> None:
"""Update the preview label and the selected quote style."""
selItems = self.listBox.selectedItems() selItems = self.listBox.selectedItems()
if selItems: if selItems:
theSymbol = selItems[0].data(self.D_KEY) theSymbol = selItems[0].data(self.D_KEY)
@@ -118,16 +137,4 @@ class GuiQuoteSelect(QDialog):
self.selectedQuote = theSymbol self.selectedQuote = theSymbol
return return
def _doAccept(self):
"""Ok button clicked.
"""
self.accept()
return
def _doReject(self):
"""Cancel button clicked.
"""
self.reject()
return
# END Class GuiQuoteSelect # END Class GuiQuoteSelect
+2 -12
View File
@@ -30,7 +30,7 @@ from datetime import datetime
from urllib.request import Request, urlopen from urllib.request import Request, urlopen
from PyQt5.QtGui import QCloseEvent, QCursor from PyQt5.QtGui import QCloseEvent, QCursor
from PyQt5.QtCore import Qt, pyqtSlot from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QWidget, qApp, QDialog, QHBoxLayout, QVBoxLayout, QDialogButtonBox, QLabel QWidget, qApp, QDialog, QHBoxLayout, QVBoxLayout, QDialogButtonBox, QLabel
) )
@@ -95,7 +95,7 @@ class GuiUpdates(QDialog):
# Buttons # Buttons
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close) self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close)
self.buttonBox.rejected.connect(self._doClose) self.buttonBox.rejected.connect(self.close)
# Assemble # Assemble
self.innerBox = QHBoxLayout() self.innerBox = QHBoxLayout()
@@ -169,14 +169,4 @@ class GuiUpdates(QDialog):
self.deleteLater() self.deleteLater()
return return
##
# Private Slots
##
@pyqtSlot()
def _doClose(self) -> None:
"""Close the dialog."""
self.close()
return
# END Class GuiUpdates # END Class GuiUpdates
+32 -19
View File
@@ -27,10 +27,11 @@ import logging
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import Qt from PyQt5.QtGui import QCloseEvent
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAbstractItemView, QDialog, QDialogButtonBox, QHBoxLayout, QLabel, QAbstractItemView, QDialog, QDialogButtonBox, QHBoxLayout, QLabel,
QLineEdit, QListWidget, QListWidgetItem, QPushButton, QVBoxLayout QLineEdit, QListWidget, QListWidgetItem, QPushButton, QVBoxLayout, qApp
) )
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
@@ -44,7 +45,9 @@ logger = logging.getLogger(__name__)
class GuiWordList(QDialog): class GuiWordList(QDialog):
def __init__(self, mainGui: GuiMain): newWordListReady = pyqtSignal()
def __init__(self, mainGui: GuiMain) -> None:
super().__init__(parent=mainGui) super().__init__(parent=mainGui)
logger.debug("Create: GuiWordList") logger.debug("Create: GuiWordList")
@@ -87,7 +90,7 @@ class GuiWordList(QDialog):
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Close) self.buttonBox = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Close)
self.buttonBox.accepted.connect(self._doSave) self.buttonBox.accepted.connect(self._doSave)
self.buttonBox.rejected.connect(self._doClose) self.buttonBox.rejected.connect(self.close)
# Assemble # Assemble
# ======== # ========
@@ -108,15 +111,27 @@ class GuiWordList(QDialog):
return return
def __del__(self): # pragma: no cover def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: GuiWordList") logger.debug("Delete: GuiWordList")
return return
## ##
# Slots # Events
## ##
def _doAdd(self): def closeEvent(self, event: QCloseEvent) -> None:
"""Capture the close event and perform cleanup."""
self._saveGuiSettings()
event.accept()
self.deleteLater()
return
##
# Private Slots
##
@pyqtSlot()
def _doAdd(self) -> None:
"""Add a new word to the word list.""" """Add a new word to the word list."""
word = self.newEntry.text().strip() word = self.newEntry.text().strip()
if word == "": if word == "":
@@ -134,16 +149,17 @@ class GuiWordList(QDialog):
return return
def _doDelete(self): @pyqtSlot()
def _doDelete(self) -> None:
"""Delete the selected item.""" """Delete the selected item."""
selItem = self.listBox.selectedItems() selItem = self.listBox.selectedItems()
if selItem: if selItem:
self.listBox.takeItem(self.listBox.row(selItem[0])) self.listBox.takeItem(self.listBox.row(selItem[0]))
return return
def _doSave(self): @pyqtSlot()
def _doSave(self) -> None:
"""Save the new word list and close.""" """Save the new word list and close."""
self._saveGuiSettings()
userDict = UserDictionary(SHARED.project) userDict = UserDictionary(SHARED.project)
for i in range(self.listBox.count()): for i in range(self.listBox.count()):
item = self.listBox.item(i) item = self.listBox.item(i)
@@ -152,20 +168,16 @@ class GuiWordList(QDialog):
if word: if word:
userDict.add(word) userDict.add(word)
userDict.save() userDict.save()
self.accept() self.newWordListReady.emit()
return True qApp.processEvents()
self.close()
def _doClose(self):
"""Close without saving the word list."""
self._saveGuiSettings()
self.reject()
return return
## ##
# Internal Functions # Internal Functions
## ##
def _loadWordList(self): def _loadWordList(self) -> None:
"""Load the project's word list, if it exists.""" """Load the project's word list, if it exists."""
userDict = UserDictionary(SHARED.project) userDict = UserDictionary(SHARED.project)
userDict.load() userDict.load()
@@ -175,11 +187,12 @@ class GuiWordList(QDialog):
self.listBox.addItem(word) self.listBox.addItem(word)
return return
def _saveGuiSettings(self): 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())
logger.debug("Saving State: GuiWordList")
pOptions = SHARED.project.options pOptions = SHARED.project.options
pOptions.setValue("GuiWordList", "winWidth", winWidth) pOptions.setValue("GuiWordList", "winWidth", winWidth)
pOptions.setValue("GuiWordList", "winHeight", winHeight) pOptions.setValue("GuiWordList", "winHeight", winHeight)
+1
View File
@@ -140,6 +140,7 @@ class nwDocInsert(Enum):
NEW_PAGE = 7 NEW_PAGE = 7
VSPACE_S = 8 VSPACE_S = 8
VSPACE_M = 9 VSPACE_M = 9
LIPSUM = 10
# END Enum nwDocInsert # END Enum nwDocInsert
+14 -7
View File
@@ -58,6 +58,7 @@ from novelwriter.common import minmax, transferCase
from novelwriter.constants import nwKeyWords, nwLabels, nwShortcode, nwUnicode, trConst from novelwriter.constants import nwKeyWords, nwLabels, nwShortcode, nwUnicode, trConst
from novelwriter.core.item import NWItem from novelwriter.core.item import NWItem
from novelwriter.core.index import countWords from novelwriter.core.index import countWords
from novelwriter.tools.lipsum import GuiLipsum
from novelwriter.core.document import NWDocument from novelwriter.core.document import NWDocument
from novelwriter.gui.dochighlight import GuiDocHighlighter from novelwriter.gui.dochighlight import GuiDocHighlighter
from novelwriter.gui.editordocument import GuiTextDocument from novelwriter.gui.editordocument import GuiTextDocument
@@ -850,18 +851,23 @@ class GuiDocEditor(QPlainTextEdit):
text = "[vspace:2]" text = "[vspace:2]"
newBlock = True newBlock = True
goAfter = False goAfter = False
elif insert == nwDocInsert.LIPSUM:
text = GuiLipsum.getLipsum(self)
newBlock = True
goAfter = False
else: else:
return False return False
else: else:
return False return False
if newBlock: if text:
self.insertNewBlock(text, defaultAfter=goAfter) if newBlock:
else: self.insertNewBlock(text, defaultAfter=goAfter)
cursor = self.textCursor() else:
cursor.beginEditBlock() cursor = self.textCursor()
cursor.insertText(text) cursor.beginEditBlock()
cursor.endEditBlock() cursor.insertText(text)
cursor.endEditBlock()
return True return True
@@ -1144,6 +1150,7 @@ class GuiDocEditor(QPlainTextEdit):
# Execute the context menu # Execute the context menu
ctxMenu.exec_(self.viewport().mapToGlobal(pos)) ctxMenu.exec_(self.viewport().mapToGlobal(pos))
ctxMenu.deleteLater()
return return
+12 -11
View File
@@ -378,33 +378,34 @@ class GuiDocViewer(QTextBrowser):
userCursor = self.textCursor() userCursor = self.textCursor()
userSelection = userCursor.hasSelection() userSelection = userCursor.hasSelection()
mnuContext = QMenu(self) ctxMenu = QMenu(self)
if userSelection: if userSelection:
mnuCopy = QAction(self.tr("Copy"), mnuContext) mnuCopy = QAction(self.tr("Copy"), ctxMenu)
mnuCopy.triggered.connect(lambda: self.docAction(nwDocAction.COPY)) mnuCopy.triggered.connect(lambda: self.docAction(nwDocAction.COPY))
mnuContext.addAction(mnuCopy) ctxMenu.addAction(mnuCopy)
mnuContext.addSeparator() ctxMenu.addSeparator()
mnuSelAll = QAction(self.tr("Select All"), mnuContext) mnuSelAll = QAction(self.tr("Select All"), ctxMenu)
mnuSelAll.triggered.connect(lambda: self.docAction(nwDocAction.SEL_ALL)) mnuSelAll.triggered.connect(lambda: self.docAction(nwDocAction.SEL_ALL))
mnuContext.addAction(mnuSelAll) ctxMenu.addAction(mnuSelAll)
mnuSelWord = QAction(self.tr("Select Word"), mnuContext) mnuSelWord = QAction(self.tr("Select Word"), ctxMenu)
mnuSelWord.triggered.connect( mnuSelWord.triggered.connect(
lambda: self._makePosSelection(QTextCursor.SelectionType.WordUnderCursor, point) lambda: self._makePosSelection(QTextCursor.SelectionType.WordUnderCursor, point)
) )
mnuContext.addAction(mnuSelWord) ctxMenu.addAction(mnuSelWord)
mnuSelPara = QAction(self.tr("Select Paragraph"), mnuContext) mnuSelPara = QAction(self.tr("Select Paragraph"), ctxMenu)
mnuSelPara.triggered.connect( mnuSelPara.triggered.connect(
lambda: self._makePosSelection(QTextCursor.SelectionType.BlockUnderCursor, point) lambda: self._makePosSelection(QTextCursor.SelectionType.BlockUnderCursor, point)
) )
mnuContext.addAction(mnuSelPara) ctxMenu.addAction(mnuSelPara)
# Open the context menu # Open the context menu
mnuContext.exec_(self.viewport().mapToGlobal(point)) ctxMenu.exec_(self.viewport().mapToGlobal(point))
ctxMenu.deleteLater()
return return
+1
View File
@@ -123,6 +123,7 @@ class GuiDocViewerPanel(QWidget):
widths = {} widths = {}
for key, tab in self.kwTabs.items(): for key, tab in self.kwTabs.items():
widths[key] = tab.getColumnWidths() widths[key] = tab.getColumnWidths()
logger.debug("Saving State: GuiDocViewerPanel")
SHARED.project.options.setValue("GuiDocViewerPanel", "colWidths", widths) SHARED.project.options.setValue("GuiDocViewerPanel", "colWidths", widths)
return return
+12 -10
View File
@@ -154,12 +154,12 @@ class GuiMainMenu(QMenuBar):
# Project > Project Settings # Project > Project Settings
self.aProjectSettings = self.projMenu.addAction(self.tr("Project Settings")) self.aProjectSettings = self.projMenu.addAction(self.tr("Project Settings"))
self.aProjectSettings.setShortcut("Ctrl+Shift+,") self.aProjectSettings.setShortcut("Ctrl+Shift+,")
self.aProjectSettings.triggered.connect(lambda: self.mainGui.showProjectSettingsDialog()) self.aProjectSettings.triggered.connect(self.mainGui.showProjectSettingsDialog)
# Project > Project Details # Project > Project Details
self.aProjectDetails = self.projMenu.addAction(self.tr("Project Details")) self.aProjectDetails = self.projMenu.addAction(self.tr("Project Details"))
self.aProjectDetails.setShortcut("Shift+F6") self.aProjectDetails.setShortcut("Shift+F6")
self.aProjectDetails.triggered.connect(lambda: self.mainGui.showProjectDetailsDialog()) self.aProjectDetails.triggered.connect(self.mainGui.showProjectDetailsDialog)
# Project > Separator # Project > Separator
self.projMenu.addSeparator() self.projMenu.addSeparator()
@@ -593,8 +593,10 @@ class GuiMainMenu(QMenuBar):
) )
# Insert > Placeholder Text # Insert > Placeholder Text
self.aLipsumText = self.mInsBreaks.addAction(self.tr("Placeholder Text")) self.aLipsumText = self.insMenu.addAction(self.tr("Placeholder Text"))
self.aLipsumText.triggered.connect(lambda: self.mainGui.showLoremIpsumDialog()) self.aLipsumText.triggered.connect(
lambda: self.requestDocInsert.emit(nwDocInsert.LIPSUM)
)
return return
@@ -872,7 +874,7 @@ class GuiMainMenu(QMenuBar):
# Tools > Project Word List # Tools > Project Word List
self.aEditWordList = self.toolsMenu.addAction(self.tr("Project Word List")) self.aEditWordList = self.toolsMenu.addAction(self.tr("Project Word List"))
self.aEditWordList.triggered.connect(lambda: self.mainGui.showProjectWordListDialog()) self.aEditWordList.triggered.connect(self.mainGui.showProjectWordListDialog)
# Tools > Add Dictionaries # Tools > Add Dictionaries
if CONFIG.osWindows or CONFIG.isDebug: if CONFIG.osWindows or CONFIG.isDebug:
@@ -902,13 +904,13 @@ class GuiMainMenu(QMenuBar):
# Tools > Writing Statistics # Tools > Writing Statistics
self.aWritingStats = self.toolsMenu.addAction(self.tr("Writing Statistics")) self.aWritingStats = self.toolsMenu.addAction(self.tr("Writing Statistics"))
self.aWritingStats.setShortcut("F6") self.aWritingStats.setShortcut("F6")
self.aWritingStats.triggered.connect(lambda: self.mainGui.showWritingStatsDialog()) self.aWritingStats.triggered.connect(self.mainGui.showWritingStatsDialog)
# Tools > Preferences # Tools > Preferences
self.aPreferences = self.toolsMenu.addAction(self.tr("Preferences")) self.aPreferences = self.toolsMenu.addAction(self.tr("Preferences"))
self.aPreferences.setShortcut("Ctrl+,") self.aPreferences.setShortcut("Ctrl+,")
self.aPreferences.setMenuRole(QAction.PreferencesRole) self.aPreferences.setMenuRole(QAction.PreferencesRole)
self.aPreferences.triggered.connect(lambda: self.mainGui.showPreferencesDialog()) self.aPreferences.triggered.connect(self.mainGui.showPreferencesDialog)
return return
@@ -920,12 +922,12 @@ class GuiMainMenu(QMenuBar):
# Help > About # Help > About
self.aAboutNW = self.helpMenu.addAction(self.tr("About novelWriter")) self.aAboutNW = self.helpMenu.addAction(self.tr("About novelWriter"))
self.aAboutNW.setMenuRole(QAction.AboutRole) self.aAboutNW.setMenuRole(QAction.AboutRole)
self.aAboutNW.triggered.connect(lambda: self.mainGui.showAboutNWDialog()) self.aAboutNW.triggered.connect(self.mainGui.showAboutNWDialog)
# Help > About Qt5 # Help > About Qt5
self.aAboutQt = self.helpMenu.addAction(self.tr("About Qt5")) self.aAboutQt = self.helpMenu.addAction(self.tr("About Qt5"))
self.aAboutQt.setMenuRole(QAction.AboutQtRole) self.aAboutQt.setMenuRole(QAction.AboutQtRole)
self.aAboutQt.triggered.connect(lambda: self.mainGui.showAboutQtDialog()) self.aAboutQt.triggered.connect(self.mainGui.showAboutQtDialog)
# Help > Separator # Help > Separator
self.helpMenu.addSeparator() self.helpMenu.addSeparator()
@@ -961,7 +963,7 @@ class GuiMainMenu(QMenuBar):
# Document > Check for Updates # Document > Check for Updates
self.aUpdates = self.helpMenu.addAction(self.tr("Check for New Release")) self.aUpdates = self.helpMenu.addAction(self.tr("Check for New Release"))
self.aUpdates.triggered.connect(lambda: self.mainGui.showUpdatesDialog()) self.aUpdates.triggered.connect(self.mainGui.showUpdatesDialog)
return return
+1
View File
@@ -145,6 +145,7 @@ class GuiNovelView(QWidget):
"""Run closing project tasks.""" """Run closing project tasks."""
lastColType = self.novelTree.lastColType lastColType = self.novelTree.lastColType
lastColSize = self.novelTree.lastColSize lastColSize = self.novelTree.lastColSize
logger.debug("Saving State: GuiNovelView")
pOptions = SHARED.project.options pOptions = SHARED.project.options
pOptions.setValue("GuiNovelView", "lastCol", lastColType) pOptions.setValue("GuiNovelView", "lastCol", lastColType)
pOptions.setValue("GuiNovelView", "lastColSize", lastColSize) pOptions.setValue("GuiNovelView", "lastColSize", lastColSize)
+1
View File
@@ -606,6 +606,7 @@ class GuiOutlineTree(QTreeWidget):
logHidden, orgWidth if logHidden and logWidth == 0 else logWidth logHidden, orgWidth if logHidden and logWidth == 0 else logWidth
] ]
logger.debug("Saving State: GuiOutline")
pOptions = SHARED.project.options pOptions = SHARED.project.options
pOptions.setValue("GuiOutline", "columnState", colState) pOptions.setValue("GuiOutline", "columnState", colState)
pOptions.saveSettings() pOptions.saveSettings()
+40 -1
View File
@@ -27,6 +27,7 @@ import logging
from time import time from time import time
from typing import TYPE_CHECKING, Literal from typing import TYPE_CHECKING, Literal
from datetime import datetime
from PyQt5.QtCore import pyqtSlot, QLocale from PyQt5.QtCore import pyqtSlot, QLocale
from PyQt5.QtGui import QColor from PyQt5.QtGui import QColor
@@ -50,8 +51,9 @@ class GuiMainStatus(QStatusBar):
logger.debug("Create: GuiMainStatus") logger.debug("Create: GuiMainStatus")
self._refTime = -1.0 self._refTime = -1.0
self._userIdle = False self._userIdle = False
self._debugInfo = False
colNone = QColor(*SHARED.theme.statNone) colNone = QColor(*SHARED.theme.statNone)
colSaved = QColor(*SHARED.theme.statSaved) colSaved = QColor(*SHARED.theme.statSaved)
@@ -223,4 +225,41 @@ class GuiMainStatus(QStatusBar):
self.setDocumentStatus(StatusLED.S_BAD if status else StatusLED.S_GOOD) self.setDocumentStatus(StatusLED.S_BAD if status else StatusLED.S_GOOD)
return return
##
# Debug
##
def memInfo(self) -> None: # pragma: no cover
"""Display memory info on the status bar. This is used to
investigate memory usage and Qt widgets that get left in memory.
Enabled by the --meminfo command line flag.
"""
import tracemalloc
from collections import Counter
widgets = qApp.allWidgets()
if not self._debugInfo:
if tracemalloc.is_tracing():
self._traceMallocRef = "Total"
else:
self._traceMallocRef = "Relative"
tracemalloc.start()
self._debugInfo = True
self._wCounts = Counter([type(x).__name__ for x in widgets])
if hasattr(self, "_wCounts"):
diff = Counter([type(x).__name__ for x in widgets]) - self._wCounts
for name, count in diff.items():
logger.debug("Widget '%s': +%d", name, count)
mem = tracemalloc.get_traced_memory()
stamp = datetime.now().strftime("%H:%M:%S")
self.showMessage((
f"Debug [{stamp}]"
f" \u2013 Widgets: {len(qApp.allWidgets())}"
f" \u2013 {self._traceMallocRef} Memory: {mem[0]:n}"
f" \u2013 Peak: {mem[1]:n}"
), 6000)
return
# END Class GuiMainStatus # END Class GuiMainStatus
+135 -177
View File
@@ -56,7 +56,6 @@ from novelwriter.dialogs.wordlist import GuiWordList
from novelwriter.dialogs.preferences import GuiPreferences from novelwriter.dialogs.preferences import GuiPreferences
from novelwriter.dialogs.projdetails import GuiProjectDetails from novelwriter.dialogs.projdetails import GuiProjectDetails
from novelwriter.dialogs.projsettings import GuiProjectSettings from novelwriter.dialogs.projsettings import GuiProjectSettings
from novelwriter.tools.lipsum import GuiLipsum
from novelwriter.tools.manuscript import GuiManuscript from novelwriter.tools.manuscript import GuiManuscript
from novelwriter.tools.projwizard import GuiProjectWizard from novelwriter.tools.projwizard import GuiProjectWizard
from novelwriter.tools.dictionaries import GuiDictionaries from novelwriter.tools.dictionaries import GuiDictionaries
@@ -66,7 +65,7 @@ from novelwriter.core.coretools import ProjectBuilder
from novelwriter.enum import ( from novelwriter.enum import (
nwDocAction, nwDocInsert, nwDocMode, nwItemType, nwItemClass, nwWidget, nwView nwDocAction, nwDocInsert, nwDocMode, nwItemType, nwItemClass, nwWidget, nwView
) )
from novelwriter.common import getGuiItem, hexToInt from novelwriter.common import hexToInt
from novelwriter.constants import nwFiles from novelwriter.constants import nwFiles
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -866,213 +865,111 @@ class GuiMain(QMainWindow):
return None return None
@pyqtSlot()
def showPreferencesDialog(self) -> None: def showPreferencesDialog(self) -> None:
"""Open the preferences dialog.""" """Open the preferences dialog."""
dlgConf = GuiPreferences(self) dialog = GuiPreferences(self)
dlgConf.exec_() dialog.newPreferencesReady.connect(self._processConfigChanges)
dialog.exec_()
if dlgConf.result() == QDialog.Accepted:
logger.debug("Applying new preferences")
self.initMain()
self.saveDocument()
if dlgConf.needsRestart:
SHARED.info(self.tr(
"Some changes will not be applied until novelWriter has been restarted."
))
if dlgConf.refreshTree:
self.projView.populateTree()
if dlgConf.updateTheme:
# We are doing this manually instead of connecting to
# qApp.paletteChanged since the processing order matters
SHARED.theme.loadTheme()
self.docEditor.updateTheme()
self.docViewer.updateTheme()
self.docViewerPanel.updateTheme()
self.sideBar.updateTheme()
self.projView.updateTheme()
self.novelView.updateTheme()
self.outlineView.updateTheme()
self.itemDetails.updateTheme()
self.mainStatus.updateTheme()
if dlgConf.updateSyntax:
SHARED.theme.loadSyntax()
self.docEditor.updateSyntaxColours()
self.docEditor.initEditor()
self.docViewer.initViewer()
self.projView.initSettings()
self.novelView.initSettings()
self.outlineView.initSettings()
self._updateStatusWordCount()
return return
@pyqtSlot()
@pyqtSlot(int) @pyqtSlot(int)
def showProjectSettingsDialog(self, focusTab: int = GuiProjectSettings.TAB_MAIN) -> bool: def showProjectSettingsDialog(self, focusTab: int = GuiProjectSettings.TAB_MAIN) -> None:
"""Open the project settings dialog.""" """Open the project settings dialog."""
if not SHARED.hasProject: if SHARED.hasProject:
logger.error("No project open") dialog = GuiProjectSettings(self, focusTab=focusTab)
return False dialog.newProjectSettingsReady.connect(self._processProjectSettingsChanges)
dialog.exec_()
dlgProj = GuiProjectSettings(self, focusTab=focusTab) return
dlgProj.exec_()
if dlgProj.result() == QDialog.Accepted:
logger.debug("Applying new project settings")
SHARED.updateSpellCheckLanguage()
self.itemDetails.refreshDetails()
self._updateWindowTitle(SHARED.project.data.name)
return True
def showProjectDetailsDialog(self) -> bool:
"""Open the project details dialog."""
if not SHARED.hasProject:
logger.error("No project open")
return False
dlgDetails = getGuiItem("GuiProjectDetails")
if dlgDetails is None:
dlgDetails = GuiProjectDetails(self)
assert isinstance(dlgDetails, GuiProjectDetails)
dlgDetails.setModal(True)
dlgDetails.show()
dlgDetails.raise_()
dlgDetails.updateValues()
return True
@pyqtSlot() @pyqtSlot()
def showBuildManuscriptDialog(self) -> bool: def showProjectDetailsDialog(self) -> None:
"""Open the project details dialog."""
if SHARED.hasProject:
dialog = GuiProjectDetails(self)
dialog.setModal(True)
dialog.show()
dialog.raise_()
qApp.processEvents()
dialog.updateValues()
return
@pyqtSlot()
def showBuildManuscriptDialog(self) -> None:
"""Open the build manuscript dialog.""" """Open the build manuscript dialog."""
if not SHARED.hasProject: if SHARED.hasProject:
logger.error("No project open") dialog = GuiManuscript(self)
return False dialog.setModal(False)
dialog.show()
dialog.raise_()
qApp.processEvents()
dialog.loadContent()
return
dlgBuild = getGuiItem("GuiManuscript") @pyqtSlot()
if dlgBuild is None: def showProjectWordListDialog(self) -> None:
dlgBuild = GuiManuscript(self)
assert isinstance(dlgBuild, GuiManuscript)
dlgBuild.setModal(False)
dlgBuild.show()
dlgBuild.raise_()
qApp.processEvents()
dlgBuild.loadContent()
return True
def showLoremIpsumDialog(self) -> bool:
"""Open the insert lorem ipsum text dialog."""
if not SHARED.hasProject:
logger.error("No project open")
return False
dlgLipsum = getGuiItem("GuiLipsum")
if dlgLipsum is None:
dlgLipsum = GuiLipsum(self)
assert isinstance(dlgLipsum, GuiLipsum)
dlgLipsum.setModal(False)
dlgLipsum.show()
dlgLipsum.raise_()
qApp.processEvents()
return True
def showProjectWordListDialog(self) -> bool:
"""Open the project word list dialog.""" """Open the project word list dialog."""
if not SHARED.hasProject: if SHARED.hasProject:
logger.error("No project open") dialog = GuiWordList(self)
return False dialog.newWordListReady.connect(self._processWordListChanges)
dialog.exec_()
return
dlgWords = GuiWordList(self) @pyqtSlot()
dlgWords.exec_() def showWritingStatsDialog(self) -> None:
if dlgWords.result() == QDialog.Accepted:
logger.debug("Reloading word list")
SHARED.updateSpellCheckLanguage(reload=True)
self.docEditor.spellCheckDocument()
return True
def showWritingStatsDialog(self) -> bool:
"""Open the session stats dialog.""" """Open the session stats dialog."""
if not SHARED.hasProject: if SHARED.hasProject:
logger.error("No project open") dialog = GuiWritingStats(self)
return False dialog.setModal(False)
dialog.show()
dialog.raise_()
qApp.processEvents()
dialog.populateGUI()
return
dlgStats = getGuiItem("GuiWritingStats") @pyqtSlot()
if dlgStats is None: def showAboutNWDialog(self, showNotes: bool = False) -> None:
dlgStats = GuiWritingStats(self) """Show the novelWriter about dialog."""
assert isinstance(dlgStats, GuiWritingStats) dialog = GuiAbout(self)
dialog.setModal(True)
dlgStats.setModal(False) dialog.show()
dlgStats.show() dialog.raise_()
dlgStats.raise_()
qApp.processEvents() qApp.processEvents()
dlgStats.populateGUI() dialog.populateGUI()
return True
def showAboutNWDialog(self, showNotes: bool = False) -> bool:
"""Show the about dialog for novelWriter."""
dlgAbout = getGuiItem("GuiAbout")
if dlgAbout is None:
dlgAbout = GuiAbout(self)
assert isinstance(dlgAbout, GuiAbout)
dlgAbout.setModal(True)
dlgAbout.show()
dlgAbout.raise_()
qApp.processEvents()
dlgAbout.populateGUI()
if showNotes: if showNotes:
dlgAbout.showReleaseNotes() dialog.showReleaseNotes()
return
return True
@pyqtSlot()
def showAboutQtDialog(self) -> None: def showAboutQtDialog(self) -> None:
"""Show the about dialog for Qt.""" """Show the Qt about dialog."""
msgBox = QMessageBox(self) msgBox = QMessageBox(self)
msgBox.aboutQt(self, "About Qt") msgBox.aboutQt(self, "About Qt")
return return
@pyqtSlot()
def showUpdatesDialog(self) -> None: def showUpdatesDialog(self) -> None:
"""Show the check for updates dialog.""" """Show the check for updates dialog."""
dlgUpdate = getGuiItem("GuiUpdates") dialog = GuiUpdates(self)
if dlgUpdate is None: dialog.setModal(True)
dlgUpdate = GuiUpdates(self) dialog.show()
assert isinstance(dlgUpdate, GuiUpdates) dialog.raise_()
dlgUpdate.setModal(True)
dlgUpdate.show()
dlgUpdate.raise_()
qApp.processEvents() qApp.processEvents()
dlgUpdate.checkLatest() dialog.checkLatest()
return return
@pyqtSlot() @pyqtSlot()
def showDictionariesDialog(self) -> None: def showDictionariesDialog(self) -> None:
"""Show the download dictionaries dialog.""" """Show the download dictionaries dialog."""
dlgDicts = GuiDictionaries(self) dialog = GuiDictionaries(self)
dlgDicts.setModal(True) dialog.setModal(True)
dlgDicts.show() dialog.show()
dlgDicts.raise_() dialog.raise_()
qApp.processEvents() qApp.processEvents()
if not dlgDicts.initDialog(): if not dialog.initDialog():
dlgDicts.close() dialog.close()
SHARED.error(self.tr("Could not initialise the dialog.")) SHARED.error(self.tr("Could not initialise the dialog."))
return return
def reportConfErr(self) -> bool: def reportConfErr(self) -> bool:
@@ -1238,6 +1135,65 @@ class GuiMain(QMainWindow):
# Private Slots # Private Slots
## ##
@pyqtSlot(bool, bool, bool, bool)
def _processConfigChanges(self, restart: bool, tree: bool, theme: bool, syntax: bool) -> None:
"""Refresh GUI based on flags from the Preferences dialog."""
logger.debug("Applying new preferences")
self.initMain()
self.saveDocument()
if restart:
SHARED.info(self.tr(
"Some changes will not be applied until novelWriter has been restarted."
))
if tree:
self.projView.populateTree()
if theme:
# We are doing this manually instead of connecting to
# qApp.paletteChanged since the processing order matters
SHARED.theme.loadTheme()
self.docEditor.updateTheme()
self.docViewer.updateTheme()
self.docViewerPanel.updateTheme()
self.sideBar.updateTheme()
self.projView.updateTheme()
self.novelView.updateTheme()
self.outlineView.updateTheme()
self.itemDetails.updateTheme()
self.mainStatus.updateTheme()
if syntax:
SHARED.theme.loadSyntax()
self.docEditor.updateSyntaxColours()
self.docEditor.initEditor()
self.docViewer.initViewer()
self.projView.initSettings()
self.novelView.initSettings()
self.outlineView.initSettings()
self._updateStatusWordCount()
return
@pyqtSlot()
def _processProjectSettingsChanges(self) -> None:
"""Refresh data dependent on project settings."""
logger.debug("Applying new project settings")
SHARED.updateSpellCheckLanguage()
self.itemDetails.refreshDetails()
self._updateWindowTitle(SHARED.project.data.name)
return
@pyqtSlot()
def _processWordListChanges(self) -> None:
"""Reload project word list."""
logger.debug("Reloading word list")
SHARED.updateSpellCheckLanguage(reload=True)
self.docEditor.spellCheckDocument()
return
@pyqtSlot(str, nwDocMode) @pyqtSlot(str, nwDocMode)
def _followTag(self, tag: str, mode: nwDocMode) -> None: def _followTag(self, tag: str, mode: nwDocMode) -> None:
"""Follow a tag after user interaction with a link.""" """Follow a tag after user interaction with a link."""
@@ -1321,6 +1277,8 @@ class GuiMain(QMainWindow):
self.mainStatus.setUserIdle(editIdle or userIdle) self.mainStatus.setUserIdle(editIdle or userIdle)
SHARED.updateIdleTime(currTime, editIdle or userIdle) SHARED.updateIdleTime(currTime, editIdle or userIdle)
self.mainStatus.updateTime(idleTime=SHARED.projectIdleTime) self.mainStatus.updateTime(idleTime=SHARED.projectIdleTime)
if CONFIG.memInfo and int(currTime) % 5 == 0: # pragma: no cover
self.mainStatus.memInfo()
return return
@pyqtSlot() @pyqtSlot()
+32 -26
View File
@@ -26,10 +26,10 @@ from __future__ import annotations
import random import random
import logging import logging
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QDialog, QGridLayout, QHBoxLayout, QVBoxLayout, QLabel, QDialogButtonBox, QDialog, QDialogButtonBox, QGridLayout, QHBoxLayout, QLabel, QSpinBox,
QSpinBox QVBoxLayout, QWidget
) )
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
@@ -41,15 +41,15 @@ logger = logging.getLogger(__name__)
class GuiLipsum(QDialog): class GuiLipsum(QDialog):
def __init__(self, mainGui): def __init__(self, parent: QWidget) -> None:
super().__init__(parent=mainGui) super().__init__(parent=parent)
logger.debug("Create: GuiLipsum") logger.debug("Create: GuiLipsum")
self.setObjectName("GuiLipsum") self.setObjectName("GuiLipsum")
if CONFIG.osDarwin: if CONFIG.osDarwin:
self.setWindowFlag(Qt.WindowType.Tool) self.setWindowFlag(Qt.WindowType.Tool)
self.mainGui = mainGui self._lipsumText = ""
self.setWindowTitle(self.tr("Insert Placeholder Text")) self.setWindowTitle(self.tr("Insert Placeholder Text"))
@@ -92,14 +92,16 @@ class GuiLipsum(QDialog):
# Buttons # Buttons
self.buttonBox = QDialogButtonBox() self.buttonBox = QDialogButtonBox()
self.buttonBox.rejected.connect(self._doClose) self.buttonBox.rejected.connect(self.close)
self.btnClose = self.buttonBox.addButton(QDialogButtonBox.Close) self.btnClose = self.buttonBox.addButton(QDialogButtonBox.Close)
self.btnClose.setAutoDefault(False) self.btnClose.setAutoDefault(False)
self.btnSave = self.buttonBox.addButton(self.tr("Insert"), QDialogButtonBox.ActionRole) self.btnInsert = self.buttonBox.addButton(self.tr("Insert"), QDialogButtonBox.ActionRole)
self.btnSave.clicked.connect(self._doInsert) self.btnInsert.clicked.connect(self._doInsert)
self.btnSave.setAutoDefault(False) self.btnInsert.setAutoDefault(False)
self.rejected.connect(self.close)
# Assemble # Assemble
self.outerBox = QVBoxLayout() self.outerBox = QVBoxLayout()
@@ -112,33 +114,37 @@ class GuiLipsum(QDialog):
return return
def __del__(self): # pragma: no cover def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: GuiLipsum") logger.debug("Delete: GuiLipsum")
return return
@property
def lipsumText(self) -> str:
"""Return the generated text."""
return self._lipsumText
@classmethod
def getLipsum(cls, parent: QWidget) -> str:
"""Pop the dialog and return the lipsum text."""
cls = GuiLipsum(parent)
cls.exec_()
text = cls.lipsumText
cls.deleteLater()
return text
## ##
# Slots # Private Slots
## ##
def _doInsert(self): @pyqtSlot()
"""Load the text and insert it in the open document. def _doInsert(self) -> None:
""" """Generate the text."""
lipsumFile = CONFIG.assetPath("text") / "lipsum.txt" lipsumFile = CONFIG.assetPath("text") / "lipsum.txt"
lipsumText = readTextFile(lipsumFile).splitlines() lipsumText = readTextFile(lipsumFile).splitlines()
if self.randSwitch.isChecked(): if self.randSwitch.isChecked():
random.shuffle(lipsumText) random.shuffle(lipsumText)
pCount = self.paraCount.value() pCount = self.paraCount.value()
inText = "\n\n".join(lipsumText[0:pCount]) + "\n\n" self._lipsumText = "\n\n".join(lipsumText[0:pCount]) + "\n\n"
self.mainGui.docEditor.insertText(inText)
return
def _doClose(self):
"""Close the dialog window without doing anything.
"""
self.close() self.close()
return return
+1 -2
View File
@@ -344,8 +344,6 @@ class GuiManuscriptBuild(QDialog):
def _saveSettings(self): def _saveSettings(self):
"""Save the user GUI settings.""" """Save the user GUI settings."""
logger.debug("Saving GuiManuscriptBuild settings")
winWidth = CONFIG.rpxInt(self.width()) winWidth = CONFIG.rpxInt(self.width())
winHeight = CONFIG.rpxInt(self.height()) winHeight = CONFIG.rpxInt(self.height())
@@ -353,6 +351,7 @@ class GuiManuscriptBuild(QDialog):
fmtWidth = CONFIG.rpxInt(mainSplit[0]) fmtWidth = CONFIG.rpxInt(mainSplit[0])
sumWidth = CONFIG.rpxInt(mainSplit[1]) sumWidth = CONFIG.rpxInt(mainSplit[1])
logger.debug("Saving State: GuiManuscriptBuild")
pOptions = SHARED.project.options pOptions = SHARED.project.options
pOptions.setValue("GuiManuscriptBuild", "winWidth", winWidth) pOptions.setValue("GuiManuscriptBuild", "winWidth", winWidth)
pOptions.setValue("GuiManuscriptBuild", "winHeight", winHeight) pOptions.setValue("GuiManuscriptBuild", "winHeight", winHeight)
+1 -2
View File
@@ -417,8 +417,6 @@ class GuiManuscript(QDialog):
def _saveSettings(self): def _saveSettings(self):
"""Save the user GUI settings.""" """Save the user GUI settings."""
logger.debug("Saving GuiManuscript settings")
buildOrder = [] buildOrder = []
for i in range(self.buildList.count()): for i in range(self.buildList.count()):
if item := self.buildList.item(i): if item := self.buildList.item(i):
@@ -442,6 +440,7 @@ class GuiManuscript(QDialog):
detailsWidth = CONFIG.rpxInt(self.buildDetails.getColumnWidth()) detailsWidth = CONFIG.rpxInt(self.buildDetails.getColumnWidth())
detailsExpanded = self.buildDetails.getExpandedState() detailsExpanded = self.buildDetails.getExpandedState()
logger.debug("Saving State: GuiManuscript")
pOptions = SHARED.project.options pOptions = SHARED.project.options
pOptions.setValue("GuiManuscript", "winWidth", winWidth) pOptions.setValue("GuiManuscript", "winWidth", winWidth)
pOptions.setValue("GuiManuscript", "winHeight", winHeight) pOptions.setValue("GuiManuscript", "winHeight", winHeight)
+1 -3
View File
@@ -253,13 +253,11 @@ class GuiBuildSettings(QDialog):
def _saveSettings(self) -> None: def _saveSettings(self) -> None:
"""Save the various user settings.""" """Save the various user settings."""
logger.debug("Saving GuiBuildSettings settings")
winWidth = CONFIG.rpxInt(self.width()) winWidth = CONFIG.rpxInt(self.width())
winHeight = CONFIG.rpxInt(self.height()) winHeight = CONFIG.rpxInt(self.height())
treeWidth, filterWidth = self.optTabSelect.mainSplitSizes() treeWidth, filterWidth = self.optTabSelect.mainSplitSizes()
logger.debug("Saving State: GuiBuildSettings")
pOptions = SHARED.project.options pOptions = SHARED.project.options
pOptions.setValue("GuiBuildSettings", "winWidth", winWidth) pOptions.setValue("GuiBuildSettings", "winWidth", winWidth)
pOptions.setValue("GuiBuildSettings", "winHeight", winHeight) pOptions.setValue("GuiBuildSettings", "winHeight", winHeight)
+15 -2
View File
@@ -29,7 +29,7 @@ import logging
from datetime import datetime from datetime import datetime
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtGui import QPixmap, QCursor from PyQt5.QtGui import QCloseEvent, QPixmap, QCursor
from PyQt5.QtCore import Qt, pyqtSlot from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
qApp, QDialog, QTreeWidget, QTreeWidgetItem, QDialogButtonBox, QGridLayout, qApp, QDialog, QTreeWidget, QTreeWidgetItem, QDialogButtonBox, QGridLayout,
@@ -307,9 +307,20 @@ class GuiWritingStats(QDialog):
return return
## ##
# Slots # Events
## ##
def closeEvent(self, event: QCloseEvent) -> None:
"""Capture the user closing the window."""
event.accept()
self.deleteLater()
return
##
# Private Slots
##
@pyqtSlot()
def _doClose(self) -> None: def _doClose(self) -> None:
"""Save the state of the window, clear cache, end close.""" """Save the state of the window, clear cache, end close."""
self.logData = [] self.logData = []
@@ -330,6 +341,7 @@ class GuiWritingStats(QDialog):
showIdleTime = self.showIdleTime.isChecked() showIdleTime = self.showIdleTime.isChecked()
histMax = self.histMax.value() histMax = self.histMax.value()
logger.debug("Saving State: GuiWritingStats")
pOptions = SHARED.project.options pOptions = SHARED.project.options
pOptions.setValue("GuiWritingStats", "winWidth", winWidth) pOptions.setValue("GuiWritingStats", "winWidth", winWidth)
pOptions.setValue("GuiWritingStats", "winHeight", winHeight) pOptions.setValue("GuiWritingStats", "winHeight", winHeight)
@@ -347,6 +359,7 @@ class GuiWritingStats(QDialog):
pOptions.setValue("GuiWritingStats", "showIdleTime", showIdleTime) pOptions.setValue("GuiWritingStats", "showIdleTime", showIdleTime)
pOptions.setValue("GuiWritingStats", "histMax", histMax) pOptions.setValue("GuiWritingStats", "histMax", histMax)
pOptions.saveSettings() pOptions.saveSettings()
self.close() self.close()
return return
+1 -1
View File
@@ -83,7 +83,7 @@ def testBaseInit_Options(monkeypatch, fncPath):
"""Test command line options for logging level.""" """Test command line options for logging level."""
monkeypatch.setattr("novelwriter.guimain.GuiMain", MockGuiMain) monkeypatch.setattr("novelwriter.guimain.GuiMain", MockGuiMain)
monkeypatch.setattr(sys, "argv", [ monkeypatch.setattr(sys, "argv", [
"novelWriter.py", "--testmode", f"--config={fncPath}", f"--data={fncPath}" "novelWriter.py", "--testmode", "--meminfo", f"--config={fncPath}", f"--data={fncPath}"
]) ])
# Defaults w/None Args # Defaults w/None Args
+2 -9
View File
@@ -35,7 +35,7 @@ from novelwriter.dialogs.about import GuiAbout
def testDlgAbout_NWDialog(qtbot, monkeypatch, nwGUI): def testDlgAbout_NWDialog(qtbot, monkeypatch, nwGUI):
"""Test the novelWriter about dialogs.""" """Test the novelWriter about dialogs."""
# NW About # NW About
assert nwGUI.showAboutNWDialog(showNotes=True) is True nwGUI.showAboutNWDialog(showNotes=True)
qtbot.waitUntil(lambda: getGuiItem("GuiAbout") is not None, timeout=1000) qtbot.waitUntil(lambda: getGuiItem("GuiAbout") is not None, timeout=1000)
msgAbout = getGuiItem("GuiAbout") msgAbout = getGuiItem("GuiAbout")
@@ -56,14 +56,7 @@ def testDlgAbout_NWDialog(qtbot, monkeypatch, nwGUI):
msgAbout.showReleaseNotes() msgAbout.showReleaseNotes()
assert msgAbout.tabBox.currentWidget() == msgAbout.pageNotes assert msgAbout.tabBox.currentWidget() == msgAbout.pageNotes
msgAbout._doClose() msgAbout.close()
# Open Again from Menu
nwGUI.mainMenu.aAboutNW.activate(QAction.Trigger)
qtbot.waitUntil(lambda: getGuiItem("GuiAbout") is not None, timeout=1000)
msgAbout = getGuiItem("GuiAbout")
assert msgAbout is not None
msgAbout._doClose()
# END Test testDlgAbout_NWDialog # END Test testDlgAbout_NWDialog
+4 -5
View File
@@ -45,12 +45,11 @@ def testDlgOther_QuoteSelect(qtbot, nwGUI):
lastItem = anItem.text()[2] lastItem = anItem.text()[2]
assert nwQuot.previewLabel.text() == lastItem assert nwQuot.previewLabel.text() == lastItem
nwQuot._doAccept() nwQuot.accept()
assert nwQuot.result() == QDialog.Accepted assert nwQuot.result() == QDialog.Accepted
assert nwQuot.selectedQuote == lastItem assert nwQuot.selectedQuote == lastItem
# qtbot.stop() # qtbot.stop()
nwQuot._doReject()
nwQuot.close() nwQuot.close()
# END Test testDlgOther_QuoteSelect # END Test testDlgOther_QuoteSelect
@@ -89,7 +88,7 @@ def testDlgOther_Updates(qtbot, monkeypatch, nwGUI):
nwGUI.mainMenu.aUpdates.activate(QAction.Trigger) nwGUI.mainMenu.aUpdates.activate(QAction.Trigger)
# qtbot.stop() # qtbot.stop()
nwUpdate._doClose() nwUpdate.close()
# END Test testDlgOther_Updates # END Test testDlgOther_Updates
@@ -101,13 +100,13 @@ def testDlgOther_EditLabel(qtbot, monkeypatch):
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(GuiEditLabel, "result", lambda *a: QDialog.Accepted) mp.setattr(GuiEditLabel, "result", lambda *a: QDialog.Accepted)
newLabel, dlgOk = GuiEditLabel.getLabel(None, text="Hello World") newLabel, dlgOk = GuiEditLabel.getLabel(None, text="Hello World") # type: ignore
assert dlgOk is True assert dlgOk is True
assert newLabel == "Hello World" assert newLabel == "Hello World"
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(GuiEditLabel, "result", lambda *a: QDialog.Rejected) mp.setattr(GuiEditLabel, "result", lambda *a: QDialog.Rejected)
newLabel, dlgOk = GuiEditLabel.getLabel(None, text="Hello World") newLabel, dlgOk = GuiEditLabel.getLabel(None, text="Hello World") # type: ignore
assert dlgOk is False assert dlgOk is False
assert newLabel == "Hello World" assert newLabel == "Hello World"
+2 -16
View File
@@ -45,23 +45,12 @@ def testDlgPreferences_Main(qtbot, monkeypatch, nwGUI, tstPaths):
monkeypatch.setattr(GuiPreferences, "result", lambda *a: QDialog.Accepted) monkeypatch.setattr(GuiPreferences, "result", lambda *a: QDialog.Accepted)
monkeypatch.setattr(SHARED._spelling, "listDictionaries", lambda: [("en", "English [en]")]) monkeypatch.setattr(SHARED._spelling, "listDictionaries", lambda: [("en", "English [en]")])
with monkeypatch.context() as mp: nwGUI.mainMenu.aPreferences.activate(QAction.Trigger)
mp.setattr(GuiPreferences, "updateTheme", lambda *a: True) qtbot.waitUntil(lambda: getGuiItem("GuiPreferences") is not None, timeout=1000)
mp.setattr(GuiPreferences, "updateSyntax", lambda *a: True)
mp.setattr(GuiPreferences, "needsRestart", lambda *a: True)
mp.setattr(GuiPreferences, "refreshTree", lambda *a: True)
nwGUI.mainMenu.aPreferences.activate(QAction.Trigger)
qtbot.waitUntil(lambda: getGuiItem("GuiPreferences") is not None, timeout=1000)
nwPrefs = getGuiItem("GuiPreferences") nwPrefs = getGuiItem("GuiPreferences")
assert isinstance(nwPrefs, GuiPreferences) assert isinstance(nwPrefs, GuiPreferences)
nwPrefs.show() nwPrefs.show()
assert nwPrefs.updateTheme is False
assert nwPrefs.updateSyntax is False
assert nwPrefs.needsRestart is False
assert nwPrefs.refreshTree is False
# General Settings # General Settings
qtbot.wait(KEY_DELAY) qtbot.wait(KEY_DELAY)
tabGeneral = nwPrefs.tabGeneral tabGeneral = nwPrefs.tabGeneral
@@ -100,8 +89,6 @@ def testDlgPreferences_Main(qtbot, monkeypatch, nwGUI, tstPaths):
qtbot.mouseClick(tabProjects.backupOnClose, Qt.LeftButton) qtbot.mouseClick(tabProjects.backupOnClose, Qt.LeftButton)
assert tabProjects.backupOnClose.isChecked() assert tabProjects.backupOnClose.isChecked()
# qtbot.stop()
# Check Browse button # Check Browse button
monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *a, **k: "") monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *a, **k: "")
assert not tabProjects._backupFolder() assert not tabProjects._backupFolder()
@@ -204,7 +191,6 @@ def testDlgPreferences_Main(qtbot, monkeypatch, nwGUI, tstPaths):
# Save and Check Config # Save and Check Config
qtbot.mouseClick(nwPrefs.buttonBox.button(QDialogButtonBox.Ok), Qt.LeftButton) qtbot.mouseClick(nwPrefs.buttonBox.button(QDialogButtonBox.Ok), Qt.LeftButton)
nwPrefs._doClose()
assert CONFIG.saveConfig() assert CONFIG.saveConfig()
projFile = tstPaths.cnfDir / "novelwriter.conf" projFile = tstPaths.cnfDir / "novelwriter.conf"
+11 -11
View File
@@ -66,12 +66,12 @@ def testDlgProjDetails_Dialog(qtbot, nwGUI, prjLipsum):
tocTab = projDet.tabContents tocTab = projDet.tabContents
tocTree = tocTab.tocTree tocTree = tocTab.tocTree
assert tocTree.topLevelItemCount() == 7 assert tocTree.topLevelItemCount() == 7
assert tocTree.topLevelItem(0).text(tocTab.C_TITLE) == "Lorem Ipsum" assert tocTree.topLevelItem(0).text(tocTab.C_TITLE) == "Lorem Ipsum" # type: ignore
assert tocTree.topLevelItem(2).text(tocTab.C_TITLE) == "Prologue" assert tocTree.topLevelItem(2).text(tocTab.C_TITLE) == "Prologue" # type: ignore
assert tocTree.topLevelItem(3).text(tocTab.C_TITLE) == "Act One" assert tocTree.topLevelItem(3).text(tocTab.C_TITLE) == "Act One" # type: ignore
assert tocTree.topLevelItem(4).text(tocTab.C_TITLE) == "Chapter One" assert tocTree.topLevelItem(4).text(tocTab.C_TITLE) == "Chapter One" # type: ignore
assert tocTree.topLevelItem(5).text(tocTab.C_TITLE) == "Chapter Two" assert tocTree.topLevelItem(5).text(tocTab.C_TITLE) == "Chapter Two" # type: ignore
assert tocTree.topLevelItem(6).text(tocTab.C_TITLE) == "END" assert tocTree.topLevelItem(6).text(tocTab.C_TITLE) == "END" # type: ignore
# Count Pages # Count Pages
tocTab.wpValue.setValue(100) tocTab.wpValue.setValue(100)
@@ -82,8 +82,8 @@ def testDlgProjDetails_Dialog(qtbot, nwGUI, prjLipsum):
thePages = ["1", "2", "1", "1", "11", "17", "0"] thePages = ["1", "2", "1", "1", "11", "17", "0"]
thePage = ["i", "ii", "1", "2", "3", "14", "31"] thePage = ["i", "ii", "1", "2", "3", "14", "31"]
for i in range(7): for i in range(7):
assert tocTree.topLevelItem(i).text(tocTab.C_PAGES) == thePages[i] assert tocTree.topLevelItem(i).text(tocTab.C_PAGES) == thePages[i] # type: ignore
assert tocTree.topLevelItem(i).text(tocTab.C_PAGE) == thePage[i] assert tocTree.topLevelItem(i).text(tocTab.C_PAGE) == thePage[i] # type: ignore
tocTab.poValue.setValue(5) tocTab.poValue.setValue(5)
tocTab.dblValue.setChecked(True) tocTab.dblValue.setChecked(True)
@@ -92,8 +92,8 @@ def testDlgProjDetails_Dialog(qtbot, nwGUI, prjLipsum):
thePages = ["2", "2", "2", "2", "12", "18", "0"] thePages = ["2", "2", "2", "2", "12", "18", "0"]
thePage = ["i", "iii", "1", "3", "5", "17", "35"] thePage = ["i", "iii", "1", "3", "5", "17", "35"]
for i in range(7): for i in range(7):
assert tocTree.topLevelItem(i).text(tocTab.C_PAGES) == thePages[i] assert tocTree.topLevelItem(i).text(tocTab.C_PAGES) == thePages[i] # type: ignore
assert tocTree.topLevelItem(i).text(tocTab.C_PAGE) == thePage[i] assert tocTree.topLevelItem(i).text(tocTab.C_PAGE) == thePage[i] # type: ignore
# Re-populate # Re-populate
assert tocTab._currentRoot is None assert tocTab._currentRoot is None
@@ -103,7 +103,7 @@ def testDlgProjDetails_Dialog(qtbot, nwGUI, prjLipsum):
# qtbot.stop() # qtbot.stop()
# Clean Up # Clean Up
projDet._doClose() projDet.close()
nwGUI.closeMain() nwGUI.closeMain()
# END Test testDlgProjDetails_Dialog # END Test testDlgProjDetails_Dialog
+23 -26
View File
@@ -57,26 +57,26 @@ def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI):
nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger) nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
qtbot.waitUntil(lambda: getGuiItem("GuiProjectSettings") is not None, timeout=1000) qtbot.waitUntil(lambda: getGuiItem("GuiProjectSettings") is not None, timeout=1000)
projEdit = getGuiItem("GuiProjectSettings") projSettings = getGuiItem("GuiProjectSettings")
assert isinstance(projEdit, GuiProjectSettings) assert isinstance(projSettings, GuiProjectSettings)
projEdit.show() projSettings.show()
qtbot.addWidget(projEdit) qtbot.addWidget(projSettings)
# Switch Tabs # Switch Tabs
projEdit._focusTab(GuiProjectSettings.TAB_REPLACE) projSettings._focusTab(GuiProjectSettings.TAB_REPLACE)
assert projEdit._tabBox.currentWidget() == projEdit.tabReplace assert projSettings._tabBox.currentWidget() == projSettings.tabReplace
projEdit._focusTab(GuiProjectSettings.TAB_IMPORT) projSettings._focusTab(GuiProjectSettings.TAB_IMPORT)
assert projEdit._tabBox.currentWidget() == projEdit.tabImport assert projSettings._tabBox.currentWidget() == projSettings.tabImport
projEdit._focusTab(GuiProjectSettings.TAB_STATUS) projSettings._focusTab(GuiProjectSettings.TAB_STATUS)
assert projEdit._tabBox.currentWidget() == projEdit.tabStatus assert projSettings._tabBox.currentWidget() == projSettings.tabStatus
projEdit._focusTab(GuiProjectSettings.TAB_MAIN) projSettings._focusTab(GuiProjectSettings.TAB_MAIN)
assert projEdit._tabBox.currentWidget() == projEdit.tabMain assert projSettings._tabBox.currentWidget() == projSettings.tabMain
# Clean Up # Clean Up
projEdit._doClose() projSettings.close()
# qtbot.stop() # qtbot.stop()
# END Test testDlgProjSettings_Dialog # END Test testDlgProjSettings_Dialog
@@ -93,10 +93,10 @@ def testDlgProjSettings_Main(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockR
CONFIG.setBackupPath(fncPath) CONFIG.setBackupPath(fncPath)
# Set some values # Set some values
theProject = SHARED.project project = SHARED.project
theProject.data.setSpellLang("en") project.data.setSpellLang("en")
theProject.data.setAuthor("Jane Smith") project.data.setAuthor("Jane Smith")
theProject.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.TAB_MAIN)
@@ -130,12 +130,13 @@ def testDlgProjSettings_Main(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockR
assert tabMain.editAuthor.text() == "Jane Doe" assert tabMain.editAuthor.text() == "Jane Doe"
projSettings._doSave() projSettings._doSave()
assert theProject.data.name == "Project Name" assert project.data.name == "Project Name"
assert theProject.data.title == "Project Title" assert project.data.title == "Project Title"
assert theProject.data.author == "Jane Doe" assert project.data.author == "Jane Doe"
nwGUI._processProjectSettingsChanges()
assert nwGUI.windowTitle() == "novelWriter - Project Name"
# Clean up
projSettings._doClose()
# qtbot.stop() # qtbot.stop()
# END Test testDlgProjSettings_Main # END Test testDlgProjSettings_Main
@@ -334,9 +335,7 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncPath, projPat
assert importItems[C.iMain]["name"] == "Main" assert importItems[C.iMain]["name"] == "Main"
assert importItems["i000014"]["name"] == "Final" assert importItems["i000014"]["name"] == "Final"
# Clean up
# qtbot.stop() # qtbot.stop()
projSettings._doClose()
# END Test testDlgProjSettings_StatusImport # END Test testDlgProjSettings_StatusImport
@@ -422,8 +421,6 @@ def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, fncPath, projPath, mo
"A": "B", "C": "D", "This": "With This Stuff" "A": "B", "C": "D", "This": "With This Stuff"
} }
# Clean up
# qtbot.stop() # qtbot.stop()
projSettings._doClose()
# END Test testDlgProjSettings_Replace # END Test testDlgProjSettings_Replace
+14 -15
View File
@@ -68,11 +68,11 @@ def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, projPath):
wList._loadWordList() wList._loadWordList()
# Check that the content was loaded # Check that the content was loaded
assert wList.listBox.item(0).text() == "word_a" assert wList.listBox.item(0).text() == "word_a" # type: ignore
assert wList.listBox.item(1).text() == "word_b" assert wList.listBox.item(1).text() == "word_b" # type: ignore
assert wList.listBox.item(2).text() == "word_c" assert wList.listBox.item(2).text() == "word_c" # type: ignore
assert wList.listBox.item(3).text() == "word_f" assert wList.listBox.item(3).text() == "word_f" # type: ignore
assert wList.listBox.item(4).text() == "word_g" assert wList.listBox.item(4).text() == "word_g" # type: ignore
assert wList.listBox.count() == 5 assert wList.listBox.count() == 5
# Add a blank word, which is ignored # Add a blank word, which is ignored
@@ -91,27 +91,27 @@ def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, projPath):
assert wList.listBox.count() == 6 assert wList.listBox.count() == 6
# Check that the content now # Check that the content now
assert wList.listBox.item(0).text() == "word_a" assert wList.listBox.item(0).text() == "word_a" # type: ignore
assert wList.listBox.item(1).text() == "word_b" assert wList.listBox.item(1).text() == "word_b" # type: ignore
assert wList.listBox.item(2).text() == "word_c" assert wList.listBox.item(2).text() == "word_c" # type: ignore
assert wList.listBox.item(3).text() == "word_d" assert wList.listBox.item(3).text() == "word_d" # type: ignore
assert wList.listBox.item(4).text() == "word_f" assert wList.listBox.item(4).text() == "word_f" # type: ignore
assert wList.listBox.item(5).text() == "word_g" assert wList.listBox.item(5).text() == "word_g" # type: ignore
# Delete a word # Delete a word
wList.newEntry.setText("delete_me") wList.newEntry.setText("delete_me")
wList._doAdd() wList._doAdd()
assert wList.listBox.item(0).text() == "delete_me" assert wList.listBox.item(0).text() == "delete_me" # type: ignore
delItem = wList.listBox.findItems("delete_me", Qt.MatchExactly)[0] delItem = wList.listBox.findItems("delete_me", Qt.MatchExactly)[0]
assert delItem.text() == "delete_me" assert delItem.text() == "delete_me"
delItem.setSelected(True) delItem.setSelected(True)
wList._doDelete() wList._doDelete()
assert wList.listBox.findItems("delete_me", Qt.MatchExactly) == [] assert wList.listBox.findItems("delete_me", Qt.MatchExactly) == []
assert wList.listBox.item(0).text() == "word_a" assert wList.listBox.item(0).text() == "word_a" # type: ignore
# Save files # Save files
assert wList._doSave() wList._doSave()
userDict.load() userDict.load()
assert len(list(userDict)) == 6 assert len(list(userDict)) == 6
assert "word_a" in userDict assert "word_a" in userDict
@@ -122,6 +122,5 @@ def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, projPath):
assert "word_g" in userDict assert "word_g" in userDict
# qtbot.stop() # qtbot.stop()
wList._doClose()
# END Test testDlgWordList_Dialog # END Test testDlgWordList_Dialog
+23 -5
View File
@@ -29,6 +29,7 @@ from tools import (
C, NWD_IGNORE, cmpFiles, buildTestProject, XML_IGNORE, getGuiItem, writeFile C, NWD_IGNORE, cmpFiles, buildTestProject, XML_IGNORE, getGuiItem, writeFile
) )
from PyQt5.QtGui import QColor, QPalette
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QDialog, QMenu, QMessageBox, QInputDialog from PyQt5.QtWidgets import QDialog, QMenu, QMessageBox, QInputDialog
@@ -62,11 +63,6 @@ def testGuiMain_ProjectBlocker(nwGUI):
assert nwGUI.openSelectedItem() is False assert nwGUI.openSelectedItem() is False
assert nwGUI.editItemLabel() is False assert nwGUI.editItemLabel() is False
assert nwGUI.rebuildIndex() is False assert nwGUI.rebuildIndex() is False
assert nwGUI.showProjectSettingsDialog() is False
assert nwGUI.showProjectDetailsDialog() is False
assert nwGUI.showBuildManuscriptDialog() is False
assert nwGUI.showProjectWordListDialog() is False
assert nwGUI.showWritingStatsDialog() is False
# END Test testGuiMain_ProjectBlocker # END Test testGuiMain_ProjectBlocker
@@ -203,6 +199,28 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
# END Test testGuiMain_ProjectTreeItems # END Test testGuiMain_ProjectTreeItems
@pytest.mark.gui
def testGuiMain_UpdateTheme(qtbot, nwGUI):
"""Test updating the theme in the GUI."""
mainTheme = SHARED.theme
CONFIG.guiTheme = "default_dark"
CONFIG.guiSyntax = "default_dark"
mainTheme.loadTheme()
mainTheme.loadSyntax()
nwGUI._processConfigChanges(True, True, True, True)
syntaxBack = QColor(*SHARED.theme.colBack)
assert nwGUI.docEditor.palette().color(QPalette.ColorRole.Window) == syntaxBack
assert nwGUI.docEditor.docHeader.palette().color(QPalette.ColorRole.Window) == syntaxBack
assert nwGUI.docViewer.palette().color(QPalette.ColorRole.Window) == syntaxBack
assert nwGUI.docViewer.docHeader.palette().color(QPalette.ColorRole.Window) == syntaxBack
# qtbot.stop()
# END Test testGuiMain_UpdateTheme
@pytest.mark.gui @pytest.mark.gui
def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd): def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
"""Test the document editor.""" """Test the document editor."""
+2 -2
View File
@@ -23,7 +23,6 @@ from __future__ import annotations
import pytest import pytest
from pathlib import Path from pathlib import Path
from configparser import ConfigParser
from mocked import causeOSError from mocked import causeOSError
from tools import writeFile from tools import writeFile
@@ -33,6 +32,7 @@ from PyQt5.QtWidgets import QApplication
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
from novelwriter.common import NWConfigParser
from novelwriter.constants import nwLabels from novelwriter.constants import nwLabels
@@ -87,7 +87,7 @@ def testGuiTheme_Main(qtbot, nwGUI, tstPaths):
# Parse Colours # Parse Colours
# ============= # =============
parser = ConfigParser() parser = NWConfigParser()
parser["Palette"] = { parser["Palette"] = {
"colour1": "100, 150, 200", "colour1": "100, 150, 200",
"colour2": "100, 150, 200, 250", "colour2": "100, 150, 200, 250",
+18 -22
View File
@@ -30,43 +30,39 @@ from novelwriter.tools.lipsum import GuiLipsum
@pytest.mark.gui @pytest.mark.gui
def testToolLipsum_Main(qtbot, nwGUI, projPath, mockRnd): def testToolLipsum_Main(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
"""Test the Lorem Ipsum tool. """Test the Lorem Ipsum tool."""
"""
# Check that we cannot open when there is no project # Check that we cannot open when there is no project
nwGUI.mainMenu.aLipsumText.activate(QAction.Trigger) nwGUI.mainMenu.aLipsumText.activate(QAction.Trigger)
assert getGuiItem("GuiLipsum") is None assert getGuiItem("GuiLipsum") is None
# Create a new project
buildTestProject(nwGUI, projPath) buildTestProject(nwGUI, projPath)
assert nwGUI.openDocument(C.hSceneDoc) is True nwLipsum = GuiLipsum(nwGUI)
assert len(nwGUI.docEditor.getText()) == 15
# Open the tool # Generate paragraphs
nwGUI.mainMenu.aLipsumText.activate(QAction.Trigger)
qtbot.waitUntil(lambda: getGuiItem("GuiLipsum") is not None, timeout=1000)
nwLipsum = getGuiItem("GuiLipsum")
assert isinstance(nwLipsum, GuiLipsum)
# Insert paragraphs
nwGUI.docEditor.setCursorPosition(100) # End of document nwGUI.docEditor.setCursorPosition(100) # End of document
nwLipsum.paraCount.setValue(2) nwLipsum.paraCount.setValue(2)
nwLipsum._doInsert() nwLipsum._doInsert()
theText = nwGUI.docEditor.getText() assert "Lorem ipsum" in nwLipsum.lipsumText
assert "Lorem ipsum" in theText
assert len(theText) == 965
# Insert random paragraph # Generate random paragraph
nwGUI.docEditor.setCursorPosition(1000) # End of document nwGUI.docEditor.setCursorPosition(1000) # End of document
nwLipsum.randSwitch.setChecked(True) nwLipsum.randSwitch.setChecked(True)
nwLipsum.paraCount.setValue(1) nwLipsum.paraCount.setValue(1)
nwLipsum._doInsert() nwLipsum._doInsert()
theText = nwGUI.docEditor.getText() assert len(nwLipsum.lipsumText) > 0
assert len(theText) > 965
# Close nwLipsum.setObjectName("")
nwLipsum._doClose() nwLipsum.close()
# Trigger insertion in document
assert nwGUI.openDocument(C.hSceneDoc) is True
nwGUI.docEditor.setCursorLine(3)
with monkeypatch.context() as mp:
mp.setattr(GuiLipsum, "exec_", lambda *a: None)
mp.setattr(GuiLipsum, "lipsumText", "FooBar")
nwGUI.mainMenu.aLipsumText.activate(QAction.Trigger)
assert nwGUI.docEditor.getText() == "### New Scene\n\nFooBar"
# qtbot.stop() # qtbot.stop()
+5 -4
View File
@@ -30,7 +30,7 @@ from tools import C, buildTestProject, getGuiItem
from mocked import causeOSError from mocked import causeOSError
from PyQt5.QtCore import Qt, pyqtSlot from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtWidgets import QDialogButtonBox from PyQt5.QtWidgets import QAction, QDialogButtonBox
from PyQt5.QtPrintSupport import QPrintPreviewDialog from PyQt5.QtPrintSupport import QPrintPreviewDialog
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
@@ -49,9 +49,11 @@ def testManuscript_Init(monkeypatch, qtbot: QtBot, nwGUI: GuiMain, projPath: Pat
SHARED.project.storage.getDocument(C.hChapterDoc).writeDocument("## A Chapter\n\n\t\tHi") SHARED.project.storage.getDocument(C.hChapterDoc).writeDocument("## A Chapter\n\n\t\tHi")
allText = "New Novel\nBy Jane Doe\nA Chapter\n\t\tHi\n* * *" allText = "New Novel\nBy Jane Doe\nA Chapter\n\t\tHi\n* * *"
manus = GuiManuscript(nwGUI) nwGUI.mainMenu.aBuildManuscript.activate(QAction.Trigger)
qtbot.waitUntil(lambda: getGuiItem("GuiManuscript") is not None, timeout=1000)
manus = getGuiItem("GuiManuscript")
assert isinstance(manus, GuiManuscript)
manus.show() manus.show()
manus.loadContent()
assert manus.docPreview.toPlainText().strip() == "" assert manus.docPreview.toPlainText().strip() == ""
# Run the default build # Run the default build
@@ -79,7 +81,6 @@ def testManuscript_Init(monkeypatch, qtbot: QtBot, nwGUI: GuiMain, projPath: Pat
assert manus.docPreview.toPlainText().strip() == "" assert manus.docPreview.toPlainText().strip() == ""
manus.close() manus.close()
# Finish
# qtbot.stop() # qtbot.stop()
# END Test testManuscript_Init # END Test testManuscript_Init