diff --git a/novelwriter/config.py b/novelwriter/config.py index c60efee1..cafa3e36 100644 --- a/novelwriter/config.py +++ b/novelwriter/config.py @@ -33,12 +33,13 @@ from pathlib import Path from PyQt5.QtGui import QFontDatabase from PyQt5.QtCore import ( - QT_VERSION, QT_VERSION_STR, PYQT_VERSION, PYQT_VERSION_STR, QStandardPaths, - QSysInfo, QLocale, QLibraryInfo, QTranslator + PYQT_VERSION, PYQT_VERSION_STR, QT_VERSION, QT_VERSION_STR, QLibraryInfo, + QLocale, QStandardPaths, QSysInfo, QTranslator ) +from PyQt5.QtWidgets import QApplication -from novelwriter.error import logException, formatException -from novelwriter.common import checkPath, formatTimeStamp, NWConfigParser +from novelwriter.error import formatException, logException +from novelwriter.common import NWConfigParser, checkInt, checkPath, formatTimeStamp from novelwriter.constants import nwFiles, nwUnicode if TYPE_CHECKING: # pragma: no cover @@ -52,7 +53,7 @@ class Config: LANG_NW = 1 LANG_PROJ = 2 - def __init__(self): + def __init__(self) -> None: # Initialisation # ============== @@ -236,11 +237,11 @@ class Config: ## @property - def hasError(self): + def hasError(self) -> bool: return self._hasError @property - def recentProjects(self): + def recentProjects(self) -> RecentProjects: return self._recentObj @property @@ -250,45 +251,45 @@ class Config: return self._themeObj @property - def mainWinSize(self): + def mainWinSize(self) -> list[int]: return [int(x*self.guiScale) for x in self._mainWinSize] @property - def preferencesWinSize(self): + def preferencesWinSize(self) -> list[int]: return [int(x*self.guiScale) for x in self._prefsWinSize] @property - def projLoadColWidths(self): + def projLoadColWidths(self) -> list[int]: return [int(x*self.guiScale) for x in self._projLoadCols] @property - def mainPanePos(self): + def mainPanePos(self) -> list[int]: return [int(x*self.guiScale) for x in self._mainPanePos] @property - def viewPanePos(self): + def viewPanePos(self) -> list[int]: return [int(x*self.guiScale) for x in self._viewPanePos] @property - def outlinePanePos(self): + def outlinePanePos(self) -> list[int]: return [int(x*self.guiScale) for x in self._outlnPanePos] ## # Getters ## - def getTextWidth(self, focusMode=False): + def getTextWidth(self, focusMode: bool = False) -> int: """Get the text with for the correct editor mode.""" if focusMode: return self.pxInt(max(self.focusWidth, 200)) else: return self.pxInt(max(self.textWidth, 200)) - def getTextMargin(self): + def getTextMargin(self) -> int: """Get the scaled text margin.""" return self.pxInt(max(self.textMargin, 0)) - def getTabWidth(self): + def getTabWidth(self) -> int: """Get the scaled tab width.""" return self.pxInt(max(self.tabWidth, 0)) @@ -301,65 +302,65 @@ class Config: self._themeObj = theme return - def setMainWinSize(self, newWidth, newHeight): + def setMainWinSize(self, width: int, height: int) -> None: """Set the size of the main window, but only if the change is larger than 5 pixels. The OS window manager will sometimes adjust it a bit, and we don't want the main window to shrink or grow each time the app is opened. """ - newWidth = int(newWidth/self.guiScale) - newHeight = int(newHeight/self.guiScale) - if abs(self._mainWinSize[0] - newWidth) > 5: - self._mainWinSize[0] = newWidth - if abs(self._mainWinSize[1] - newHeight) > 5: - self._mainWinSize[1] = newHeight + width = int(width/self.guiScale) + height = int(height/self.guiScale) + if abs(self._mainWinSize[0] - width) > 5: + self._mainWinSize[0] = width + if abs(self._mainWinSize[1] - height) > 5: + self._mainWinSize[1] = height return - def setPreferencesWinSize(self, newWidth, newHeight): + def setPreferencesWinSize(self, width: int, height: int) -> None: """Set the size of the Preferences dialog window.""" - self._prefsWinSize[0] = int(newWidth/self.guiScale) - self._prefsWinSize[1] = int(newHeight/self.guiScale) + self._prefsWinSize[0] = int(width/self.guiScale) + self._prefsWinSize[1] = int(height/self.guiScale) return - def setProjLoadColWidths(self, colWidths): + def setProjLoadColWidths(self, widths: list[int]) -> None: """Set the column widths of the Load Project dialog.""" - self._projLoadCols = [int(x/self.guiScale) for x in colWidths] + self._projLoadCols = [int(x/self.guiScale) for x in widths] return - def setMainPanePos(self, panePos): + def setMainPanePos(self, pos: list[int]) -> None: """Set the position of the main GUI splitter.""" - self._mainPanePos = [int(x/self.guiScale) for x in panePos] + self._mainPanePos = [int(x/self.guiScale) for x in pos] return - def setViewPanePos(self, panePos): + def setViewPanePos(self, pos: list[int]) -> None: """Set the position of the viewer meta data splitter.""" - self._viewPanePos = [int(x/self.guiScale) for x in panePos] + self._viewPanePos = [int(x/self.guiScale) for x in pos] return - def setOutlinePanePos(self, panePos): + def setOutlinePanePos(self, pos: list[int]) -> None: """Set the position of the outline details splitter.""" - self._outlnPanePos = [int(x/self.guiScale) for x in panePos] + self._outlnPanePos = [int(x/self.guiScale) for x in pos] return - def setLastPath(self, lastPath): + def setLastPath(self, path: str | Path) -> None: """Set the last used path. Only the folder is saved, so if the path is not a folder, the parent of the path is used instead. """ - if isinstance(lastPath, (str, Path)): - lastPath = checkPath(lastPath, self._homePath) - if not lastPath.is_dir(): - lastPath = lastPath.parent - if lastPath.is_dir(): - self._lastPath = lastPath + if isinstance(path, (str, Path)): + path = checkPath(path, self._homePath) + if not path.is_dir(): + path = path.parent + if path.is_dir(): + self._lastPath = path logger.debug("Last path updated: %s" % self._lastPath) return - def setBackupPath(self, backupPath: Path | str): + def setBackupPath(self, path: Path | str) -> None: """Set the current backup path.""" - self._backupPath = checkPath(backupPath, self._backPath) + self._backupPath = checkPath(path, self._backPath) return - def setTextFont(self, family: str | None, pointSize: int = 12): + def setTextFont(self, family: str | None, pointSize: int = 12) -> None: """Set the text font if it exists. If it doesn't, or is None, set to default font. """ @@ -383,15 +384,11 @@ class Config: ## def pxInt(self, value: int) -> int: - """Used to scale fixed gui sizes by the screen scale factor. - This function returns an int, which is always rounded down. - """ + """Scale fixed gui sizes by the screen scale factor.""" return int(value*self.guiScale) def rpxInt(self, value: int) -> int: - """Used to un-scale fixed gui sizes by the screen scale factor. - This function returns an int, which is always rounded down. - """ + """Un-scale fixed gui sizes by the screen scale factor.""" return int(value/self.guiScale) def dataPath(self, target: str | None = None) -> Path: @@ -461,7 +458,8 @@ class Config: # Config Actions ## - def initConfig(self, confPath: str | Path | None = None, dataPath: str | Path | None = None): + def initConfig(self, confPath: str | Path | None = None, + dataPath: str | Path | None = None) -> None: """Initialise the config class. The manual setting of confPath and dataPath is mainly intended for the test suite. """ @@ -505,9 +503,8 @@ class Config: return - def initLocalisation(self, nwApp): - """Initialise the localisation of the GUI. - """ + def initLocalisation(self, nwApp: QApplication) -> None: + """Initialise the localisation of the GUI.""" self._qLocale = QLocale(self.guiLocale) QLocale.setDefault(self._qLocale) self._qtTrans = {} @@ -528,16 +525,15 @@ class Config: return - def loadConfig(self): - """Load preferences from file and replace default settings. - """ + def loadConfig(self) -> bool: + """Load preferences from file and replace default settings.""" logger.debug("Loading config file") - theConf = NWConfigParser() + conf = NWConfigParser() cnfPath = self._confPath / nwFiles.CONF_FILE try: with open(cnfPath, mode="r", encoding="utf-8") as inFile: - theConf.read_file(inFile) + conf.read_file(inFile) except Exception as exc: logger.error("Could not load config file") logException() @@ -547,98 +543,98 @@ class Config: return False # Main - cnfSec = "Main" - self.guiTheme = theConf.rdStr(cnfSec, "theme", self.guiTheme) - self.guiSyntax = theConf.rdStr(cnfSec, "syntax", self.guiSyntax) - self.guiFont = theConf.rdStr(cnfSec, "font", self.guiFont) - self.guiFontSize = theConf.rdInt(cnfSec, "fontsize", self.guiFontSize) - self.guiLocale = theConf.rdStr(cnfSec, "localisation", self.guiLocale) - self.hideVScroll = theConf.rdBool(cnfSec, "hidevscroll", self.hideVScroll) - self.hideHScroll = theConf.rdBool(cnfSec, "hidehscroll", self.hideHScroll) - self.lastNotes = theConf.rdStr(cnfSec, "lastnotes", self.lastNotes) - self._lastPath = theConf.rdPath(cnfSec, "lastpath", self._lastPath) + sec = "Main" + self.guiTheme = conf.rdStr(sec, "theme", self.guiTheme) + self.guiSyntax = conf.rdStr(sec, "syntax", self.guiSyntax) + self.guiFont = conf.rdStr(sec, "font", self.guiFont) + self.guiFontSize = conf.rdInt(sec, "fontsize", self.guiFontSize) + self.guiLocale = conf.rdStr(sec, "localisation", self.guiLocale) + self.hideVScroll = conf.rdBool(sec, "hidevscroll", self.hideVScroll) + self.hideHScroll = conf.rdBool(sec, "hidehscroll", self.hideHScroll) + self.lastNotes = conf.rdStr(sec, "lastnotes", self.lastNotes) + self._lastPath = conf.rdPath(sec, "lastpath", self._lastPath) # Sizes - cnfSec = "Sizes" - self._mainWinSize = theConf.rdIntList(cnfSec, "mainwindow", self._mainWinSize) - self._prefsWinSize = theConf.rdIntList(cnfSec, "preferences", self._prefsWinSize) - self._projLoadCols = theConf.rdIntList(cnfSec, "projloadcols", self._projLoadCols) - self._mainPanePos = theConf.rdIntList(cnfSec, "mainpane", self._mainPanePos) - self._viewPanePos = theConf.rdIntList(cnfSec, "viewpane", self._viewPanePos) - self._outlnPanePos = theConf.rdIntList(cnfSec, "outlinepane", self._outlnPanePos) + sec = "Sizes" + self._mainWinSize = conf.rdIntList(sec, "mainwindow", self._mainWinSize) + self._prefsWinSize = conf.rdIntList(sec, "preferences", self._prefsWinSize) + self._projLoadCols = conf.rdIntList(sec, "projloadcols", self._projLoadCols) + self._mainPanePos = conf.rdIntList(sec, "mainpane", self._mainPanePos) + self._viewPanePos = conf.rdIntList(sec, "viewpane", self._viewPanePos) + self._outlnPanePos = conf.rdIntList(sec, "outlinepane", self._outlnPanePos) # Project - cnfSec = "Project" - self.autoSaveProj = theConf.rdInt(cnfSec, "autosaveproject", self.autoSaveProj) - self.autoSaveDoc = theConf.rdInt(cnfSec, "autosavedoc", self.autoSaveDoc) - self.emphLabels = theConf.rdBool(cnfSec, "emphlabels", self.emphLabels) - self._backupPath = theConf.rdPath(cnfSec, "backuppath", self._backupPath) - self.backupOnClose = theConf.rdBool(cnfSec, "backuponclose", self.backupOnClose) - self.askBeforeBackup = theConf.rdBool(cnfSec, "askbeforebackup", self.askBeforeBackup) + sec = "Project" + self.autoSaveProj = conf.rdInt(sec, "autosaveproject", self.autoSaveProj) + self.autoSaveDoc = conf.rdInt(sec, "autosavedoc", self.autoSaveDoc) + self.emphLabels = conf.rdBool(sec, "emphlabels", self.emphLabels) + self._backupPath = conf.rdPath(sec, "backuppath", self._backupPath) + self.backupOnClose = conf.rdBool(sec, "backuponclose", self.backupOnClose) + self.askBeforeBackup = conf.rdBool(sec, "askbeforebackup", self.askBeforeBackup) # Editor - cnfSec = "Editor" - self.textFont = theConf.rdStr(cnfSec, "textfont", self.textFont) - self.textSize = theConf.rdInt(cnfSec, "textsize", self.textSize) - self.textWidth = theConf.rdInt(cnfSec, "width", self.textWidth) - self.textMargin = theConf.rdInt(cnfSec, "margin", self.textMargin) - self.tabWidth = theConf.rdInt(cnfSec, "tabwidth", self.tabWidth) - self.focusWidth = theConf.rdInt(cnfSec, "focuswidth", self.focusWidth) - self.hideFocusFooter = theConf.rdBool(cnfSec, "hidefocusfooter", self.hideFocusFooter) - self.doJustify = theConf.rdBool(cnfSec, "justify", self.doJustify) - self.autoSelect = theConf.rdBool(cnfSec, "autoselect", self.autoSelect) - self.doReplace = theConf.rdBool(cnfSec, "autoreplace", self.doReplace) - self.doReplaceSQuote = theConf.rdBool(cnfSec, "repsquotes", self.doReplaceSQuote) - self.doReplaceDQuote = theConf.rdBool(cnfSec, "repdquotes", self.doReplaceDQuote) - self.doReplaceDash = theConf.rdBool(cnfSec, "repdash", self.doReplaceDash) - self.doReplaceDots = theConf.rdBool(cnfSec, "repdots", self.doReplaceDots) - self.scrollPastEnd = theConf.rdInt(cnfSec, "scrollpastend", self.scrollPastEnd) - self.autoScroll = theConf.rdBool(cnfSec, "autoscroll", self.autoScroll) - self.autoScrollPos = theConf.rdInt(cnfSec, "autoscrollpos", self.autoScrollPos) - self.fmtSQuoteOpen = theConf.rdStr(cnfSec, "fmtsquoteopen", self.fmtSQuoteOpen) - self.fmtSQuoteClose = theConf.rdStr(cnfSec, "fmtsquoteclose", self.fmtSQuoteClose) - self.fmtDQuoteOpen = theConf.rdStr(cnfSec, "fmtdquoteopen", self.fmtDQuoteOpen) - self.fmtDQuoteClose = theConf.rdStr(cnfSec, "fmtdquoteclose", self.fmtDQuoteClose) - self.fmtPadBefore = theConf.rdStr(cnfSec, "fmtpadbefore", self.fmtPadBefore) - self.fmtPadAfter = theConf.rdStr(cnfSec, "fmtpadafter", self.fmtPadAfter) - self.fmtPadThin = theConf.rdBool(cnfSec, "fmtpadthin", self.fmtPadThin) - self.spellLanguage = theConf.rdStr(cnfSec, "spellcheck", self.spellLanguage) - self.showTabsNSpaces = theConf.rdBool(cnfSec, "showtabsnspaces", self.showTabsNSpaces) - self.showLineEndings = theConf.rdBool(cnfSec, "showlineendings", self.showLineEndings) - self.showMultiSpaces = theConf.rdBool(cnfSec, "showmultispaces", self.showMultiSpaces) - self.wordCountTimer = theConf.rdFlt(cnfSec, "wordcounttimer", self.wordCountTimer) - self.bigDocLimit = theConf.rdInt(cnfSec, "bigdoclimit", self.bigDocLimit) - self.incNotesWCount = theConf.rdBool(cnfSec, "incnoteswcount", self.incNotesWCount) - self.showFullPath = theConf.rdBool(cnfSec, "showfullpath", self.showFullPath) - self.highlightQuotes = theConf.rdBool(cnfSec, "highlightquotes", self.highlightQuotes) - self.allowOpenSQuote = theConf.rdBool(cnfSec, "allowopensquote", self.allowOpenSQuote) - self.allowOpenDQuote = theConf.rdBool(cnfSec, "allowopendquote", self.allowOpenDQuote) - self.highlightEmph = theConf.rdBool(cnfSec, "highlightemph", self.highlightEmph) - self.stopWhenIdle = theConf.rdBool(cnfSec, "stopwhenidle", self.stopWhenIdle) - self.userIdleTime = theConf.rdInt(cnfSec, "useridletime", self.userIdleTime) + sec = "Editor" + self.textFont = conf.rdStr(sec, "textfont", self.textFont) + self.textSize = conf.rdInt(sec, "textsize", self.textSize) + self.textWidth = conf.rdInt(sec, "width", self.textWidth) + self.textMargin = conf.rdInt(sec, "margin", self.textMargin) + self.tabWidth = conf.rdInt(sec, "tabwidth", self.tabWidth) + self.focusWidth = conf.rdInt(sec, "focuswidth", self.focusWidth) + self.hideFocusFooter = conf.rdBool(sec, "hidefocusfooter", self.hideFocusFooter) + self.doJustify = conf.rdBool(sec, "justify", self.doJustify) + self.autoSelect = conf.rdBool(sec, "autoselect", self.autoSelect) + self.doReplace = conf.rdBool(sec, "autoreplace", self.doReplace) + self.doReplaceSQuote = conf.rdBool(sec, "repsquotes", self.doReplaceSQuote) + self.doReplaceDQuote = conf.rdBool(sec, "repdquotes", self.doReplaceDQuote) + self.doReplaceDash = conf.rdBool(sec, "repdash", self.doReplaceDash) + self.doReplaceDots = conf.rdBool(sec, "repdots", self.doReplaceDots) + self.scrollPastEnd = conf.rdInt(sec, "scrollpastend", self.scrollPastEnd) + self.autoScroll = conf.rdBool(sec, "autoscroll", self.autoScroll) + self.autoScrollPos = conf.rdInt(sec, "autoscrollpos", self.autoScrollPos) + self.fmtSQuoteOpen = conf.rdStr(sec, "fmtsquoteopen", self.fmtSQuoteOpen) + self.fmtSQuoteClose = conf.rdStr(sec, "fmtsquoteclose", self.fmtSQuoteClose) + self.fmtDQuoteOpen = conf.rdStr(sec, "fmtdquoteopen", self.fmtDQuoteOpen) + self.fmtDQuoteClose = conf.rdStr(sec, "fmtdquoteclose", self.fmtDQuoteClose) + self.fmtPadBefore = conf.rdStr(sec, "fmtpadbefore", self.fmtPadBefore) + self.fmtPadAfter = conf.rdStr(sec, "fmtpadafter", self.fmtPadAfter) + self.fmtPadThin = conf.rdBool(sec, "fmtpadthin", self.fmtPadThin) + self.spellLanguage = conf.rdStr(sec, "spellcheck", self.spellLanguage) + self.showTabsNSpaces = conf.rdBool(sec, "showtabsnspaces", self.showTabsNSpaces) + self.showLineEndings = conf.rdBool(sec, "showlineendings", self.showLineEndings) + self.showMultiSpaces = conf.rdBool(sec, "showmultispaces", self.showMultiSpaces) + self.wordCountTimer = conf.rdFlt(sec, "wordcounttimer", self.wordCountTimer) + self.bigDocLimit = conf.rdInt(sec, "bigdoclimit", self.bigDocLimit) + self.incNotesWCount = conf.rdBool(sec, "incnoteswcount", self.incNotesWCount) + self.showFullPath = conf.rdBool(sec, "showfullpath", self.showFullPath) + self.highlightQuotes = conf.rdBool(sec, "highlightquotes", self.highlightQuotes) + self.allowOpenSQuote = conf.rdBool(sec, "allowopensquote", self.allowOpenSQuote) + self.allowOpenDQuote = conf.rdBool(sec, "allowopendquote", self.allowOpenDQuote) + self.highlightEmph = conf.rdBool(sec, "highlightemph", self.highlightEmph) + self.stopWhenIdle = conf.rdBool(sec, "stopwhenidle", self.stopWhenIdle) + self.userIdleTime = conf.rdInt(sec, "useridletime", self.userIdleTime) # State - cnfSec = "State" - self.showRefPanel = theConf.rdBool(cnfSec, "showrefpanel", self.showRefPanel) - self.viewComments = theConf.rdBool(cnfSec, "viewcomments", self.viewComments) - self.viewSynopsis = theConf.rdBool(cnfSec, "viewsynopsis", self.viewSynopsis) - self.searchCase = theConf.rdBool(cnfSec, "searchcase", self.searchCase) - self.searchWord = theConf.rdBool(cnfSec, "searchword", self.searchWord) - self.searchRegEx = theConf.rdBool(cnfSec, "searchregex", self.searchRegEx) - self.searchLoop = theConf.rdBool(cnfSec, "searchloop", self.searchLoop) - self.searchNextFile = theConf.rdBool(cnfSec, "searchnextfile", self.searchNextFile) - self.searchMatchCap = theConf.rdBool(cnfSec, "searchmatchcap", self.searchMatchCap) + sec = "State" + self.showRefPanel = conf.rdBool(sec, "showrefpanel", self.showRefPanel) + self.viewComments = conf.rdBool(sec, "viewcomments", self.viewComments) + self.viewSynopsis = conf.rdBool(sec, "viewsynopsis", self.viewSynopsis) + self.searchCase = conf.rdBool(sec, "searchcase", self.searchCase) + self.searchWord = conf.rdBool(sec, "searchword", self.searchWord) + self.searchRegEx = conf.rdBool(sec, "searchregex", self.searchRegEx) + self.searchLoop = conf.rdBool(sec, "searchloop", self.searchLoop) + self.searchNextFile = conf.rdBool(sec, "searchnextfile", self.searchNextFile) + self.searchMatchCap = conf.rdBool(sec, "searchmatchcap", self.searchMatchCap) # Deprecated Settings or Locations as of 2.0 # ToDo: These will be loaded for a few minor releases until the users have converted them - self.guiFont = theConf.rdStr("Main", "guifont", self.guiFont) - self.guiFontSize = theConf.rdInt("Main", "guifontsize", self.guiFontSize) - self.guiLocale = theConf.rdStr("Main", "guilang", self.guiLocale) - self._backupPath = theConf.rdPath("Backup", "backuppath", self._backupPath) - self.backupOnClose = theConf.rdBool("Backup", "backuponclose", self.backupOnClose) - self.askBeforeBackup = theConf.rdBool("Backup", "askbeforebackup", self.askBeforeBackup) - fmtSingleQuotes = theConf.rdStrList(cnfSec, "fmtsinglequote", []) - fmtDoubleQuotes = theConf.rdStrList(cnfSec, "fmtdoublequote", []) + self.guiFont = conf.rdStr("Main", "guifont", self.guiFont) + self.guiFontSize = conf.rdInt("Main", "guifontsize", self.guiFontSize) + self.guiLocale = conf.rdStr("Main", "guilang", self.guiLocale) + self._backupPath = conf.rdPath("Backup", "backuppath", self._backupPath) + self.backupOnClose = conf.rdBool("Backup", "backuponclose", self.backupOnClose) + self.askBeforeBackup = conf.rdBool("Backup", "askbeforebackup", self.askBeforeBackup) + fmtSingleQuotes = conf.rdStrList(sec, "fmtsinglequote", []) + fmtDoubleQuotes = conf.rdStrList(sec, "fmtdoublequote", []) if isinstance(fmtSingleQuotes, list) and len(fmtSingleQuotes) == 2: self.fmtSQuoteOpen = fmtSingleQuotes[0] @@ -661,18 +657,17 @@ class Config: return True - def saveConfig(self): - """Save the current preferences to file. - """ + def saveConfig(self) -> bool: + """Save the current preferences to file.""" logger.debug("Saving config file") - theConf = NWConfigParser() + conf = NWConfigParser() - theConf["Meta"] = { + conf["Meta"] = { "timestamp": formatTimeStamp(time()), } - theConf["Main"] = { + conf["Main"] = { "theme": str(self.guiTheme), "syntax": str(self.guiSyntax), "font": str(self.guiFont), @@ -684,7 +679,7 @@ class Config: "lastpath": str(self._lastPath), } - theConf["Sizes"] = { + conf["Sizes"] = { "mainwindow": self._packList(self._mainWinSize), "preferences": self._packList(self._prefsWinSize), "projloadcols": self._packList(self._projLoadCols), @@ -693,7 +688,7 @@ class Config: "outlinepane": self._packList(self._outlnPanePos), } - theConf["Project"] = { + conf["Project"] = { "autosaveproject": str(self.autoSaveProj), "autosavedoc": str(self.autoSaveDoc), "emphlabels": str(self.emphLabels), @@ -702,7 +697,7 @@ class Config: "askbeforebackup": str(self.askBeforeBackup), } - theConf["Editor"] = { + conf["Editor"] = { "textfont": str(self.textFont), "textsize": str(self.textSize), "width": str(self.textWidth), @@ -743,7 +738,7 @@ class Config: "useridletime": str(self.userIdleTime), } - theConf["State"] = { + conf["State"] = { "showrefpanel": str(self.showRefPanel), "viewcomments": str(self.viewComments), "viewsynopsis": str(self.viewSynopsis), @@ -759,7 +754,7 @@ class Config: cnfPath = self._confPath / nwFiles.CONF_FILE try: with open(cnfPath, mode="w", encoding="utf-8") as outFile: - theConf.write(outFile) + conf.write(outFile) except Exception as exc: logger.error("Could not save config file") logException() @@ -774,26 +769,14 @@ class Config: # Internal Functions ## - def _packList(self, inData): + def _packList(self, data: list) -> str: """Pack a list of items into a comma-separated string for saving to the config file. """ - return ", ".join([str(inVal) for inVal in inData]) + return ", ".join([str(inVal) for inVal in data]) - def _checkNone(self, checkVal): - """Return a NoneType if the value corresponds to None, otherwise - return the value unchanged. - """ - if checkVal is None: - return None - if isinstance(checkVal, str): - if checkVal.lower() == "none": - return None - return checkVal - - def _checkOptionalPackages(self): - """Check if we have the optional packages used by some features. - """ + def _checkOptionalPackages(self) -> None: + """Check optional packages used by some features.""" try: import enchant # noqa: F401 except ImportError: @@ -809,14 +792,13 @@ class Config: class RecentProjects: - def __init__(self, config): + def __init__(self, config: Config) -> None: self._conf = config self._data = {} return - def loadCache(self): - """Load the cache file for recent projects. - """ + def loadCache(self) -> bool: + """Load the cache file for recent projects.""" self._data = {} cacheFile = self._conf.dataPath(nwFiles.RECENT_FILE) @@ -839,9 +821,8 @@ class RecentProjects: return True - def saveCache(self): - """Save the cache dictionary of recent projects. - """ + def saveCache(self) -> bool: + """Save the cache dictionary of recent projects.""" cacheFile = self._conf.dataPath(nwFiles.RECENT_FILE) cacheTemp = cacheFile.with_suffix(".tmp") try: @@ -855,27 +836,27 @@ class RecentProjects: return True - def listEntries(self): - """List all items in the cache. - """ - return [(k, e["title"], e["words"], e["time"]) for k, e in self._data.items()] + def listEntries(self) -> list[tuple[str, str, int, int]]: + """List all items in the cache.""" + return [ + (str(k), str(e["title"]), checkInt(e["words"], 0), checkInt(e["time"], 0)) + for k, e in self._data.items() + ] - def update(self, projPath, projTitle, wordCount, saveTime): - """Add or update recent cache information on a given project. - """ - self._data[str(projPath)] = { - "title": projTitle, - "words": int(wordCount), - "time": int(saveTime), + def update(self, path: str | Path, title: str, words: int, saved: float | int) -> None: + """Add or update recent cache information on a given project.""" + self._data[str(path)] = { + "title": title, + "words": int(words), + "time": int(saved), } self.saveCache() return - def remove(self, projPath): - """Try to remove a path from the recent projects cache. - """ - if self._data.pop(str(projPath), None) is not None: - logger.debug("Removed recent: %s", projPath) + def remove(self, path: str | Path) -> None: + """Try to remove a path from the recent projects cache.""" + if self._data.pop(str(path), None) is not None: + logger.debug("Removed recent: %s", path) self.saveCache() return diff --git a/novelwriter/dialogs/projload.py b/novelwriter/dialogs/projload.py index 1ae6fb50..09fabd4d 100644 --- a/novelwriter/dialogs/projload.py +++ b/novelwriter/dialogs/projload.py @@ -100,8 +100,9 @@ class GuiProjectLoad(QDialog): self.listBox.setIconSize(QSize(iPx, iPx)) treeHead = self.listBox.headerItem() - treeHead.setTextAlignment(self.C_COUNT, Qt.AlignRight) - treeHead.setTextAlignment(self.C_TIME, Qt.AlignRight) + if treeHead: + treeHead.setTextAlignment(self.C_COUNT, Qt.AlignRight) + treeHead.setTextAlignment(self.C_TIME, Qt.AlignRight) self.lblRecent = QLabel("%s" % self.tr("Recently Opened Projects")) self.lblPath = QLabel("%s" % self.tr("Path")) @@ -281,8 +282,7 @@ class GuiProjectLoad(QDialog): newItem.setFont(self.C_TIME, CONFIG.theme.guiFontFixed) self.listBox.addTopLevelItem(newItem) - if self.listBox.topLevelItemCount() > 0: - self.listBox.topLevelItem(0).setSelected(True) + self.listBox.setCurrentItem(self.listBox.topLevelItem(0)) projColWidth = CONFIG.projLoadColWidths if len(projColWidth) == 3: diff --git a/novelwriter/extensions/circularprogress.py b/novelwriter/extensions/circularprogress.py index 79dacfd1..b53bf6bd 100644 --- a/novelwriter/extensions/circularprogress.py +++ b/novelwriter/extensions/circularprogress.py @@ -42,7 +42,7 @@ class NProgressCircle(QProgressBar): "_cPen", "_bPen", "_tColor" ) - def __init__(self, parent: QWidget, size: int, point: int): + def __init__(self, parent: QWidget, size: int, point: int) -> None: super().__init__(parent=parent) self._text = None self._point = point @@ -60,10 +60,8 @@ class NProgressCircle(QProgressBar): self.setFixedHeight(size) return - def setColours( - self, back: QColor | None = None, track: QColor | None = None, - bar: QColor | None = None, text: QColor | None = None - ): + def setColours(self, back: QColor | None = None, track: QColor | None = None, + bar: QColor | None = None, text: QColor | None = None) -> None: """Set the colours of the widget.""" if isinstance(back, QColor): self._dPen = QPen(back) @@ -76,13 +74,13 @@ class NProgressCircle(QProgressBar): self._tColor = text return - def setCentreText(self, text: str | None): + def setCentreText(self, text: str | None) -> None: """Replace the progress text with a custom string.""" self._text = text self.setValue(self.value()) # Triggers a redraw return - def paintEvent(self, event: QPaintEvent): + def paintEvent(self, event: QPaintEvent) -> None: """Custom painter for the progress bar.""" progress = 100.0*self.value()/self.maximum() angle = ceil(16*3.6*progress) diff --git a/novelwriter/extensions/configlayout.py b/novelwriter/extensions/configlayout.py index 68ac39e2..3ab6b87b 100644 --- a/novelwriter/extensions/configlayout.py +++ b/novelwriter/extensions/configlayout.py @@ -37,7 +37,7 @@ FONT_SCALE = 0.9 class NConfigLayout(QGridLayout): - def __init__(self): + def __init__(self) -> None: super().__init__() self._nextRow = 0 @@ -56,7 +56,8 @@ class NConfigLayout(QGridLayout): # Getters and Setters ## - def setHelpTextStyle(self, color: QColor | list | tuple, fontScale: float = FONT_SCALE): + def setHelpTextStyle(self, color: QColor | list | tuple, + fontScale: float = FONT_SCALE) -> None: """Set the text color for the help text.""" if isinstance(color, QColor): self._helpCol = color @@ -65,7 +66,7 @@ class NConfigLayout(QGridLayout): self._fontScale = fontScale return - def setHelpText(self, row: int, text: str): + def setHelpText(self, row: int, text: str) -> None: """Set the text for the help label.""" if row in self._itemMap: qHelp = self._itemMap[row][1] @@ -73,7 +74,7 @@ class NConfigLayout(QGridLayout): qHelp.setText(text) return - def setLabelText(self, row: int, text: str): + def setLabelText(self, row: int, text: str) -> None: """Set the text for the main label.""" if row in self._itemMap: self._itemMap[row](0).setText(text) @@ -83,7 +84,7 @@ class NConfigLayout(QGridLayout): # Class Methods ## - def addGroupLabel(self, label: str): + def addGroupLabel(self, label: str) -> None: """Add a text label to separate groups of settings.""" hM = CONFIG.pxInt(4) qLabel = QLabel("%s" % label) @@ -94,10 +95,8 @@ class NConfigLayout(QGridLayout): self._nextRow += 1 return - def addRow( - self, label: str, widget: QWidget, helpText: str | None = None, - unit: str | None = None, button: QWidget | None = None - ) -> int: + def addRow(self, label: str, widget: QWidget, helpText: str | None = None, + unit: str | None = None, button: QWidget | None = None) -> int: """Add a label and a widget as a new row of the grid.""" wSp = CONFIG.pxInt(8) qLabel = QLabel(label) @@ -155,7 +154,7 @@ class NSimpleLayout(QGridLayout): column layout. """ - def __init__(self): + def __init__(self) -> None: super().__init__() self._nextRow = 0 @@ -170,7 +169,7 @@ class NSimpleLayout(QGridLayout): # Methods ## - def addGroupLabel(self, label: str): + def addGroupLabel(self, label: str) -> None: """Add a text label to separate groups of settings.""" hM = CONFIG.pxInt(4) qLabel = QLabel("%s" % label) @@ -181,7 +180,7 @@ class NSimpleLayout(QGridLayout): self._nextRow += 1 return - def addRow(self, label: str, widget: QWidget): + def addRow(self, label: str, widget: QWidget) -> None: """Add a label and a widget as a new row of the grid.""" wSp = CONFIG.pxInt(8) qLabel = QLabel(label) @@ -208,7 +207,8 @@ class NSimpleLayout(QGridLayout): class NHelpLabel(QLabel): - def __init__(self, text: str, color: QColor | list | tuple, fontSize: float = FONT_SCALE): + def __init__(self, text: str, color: QColor | list | tuple, + fontSize: float = FONT_SCALE) -> None: super().__init__(text) if isinstance(color, QColor): diff --git a/novelwriter/extensions/pageddialog.py b/novelwriter/extensions/pageddialog.py index aa8de6e2..a4170a2a 100644 --- a/novelwriter/extensions/pageddialog.py +++ b/novelwriter/extensions/pageddialog.py @@ -23,10 +23,11 @@ along with this program. If not, see . """ from __future__ import annotations -from PyQt5.QtCore import QRect, QPoint +from PyQt5.QtGui import QPaintEvent +from PyQt5.QtCore import QRect, QPoint, QSize from PyQt5.QtWidgets import ( QDialog, QHBoxLayout, QStyle, QStyleOptionTab, QStylePainter, QTabBar, - QTabWidget, QVBoxLayout + QTabWidget, QVBoxLayout, QWidget ) from novelwriter import CONFIG @@ -34,7 +35,7 @@ from novelwriter import CONFIG class NPagedDialog(QDialog): - def __init__(self, parent=None): + def __init__(self, parent: QWidget) -> None: super().__init__(parent=parent) self._tabBar = NVerticalTabBar(self) @@ -67,21 +68,18 @@ class NPagedDialog(QDialog): return - def addTab(self, widget, label): - """Forward the adding of tabs to the QTabWidget. - """ + def addTab(self, widget: QWidget, label: str) -> None: + """Forward the adding of tabs to the QTabWidget.""" self._tabBox.addTab(widget, label) return - def addControls(self, buttonBar): - """Add a button bar to the dialog. - """ + def addControls(self, buttonBar: QWidget) -> None: + """Add a button bar to the dialog.""" self._buttonBox.addWidget(buttonBar) return - def setCurrentWidget(self, widget): - """Forward the changing of tab to the QTabWidget. - """ + def setCurrentWidget(self, widget: QWidget) -> None: + """Forward the changing of tab to the QTabWidget.""" self._tabBox.setCurrentWidget(widget) return @@ -90,20 +88,19 @@ class NPagedDialog(QDialog): class NVerticalTabBar(QTabBar): - def __init__(self, parent=None): + def __init__(self, parent: QWidget) -> None: super().__init__(parent=parent) self._mW = CONFIG.pxInt(150) return - def tabSizeHint(self, index): - """Return a transposed size hint for the rotated bar. - """ + def tabSizeHint(self, index: int) -> QSize: + """Return a transposed size hint for the rotated bar.""" tSize = super().tabSizeHint(index) tSize.transpose() tSize.setWidth(min(tSize.width(), self._mW)) return tSize - def paintEvent(self, event): + def paintEvent(self, event: QPaintEvent) -> None: """Custom implementation of the label painter that rotates the label 90 degrees. """ diff --git a/novelwriter/extensions/simpleprogress.py b/novelwriter/extensions/simpleprogress.py index 2dbf4e43..30a11168 100644 --- a/novelwriter/extensions/simpleprogress.py +++ b/novelwriter/extensions/simpleprogress.py @@ -35,11 +35,11 @@ class NProgressSimple(QProgressBar): A custom widget that paints a plain bar with no other styling. """ - def __init__(self, parent: QWidget): + def __init__(self, parent: QWidget) -> None: super().__init__(parent=parent) return - def paintEvent(self, event: QPaintEvent): + def paintEvent(self, event: QPaintEvent) -> None: """Custom painter for the progress bar.""" if self.value() == 0: return diff --git a/novelwriter/extensions/switch.py b/novelwriter/extensions/switch.py index be90bec7..e702a6b6 100644 --- a/novelwriter/extensions/switch.py +++ b/novelwriter/extensions/switch.py @@ -23,9 +23,9 @@ along with this program. If not, see . """ from __future__ import annotations -from PyQt5.QtGui import QPainter -from PyQt5.QtCore import Qt, QRectF, QPropertyAnimation, pyqtProperty -from PyQt5.QtWidgets import QSizePolicy, QAbstractButton +from PyQt5.QtGui import QMouseEvent, QPainter, QPaintEvent, QResizeEvent +from PyQt5.QtCore import QEvent, QPropertyAnimation, QRectF, Qt, pyqtProperty +from PyQt5.QtWidgets import QAbstractButton, QSizePolicy, QWidget from novelwriter import CONFIG from novelwriter.constants import nwUnicode @@ -33,7 +33,8 @@ from novelwriter.constants import nwUnicode class NSwitch(QAbstractButton): - def __init__(self, parent=None, width=None, height=None): + def __init__(self, parent: QWidget | None = None, + width: int | None = None, height: int | None = None) -> None: super().__init__(parent=parent) if width is None: @@ -64,12 +65,12 @@ class NSwitch(QAbstractButton): # Properties ## - @pyqtProperty(int) - def offset(self): + @pyqtProperty(int) # type: ignore + def offset(self) -> int: # type: ignore return self._offset - @offset.setter - def offset(self, offset): + @offset.setter # type: ignore + def offset(self, offset: int): self._offset = offset self.update() return @@ -78,33 +79,30 @@ class NSwitch(QAbstractButton): # Getters and Setters ## - def setChecked(self, checked): - """Overload setChecked to also alter the offset. - """ + def setChecked(self, checked: bool) -> None: + """Overload setChecked to also alter the offset.""" super().setChecked(checked) if checked: - self.offset = self._xW - self._xR + self._offset = self._xW - self._xR else: - self.offset = self._xR + self._offset = self._xR return ## # Events ## - def resizeEvent(self, event): - """Overload resize to ensure correct offset. - """ + def resizeEvent(self, event: QResizeEvent) -> None: + """Overload resize to ensure correct offset.""" super().resizeEvent(event) if self.isChecked(): - self.offset = self._xW - self._xR + self._offset = self._xW - self._xR else: - self.offset = self._xR + self._offset = self._xR return - def paintEvent(self, event): - """Drawing the switch itself. - """ + def paintEvent(self, event: QPaintEvent) -> None: + """Drawing the switch itself.""" qPaint = QPainter(self) qPaint.setRenderHint(QPainter.Antialiasing, True) qPaint.setPen(Qt.NoPen) @@ -134,27 +132,26 @@ class NSwitch(QAbstractButton): qPaint.drawRoundedRect(0, 0, self._xW, self._xH, self._xR, self._xR) qPaint.setBrush(thumbBrush) - qPaint.drawEllipse(self.offset - self._rR, self._rB, self._rH, self._rH) + qPaint.drawEllipse(self._offset - self._rR, self._rB, self._rH, self._rH) theFont = qPaint.font() theFont.setPixelSize(self._xT) qPaint.setPen(textColor) qPaint.setFont(theFont) qPaint.drawText( - QRectF(self.offset - self._rR, self._rB, self._rH, self._rH), + QRectF(self._offset - self._rR, self._rB, self._rH, self._rH), Qt.AlignCenter, thumbText ) return - def mouseReleaseEvent(self, event): - """Animate the switch on mouse release. - """ + def mouseReleaseEvent(self, event: QMouseEvent) -> None: + """Animate the switch on mouse release.""" super().mouseReleaseEvent(event) if event.button() == Qt.LeftButton: doAnim = QPropertyAnimation(self, b"offset", self) doAnim.setDuration(120) - doAnim.setStartValue(self.offset) + doAnim.setStartValue(self._offset) if self.isChecked(): doAnim.setEndValue(self._xW - self._xR) else: @@ -162,9 +159,8 @@ class NSwitch(QAbstractButton): doAnim.start() return - def enterEvent(self, event): - """Change the cursor when hovering the button. - """ + def enterEvent(self, event: QEvent) -> None: + """Change the cursor when hovering the button.""" self.setCursor(Qt.PointingHandCursor) super().enterEvent(event) return diff --git a/novelwriter/extensions/switchbox.py b/novelwriter/extensions/switchbox.py index f7385058..98db295a 100644 --- a/novelwriter/extensions/switchbox.py +++ b/novelwriter/extensions/switchbox.py @@ -23,8 +23,8 @@ along with this program. If not, see . """ from __future__ import annotations -from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtGui import QIcon +from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtWidgets import QGridLayout, QLabel, QScrollArea, QSizePolicy, QWidget from novelwriter.extensions.switch import NSwitch @@ -39,7 +39,7 @@ class NSwitchBox(QScrollArea): switchToggled = pyqtSignal(str, bool) - def __init__(self, parent: QWidget, baseSize: int): + def __init__(self, parent: QWidget, baseSize: int) -> None: super().__init__(parent=parent) self._index = 0 self._hSwitch = baseSize @@ -49,7 +49,7 @@ class NSwitchBox(QScrollArea): self.clear() return - def clear(self): + def clear(self) -> None: """Rebuild the content of the core widget.""" self._index = 0 self._widgets = [] @@ -66,7 +66,7 @@ class NSwitchBox(QScrollArea): return - def addLabel(self, text: str): + def addLabel(self, text: str) -> None: """Add a header label to the content box.""" label = QLabel(text) font = label.font() @@ -77,7 +77,7 @@ class NSwitchBox(QScrollArea): self._bumpIndex() return - def addItem(self, qIcon: QIcon, text: str, identifier: str, default: bool = False): + def addItem(self, qIcon: QIcon, text: str, identifier: str, default: bool = False) -> None: """Add an item to the content box.""" icon = QLabel("") icon.setAlignment(Qt.AlignRight | Qt.AlignVCenter) @@ -97,7 +97,7 @@ class NSwitchBox(QScrollArea): return - def addSeparator(self): + def addSeparator(self) -> None: """Add a blank entry in the content box.""" spacer = QWidget() spacer.setFixedHeight(int(0.5*self._sIcon)) @@ -106,7 +106,7 @@ class NSwitchBox(QScrollArea): self._bumpIndex() return - def setInnerContentsMargins(self, left: int, top: int, right: int, bottom: int): + def setInnerContentsMargins(self, left: int, top: int, right: int, bottom: int) -> None: """Set the contents margins of the inner layout.""" self._content.setContentsMargins(left, top, right, bottom) return @@ -115,12 +115,12 @@ class NSwitchBox(QScrollArea): # Internal Functions ## - def _emitSwitchSignal(self, identifier: str, state: bool): + def _emitSwitchSignal(self, identifier: str, state: bool) -> None: """Emit a signal for a switch toggle.""" self.switchToggled.emit(identifier, state) return - def _bumpIndex(self): + def _bumpIndex(self) -> None: """Increase the index counter and make sure only the last columns is stretching. """ diff --git a/tests/test_base/test_base_config.py b/tests/test_base/test_base_config.py index 524cd8a1..0f953f81 100644 --- a/tests/test_base/test_base_config.py +++ b/tests/test_base/test_base_config.py @@ -366,14 +366,6 @@ def testBaseConfig_Internal(monkeypatch, fncPath): # Function _packList assert tstConf._packList(["A", 1, 2.0, None, False]) == "A, 1, 2.0, None, False" - # Function _checkNone - assert tstConf._checkNone(None) is None - assert tstConf._checkNone("None") is None - assert tstConf._checkNone("none") is None - assert tstConf._checkNone("NONE") is None - assert tstConf._checkNone("NoNe") is None - assert tstConf._checkNone(123456) == 123456 - # Function _checkOptionalPackages # (Assumes enchant package exists and is importable) tstConf._checkOptionalPackages()