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