Example #1
0
    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
Example #2
0
    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
Example #3
0
    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
Example #4
0
    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
Example #5
0
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>&nbsp;-&nbsp;%s</p>"
            "<p>Shutting down ...</p>"
        ) % (
            "<br>&nbsp;-&nbsp;".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_())