Exemple #1
0
    def _doSave(self):
        """Save the new word list and close.
        """
        self._saveGuiSettings()

        dctFile = os.path.join(self.theProject.projMeta, nwFiles.PROJ_DICT)
        tmpFile = dctFile + "~"

        try:
            with open(tmpFile, mode="w", encoding="utf-8") as outFile:
                for i in range(self.listBox.count()):
                    outFile.write(self.listBox.item(i).text() + "\n")

        except Exception:
            logger.error("Could not save new word list")
            logException()
            self.reject()
            return False

        if os.path.isfile(dctFile):
            os.unlink(dctFile)
        os.rename(tmpFile, dctFile)
        self.accept()

        return True
Exemple #2
0
    def loadIndex(self):
        """Load index from last session from the project meta folder.
        """
        theData = {}
        indexFile = os.path.join(self.theProject.projMeta, nwFiles.INDEX_FILE)
        tStart = time()

        if os.path.isfile(indexFile):
            logger.debug("Loading index file")
            try:
                with open(indexFile, mode="r", encoding="utf-8") as inFile:
                    theData = json.load(inFile)

            except Exception:
                logger.error("Failed to load index file")
                logException()
                self._indexBroken = True
                return False

            self._tagIndex = theData.get("tagIndex", {})
            self._refIndex = theData.get("refIndex", {})
            self._fileIndex = theData.get("fileIndex", {})
            self._fileMeta = theData.get("fileMeta", {})

            nowTime = round(time())
            self._timeNovel = nowTime
            self._timeNotes = nowTime
            self._timeIndex = nowTime

        logger.verbose("Index loaded in %.3f ms", (time() - tStart)*1000)

        self._checkIndex()

        return True
Exemple #3
0
    def saveIndex(self):
        """Save the current index as a json file in the project meta
        data folder.
        """
        logger.debug("Saving index file")
        indexFile = os.path.join(self.theProject.projMeta, nwFiles.INDEX_FILE)
        tStart = time()

        try:
            with open(indexFile, mode="w+", encoding="utf-8") as outFile:
                outFile.write("{\n")
                outFile.write(f'  "tagIndex": {jsonEncode(self._tagIndex, n=1, nmax=2)},\n')
                outFile.write(f'  "refIndex": {jsonEncode(self._refIndex, n=1, nmax=3)},\n')
                outFile.write(f'  "fileIndex": {jsonEncode(self._fileIndex, n=1, nmax=3)},\n')
                outFile.write(f'  "fileMeta": {jsonEncode(self._fileMeta, n=1, nmax=2)}\n')
                outFile.write("}\n")

        except Exception:
            logger.error("Failed to save index file")
            logException()
            return False

        logger.verbose("Index saved in %.3f ms", (time() - tStart)*1000)

        return True
Exemple #4
0
    def _checkIndex(self):
        """Check that the entries in the index are valid and contain the
        elements it should. Also check that each file present in the
        contents folder when the project was loaded are also present in
        the fileMeta index.
        """
        logger.debug("Checking index")
        tStart = time()

        try:
            self._checkTagIndex()
            self._checkRefIndex()
            self._checkFileIndex()
            self._checkFileMeta()
            self._indexBroken = False

        except Exception:
            logger.error("Error while checking index")
            logException()
            self._indexBroken = True

        # Check that project files are indexed
        for fHandle in self.theProject.projFiles:
            if fHandle not in self._fileMeta:
                self._indexBroken = True
                break

        logger.verbose("Index check completed in %.3f ms", (time() - tStart)*1000)

        if self._indexBroken:
            self.clearIndex()

        return
Exemple #5
0
    def _loadCache(self):
        """Save the current data to cache.
        """
        buildCache = os.path.join(self.theProject.projCache, nwFiles.BUILD_CACHE)
        dataCount = 0
        if os.path.isfile(buildCache):
            logger.debug("Loading build cache")
            try:
                with open(buildCache, mode="r", encoding="utf-8") as inFile:
                    theJson = inFile.read()
                theData = json.loads(theJson)
            except Exception:
                logger.error("Failed to load build cache")
                logException()
                return False

            if "buildTime" in theData.keys():
                self.buildTime = theData["buildTime"]
            if "htmlStyle" in theData.keys():
                self.htmlStyle = theData["htmlStyle"]
                dataCount += 1
            if "htmlText" in theData.keys():
                self.htmlText = theData["htmlText"]
                dataCount += 1

        return dataCount == 2
