def writeDocument(self, docText, forceWrite=False): """Write the document specified by the handle attribute. Handle any IO errors in the process Returns True if successful, False if not. """ self._docError = "" if self._docHandle is None: logger.error("No document handle set") return False self.theProject.ensureFolderStructure() docFile = self._docHandle+".nwd" logger.debug("Saving document: %s", docFile) docPath = os.path.join(self.theProject.projContent, docFile) docTemp = os.path.join(self.theProject.projContent, docFile+"~") if self._prevHash is not None and not forceWrite: self._currHash = sha256sum(docPath) if self._currHash is not None and self._currHash != self._prevHash: logger.error("File has been altered on disk since opened") return False # DocMeta Line if self._theItem is None: docMeta = "" else: docMeta = ( f"%%~name: {self._theItem.itemName}\n" f"%%~path: {self._theItem.itemParent}/{self._theItem.itemHandle}\n" f"%%~kind: {self._theItem.itemClass.name}/{self._theItem.itemLayout.name}\n" ) try: with open(docTemp, mode="w", encoding="utf-8") as outFile: outFile.write(docMeta) outFile.write(docText) except Exception as exc: self._docError = formatException(exc) return False # If we're here, the file was successfully saved, so we can # replace the temp file with the actual file try: os.replace(docTemp, docPath) except OSError as exc: self._docError = formatException(exc) return False self._prevHash = sha256sum(docPath) self._currHash = self._prevHash return True
def loadRecentCache(self): """Load the cache file for recent projects. """ if self.dataPath is None: return False self.recentProj = {} cacheFile = os.path.join(self.dataPath, nwFiles.RECENT_FILE) if not os.path.isfile(cacheFile): return True try: with open(cacheFile, mode="r", encoding="utf-8") as inFile: theData = json.load(inFile) for projPath, theEntry in theData.items(): self.recentProj[projPath] = { "title": theEntry.get("title", ""), "time": theEntry.get("time", 0), "words": theEntry.get("words", 0), } except Exception as exc: self.hasError = True self.errData.append("Could not load recent project cache") self.errData.append(formatException(exc)) return False return True
def readDocument(self, isOrphan=False): """Read the document specified by the handle set in the contructor, capturing potential file system errors and parse meta data. If the document doesn't exist on disk, return an empty string. If something went wrong, return None. """ self._docError = "" if self._docHandle is None: logger.error("No document handle set") return None if self._theItem is None and not isOrphan: logger.error("Unknown novelWriter document") return None docFile = self._docHandle+".nwd" logger.debug("Opening document: %s", docFile) docPath = os.path.join(self.theProject.projContent, docFile) self._fileLoc = docPath theText = "" self._docMeta = {} self._prevHash = None if os.path.isfile(docPath): self._prevHash = sha256sum(docPath) try: with open(docPath, mode="r", encoding="utf-8") as inFile: # Check the first <= 10 lines for metadata for i in range(10): inLine = inFile.readline() if inLine.startswith(r"%%~"): self._parseMeta(inLine) else: theText = inLine break # Load the rest of the file theText += inFile.read() except Exception as exc: self._docError = formatException(exc) return None else: # The document file does not exist, so we assume it's a new # document and initialise an empty text string. logger.debug("The requested document does not exist") return "" return theText
def saveRecentCache(self): """Save the cache dictionary of recent projects. """ if self.dataPath is None: return False cacheFile = os.path.join(self.dataPath, nwFiles.RECENT_FILE) cacheTemp = os.path.join(self.dataPath, nwFiles.RECENT_FILE + "~") try: with open(cacheTemp, mode="w+", encoding="utf-8") as outFile: json.dump(self.recentProj, outFile, indent=2) except Exception as exc: self.hasError = True self.errData.append("Could not save recent project cache") self.errData.append(formatException(exc)) return False if os.path.isfile(cacheFile): os.unlink(cacheFile) os.rename(cacheTemp, cacheFile) return True
def deleteDocument(self): """Permanently delete a document source file and related files from the project data folder. """ self._docError = "" if self._docHandle is None: logger.error("No document handle set") return False chkList = [ os.path.join(self.theProject.projContent, f"{self._docHandle}.nwd"), os.path.join(self.theProject.projContent, f"{self._docHandle}.nwd~"), ] for chkFile in chkList: if os.path.isfile(chkFile): try: os.unlink(chkFile) logger.debug("Deleted: %s", chkFile) except Exception as exc: self._docError = formatException(exc) return False return True
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
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
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
def _saveDocument(self, theFmt): """Save the document to various formats. """ replaceTabs = self.replaceTabs.isChecked() fileExt = "" textFmt = "" # Settings # ======== if theFmt == self.FMT_ODT: fileExt = "odt" textFmt = self.tr("Open Document") elif theFmt == self.FMT_FODT: fileExt = "fodt" textFmt = self.tr("Flat Open Document") elif theFmt == self.FMT_HTM: fileExt = "htm" textFmt = self.tr("Plain HTML") elif theFmt == self.FMT_NWD: fileExt = "nwd" textFmt = self.tr("novelWriter Markdown") elif theFmt == self.FMT_MD: fileExt = "md" textFmt = self.tr("Standard Markdown") elif theFmt == self.FMT_GH: fileExt = "md" textFmt = self.tr("GitHub Markdown") elif theFmt == self.FMT_JSON_H: fileExt = "json" textFmt = self.tr("JSON + novelWriter HTML") elif theFmt == self.FMT_JSON_M: fileExt = "json" textFmt = self.tr("JSON + novelWriter Markdown") elif theFmt == self.FMT_PDF: fileExt = "pdf" textFmt = self.tr("PDF") else: return False # Generate File Name # ================== cleanName = makeFileNameSafe(self.theProject.projName) fileName = "%s.%s" % (cleanName, fileExt) saveDir = self.mainConf.lastPath if not os.path.isdir(saveDir): saveDir = os.path.expanduser("~") savePath = os.path.join(saveDir, fileName) savePath, _ = QFileDialog.getSaveFileName( self, self.tr("Save Document As"), savePath ) if not savePath: return False self.mainConf.setLastPath(savePath) # Build and Write # =============== errMsg = "" wSuccess = False if theFmt == self.FMT_ODT: makeOdt = ToOdt(self.theProject, isFlat=False) self._doBuild(makeOdt) try: makeOdt.saveOpenDocText(savePath) wSuccess = True except Exception as exc: errMsg = formatException(exc) elif theFmt == self.FMT_FODT: makeOdt = ToOdt(self.theProject, isFlat=True) self._doBuild(makeOdt) try: makeOdt.saveFlatXML(savePath) wSuccess = True except Exception as exc: errMsg = formatException(exc) elif theFmt == self.FMT_HTM: makeHtml = ToHtml(self.theProject) self._doBuild(makeHtml) if replaceTabs: makeHtml.replaceTabs() try: makeHtml.saveHTML5(savePath) wSuccess = True except Exception as exc: errMsg = formatException(exc) elif theFmt == self.FMT_NWD: makeNwd = ToMarkdown(self.theProject) makeNwd.setKeepMarkdown(True) self._doBuild(makeNwd, doConvert=False) if replaceTabs: makeNwd.replaceTabs(spaceChar=" ") try: makeNwd.saveRawMarkdown(savePath) wSuccess = True except Exception as exc: errMsg = formatException(exc) elif theFmt in (self.FMT_MD, self.FMT_GH): makeMd = ToMarkdown(self.theProject) if theFmt == self.FMT_GH: makeMd.setGitHubMarkdown() else: makeMd.setStandardMarkdown() self._doBuild(makeMd) if replaceTabs: makeMd.replaceTabs(nSpaces=4, spaceChar=" ") try: makeMd.saveMarkdown(savePath) wSuccess = True except Exception as exc: errMsg = formatException(exc) elif theFmt == self.FMT_JSON_H or theFmt == self.FMT_JSON_M: jsonData = { "meta": { "workingTitle": self.theProject.projName, "novelTitle": self.theProject.bookTitle, "authors": self.theProject.bookAuthors, "buildTime": self.buildTime, } } if theFmt == self.FMT_JSON_H: makeHtml = ToHtml(self.theProject) self._doBuild(makeHtml) if replaceTabs: makeHtml.replaceTabs() theBody = [] for htmlPage in makeHtml.fullHTML: theBody.append(htmlPage.rstrip("\n").split("\n")) jsonData["text"] = { "css": self.htmlStyle, "html": theBody, } elif theFmt == self.FMT_JSON_M: makeMd = ToHtml(self.theProject) makeMd.setKeepMarkdown(True) self._doBuild(makeMd, doConvert=False) if replaceTabs: makeMd.replaceTabs(spaceChar=" ") theBody = [] for nwdPage in makeMd.theMarkdown: theBody.append(nwdPage.split("\n")) jsonData["text"] = { "nwd": theBody, } try: with open(savePath, mode="w", encoding="utf-8") as outFile: outFile.write(json.dumps(jsonData, indent=2)) wSuccess = True except Exception as exc: errMsg = formatException(exc) elif theFmt == self.FMT_PDF: try: thePrinter = QPrinter() thePrinter.setOutputFormat(QPrinter.PdfFormat) thePrinter.setOrientation(QPrinter.Portrait) thePrinter.setDuplex(QPrinter.DuplexLongSide) thePrinter.setFontEmbeddingEnabled(True) thePrinter.setColorMode(QPrinter.Color) thePrinter.setOutputFileName(savePath) self.docView.document().print(thePrinter) wSuccess = True except Exception as exc: errMsg = formatException(exc) else: # If the if statements above and here match, it should not # be possible to reach this else statement. return False # pragma: no cover # Report to User # ============== if wSuccess: self.mainGui.makeAlert([ self.tr("{0} file successfully written to:").format(textFmt), savePath ], nwAlert.INFO) else: self.mainGui.makeAlert(self.tr( "Failed to write {0} file. {1}" ).format(textFmt, errMsg), nwAlert.ERROR) return wSuccess
def _saveData(self, dataFmt): """Save the content of the list box to a file. """ fileExt = "" textFmt = "" if dataFmt == self.FMT_JSON: fileExt = "json" textFmt = self.tr("JSON Data File") elif dataFmt == self.FMT_CSV: fileExt = "csv" textFmt = self.tr("CSV Data File") else: return False # Generate the file name saveDir = self.mainConf.lastPath if not os.path.isdir(saveDir): saveDir = os.path.expanduser("~") fileName = "sessionStats.%s" % fileExt savePath = os.path.join(saveDir, fileName) savePath, _ = QFileDialog.getSaveFileName( self, self.tr("Save Data As"), savePath, "%s (*.%s)" % (textFmt, fileExt)) if not savePath: return False self.mainConf.setLastPath(savePath) # Do the actual writing wSuccess = False errMsg = "" try: with open(savePath, mode="w", encoding="utf-8") as outFile: if dataFmt == self.FMT_JSON: jsonData = [] for _, sD, tT, wD, wA, wB, tI in self.filterData: jsonData.append({ "date": sD, "length": tT, "newWords": wD, "novelWords": wA, "noteWords": wB, "idleTime": tI, }) json.dump(jsonData, outFile, indent=2) wSuccess = True if dataFmt == self.FMT_CSV: outFile.write( '"Date","Length (sec)","Words Changed",' '"Novel Words","Note Words","Idle Time (sec)"\n') for _, sD, tT, wD, wA, wB, tI in self.filterData: outFile.write(f'"{sD}",{tT:.0f},{wD},{wA},{wB},{tI}\n') wSuccess = True except Exception as exc: errMsg = formatException(exc) wSuccess = False # Report to user if wSuccess: self.theParent.makeAlert([ self.tr("{0} file successfully written to:").format(textFmt), savePath ], nwAlert.INFO) else: self.theParent.makeAlert( [self.tr("Failed to write {0} file.").format(textFmt), errMsg], nwAlert.ERROR) return wSuccess