Annotate config class and extensions

This commit is contained in:
Veronica Berglyd Olsen
2023-08-10 00:49:33 +02:00
parent 71e961ba38
commit ddacfc743e
9 changed files with 244 additions and 280 deletions
+171 -190
View File
@@ -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
+4 -4
View File
@@ -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("<b>%s</b>" % self.tr("Recently Opened Projects"))
self.lblPath = QLabel("<b>%s</b>" % 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:
+5 -7
View File
@@ -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)
+13 -13
View File
@@ -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("<b>%s</b>" % 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("<b>%s</b>" % 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):
+14 -17
View File
@@ -23,10 +23,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
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.
"""
+2 -2
View File
@@ -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
+26 -30
View File
@@ -23,9 +23,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
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
+9 -9
View File
@@ -23,8 +23,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
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.
"""
-8
View File
@@ -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()