Exemple #6
0
    def loadSettings(self):
        """Load the options dictionary from the project settings file.
        """
        if self.theProject.projMeta is None:
            return False

        stateFile = os.path.join(self.theProject.projMeta, nwFiles.OPTS_FILE)
        theState = {}

        if os.path.isfile(stateFile):
            logger.debug("Loading GUI options file")
            try:
                with open(stateFile, mode="r", encoding="utf-8") as inFile:
                    theState = json.load(inFile)
            except Exception:
                logger.error("Failed to load GUI options file")
                logException()
                return False

        # Filter out unused variables
        for aGroup in theState:
            if aGroup in VALID_MAP:
                self._theState[aGroup] = {}
                for anOpt in theState[aGroup]:
                    if anOpt in VALID_MAP[aGroup]:
                        self._theState[aGroup][anOpt] = theState[aGroup][anOpt]

        return True
Exemple #7
0
    def _readProjectDictionary(self, projectDict):
        """Read the content of the project dictionary, and add it to the
        lookup lists.
        """
        self._projDict = set()
        self._projectDict = projectDict

        if projectDict is None:
            return False

        if not os.path.isfile(projectDict):
            return False

        try:
            logger.debug("Loading project word list")
            with open(projectDict, mode="r", encoding="utf-8") as wordsFile:
                for theLine in wordsFile:
                    theLine = theLine.strip()
                    if len(theLine) > 0 and theLine not in self._projDict:
                        self._projDict.add(theLine)
            logger.debug("Project word list contains %d words", len(self._projDict))

        except Exception:
            logger.error("Failed to load project word list")
            logException()
            return False

        return True
Exemple #8
0
def _loadInternalName(confParser, confFile):
    """Open a conf file and read the 'name' setting.
    """
    try:
        with open(confFile, mode="r", encoding="utf-8") as inFile:
            confParser.read_file(inFile)
    except Exception:
        logger.error("Could not load file: %s", confFile)
        logException()
        return ""

    return confParser.rdStr("Main", "name", "")
Exemple #9
0
    def loadSyntax(self):
        """Load the currently specified syntax highlighter theme.
        """
        logger.info("Loading syntax theme '%s'", self.guiSyntax)

        confParser = NWConfigParser()
        try:
            with open(self.syntaxFile, mode="r", encoding="utf-8") as inFile:
                confParser.read_file(inFile)
        except Exception:
            logger.error("Could not load syntax colours from: %s",
                         self.syntaxFile)
            logException()
            return False

        # Main
        cnfSec = "Main"
        if confParser.has_section(cnfSec):
            self.syntaxName = confParser.rdStr(cnfSec, "name", "")
            self.syntaxDescription = confParser.rdStr(cnfSec, "description",
                                                      "")
            self.syntaxAuthor = confParser.rdStr(cnfSec, "author", "")
            self.syntaxCredit = confParser.rdStr(cnfSec, "credit", "")
            self.syntaxUrl = confParser.rdStr(cnfSec, "url", "")
            self.syntaxLicense = confParser.rdStr(cnfSec, "license", "")
            self.syntaxLicenseUrl = confParser.rdStr(cnfSec, "licenseurl", "")

        # Syntax
        cnfSec = "Syntax"
        if confParser.has_section(cnfSec):
            self.colBack = self._loadColour(confParser, cnfSec, "background")
            self.colText = self._loadColour(confParser, cnfSec, "text")
            self.colLink = self._loadColour(confParser, cnfSec, "link")
            self.colHead = self._loadColour(confParser, cnfSec, "headertext")
            self.colHeadH = self._loadColour(confParser, cnfSec, "headertag")
            self.colEmph = self._loadColour(confParser, cnfSec, "emphasis")
            self.colDialN = self._loadColour(confParser, cnfSec,
                                             "straightquotes")
            self.colDialD = self._loadColour(confParser, cnfSec,
                                             "doublequotes")
            self.colDialS = self._loadColour(confParser, cnfSec,
                                             "singlequotes")
            self.colHidden = self._loadColour(confParser, cnfSec, "hidden")
            self.colKey = self._loadColour(confParser, cnfSec, "keyword")
            self.colVal = self._loadColour(confParser, cnfSec, "value")
            self.colSpell = self._loadColour(confParser, cnfSec,
                                             "spellcheckline")
            self.colError = self._loadColour(confParser, cnfSec, "errorline")
            self.colRepTag = self._loadColour(confParser, cnfSec, "replacetag")
            self.colMod = self._loadColour(confParser, cnfSec, "modifier")

        return True
Exemple #10
0
    def describeDict(self):
        """Return the tag and provider of the currently loaded
        dictionary.
        """
        try:
            spTag = self._theDict.tag
            spName = self._theDict.provider.name
        except Exception:
            logger.error("Failed to extract information about the dictionary")
            logException()
            spTag = ""
            spName = ""

        return spTag, spName
Exemple #11
0
def readTextFile(filePath):
    """Read the content of a text file in a robust manner.
    """
    if not os.path.isfile(filePath):
        return ""

    fileText = ""
    try:
        with open(filePath, mode="r", encoding="utf-8") as inFile:
            fileText = inFile.read()
    except Exception:
        logger.error("Could not read file: %s", filePath)
        logException()
        return ""

    return fileText
