def _parseLine(self, cnfParse, cnfSec, cnfName, cnfType, cnfDefault): """Parse a line and return the correct datatype. """ if cnfParse.has_section(cnfSec): if cnfParse.has_option(cnfSec, cnfName): try: 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_I_LST: return self._unpackList(cnfParse.get(cnfSec, cnfName), cnfDefault, self.CNF_I_LST) elif cnfType == self.CNF_S_LST: return self._unpackList(cnfParse.get(cnfSec, cnfName), cnfDefault, self.CNF_S_LST) except ValueError: logger.error("Failed to load value from config file.") logException() return cnfDefault return cnfDefault
def saveConfig(self): """Save the current preferences to file. """ logger.debug("Saving config file") if self.confPath is None: return False cnfParse = configparser.ConfigParser() # Set options ## Main cnfSec = "Main" cnfParse.add_section(cnfSec) cnfParse.set(cnfSec, "timestamp", formatTimeStamp(time())) cnfParse.set(cnfSec, "theme", str(self.guiTheme)) cnfParse.set(cnfSec, "syntax", str(self.guiSyntax)) cnfParse.set(cnfSec, "icons", str(self.guiIcons)) cnfParse.set(cnfSec, "guidark", str(self.guiDark)) cnfParse.set(cnfSec, "guifont", str(self.guiFont)) cnfParse.set(cnfSec, "guifontsize", str(self.guiFontSize)) cnfParse.set(cnfSec, "lastnotes", str(self.lastNotes)) cnfParse.set(cnfSec, "guilang", str(self.guiLang)) ## Sizes cnfSec = "Sizes" cnfParse.add_section(cnfSec) cnfParse.set(cnfSec, "geometry", self._packList(self.winGeometry)) cnfParse.set(cnfSec, "preferences", self._packList(self.prefGeometry)) cnfParse.set(cnfSec, "treecols", self._packList(self.treeColWidth)) cnfParse.set(cnfSec, "novelcols", self._packList(self.novelColWidth)) cnfParse.set(cnfSec, "projcols", self._packList(self.projColWidth)) cnfParse.set(cnfSec, "mainpane", self._packList(self.mainPanePos)) cnfParse.set(cnfSec, "docpane", self._packList(self.docPanePos)) cnfParse.set(cnfSec, "viewpane", self._packList(self.viewPanePos)) cnfParse.set(cnfSec, "outlinepane", self._packList(self.outlnPanePos)) cnfParse.set(cnfSec, "fullscreen", str(self.isFullScreen)) cnfParse.set(cnfSec, "hidevscroll", str(self.hideVScroll)) cnfParse.set(cnfSec, "hidehscroll", str(self.hideHScroll)) ## 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, "focuswidth", str(self.focusWidth)) cnfParse.set(cnfSec, "hidefocusfooter", str(self.hideFocusFooter)) 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, "scrollpastend", str(self.scrollPastEnd)) cnfParse.set(cnfSec, "autoscroll", str(self.autoScroll)) cnfParse.set(cnfSec, "autoscrollpos", str(self.autoScrollPos)) cnfParse.set(cnfSec, "fmtsinglequote", self._packList(self.fmtSingleQuotes)) cnfParse.set(cnfSec, "fmtdoublequote", self._packList(self.fmtDoubleQuotes)) cnfParse.set(cnfSec, "fmtpadbefore", str(self.fmtPadBefore)) cnfParse.set(cnfSec, "fmtpadafter", str(self.fmtPadAfter)) cnfParse.set(cnfSec, "fmtpadthin", str(self.fmtPadThin)) cnfParse.set(cnfSec, "spelltool", str(self.spellTool)) cnfParse.set(cnfSec, "spellcheck", str(self.spellLanguage)) cnfParse.set(cnfSec, "showtabsnspaces", str(self.showTabsNSpaces)) cnfParse.set(cnfSec, "showlineendings", str(self.showLineEndings)) cnfParse.set(cnfSec, "bigdoclimit", str(self.bigDocLimit)) cnfParse.set(cnfSec, "showfullpath", str(self.showFullPath)) cnfParse.set(cnfSec, "highlightquotes", str(self.highlightQuotes)) cnfParse.set(cnfSec, "allowopensquote", str(self.allowOpenSQuote)) cnfParse.set(cnfSec, "allowopendquote", str(self.allowOpenDQuote)) cnfParse.set(cnfSec, "highlightemph", str(self.highlightEmph)) cnfParse.set(cnfSec, "stopwhenidle", str(self.stopWhenIdle)) cnfParse.set(cnfSec, "useridletime", str(self.userIdleTime)) ## 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)) ## State cnfSec = "State" cnfParse.add_section(cnfSec) cnfParse.set(cnfSec, "showrefpanel", str(self.showRefPanel)) cnfParse.set(cnfSec, "viewcomments", str(self.viewComments)) cnfParse.set(cnfSec, "viewsynopsis", str(self.viewSynopsis)) cnfParse.set(cnfSec, "searchcase", str(self.searchCase)) cnfParse.set(cnfSec, "searchword", str(self.searchWord)) cnfParse.set(cnfSec, "searchregex", str(self.searchRegEx)) cnfParse.set(cnfSec, "searchloop", str(self.searchLoop)) cnfParse.set(cnfSec, "searchnextfile", str(self.searchNextFile)) cnfParse.set(cnfSec, "searchmatchcap", str(self.searchMatchCap)) ## Path cnfSec = "Path" cnfParse.add_section(cnfSec) cnfParse.set(cnfSec, "lastpath", str(self.lastPath)) # Write config file cnfPath = os.path.join(self.confPath, self.confFile) try: with open(cnfPath, mode="w", encoding="utf8") as outFile: cnfParse.write(outFile) self.confChanged = False except Exception as e: logger.error("Could not save config file") logException() self.hasError = True self.errData.append("Could not save config file") self.errData.append(str(e)) return False return True
def loadConfig(self): """Load preferences from file and replace default settings. """ logger.debug("Loading config file") if self.confPath is None: return False cnfParse = configparser.ConfigParser() cnfPath = os.path.join(self.confPath, self.confFile) try: with open(cnfPath, mode="r", encoding="utf8") as inFile: cnfParse.read_file(inFile) except Exception as e: logger.error("Could not load config file") logException() self.hasError = True self.errData.append("Could not load config file") self.errData.append(str(e)) 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) self.guiIcons = self._parseLine(cnfParse, cnfSec, "icons", self.CNF_STR, self.guiIcons) self.guiDark = self._parseLine(cnfParse, cnfSec, "guidark", self.CNF_BOOL, self.guiDark) self.guiFont = self._parseLine(cnfParse, cnfSec, "guifont", self.CNF_STR, self.guiFont) self.guiFontSize = self._parseLine(cnfParse, cnfSec, "guifontsize", self.CNF_INT, self.guiFontSize) self.lastNotes = self._parseLine(cnfParse, cnfSec, "lastnotes", self.CNF_STR, self.lastNotes) self.guiLang = self._parseLine(cnfParse, cnfSec, "guilang", self.CNF_STR, self.guiLang) ## Sizes cnfSec = "Sizes" self.winGeometry = self._parseLine(cnfParse, cnfSec, "geometry", self.CNF_I_LST, self.winGeometry) self.prefGeometry = self._parseLine(cnfParse, cnfSec, "preferences", self.CNF_I_LST, self.prefGeometry) self.treeColWidth = self._parseLine(cnfParse, cnfSec, "treecols", self.CNF_I_LST, self.treeColWidth) self.novelColWidth = self._parseLine(cnfParse, cnfSec, "novelcols", self.CNF_I_LST, self.novelColWidth) self.projColWidth = self._parseLine(cnfParse, cnfSec, "projcols", self.CNF_I_LST, self.projColWidth) self.mainPanePos = self._parseLine(cnfParse, cnfSec, "mainpane", self.CNF_I_LST, self.mainPanePos) self.docPanePos = self._parseLine(cnfParse, cnfSec, "docpane", self.CNF_I_LST, self.docPanePos) self.viewPanePos = self._parseLine(cnfParse, cnfSec, "viewpane", self.CNF_I_LST, self.viewPanePos) self.outlnPanePos = self._parseLine(cnfParse, cnfSec, "outlinepane", self.CNF_I_LST, self.outlnPanePos) self.isFullScreen = self._parseLine(cnfParse, cnfSec, "fullscreen", self.CNF_BOOL, self.isFullScreen) self.hideVScroll = self._parseLine(cnfParse, cnfSec, "hidevscroll", self.CNF_BOOL, self.hideVScroll) self.hideHScroll = self._parseLine(cnfParse, cnfSec, "hidehscroll", self.CNF_BOOL, self.hideHScroll) ## 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.focusWidth = self._parseLine(cnfParse, cnfSec, "focuswidth", self.CNF_INT, self.focusWidth) self.hideFocusFooter = self._parseLine(cnfParse, cnfSec, "hidefocusfooter", self.CNF_BOOL, self.hideFocusFooter) 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.scrollPastEnd = self._parseLine(cnfParse, cnfSec, "scrollpastend", self.CNF_BOOL, self.scrollPastEnd) self.autoScroll = self._parseLine(cnfParse, cnfSec, "autoscroll", self.CNF_BOOL, self.autoScroll) self.autoScrollPos = self._parseLine(cnfParse, cnfSec, "autoscrollpos", self.CNF_INT, self.autoScrollPos) self.fmtSingleQuotes = self._parseLine(cnfParse, cnfSec, "fmtsinglequote", self.CNF_S_LST, self.fmtSingleQuotes) self.fmtDoubleQuotes = self._parseLine(cnfParse, cnfSec, "fmtdoublequote", self.CNF_S_LST, self.fmtDoubleQuotes) self.fmtPadBefore = self._parseLine(cnfParse, cnfSec, "fmtpadbefore", self.CNF_STR, self.fmtPadBefore) self.fmtPadAfter = self._parseLine(cnfParse, cnfSec, "fmtpadafter", self.CNF_STR, self.fmtPadAfter) self.fmtPadThin = self._parseLine(cnfParse, cnfSec, "fmtpadthin", self.CNF_BOOL, self.fmtPadThin) self.spellTool = self._parseLine(cnfParse, cnfSec, "spelltool", self.CNF_STR, self.spellTool) self.spellLanguage = self._parseLine(cnfParse, cnfSec, "spellcheck", self.CNF_STR, self.spellLanguage) self.showTabsNSpaces = self._parseLine(cnfParse, cnfSec, "showtabsnspaces", self.CNF_BOOL, self.showTabsNSpaces) self.showLineEndings = self._parseLine(cnfParse, cnfSec, "showlineendings", self.CNF_BOOL, self.showLineEndings) self.bigDocLimit = self._parseLine(cnfParse, cnfSec, "bigdoclimit", self.CNF_INT, self.bigDocLimit) self.showFullPath = self._parseLine(cnfParse, cnfSec, "showfullpath", self.CNF_BOOL, self.showFullPath) self.highlightQuotes = self._parseLine(cnfParse, cnfSec, "highlightquotes", self.CNF_BOOL, self.highlightQuotes) self.allowOpenSQuote = self._parseLine(cnfParse, cnfSec, "allowopensquote", self.CNF_BOOL, self.allowOpenSQuote) self.allowOpenDQuote = self._parseLine(cnfParse, cnfSec, "allowopendquote", self.CNF_BOOL, self.allowOpenDQuote) self.highlightEmph = self._parseLine(cnfParse, cnfSec, "highlightemph", self.CNF_BOOL, self.highlightEmph) self.stopWhenIdle = self._parseLine(cnfParse, cnfSec, "stopwhenidle", self.CNF_BOOL, self.stopWhenIdle) self.userIdleTime = self._parseLine(cnfParse, cnfSec, "useridletime", self.CNF_INT, self.userIdleTime) ## 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) ## State cnfSec = "State" self.showRefPanel = self._parseLine(cnfParse, cnfSec, "showrefpanel", self.CNF_BOOL, self.showRefPanel) self.viewComments = self._parseLine(cnfParse, cnfSec, "viewcomments", self.CNF_BOOL, self.viewComments) self.viewSynopsis = self._parseLine(cnfParse, cnfSec, "viewsynopsis", self.CNF_BOOL, self.viewSynopsis) self.searchCase = self._parseLine(cnfParse, cnfSec, "searchcase", self.CNF_BOOL, self.searchCase) self.searchWord = self._parseLine(cnfParse, cnfSec, "searchword", self.CNF_BOOL, self.searchWord) self.searchRegEx = self._parseLine(cnfParse, cnfSec, "searchregex", self.CNF_BOOL, self.searchRegEx) self.searchLoop = self._parseLine(cnfParse, cnfSec, "searchloop", self.CNF_BOOL, self.searchLoop) self.searchNextFile = self._parseLine(cnfParse, cnfSec, "searchnextfile", self.CNF_BOOL, self.searchNextFile) self.searchMatchCap = self._parseLine(cnfParse, cnfSec, "searchmatchcap", self.CNF_BOOL, self.searchMatchCap) ## Path cnfSec = "Path" self.lastPath = self._parseLine(cnfParse, cnfSec, "lastpath", self.CNF_STR, self.lastPath) # Check Certain Values for None self.spellLanguage = self._checkNone(self.spellLanguage) # If we're using straight quotes, disable auto-replace if self.fmtSingleQuotes == ["'", "'"] and self.doReplaceSQuote: logger.info( "Using straight single quotes, so disabling auto-replace") self.doReplaceSQuote = False if self.fmtDoubleQuotes == ["\"", "\""] and self.doReplaceDQuote: logger.info( "Using straight double quotes, so disabling auto-replace") self.doReplaceDQuote = False return True
def initConfig(self, confPath=None, dataPath=None): """Initialise the config class. The manual setting of confPath and dataPath is mainly intended for the test suite. """ logger.debug("Initialising Config ...") if confPath is None: confRoot = QStandardPaths.writableLocation( QStandardPaths.ConfigLocation) self.confPath = os.path.join(os.path.abspath(confRoot), self.appHandle) else: logger.info("Setting config from alternative path: %s" % confPath) self.confPath = confPath if dataPath is None: if self.verQtValue >= 50400: dataRoot = QStandardPaths.writableLocation( QStandardPaths.AppDataLocation) else: dataRoot = QStandardPaths.writableLocation( QStandardPaths.DataLocation) self.dataPath = os.path.join(os.path.abspath(dataRoot), self.appHandle) else: logger.info("Setting data path from alternative path: %s" % dataPath) self.dataPath = dataPath logger.verbose("Config path: %s" % self.confPath) logger.verbose("Data path: %s" % self.dataPath) self.confFile = self.appHandle + ".conf" self.lastPath = os.path.expanduser("~") self.appPath = getattr(sys, "_MEIPASS", os.path.abspath(os.path.dirname(__file__))) self.appRoot = os.path.abspath( os.path.join(self.appPath, os.path.pardir)) if os.path.isfile(self.appRoot): # novelWriter is packaged as a single file, so the app and # root paths are the same, and equal to the folder that # contains the single executable. self.appRoot = os.path.dirname(self.appRoot) self.appPath = self.appRoot # Assets self.assetPath = os.path.join(self.appPath, "assets") self.themeRoot = os.path.join(self.assetPath, "themes") self.dictPath = os.path.join(self.assetPath, "dict") self.iconPath = os.path.join(self.assetPath, "icons") self.appIcon = os.path.join(self.iconPath, "novelwriter.svg") # Internationalisation self.nwLangPath = os.path.join(self.appRoot, "i18n") logger.verbose("App path: %s" % self.appPath) logger.verbose("Last path: %s" % self.lastPath) # If config folder does not exist, create it. # This assumes that the os config folder itself exists. if not os.path.isdir(self.confPath): try: os.mkdir(self.confPath) except Exception as e: logger.error("Could not create folder: %s" % self.confPath) logException() self.hasError = True self.errData.append("Could not create folder: %s" % self.confPath) self.errData.append(str(e)) self.confPath = None # Check if config file exists if self.confPath is not None: if os.path.isfile(os.path.join(self.confPath, self.confFile)): # If it exists, load it self.loadConfig() else: # If it does not exist, save a copy of the default values self.saveConfig() # If data folder does not exist, make it. # This assumes that the os data folder itself exists. if self.dataPath is not None: if not os.path.isdir(self.dataPath): try: os.mkdir(self.dataPath) except Exception as e: logger.error("Could not create folder: %s" % self.dataPath) logException() self.hasError = True self.errData.append("Could not create folder: %s" % self.dataPath) self.errData.append(str(e)) self.dataPath = None # Host and Kernel if self.verQtValue >= 50600: self.hostName = QSysInfo.machineHostName() self.kernelVer = QSysInfo.kernelVersion() # Load recent projects cache self.loadRecentCache() # Check the availability of optional packages self._checkOptionalPackages() if self.spellTool is None: self.spellTool = nwConst.SP_INTERNAL if self.spellLanguage is None: self.spellLanguage = "en" # Check if local help files exist self.helpPath = os.path.join(self.assetPath, "help", "novelWriter.qhc") self.hasHelp = os.path.isfile(self.helpPath) self.hasHelp &= os.path.isfile( os.path.join(self.assetPath, "help", "novelWriter.qch")) logger.debug("Config initialisation complete") return True
def main(sysArgs=None): """Parses command line, sets up logging, and launches main GUI. """ if sysArgs is None: sysArgs = sys.argv[1:] # Valid Input Options shortOpt = "hv" longOpt = [ "help", "version", "info", "debug", "verbose", "style=", "config=", "data=", "testmode", ] helpMsg = ( "novelWriter {version} ({date})\n" "{copyright}\n" "\n" "This program is distributed in the hope that it will be useful,\n" "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" "GNU General Public Licence for more details.\n" "\n" "Usage:\n" " -h, --help Print this message.\n" " -v, --version Print program version and exit.\n" " --info Print additional runtime information.\n" " --debug Print debug output. Includes --info.\n" " --verbose Increase verbosity of debug output. Includes --debug.\n" " --style= Sets Qt5 style flag. Defaults to 'Fusion'.\n" " --config= Alternative config file.\n" " --data= Alternative user data path.\n" ).format( version = __version__, copyright = __copyright__, date = __date__, ) # Defaults logLevel = logging.WARN logFormat = "{levelname:8} {message:}" confPath = None dataPath = None testMode = False qtStyle = "Fusion" cmdOpen = None # Parse Options try: inOpts, inRemain = getopt.getopt(sysArgs, shortOpt, longOpt) except getopt.GetoptError as E: print(helpMsg) print("ERROR: %s" % str(E)) sys.exit(2) if len(inRemain) > 0: cmdOpen = inRemain[0] for inOpt, inArg in inOpts: if inOpt in ("-h", "--help"): print(helpMsg) sys.exit(0) elif inOpt in ("-v", "--version"): print("novelWriter Version %s [%s]" % (__version__, __date__)) sys.exit(0) elif inOpt == "--info": logLevel = logging.INFO elif inOpt == "--debug": logLevel = logging.DEBUG logFormat = "[{asctime:}] {name:>22}:{lineno:<4d} {levelname:8} {message:}" elif inOpt == "--verbose": logLevel = VERBOSE logFormat = "[{asctime:}] {name:>22}:{lineno:<4d} {levelname:8} {message:}" elif inOpt == "--style": qtStyle = inArg elif inOpt == "--config": confPath = inArg elif inOpt == "--data": dataPath = inArg elif inOpt == "--testmode": testMode = True # Set Config Options CONFIG.cmdOpen = cmdOpen # Set Logging cHandle = logging.StreamHandler() cHandle.setFormatter(logging.Formatter(fmt=logFormat, style="{")) pkgLogger = logging.getLogger(__package__) pkgLogger.addHandler(cHandle) pkgLogger.setLevel(logLevel) logger.info("Starting novelWriter %s (%s) %s" % ( __version__, __hexversion__, __date__ )) # Check Packages and Versions errorData = [] errorCode = 0 if sys.hexversion < 0x030600f0: errorData.append( "At least Python 3.6.0 is required, found %s." % CONFIG.verPyString ) errorCode |= 4 if CONFIG.verQtValue < 50300: errorData.append( "At least Qt5 version 5.3 is required, found %s." % CONFIG.verQtString ) errorCode |= 8 if CONFIG.verPyQtValue < 50300: errorData.append( "At least PyQt5 version 5.3 is required, found %s." % CONFIG.verPyQtString ) errorCode |= 16 try: import lxml # noqa: F401 except ImportError: errorData.append("Python module 'lxml' is missing.") errorCode |= 32 if errorData: errApp = QApplication([]) errMsg = QErrorMessage() errMsg.resize(500, 300) errMsg.showMessage(( "<h3>A critical error has been encountered</h3>" "<p>novelWriter cannot start due to the following issues:<p>" "<p> - %s</p>" "<p>Shutting down ...</p>" ) % ( "<br> - ".join(errorData) )) for errMsg in errorData: logger.critical(errMsg) errApp.exec_() sys.exit(errorCode) # Finish initialising config CONFIG.initConfig(confPath, dataPath) if CONFIG.osDarwin: try: from Foundation import NSBundle bundle = NSBundle.mainBundle() info = bundle.localizedInfoDictionary() or bundle.infoDictionary() info["CFBundleName"] = "novelWriter" except ImportError: logger.error("Failed to set application name") logException() # Import GUI (after dependency checks), and launch from nw.guimain import GuiMain if testMode: nwGUI = GuiMain() return nwGUI else: nwApp = QApplication([CONFIG.appName, ("-style=%s" % qtStyle)]) nwApp.setApplicationName(CONFIG.appName) nwApp.setApplicationVersion(__version__) nwApp.setWindowIcon(QIcon(CONFIG.appIcon)) nwApp.setOrganizationDomain(__domain__) # Connect the exception handler before making the main GUI sys.excepthook = exceptionHandler # Launch main GUI CONFIG.initLocalisation(nwApp) nwGUI = GuiMain() if not nwGUI.hasProject: nwGUI.showProjectLoadDialog() nwGUI.releaseNotes() sys.exit(nwApp.exec_())