diff --git a/nw/core/options.py b/nw/core/options.py new file mode 100644 index 00000000..12593c0c --- /dev/null +++ b/nw/core/options.py @@ -0,0 +1,251 @@ +# -*- coding: utf-8 -*- +"""novelWriter Project Options Cache + + novelWriter – Project Options Cache +===================================== + Class wrapping the project options state + + File History: + Created: 2019-10-21 [0.3.1] + Rewritten: 2020-02-19 [0.4.5] + + This file is a part of novelWriter + Copyright 2020, Veronica Berglyd Olsen + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +""" + +import logging +import json +import nw + +from os import path + +from nw.constants import nwFiles + +logger = logging.getLogger(__name__) + +class OptionState(): + + def __init__(self, theProject): + + self.mainConf = nw.CONFIG + self.theProject = theProject + + self.theState = {} + self.validMap = { + "GuiSession": set([ + "widthCol0", + "widthCol1", + "widthCol2", + "sortCol", + "sortOrder", + "hideZeros", + "hideNegative", + ]), + "GuiDocSplit": set([ + "spLevel", + ]), + "GuiBuildNovel": set([ + "winWidth", + "winHeight", + "addNovel", + "addNotes", + "ignoreFlag", + "justifyText", + "excludeBody", + "textFont", + "textSize", + "noStyling", + "incSynopsis", + "incComments", + "incKeywords", + "incBodyText", + ]), + "GuiOutline": set([ + "headerOrder", + "columnWidth", + "columnHidden", + ]) + } + + return + + ## + # Load and Save Cache + ## + + def loadSettings(self): + """Load the options dictionary from the project settings file. + """ + if self.theProject.projMeta is None: + return False + + stateFile = path.join(self.theProject.projMeta, nwFiles.OPTS_FILE) + theState = {} + + if path.isfile(stateFile): + logger.debug("Loading GUI options file") + try: + with open(stateFile, mode="r", encoding="utf8") as inFile: + theJson = inFile.read() + theState = json.loads(theJson) + except Exception as e: + logger.error("Failed to load GUI options file") + logger.error(str(e)) + return False + + # Filter out unused variables + for aGroup in theState: + if aGroup in self.validMap: + self.theState[aGroup] = {} + for anOpt in theState[aGroup]: + if anOpt in self.validMap[aGroup]: + self.theState[aGroup][anOpt] = theState[aGroup][anOpt] + + return True + + def saveSettings(self): + """Save the options dictionary to the project settings file. + """ + if self.theProject.projMeta is None: + return False + + stateFile = path.join(self.theProject.projMeta, nwFiles.OPTS_FILE) + logger.debug("Saving GUI options file") + + try: + with open(stateFile, mode="w+", encoding="utf8") as outFile: + outFile.write(json.dumps(self.theState, indent=2)) + except Exception as e: + logger.error("Failed to save GUI options file") + logger.error(str(e)) + return False + + return True + + ## + # Setters + ## + + def setValue(self, setGroup, setName, setValue): + """Saves a value, with a given group and name. + """ + if not setGroup in self.validMap: + logger.error("Unknown option group '%s'" % setGroup) + return False + + if not setName in self.validMap[setGroup]: + logger.error("Unknown option name '%s'" % setName) + return False + + if not setGroup in self.theState: + self.theState[setGroup] = {} + + self.theState[setGroup][setName] = setValue + + return True + + ## + # Getters + ## + + def getValue(self, getGroup, getName, defaultValue): + """Return an arbitrary type value, if it exists. Otherwise, + return the default value. + """ + if getGroup in self.theState: + if getName in self.theState[getGroup]: + try: + return self.theState[getGroup][getName] + except Exception as e: + logger.warning(str(e)) + return defaultValue + return defaultValue + + def getString(self, getGroup, getName, defaultValue): + """Return the value as a string, if it exists. Otherwise, return + the default value. + """ + if getGroup in self.theState: + if getName in self.theState[getGroup]: + try: + return str(self.theState[getGroup][getName]) + except Exception as e: + logger.warning(str(e)) + return defaultValue + return defaultValue + + def getInt(self, getGroup, getName, defaultValue): + """Return the value as an int, if it exists. Otherwise, return + the default value. + """ + if getGroup in self.theState: + if getName in self.theState[getGroup]: + try: + return int(self.theState[getGroup][getName]) + except Exception as e: + logger.warning(str(e)) + return defaultValue + return defaultValue + + def getFloat(self, getGroup, getName, defaultValue): + """Return the value as a float, if it exists. Otherwise, return + the default value. + """ + if getGroup in self.theState: + if getName in self.theState[getGroup]: + try: + return float(self.theState[getGroup][getName]) + except Exception as e: + logger.warning(str(e)) + return defaultValue + return defaultValue + + def getBool(self, getGroup, getName, defaultValue): + """Return the value as a bool, if it exists. Otherwise, return + the default value. + """ + if getGroup in self.theState: + if getName in self.theState[getGroup]: + try: + return bool(self.theState[getGroup][getName]) + except Exception as e: + logger.warning(str(e)) + return defaultValue + return defaultValue + + ## + # Validators + ## + + def validIntRange(self, theValue, intA, intB, intDefault): + """Check that an int is in a given range. If it isn't, return + the default value. + """ + if isinstance(theValue, int): + if theValue >= intA and theValue <= intB: + return theValue + return intDefault + + def validIntTuple(self, theValue, theTuple, intDefault): + """Check that an int is an element of a tuple. If it isn't, + return the default value. + """ + if isinstance(theValue, int): + if theValue in theTuple: + return theValue + return intDefault + +# END Class OptionState diff --git a/nw/core/project.py b/nw/core/project.py index 534a2554..2f700f4f 100644 --- a/nw/core/project.py +++ b/nw/core/project.py @@ -43,6 +43,7 @@ from nw.core.tree import NWTree from nw.core.item import NWItem from nw.core.document import NWDoc from nw.core.status import NWStatus +from nw.core.options import OptionState from nw.common import checkString, checkBool, checkInt, formatTimeStamp from nw.constants import ( nwFiles, nwItemType, nwItemClass, nwItemLayout, nwAlert @@ -1222,222 +1223,3 @@ class NWProject(): return # END Class NWProject - -# =============================================================================================== # -# OptionState -# Save the project-wise state of options that don't go into project XML or main config -# =============================================================================================== # - -class OptionState(): - - def __init__(self, theProject): - - self.mainConf = nw.CONFIG - self.theProject = theProject - - self.theState = {} - self.validMap = { - "GuiSession": set([ - "widthCol0", - "widthCol1", - "widthCol2", - "sortCol", - "sortOrder", - "hideZeros", - "hideNegative", - ]), - "GuiDocSplit": set([ - "spLevel", - ]), - "GuiBuildNovel": set([ - "winWidth", - "winHeight", - "addNovel", - "addNotes", - "ignoreFlag", - "justifyText", - "excludeBody", - "textFont", - "textSize", - "noStyling", - "incSynopsis", - "incComments", - "incKeywords", - "incBodyText", - ]), - "GuiOutline": set([ - "headerOrder", - "columnWidth", - "columnHidden", - ]) - } - - return - - ## - # Load and Save Cache - ## - - def loadSettings(self): - """Load the options dictionary from the project settings file. - """ - if self.theProject.projMeta is None: - return False - - stateFile = path.join(self.theProject.projMeta, nwFiles.OPTS_FILE) - theState = {} - - if path.isfile(stateFile): - logger.debug("Loading GUI options file") - try: - with open(stateFile, mode="r", encoding="utf8") as inFile: - theJson = inFile.read() - theState = json.loads(theJson) - except Exception as e: - logger.error("Failed to load GUI options file") - logger.error(str(e)) - return False - - # Filter out unused variables - for aGroup in theState: - if aGroup in self.validMap: - self.theState[aGroup] = {} - for anOpt in theState[aGroup]: - if anOpt in self.validMap[aGroup]: - self.theState[aGroup][anOpt] = theState[aGroup][anOpt] - - return True - - def saveSettings(self): - """Save the options dictionary to the project settings file. - """ - if self.theProject.projMeta is None: - return False - - stateFile = path.join(self.theProject.projMeta, nwFiles.OPTS_FILE) - logger.debug("Saving GUI options file") - - try: - with open(stateFile, mode="w+", encoding="utf8") as outFile: - outFile.write(json.dumps(self.theState, indent=2)) - except Exception as e: - logger.error("Failed to save GUI options file") - logger.error(str(e)) - return False - - return True - - ## - # Setters - ## - - def setValue(self, setGroup, setName, setValue): - """Saves a value, with a given group and name. - """ - if not setGroup in self.validMap: - logger.error("Unknown option group '%s'" % setGroup) - return False - - if not setName in self.validMap[setGroup]: - logger.error("Unknown option name '%s'" % setName) - return False - - if not setGroup in self.theState: - self.theState[setGroup] = {} - - self.theState[setGroup][setName] = setValue - - return True - - ## - # Getters - ## - - def getValue(self, getGroup, getName, defaultValue): - """Return an arbitrary type value, if it exists. Otherwise, - return the default value. - """ - if getGroup in self.theState: - if getName in self.theState[getGroup]: - try: - return self.theState[getGroup][getName] - except Exception as e: - logger.warning(str(e)) - return defaultValue - return defaultValue - - def getString(self, getGroup, getName, defaultValue): - """Return the value as a string, if it exists. Otherwise, return - the default value. - """ - if getGroup in self.theState: - if getName in self.theState[getGroup]: - try: - return str(self.theState[getGroup][getName]) - except Exception as e: - logger.warning(str(e)) - return defaultValue - return defaultValue - - def getInt(self, getGroup, getName, defaultValue): - """Return the value as an int, if it exists. Otherwise, return - the default value. - """ - if getGroup in self.theState: - if getName in self.theState[getGroup]: - try: - return int(self.theState[getGroup][getName]) - except Exception as e: - logger.warning(str(e)) - return defaultValue - return defaultValue - - def getFloat(self, getGroup, getName, defaultValue): - """Return the value as a float, if it exists. Otherwise, return - the default value. - """ - if getGroup in self.theState: - if getName in self.theState[getGroup]: - try: - return float(self.theState[getGroup][getName]) - except Exception as e: - logger.warning(str(e)) - return defaultValue - return defaultValue - - def getBool(self, getGroup, getName, defaultValue): - """Return the value as a bool, if it exists. Otherwise, return - the default value. - """ - if getGroup in self.theState: - if getName in self.theState[getGroup]: - try: - return bool(self.theState[getGroup][getName]) - except Exception as e: - logger.warning(str(e)) - return defaultValue - return defaultValue - - ## - # Validators - ## - - def validIntRange(self, theValue, intA, intB, intDefault): - """Check that an int is in a given range. If it isn't, return - the default value. - """ - if isinstance(theValue, int): - if theValue >= intA and theValue <= intB: - return theValue - return intDefault - - def validIntTuple(self, theValue, theTuple, intDefault): - """Check that an int is an element of a tuple. If it isn't, - return the default value. - """ - if isinstance(theValue, int): - if theValue in theTuple: - return theValue - return intDefault - -# END Class OptionState