Exemple #12
0
def sha256sum(filePath):
    """Make a shasum of a file using a buffer.
    Based on: https://stackoverflow.com/a/44873382/5825851
    """
    hDigest = hashlib.sha256()
    bData = bytearray(65536)
    mData = memoryview(bData)
    try:
        with open(filePath, mode="rb", buffering=0) as inFile:
            for n in iter(lambda: inFile.readinto(mData), 0):
                hDigest.update(mData[:n])
    except Exception:
        logger.error("Could not create sha256sum of: %s", filePath)
        logException()
        return None

    return hDigest.hexdigest()
Exemple #13
0
    def _saveCache(self):
        """Save the current data to cache.
        """
        buildCache = os.path.join(self.theProject.projCache, nwFiles.BUILD_CACHE)
        logger.debug("Saving build cache")
        try:
            with open(buildCache, mode="w+", encoding="utf-8") as outFile:
                outFile.write(json.dumps({
                    "buildTime": self.buildTime,
                    "htmlStyle": self.htmlStyle,
                    "htmlText": self.htmlText,
                }, indent=2))
        except Exception:
            logger.error("Failed to save build cache")
            logException()
            return False

        return True
Exemple #14
0
    def saveSettings(self):
        """Save the options dictionary to the project settings file.
        """
        if self.theProject.projMeta is None:
            return False

        stateFile = os.path.join(self.theProject.projMeta, nwFiles.OPTS_FILE)
        logger.debug("Saving GUI options file")

        try:
            with open(stateFile, mode="w+", encoding="utf-8") as outFile:
                json.dump(self._theState, outFile, indent=2)
        except Exception:
            logger.error("Failed to save GUI options file")
            logException()
            return False

        return True
Exemple #15
0
    def writeToCFile(self):
        """Write the convenience table of contents file in the root of
        the project directory.
        """
        tocList = []
        tocLen = 0
        for tHandle in self._treeOrder:
            tItem = self.__getitem__(tHandle)
            if tItem is None:
                continue
            tFile = tHandle + ".nwd"
            if os.path.isfile(os.path.join(self.theProject.projContent,
                                           tFile)):
                tocLine = "{0:<25s}  {1:<9s}  {2:<8s}  {3:s}".format(
                    os.path.join("content", tFile),
                    tItem.itemClass.name,
                    tItem.itemLayout.name,
                    tItem.itemName,
                )
                tocList.append(tocLine)
                tocLen = max(tocLen, len(tocLine))

        try:
            # Dump the text
            tocText = os.path.join(self.theProject.projPath, nwFiles.TOC_TXT)
            with open(tocText, mode="w", encoding="utf-8") as outFile:
                outFile.write("\n")
                outFile.write("Table of Contents\n")
                outFile.write("=================\n")
                outFile.write("\n")
                outFile.write("{0:<25s}  {1:<9s}  {2:<8s}  {3:s}\n".format(
                    "File Name", "Class", "Layout", "Document Label"))
                outFile.write("-" * max(tocLen, 62) + "\n")
                outFile.write("\n".join(tocList))
                outFile.write("\n")

        except Exception:
            logger.error("Could not write ToC file")
            logException()
            return False

        return True
Exemple #16
0
    def loadIndex(self):
        """Load index from last session from the project meta folder.
        """
        theData = {}
        indexFile = os.path.join(self.theProject.projMeta, nwFiles.INDEX_FILE)
        tStart = time()

        self._indexBroken = False
        if os.path.isfile(indexFile):
            logger.debug("Loading index file")
            try:
                with open(indexFile, mode="r", encoding="utf-8") as inFile:
                    theData = json.load(inFile)
            except Exception:
                logger.error("Failed to load index file")
                logException()
                self._indexBroken = True
                return False

            try:
                self._tagsIndex.unpackData(theData["tagsIndex"])
                self._itemIndex.unpackData(theData["itemIndex"])
            except Exception:
                logger.error("The index content is invalid")
                logException()
                self._indexBroken = True
                return False

        logger.debug("Checking index")

        # Check that all files are indexed
        for fHandle in self.theProject.projFiles:
            if fHandle not in self._itemIndex:
                logger.warning("Item '%s' is not in the index", fHandle)
                self.reIndexHandle(fHandle)

        self._indexChange = round(time())

        logger.verbose("Index loaded in %.3f ms", (time() - tStart) * 1000)

        return True
Exemple #17
0
    def addWord(self, newWord):
        """Add a word to the project dictionary.
        """
        try:
            self._theDict.add_to_session(newWord)
        except Exception:
            return False

        if self._projectDict is not None and newWord not in self._projDict:
            newWord = newWord.strip()
            try:
                with open(self._projectDict, mode="a+", encoding="utf-8") as outFile:
                    outFile.write("%s\n" % newWord)
                self._projDict.add(newWord)
            except Exception:
                logger.error("Failed to add word to project word list %s", str(self._projectDict))
                logException()
                return False
            return True

        return False
