# -*- coding: utf-8 -*- """novelWriter Config Class novelWriter – Config Class ============================ This class reads and store the main preferences of the application File History: Created: 2018-09-22 [0.0.1] """ import logging import configparser import nw from os import path, mkdir, getcwd from appdirs import user_config_dir from datetime import datetime from nw.constants import nwFiles from nw.common import splitVersionNumber from PyQt5.Qt import PYQT_VERSION_STR from PyQt5.QtCore import QT_VERSION_STR logger = logging.getLogger(__name__) class Config: CNF_STR = 0 CNF_INT = 1 CNF_BOOL = 2 CNF_LIST = 3 def __init__(self): # Set Application Variables self.appName = nw.__package__ self.appHandle = nw.__package__.lower() self.showGUI = True self.debugGUI = False self.debugInfo = False self.spellTool = None # Set Paths self.confPath = None self.confFile = None self.homePath = None self.appPath = None self.appRoot = None self.appIcon = None self.guiPath = None self.themeRoot = None self.themePath = None # Set default values self.confChanged = False ## General self.guiTheme = "default" self.guiSyntax = "default_light" self.winGeometry = [1100, 650] self.treeColWidth = [120, 30, 50] self.mainPanePos = [300, 800] self.docPanePos = [400, 400] ## Dialogs self.dlgTimeLine = [600, 400] ## Project self.autoSaveProj = 60 self.autoSaveDoc = 30 ## Text Editor self.textFont = None self.textSize = 12 self.textFixedW = True self.textWidth = 600 self.textMargin = 40 self.tabWidth = 40 self.doJustify = True self.autoSelect = True self.doReplace = True self.doReplaceSQuote = True self.doReplaceDQuote = True self.doReplaceDash = True self.doReplaceDots = True self.wordCountTimer = 5.0 self.fmtSingleQuotes = ["‘","’"] self.fmtDoubleQuotes = ["“","”"] self.spellLanguage = "en_GB" # Backup self.backupPath = "" self.backupOnClose = False self.askBeforeBackup = True # Path self.recentList = [""]*10 # Check Qt5 Versions verQt = splitVersionNumber(QT_VERSION_STR) self.verQtString = QT_VERSION_STR self.verQtMajor = verQt[0] self.verQtMinor = verQt[1] self.verQtPatch = verQt[2] self.verQtValue = verQt[3] verQt = splitVersionNumber(PYQT_VERSION_STR) self.verPyQtString = PYQT_VERSION_STR self.verPyQtMajor = verQt[0] self.verPyQtMinor = verQt[1] self.verPyQtPatch = verQt[2] self.verPyQtValue = verQt[3] return ## # Actions ## def initConfig(self, confPath=None): if confPath is None: self.confPath = user_config_dir(self.appHandle) else: logger.info("Setting config from alternative path: %s" % confPath) self.confPath = confPath self.confFile = self.appHandle+".conf" self.homePath = path.expanduser("~") self.appPath = path.dirname(__file__) self.appRoot = path.join(self.appPath,path.pardir) self.helpPath = path.join(self.appRoot,"help","en_GB") self.guiPath = path.join(self.appPath,"gui") self.themeRoot = path.join(self.appPath,"themes") self.themePath = path.join(self.themeRoot) self.appIcon = path.join(self.appRoot, nwFiles.APP_ICON) # If config folder does not exist, make it. # This assumes that the os config folder itself exists. # TODO: This does not work on Windows if not path.isdir(self.confPath): mkdir(self.confPath) # Check if config file exists if path.isfile(path.join(self.confPath,self.confFile)): # If it exists, load it self.loadConfig() else: # If it does not exist, save a copy of the defaults self.saveConfig() if self.spellTool is None: logger.warning("No spell check tool available") else: logger.debug("Using spell check tool '%s'" % self.spellTool) return True def loadConfig(self): logger.debug("Loading config file") cnfParse = configparser.ConfigParser() try: cnfParse.read_file(open(path.join(self.confPath,self.confFile))) except Exception as e: logger.error("Could not load config file") return False ## Main cnfSec = "Main" self.guiTheme = self._parseLine(cnfParse, cnfSec, "theme", self.CNF_STR, self.guiTheme) self.guiSyntax = self._parseLine(cnfParse, cnfSec, "syntax", self.CNF_STR, self.guiSyntax) ## Sizes cnfSec = "Sizes" self.winGeometry = self._parseLine(cnfParse, cnfSec, "geometry", self.CNF_LIST, self.winGeometry) self.treeColWidth = self._parseLine(cnfParse, cnfSec, "treecols", self.CNF_LIST, self.treeColWidth) self.mainPanePos = self._parseLine(cnfParse, cnfSec, "mainpane", self.CNF_LIST, self.mainPanePos) self.docPanePos = self._parseLine(cnfParse, cnfSec, "docpane", self.CNF_LIST, self.docPanePos) self.dlgTimeLine = self._parseLine(cnfParse, cnfSec, "timeline", self.CNF_LIST, self.dlgTimeLine) ## Project cnfSec = "Project" self.autoSaveProj = self._parseLine(cnfParse, cnfSec, "autosaveproject", self.CNF_INT, self.autoSaveProj) self.autoSaveDoc = self._parseLine(cnfParse, cnfSec, "autosavedoc", self.CNF_INT, self.autoSaveDoc) ## Editor cnfSec = "Editor" self.textFont = self._parseLine(cnfParse, cnfSec, "textfont", self.CNF_STR, self.textFont) self.textSize = self._parseLine(cnfParse, cnfSec, "textsize", self.CNF_INT, self.textSize) self.textFixedW = self._parseLine(cnfParse, cnfSec, "fixedwidth", self.CNF_BOOL, self.textFixedW) self.textWidth = self._parseLine(cnfParse, cnfSec, "width", self.CNF_INT, self.textWidth) self.textMargin = self._parseLine(cnfParse, cnfSec, "margin", self.CNF_INT, self.textMargin) self.tabWidth = self._parseLine(cnfParse, cnfSec, "tabwidth", self.CNF_INT, self.tabWidth) self.doJustify = self._parseLine(cnfParse, cnfSec, "justify", self.CNF_BOOL, self.doJustify) self.autoSelect = self._parseLine(cnfParse, cnfSec, "autoselect", self.CNF_BOOL, self.autoSelect) self.doReplace = self._parseLine(cnfParse, cnfSec, "autoreplace", self.CNF_BOOL, self.doReplace) self.doReplaceSQuote = self._parseLine(cnfParse, cnfSec, "repsquotes", self.CNF_BOOL, self.doReplaceSQuote) self.doReplaceDQuote = self._parseLine(cnfParse, cnfSec, "repdquotes", self.CNF_BOOL, self.doReplaceDQuote) self.doReplaceDash = self._parseLine(cnfParse, cnfSec, "repdash", self.CNF_BOOL, self.doReplaceDash) self.doReplaceDots = self._parseLine(cnfParse, cnfSec, "repdots", self.CNF_BOOL, self.doReplaceDots) self.fmtSingleQuotes = self._parseLine(cnfParse, cnfSec, "fmtsinglequote", self.CNF_LIST, self.fmtSingleQuotes) self.fmtDoubleQuotes = self._parseLine(cnfParse, cnfSec, "fmtdoublequote", self.CNF_LIST, self.fmtDoubleQuotes) self.spellLanguage = self._parseLine(cnfParse, cnfSec, "spellcheck", self.CNF_STR, self.spellLanguage) ## Backup cnfSec = "Backup" self.backupPath = self._parseLine(cnfParse, cnfSec, "backuppath", self.CNF_STR, self.backupPath) self.backupOnClose = self._parseLine(cnfParse, cnfSec, "backuponclose", self.CNF_BOOL, self.backupOnClose) self.askBeforeBackup = self._parseLine(cnfParse, cnfSec, "askbeforebackup", self.CNF_BOOL, self.askBeforeBackup) ## Path cnfSec = "Path" for i in range(10): self.recentList[i] = self._parseLine(cnfParse, cnfSec, "recent%d" % i,self.CNF_STR, self.recentList[i]) # Check Certain Values for None self.spellLanguage = self._checkNone(self.spellLanguage) return True def saveConfig(self): logger.debug("Saving config file") cnfParse = configparser.ConfigParser() # Set options ## Main cnfSec = "Main" cnfParse.add_section(cnfSec) cnfParse.set(cnfSec,"timestamp", datetime.now().strftime("%Y-%m-%d %H:%M:%S")) cnfParse.set(cnfSec,"theme", str(self.guiTheme)) cnfParse.set(cnfSec,"syntax", str(self.guiSyntax)) ## Sizes cnfSec = "Sizes" cnfParse.add_section(cnfSec) cnfParse.set(cnfSec,"geometry", self._packList(self.winGeometry)) cnfParse.set(cnfSec,"treecols", self._packList(self.treeColWidth)) cnfParse.set(cnfSec,"mainpane", self._packList(self.mainPanePos)) cnfParse.set(cnfSec,"docpane", self._packList(self.docPanePos)) cnfParse.set(cnfSec,"timeline", self._packList(self.dlgTimeLine)) ## Project cnfSec = "Project" cnfParse.add_section(cnfSec) cnfParse.set(cnfSec,"autosaveproject", str(self.autoSaveProj)) cnfParse.set(cnfSec,"autosavedoc", str(self.autoSaveDoc)) ## Editor cnfSec = "Editor" cnfParse.add_section(cnfSec) cnfParse.set(cnfSec,"textfont", str(self.textFont)) cnfParse.set(cnfSec,"textsize", str(self.textSize)) cnfParse.set(cnfSec,"fixedwidth", str(self.textFixedW)) cnfParse.set(cnfSec,"width", str(self.textWidth)) cnfParse.set(cnfSec,"margin", str(self.textMargin)) cnfParse.set(cnfSec,"tabwidth", str(self.tabWidth)) cnfParse.set(cnfSec,"justify", str(self.doJustify)) cnfParse.set(cnfSec,"autoselect", str(self.autoSelect)) cnfParse.set(cnfSec,"autoreplace", str(self.doReplace)) cnfParse.set(cnfSec,"repsquotes", str(self.doReplaceSQuote)) cnfParse.set(cnfSec,"repdquotes", str(self.doReplaceDQuote)) cnfParse.set(cnfSec,"repdash", str(self.doReplaceDash)) cnfParse.set(cnfSec,"repdots", str(self.doReplaceDots)) cnfParse.set(cnfSec,"fmtsinglequote",self._packList(self.fmtSingleQuotes)) cnfParse.set(cnfSec,"fmtdoublequote",self._packList(self.fmtDoubleQuotes)) cnfParse.set(cnfSec,"spellcheck", str(self.spellLanguage)) ## Backup cnfSec = "Backup" cnfParse.add_section(cnfSec) cnfParse.set(cnfSec,"backuppath", str(self.backupPath)) cnfParse.set(cnfSec,"backuponclose", str(self.backupOnClose)) cnfParse.set(cnfSec,"askbeforebackup",str(self.askBeforeBackup)) ## Path cnfSec = "Path" cnfParse.add_section(cnfSec) for i in range(10): cnfParse.set(cnfSec,"recent%d" % i, str(self.recentList[i])) # Write config file try: cnfParse.write(open(path.join(self.confPath,self.confFile),"w")) self.confChanged = False except Exception as e: logger.error("Could not save config file") return False return True ## # Setters ## def setRecent(self, recentPath): if recentPath == "": return if recentPath in self.recentList[0:10]: self.recentList.remove(recentPath) self.recentList.insert(0,recentPath) return def setConfPath(self, newPath): if newPath is None: return True if not path.isfile(newPath): logger.error("Config: File not found. Using default config path instead.") return False self.confPath = path.dirname(newPath) self.confFile = path.basename(newPath) return True def setWinSize(self, newWidth, newHeight): if abs(self.winGeometry[0] - newWidth) > 5: self.winGeometry[0] = newWidth self.confChanged = True if abs(self.winGeometry[1] - newHeight) > 5: self.winGeometry[1] = newHeight self.confChanged = True return True def setTLineSize(self, newWidth, newHeight): if abs(self.dlgTimeLine[0] - newWidth) > 5: self.dlgTimeLine[0] = newWidth self.confChanged = True if abs(self.dlgTimeLine[1] - newHeight) > 5: self.dlgTimeLine[1] = newHeight self.confChanged = True return True def setTreeColWidths(self, colWidths): self.treeColWidth = colWidths self.confChanged = True return True def setMainPanePos(self, panePos): self.mainPanePos = panePos self.confChanged = True return True def setDocPanePos(self, panePos): self.docPanePos = panePos self.confChanged = True return True ## # Internal Functions ## def _unpackList(self, inStr, listLen, listDefault, castTo=int): inData = inStr.split(",") outData = [] for i in range(listLen): try: outData.append(castTo(inData[i])) except: outData.append(listDefault[i]) return outData def _packList(self, inData): return ", ".join(str(inVal) for inVal in inData) def _parseLine(self, cnfParse, cnfSec, cnfName, cnfType, cnfDefault): if cnfParse.has_section(cnfSec): if cnfParse.has_option(cnfSec, cnfName): if cnfType == self.CNF_STR: return cnfParse.get(cnfSec, cnfName) elif cnfType == self.CNF_INT: return cnfParse.getint(cnfSec, cnfName) elif cnfType == self.CNF_BOOL: return cnfParse.getboolean(cnfSec, cnfName) elif cnfType == self.CNF_LIST: return self._unpackList( cnfParse.get(cnfSec, cnfName), len(cnfDefault), cnfDefault ) return cnfDefault def _checkNone(self, checkVal): if checkVal is None: return None if isinstance(checkVal, str): if checkVal.lower == "none": return None return checkVal # End Class Config