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="utf8") as inFile: theState = json.load(inFile) except Exception: logger.error("Failed to load GUI options file") nw.logException() return False # Filter out unused variables for aGroup in theState: if aGroup in self.validMap: self.theState[aGroup] = {} for anOpt in theState[aGroup]: if anOpt in self.validMap[aGroup]: self.theState[aGroup][anOpt] = theState[aGroup][anOpt] return True
def setLanguage(self, theLang, projectDict=None): """Load a dictionary as a list from the app assets folder. """ self.theLang = theLang self.theWords = set() dictFile = os.path.join(self.mainConf.dictPath, theLang + ".dict") try: with open(dictFile, mode="r", encoding="utf-8") as wordsFile: for theLine in wordsFile: if len(theLine) == 0 or theLine.startswith("#"): continue self.theWords.add(theLine.strip().lower()) logger.debug("Spell check dictionary for language %s loaded" % theLang) logger.debug("Dictionary contains %d words" % len(self.theWords)) self.spellLanguage = theLang except Exception: logger.error( "Failed to load spell check word list for language %s" % theLang) nw.logException() self.spellLanguage = None self._readProjectDictionary(projectDict) for pWord in self.projDict: self.theWords.add(pWord) return
def loadIndex(self): """Load index from last session from the project meta folder. """ theData = {} indexFile = os.path.join(self.theProject.projMeta, nwFiles.INDEX_FILE) if os.path.isfile(indexFile): logger.debug("Loading index file") try: with open(indexFile, mode="r", encoding="utf8") as inFile: theData = json.load(inFile) except Exception: logger.error("Failed to load index file") nw.logException() self.indexBroken = True return False self._tagIndex = theData.get("tagIndex", {}) self._refIndex = theData.get("refIndex", {}) self._novelIndex = theData.get("novelIndex", {}) self._noteIndex = theData.get("noteIndex", {}) self._textCounts = theData.get("textCounts", {}) nowTime = round(time()) self._timeNovel = nowTime self._timeNotes = nowTime self._timeIndex = nowTime self.checkIndex() return True
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="utf8") as inFile: theJson = inFile.read() theData = json.loads(theJson) except Exception: logger.error("Failed to load build cache") nw.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
def checkIndex(self): """Check that the entries in the index are valid and contain the elements it should. """ logger.debug("Checking index") tStart = time() try: self._checkTagIndex() self._checkRefIndex() self._checkNovelNoteIndex("novelIndex") self._checkNovelNoteIndex("noteIndex") self._checkTextCounts() self.indexBroken = False except Exception: logger.error("Error while checking index") nw.logException() self.indexBroken = True tEnd = time() logger.debug("Index check took %.3f ms" % ((tEnd - tStart)*1000)) logger.debug("Index check complete") if self.indexBroken: self.clearIndex() return
def _readProjectDictionary(self, projectDict): """Read the content of the project dictionary, and add it to the lookup lists. """ self.projDict = [] 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.append(theLine) logger.debug("Project word list contains %d words" % len(self.projDict)) except Exception: logger.error("Failed to load project word list") nw.logException() return False return True
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="utf8") 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") nw.logException() self.reject() return False if os.path.isfile(dctFile): os.unlink(dctFile) os.rename(tmpFile, dctFile) self.accept() return True
def loadSyntax(self): """Load the currently specified syntax highlighter theme. """ logger.debug("Loading syntax theme files") confParser = configparser.ConfigParser() try: with open(self.syntaxFile, mode="r", encoding="utf8") as inFile: confParser.read_file(inFile) except Exception: logger.error("Could not load syntax colours from: %s" % self.syntaxFile) nw.logException() return False ## Main cnfSec = "Main" if confParser.has_section(cnfSec): self.syntaxName = self._parseLine(confParser, cnfSec, "name", "") self.syntaxDescription = self._parseLine(confParser, cnfSec, "description", "") self.syntaxAuthor = self._parseLine(confParser, cnfSec, "author", "") self.syntaxCredit = self._parseLine(confParser, cnfSec, "credit", "") self.syntaxUrl = self._parseLine(confParser, cnfSec, "url", "") self.syntaxLicense = self._parseLine(confParser, cnfSec, "license", "") self.syntaxLicenseUrl = self._parseLine(confParser, 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.colTagErr = self._loadColour(confParser, cnfSec, "tagerror") self.colRepTag = self._loadColour(confParser, cnfSec, "replacetag") self.colMod = self._loadColour(confParser, cnfSec, "modifier") logger.info("Loaded syntax theme '%s'" % self.guiSyntax) return True
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") nw.logException() spTag = "" spName = "" return spTag, spName
def addWord(self, newWord): """Add a word to the project dictionary. """ 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.append(newWord) except Exception: logger.error("Failed to add word to project word list %s" % str(self.projectDict)) nw.logException() return False return True return False
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="utf8") as outFile: json.dump(self.theState, outFile, indent=2) except Exception: logger.error("Failed to save GUI options file") nw.logException() return False return True
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 = "%-25s %-9s %-10s %s" % ( 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="utf8") as outFile: outFile.write("\n") outFile.write("Table of Contents\n") outFile.write("=================\n") outFile.write("\n") outFile.write( "%-25s %-9s %-10s %s\n" % ("File Name", "Class", "Layout", "Document Label")) outFile.write("-" * tocLen + "\n") outFile.write("\n".join(tocList)) outFile.write("\n") except Exception: logger.error("Could not write ToC file") nw.logException() return False return True
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) try: with open(indexFile, mode="w+", encoding="utf8") as outFile: json.dump({ "tagIndex" : self._tagIndex, "refIndex" : self._refIndex, "novelIndex" : self._novelIndex, "noteIndex" : self._noteIndex, "textCounts" : self._textCounts, }, outFile, indent=2) except Exception: logger.error("Failed to save index file") nw.logException() return False return True
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="utf8") 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") nw.logException() return False return True
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. """ logger.debug("Loading icon theme files") self.themeMap = {} checkPath = os.path.join(self.mainConf.iconPath, self.mainConf.guiIcons) if os.path.isdir(checkPath): logger.debug("Loading icon theme '%s'" % self.mainConf.guiIcons) self.iconPath = checkPath self.confFile = os.path.join(checkPath, self.confName) else: return False # Config File confParser = configparser.ConfigParser() try: with open(self.confFile, mode="r", encoding="utf8") as inFile: confParser.read_file(inFile) except Exception: logger.error("Could not load icon theme settings from: %s" % self.confFile) nw.logException() return False ## Main cnfSec = "Main" if confParser.has_section(cnfSec): self.themeName = self._parseLine(confParser, cnfSec, "name", "") self.themeDescription = self._parseLine(confParser, cnfSec, "description", "") self.themeAuthor = self._parseLine(confParser, cnfSec, "author", "N/A") self.themeCredit = self._parseLine(confParser, cnfSec, "credit", "N/A") self.themeUrl = self._parseLine(confParser, cnfSec, "url", "") self.themeLicense = self._parseLine(confParser, cnfSec, "license", "N/A") self.themeLicenseUrl = self._parseLine(confParser, cnfSec, "licenseurl", "") ## Palette cnfSec = "Map" if confParser.has_section(cnfSec): for iconName, iconFile in confParser.items(cnfSec): if iconName not in self.ICON_MAP: logger.error("Unknown icon name '%s' in config file" % iconName) else: iconPath = os.path.join(self.iconPath, 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) logger.info("Loaded icon theme '%s'" % self.mainConf.guiIcons) return True
def loadTheme(self): """Load the currently specified GUI theme. """ logger.debug("Loading theme files") logger.debug("System icon theme is '%s'" % str(QIcon.themeName())) # CSS File cssData = "" try: if os.path.isfile(self.cssFile): with open(self.cssFile, mode="r", encoding="utf8") as inFile: cssData = inFile.read() except Exception: logger.error("Could not load theme css file") nw.logException() return False # Config File confParser = configparser.ConfigParser() try: with open(self.confFile, mode="r", encoding="utf8") as inFile: confParser.read_file(inFile) except Exception: logger.error("Could not load theme settings from: %s" % self.confFile) nw.logException() return False ## Main cnfSec = "Main" if confParser.has_section(cnfSec): self.themeName = self._parseLine(confParser, cnfSec, "name", "") self.themeDescription = self._parseLine(confParser, cnfSec, "description", "N/A") self.themeAuthor = self._parseLine(confParser, cnfSec, "author", "N/A") self.themeCredit = self._parseLine(confParser, cnfSec, "credit", "N/A") self.themeUrl = self._parseLine(confParser, cnfSec, "url", "") self.themeLicense = self._parseLine(confParser, cnfSec, "license", "N/A") self.themeLicenseUrl = self._parseLine(confParser, 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") # Apply Styles qApp.setStyleSheet(cssData) qApp.setPalette(self.guiPalette) logger.info("Loaded theme '%s'" % self.guiTheme) return True
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().strip() fmtChapter = self.fmtChapter.text().strip() fmtUnnumbered = self.fmtUnnumbered.text().strip() fmtScene = self.fmtScene.text().strip() fmtSection = self.fmtSection.text().strip() 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(self.buildLang.currentData()) # 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, fmtScene == "") bldObj.setSectionFormat(fmtSection, fmtSection == "") 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.initDocument() # Make sure the project and document is up to date self.theParent.treeView.flushTreeOrder() self.theParent.saveDocument() self.buildProgress.setMaximum(len(self.theProject.projTree)) self.buildProgress.setValue(0) for nItt, tItem in enumerate(self.theProject.projTree): 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) nw.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.theParent.makeAlert( "%s:<br>- %s" % (self.tr("There were problems when building the project"), "<br>- ".join(bldObj.errData)), nwAlert.ERROR) return
def loadText(self, tHandle, updateHistory=True): """Load text into the viewer from an item handle. """ tItem = self.theProject.projTree[tHandle] if tItem is None: logger.warning("Item not found") return False if tItem.itemType != nwItemType.FILE: return False logger.debug("Generating preview for item %s" % tHandle) qApp.setOverrideCursor(QCursor(Qt.WaitCursor)) sPos = self.verticalScrollBar().value() aDoc = ToHtml(self.theProject, self.theParent) 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) nw.logException() self.setText( self.tr("An error occurred while generating the preview.")) 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.theHandle == tHandle: self.verticalScrollBar().setValue(sPos) self.theHandle = tHandle self.theProject.setLastViewed(tHandle) self.docHeader.setTitleFromHandle(self.theHandle) self.updateDocMargins() # Make sure the main GUI knows we changed the content self.theParent.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