Exemple #18
0
    def _parseLine(self, section, option, default, type):
        """Parse a line and return the correct datatype.
        """
        if self.has_option(section, option):
            try:
                if type == self.CNF_STR:
                    return self.get(section, option)
                elif type == self.CNF_INT:
                    return self.getint(section, option)
                elif type == self.CNF_FLOAT:
                    return self.getfloat(section, option)
                elif type == self.CNF_BOOL:
                    return self.getboolean(section, option)
                elif type in (self.CNF_I_LST, self.CNF_S_LST):
                    return self._unpackList(self.get(section, option), default, type)
            except ValueError:
                logger.error("Could not read '%s':'%s' from config", str(section), str(option))
                logException()
                return default

        return default
Exemple #19
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)

        # Check Data Path Subdirs
        dataDirs = ["syntax", "themes"]
        for dataDir in dataDirs:
            dirPath = os.path.join(self.dataPath, dataDir)
            if not os.path.isdir(dirPath):
                try:
                    os.mkdir(dirPath)
                    logger.info("Created folder: %s", dirPath)
                except Exception:
                    logger.error("Could not create folder: %s", dirPath)
                    logException()

        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.appIcon = os.path.join(self.assetPath, "icons", "novelwriter.svg")

        # Internationalisation
        self.nwLangPath = os.path.join(self.assetPath, "i18n")

        logger.debug("Assets: %s", self.assetPath)
        logger.verbose("App path: %s", self.appPath)
        logger.verbose("Last path: %s", self.lastPath)

        # If the 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 exc:
                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(formatException(exc))
                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 the data folder does not exist, create 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 exc:
                    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(formatException(exc))
                    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.spellLanguage is None:
            self.spellLanguage = "en"

        # Look for a PDF version of the manual
        pdfDocs = os.path.join(self.assetPath, "manual.pdf")
        if os.path.isfile(pdfDocs):
            logger.debug("Found manual: %s", pdfDocs)
            self.pdfDocs = pdfDocs

        logger.debug("Config initialisation complete")

        return True
Exemple #20
0
def main(sysArgs=None):
    """Parse command line, set up logging, and launch 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 = (
        f"novelWriter {__version__} ({__date__})\n"
        f"{__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")

    # 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 exc:
        print(helpMsg)
        print(f"ERROR: {str(exc)}")
        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:}]  {filename:>17}:{lineno:<4d}  {levelname:8}  {message:}"
        elif inOpt == "--verbose":
            logLevel = VERBOSE
            logFormat = "[{asctime:}]  {filename:>17}:{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 < 0x030700f0:
        errorData.append("At least Python 3.7 is required, found %s" %
                         CONFIG.verPyString)
        errorCode |= 0x04
    if CONFIG.verQtValue < 50300:
        errorData.append("At least Qt5 version 5.3 is required, found %s" %
                         CONFIG.verQtString)
        errorCode |= 0x08
    if CONFIG.verPyQtValue < 50300:
        errorData.append("At least PyQt5 version 5.3 is required, found %s" %
                         CONFIG.verPyQtString)
        errorCode |= 0x10

    try:
        import lxml  # noqa: F401
    except ImportError:
        errorData.append("Python module 'lxml' is missing")
        errorCode |= 0x20

    if errorData:
        errApp = QApplication([])
        errDlg = QErrorMessage()
        errDlg.resize(500, 300)
        errDlg.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 errLine in errorData:
            logger.critical(errLine)
        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 Exception:
            logger.error("Failed to set application name")
            logException()

    elif CONFIG.osWindows:
        try:
            import ctypes
            appID = f"io.novelwriter.{__version__}"
            ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
                appID)
        except Exception:
            logger.error("Failed to set application name")
            logException()

    # Import GUI (after dependency checks), and launch
    from novelwriter.guimain import GuiMain
    if testMode:
        nwGUI = GuiMain()
        return nwGUI

    else:
        nwApp = QApplication([CONFIG.appName, (f"-style={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_())
Exemple #21
0
    def _doBuild(self, bldObj, isPreview=False, doConvert=True):
        """Rund the build with a specific build object.
        """
        tStart = int(time())

        # Get Settings
        fmtTitle      = self.fmtTitle.text()
        fmtChapter    = self.fmtChapter.text()
        fmtUnnumbered = self.fmtUnnumbered.text()
        fmtScene      = self.fmtScene.text()
        fmtSection    = self.fmtSection.text()
        buildLang     = self.buildLang.currentData()
        hideScene     = self.hideScene.isChecked()
        hideSection   = self.hideSection.isChecked()
        textFont      = self.textFont.text()
        textSize      = self.textSize.value()
        lineHeight    = self.lineHeight.value()
        justifyText   = self.justifyText.isChecked()
        noStyling     = self.noStyling.isChecked()
        incSynopsis   = self.includeSynopsis.isChecked()
        incComments   = self.includeComments.isChecked()
        incKeywords   = self.includeKeywords.isChecked()
        novelFiles    = self.novelFiles.isChecked()
        noteFiles     = self.noteFiles.isChecked()
        ignoreFlag    = self.ignoreFlag.isChecked()
        includeBody   = self.includeBody.isChecked()
        replaceUCode  = self.replaceUCode.isChecked()

        # The language lookup dict is reloaded if needed
        self.theProject.setProjectLang(buildLang)

        # Get font information
        fontInfo = QFontInfo(QFont(textFont, textSize))
        textFixed = fontInfo.fixedPitch()

        isHtml = isinstance(bldObj, ToHtml)
        isOdt = isinstance(bldObj, ToOdt)

        bldObj.setTitleFormat(fmtTitle)
        bldObj.setChapterFormat(fmtChapter)
        bldObj.setUnNumberedFormat(fmtUnnumbered)
        bldObj.setSceneFormat(fmtScene, hideScene)
        bldObj.setSectionFormat(fmtSection, hideSection)

        bldObj.setFont(textFont, textSize, textFixed)
        bldObj.setJustify(justifyText)
        bldObj.setLineHeight(lineHeight)

        bldObj.setSynopsis(incSynopsis)
        bldObj.setComments(incComments)
        bldObj.setKeywords(incKeywords)
        bldObj.setBodyText(includeBody)

        if isHtml:
            bldObj.setStyles(not noStyling)
            bldObj.setReplaceUnicode(replaceUCode)

        if isOdt:
            bldObj.setColourHeaders(not noStyling)
            bldObj.setLanguage(buildLang)
            bldObj.initDocument()

        # Make sure the project and document is up to date
        self.mainGui.saveDocument()

        self.buildProgress.setMaximum(len(self.theProject.tree))
        self.buildProgress.setValue(0)

        for nItt, tItem in enumerate(self.theProject.tree):

            noteRoot = noteFiles
            noteRoot &= tItem.itemType == nwItemType.ROOT
            noteRoot &= tItem.itemClass != nwItemClass.NOVEL
            noteRoot &= tItem.itemClass != nwItemClass.ARCHIVE

            try:
                if noteRoot:
                    # Add headers for root folders of notes
                    bldObj.addRootHeading(tItem.itemHandle)
                    if doConvert:
                        bldObj.doConvert()

                elif self._checkInclude(tItem, noteFiles, novelFiles, ignoreFlag):
                    bldObj.setText(tItem.itemHandle)
                    bldObj.doPreProcessing()
                    bldObj.tokenizeText()
                    bldObj.doHeaders()
                    if doConvert:
                        bldObj.doConvert()
                    bldObj.doPostProcessing()

            except Exception:
                logger.error("Failed to build document '%s'", tItem.itemHandle)
                logException()
                if isPreview:
                    self.docView.setText((
                        "Failed to generate preview. "
                        "Document with title '%s' could not be parsed."
                    ) % tItem.itemName)

                return False

            # Update progress bar, also for skipped items
            self.buildProgress.setValue(nItt+1)

        if isOdt:
            bldObj.closeDocument()

        tEnd = int(time())
        logger.debug("Built project in %.3f ms", 1000*(tEnd - tStart))

        if bldObj.errData:
            self.mainGui.makeAlert([
                self.tr("There were problems when building the project:")
            ] + bldObj.errData, nwAlert.ERROR)

        return
Exemple #22
0
    def loadText(self, tHandle, updateHistory=True):
        """Load text into the viewer from an item handle.
        """
        if not self.theProject.tree.checkType(tHandle, nwItemType.FILE):
            logger.warning("Item not found")
            return False

        logger.debug("Generating preview for item '%s'", tHandle)
        qApp.setOverrideCursor(QCursor(Qt.WaitCursor))

        sPos = self.verticalScrollBar().value()
        aDoc = ToHtml(self.theProject)
        aDoc.setPreview(self.mainConf.viewComments, self.mainConf.viewSynopsis)
        aDoc.setLinkHeaders(True)

        # Be extra careful here to prevent crashes when first opening a
        # project as a crash here leaves no way of recovering.
        # See issue #298
        try:
            aDoc.setText(tHandle)
            aDoc.doPreProcessing()
            aDoc.tokenizeText()
            aDoc.doConvert()
            aDoc.doPostProcessing()
        except Exception:
            logger.error("Failed to generate preview for document with handle '%s'", tHandle)
            logException()
            self.setText(self.tr("An error occurred while generating the preview."))
            qApp.restoreOverrideCursor()
            return False

        # Refresh the tab stops
        if self.mainConf.verQtValue >= 51000:
            self.setTabStopDistance(self.mainConf.getTabWidth())
        else:
            self.setTabStopWidth(self.mainConf.getTabWidth())

        # Must be before setHtml
        if updateHistory:
            self.docHistory.append(tHandle)

        self.setHtml(aDoc.theResult.replace("\t", "!!tab!!"))
        self.setDocumentTitle(tHandle)

        # Loop through the text and put back in the tabs. Tabs are removed by
        # the setHtml function, so the ToHtml class puts in a placeholder.
        while self.find("!!tab!!"):
            theCursor = self.textCursor()
            theCursor.insertText("\t")

        if self._docHandle == tHandle:
            # This is a refresh, so we set the scrollbar back to where it was
            self.verticalScrollBar().setValue(sPos)

        self._docHandle = tHandle
        self.theProject.setLastViewed(tHandle)
        self.docHeader.setTitleFromHandle(self._docHandle)
        self.updateDocMargins()

        # Make sure the main GUI knows we changed the content
        self.mainGui.viewMeta.refreshReferences(tHandle)

        # Since we change the content while it may still be rendering, we mark
        # the document dirty again to make sure it's re-rendered properly.
        self.redrawText()
        qApp.restoreOverrideCursor()

        return True
Exemple #23
0
    def loadTheme(self):
        """Load the currently specified GUI theme.
        """
        logger.info("Loading GUI theme '%s'", self.guiTheme)

        # Config File
        confParser = NWConfigParser()
        try:
            with open(self.themeFile, mode="r", encoding="utf-8") as inFile:
                confParser.read_file(inFile)
        except Exception:
            logger.error("Could not load theme settings from: %s",
                         self.themeFile)
            logException()
            return False

        # Main
        cnfSec = "Main"
        if confParser.has_section(cnfSec):
            self.themeName = confParser.rdStr(cnfSec, "name", "")
            self.themeDescription = confParser.rdStr(cnfSec, "description",
                                                     "N/A")
            self.themeAuthor = confParser.rdStr(cnfSec, "author", "N/A")
            self.themeCredit = confParser.rdStr(cnfSec, "credit", "N/A")
            self.themeUrl = confParser.rdStr(cnfSec, "url", "")
            self.themeLicense = confParser.rdStr(cnfSec, "license", "N/A")
            self.themeLicenseUrl = confParser.rdStr(cnfSec, "licenseurl", "")

        # Palette
        cnfSec = "Palette"
        if confParser.has_section(cnfSec):
            self._setPalette(confParser, cnfSec, "window", QPalette.Window)
            self._setPalette(confParser, cnfSec, "windowtext",
                             QPalette.WindowText)
            self._setPalette(confParser, cnfSec, "base", QPalette.Base)
            self._setPalette(confParser, cnfSec, "alternatebase",
                             QPalette.AlternateBase)
            self._setPalette(confParser, cnfSec, "text", QPalette.Text)
            self._setPalette(confParser, cnfSec, "tooltipbase",
                             QPalette.ToolTipBase)
            self._setPalette(confParser, cnfSec, "tooltiptext",
                             QPalette.ToolTipText)
            self._setPalette(confParser, cnfSec, "button", QPalette.Button)
            self._setPalette(confParser, cnfSec, "buttontext",
                             QPalette.ButtonText)
            self._setPalette(confParser, cnfSec, "brighttext",
                             QPalette.BrightText)
            self._setPalette(confParser, cnfSec, "highlight",
                             QPalette.Highlight)
            self._setPalette(confParser, cnfSec, "highlightedtext",
                             QPalette.HighlightedText)
            self._setPalette(confParser, cnfSec, "link", QPalette.Link)
            self._setPalette(confParser, cnfSec, "linkvisited",
                             QPalette.LinkVisited)

        # GUI
        cnfSec = "GUI"
        if confParser.has_section(cnfSec):
            self.statNone = self._loadColour(confParser, cnfSec, "statusnone")
            self.statUnsaved = self._loadColour(confParser, cnfSec,
                                                "statusunsaved")
            self.statSaved = self._loadColour(confParser, cnfSec,
                                              "statussaved")

        # CSS File
        cssData = readTextFile(self.cssFile)
        if cssData:
            qApp.setStyleSheet(cssData)

        # Apply Styles
        qApp.setPalette(self._guiPalette)

        return True
Exemple #24
0
    def updateTheme(self):
        """Update the theme map. This is more of an init, since many of
        the GUI icons cannot really be replaced without writing specific
        update functions for the classes where they're used.
        """
        self._themeMap = {}
        themePath = self._getThemePath()
        if themePath is None:
            logger.warning("No icons loaded")
            return False

        self._themePath = themePath
        themeConf = os.path.join(themePath, self._confName)
        logger.info("Loading icon theme '%s'", self.mainConf.guiIcons)

        # Config File
        confParser = NWConfigParser()
        try:
            with open(themeConf, mode="r", encoding="utf-8") as inFile:
                confParser.read_file(inFile)
        except Exception:
            logger.error("Could not load icon theme settings from: %s",
                         themeConf)
            logException()
            return False

        # Main
        cnfSec = "Main"
        if confParser.has_section(cnfSec):
            self.themeName = confParser.rdStr(cnfSec, "name", "")
            self.themeDescription = confParser.rdStr(cnfSec, "description", "")
            self.themeAuthor = confParser.rdStr(cnfSec, "author", "N/A")
            self.themeCredit = confParser.rdStr(cnfSec, "credit", "N/A")
            self.themeUrl = confParser.rdStr(cnfSec, "url", "")
            self.themeLicense = confParser.rdStr(cnfSec, "license", "N/A")
            self.themeLicenseUrl = confParser.rdStr(cnfSec, "licenseurl", "")

        # Populate Icon Map
        cnfSec = "Map"
        if confParser.has_section(cnfSec):
            for iconName, iconFile in confParser.items(cnfSec):
                if iconName not in self.ICON_KEYS:
                    logger.error("Unknown icon name '%s' in config file",
                                 iconName)
                else:
                    iconPath = os.path.join(self._themePath, iconFile)
                    if os.path.isfile(iconPath):
                        self._themeMap[iconName] = iconPath
                        logger.verbose("Icon slot '%s' using file '%s'",
                                       iconName, iconFile)
                    else:
                        logger.error("Icon file '%s' not in theme folder",
                                     iconFile)

        # Check that icons have been defined
        logger.debug("Scanning theme icons")
        for iconKey in self.ICON_KEYS:
            if iconKey in ("novelwriter", "proj_nwx"):
                # These are not part of the theme itself
                continue
            if iconKey not in self._themeMap:
                logger.error("No icon file specified for '%s'", iconKey)

        return True
Exemple #25
0
    def loadConfig(self):
        """Load preferences from file and replace default settings.
        """
        logger.debug("Loading config file")
        if self.confPath is None:
            return False

        theConf = NWConfigParser()
        cnfPath = os.path.join(self.confPath, self.confFile)
        try:
            with open(cnfPath, mode="r", encoding="utf-8") as inFile:
                theConf.read_file(inFile)
        except Exception as exc:
            logger.error("Could not load config file")
            logException()
            self.hasError = True
            self.errData.append("Could not load config file")
            self.errData.append(formatException(exc))
            return False

        # Main
        cnfSec = "Main"
        self.guiTheme = theConf.rdStr(cnfSec, "theme", self.guiTheme)
        self.guiSyntax = theConf.rdStr(cnfSec, "syntax", self.guiSyntax)
        self.guiIcons = theConf.rdStr(cnfSec, "icons", self.guiIcons)
        self.guiFont = theConf.rdStr(cnfSec, "guifont", self.guiFont)
        self.guiFontSize = theConf.rdInt(cnfSec, "guifontsize",
                                         self.guiFontSize)
        self.lastNotes = theConf.rdStr(cnfSec, "lastnotes", self.lastNotes)
        self.guiLang = theConf.rdStr(cnfSec, "guilang", self.guiLang)
        self.hideVScroll = theConf.rdBool(cnfSec, "hidevscroll",
                                          self.hideVScroll)
        self.hideHScroll = theConf.rdBool(cnfSec, "hidehscroll",
                                          self.hideHScroll)

        # Sizes
        cnfSec = "Sizes"
        self.winGeometry = theConf.rdIntList(cnfSec, "geometry",
                                             self.winGeometry)
        self.prefGeometry = theConf.rdIntList(cnfSec, "preferences",
                                              self.prefGeometry)
        self.projColWidth = theConf.rdIntList(cnfSec, "projcols",
                                              self.projColWidth)
        self.mainPanePos = theConf.rdIntList(cnfSec, "mainpane",
                                             self.mainPanePos)
        self.docPanePos = theConf.rdIntList(cnfSec, "docpane", self.docPanePos)
        self.viewPanePos = theConf.rdIntList(cnfSec, "viewpane",
                                             self.viewPanePos)
        self.outlnPanePos = theConf.rdIntList(cnfSec, "outlinepane",
                                              self.outlnPanePos)
        self.isFullScreen = theConf.rdBool(cnfSec, "fullscreen",
                                           self.isFullScreen)

        # 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)

        # 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.fmtSingleQuotes = theConf.rdStrList(cnfSec, "fmtsinglequote",
                                                 self.fmtSingleQuotes)
        self.fmtDoubleQuotes = theConf.rdStrList(cnfSec, "fmtdoublequote",
                                                 self.fmtDoubleQuotes)
        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)

        # Backup
        cnfSec = "Backup"
        self.backupPath = theConf.rdStr(cnfSec, "backuppath", self.backupPath)
        self.backupOnClose = theConf.rdBool(cnfSec, "backuponclose",
                                            self.backupOnClose)
        self.askBeforeBackup = theConf.rdBool(cnfSec, "askbeforebackup",
                                              self.askBeforeBackup)

        # 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)

        # Path
        cnfSec = "Path"
        self.lastPath = theConf.rdStr(cnfSec, "lastpath", 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

        # Check deprecated settings
        if self.guiIcons in ("typicons_colour_dark", "typicons_grey_dark"):
            self.guiIcons = "typicons_dark"
        elif self.guiIcons in ("typicons_colour_light", "typicons_grey_light"):
            self.guiIcons = "typicons_light"

        return True
Exemple #26
0
    def saveConfig(self):
        """Save the current preferences to file.
        """
        logger.debug("Saving config file")
        if self.confPath is None:
            return False

        theConf = NWConfigParser()

        theConf["Main"] = {
            "timestamp": formatTimeStamp(time()),
            "theme": str(self.guiTheme),
            "syntax": str(self.guiSyntax),
            "icons": str(self.guiIcons),
            "guifont": str(self.guiFont),
            "guifontsize": str(self.guiFontSize),
            "lastnotes": str(self.lastNotes),
            "guilang": str(self.guiLang),
            "hidevscroll": str(self.hideVScroll),
            "hidehscroll": str(self.hideHScroll),
        }

        theConf["Sizes"] = {
            "geometry": self._packList(self.winGeometry),
            "preferences": self._packList(self.prefGeometry),
            "projcols": self._packList(self.projColWidth),
            "mainpane": self._packList(self.mainPanePos),
            "docpane": self._packList(self.docPanePos),
            "viewpane": self._packList(self.viewPanePos),
            "outlinepane": self._packList(self.outlnPanePos),
            "fullscreen": str(self.isFullScreen),
        }

        theConf["Project"] = {
            "autosaveproject": str(self.autoSaveProj),
            "autosavedoc": str(self.autoSaveDoc),
            "emphlabels": str(self.emphLabels),
        }

        theConf["Editor"] = {
            "textfont": str(self.textFont),
            "textsize": str(self.textSize),
            "width": str(self.textWidth),
            "margin": str(self.textMargin),
            "tabwidth": str(self.tabWidth),
            "focuswidth": str(self.focusWidth),
            "hidefocusfooter": str(self.hideFocusFooter),
            "justify": str(self.doJustify),
            "autoselect": str(self.autoSelect),
            "autoreplace": str(self.doReplace),
            "repsquotes": str(self.doReplaceSQuote),
            "repdquotes": str(self.doReplaceDQuote),
            "repdash": str(self.doReplaceDash),
            "repdots": str(self.doReplaceDots),
            "scrollpastend": str(self.scrollPastEnd),
            "autoscroll": str(self.autoScroll),
            "autoscrollpos": str(self.autoScrollPos),
            "fmtsinglequote": self._packList(self.fmtSingleQuotes),
            "fmtdoublequote": self._packList(self.fmtDoubleQuotes),
            "fmtpadbefore": str(self.fmtPadBefore),
            "fmtpadafter": str(self.fmtPadAfter),
            "fmtpadthin": str(self.fmtPadThin),
            "spellcheck": str(self.spellLanguage),
            "showtabsnspaces": str(self.showTabsNSpaces),
            "showlineendings": str(self.showLineEndings),
            "showmultispaces": str(self.showMultiSpaces),
            "wordcounttimer": str(self.wordCountTimer),
            "bigdoclimit": str(self.bigDocLimit),
            "incnoteswcount": str(self.incNotesWCount),
            "showfullpath": str(self.showFullPath),
            "highlightquotes": str(self.highlightQuotes),
            "allowopensquote": str(self.allowOpenSQuote),
            "allowopendquote": str(self.allowOpenDQuote),
            "highlightemph": str(self.highlightEmph),
            "stopwhenidle": str(self.stopWhenIdle),
            "useridletime": str(self.userIdleTime),
        }

        theConf["Backup"] = {
            "backuppath": str(self.backupPath),
            "backuponclose": str(self.backupOnClose),
            "askbeforebackup": str(self.askBeforeBackup),
        }

        theConf["State"] = {
            "showrefpanel": str(self.showRefPanel),
            "viewcomments": str(self.viewComments),
            "viewsynopsis": str(self.viewSynopsis),
            "searchcase": str(self.searchCase),
            "searchword": str(self.searchWord),
            "searchregex": str(self.searchRegEx),
            "searchloop": str(self.searchLoop),
            "searchnextfile": str(self.searchNextFile),
            "searchmatchcap": str(self.searchMatchCap),
        }

        theConf["Path"] = {
            "lastpath": str(self.lastPath),
        }

        # Write config file
        cnfPath = os.path.join(self.confPath, self.confFile)
        try:
            with open(cnfPath, mode="w", encoding="utf-8") as outFile:
                theConf.write(outFile)
            self.confChanged = False
        except Exception as exc:
            logger.error("Could not save config file")
            logException()
            self.hasError = True
            self.errData.append("Could not save config file")
            self.errData.append(formatException(exc))
            return False

        return True