def _sortedPages(self, pages): try: pageRange = Pages.RulesForName[ self.model.pageRangeRules()].function return Pages.sortedPages(pages, pageRange) except Pages.Error as err: say(err)
def renumber(self, options, reportProgress): description = "renumber " if options.romanchange: description += "{}\u2013{} {:+}".format( roman.toRoman(options.romanfrom), roman.toRoman(options.romanto), options.romanchange) if options.decimalchange: description += "; " if options.decimalchange: description += "{:,}\u2013{:,} {:+}".format( options.decimalfrom, options.decimalto, options.decimalchange) percents = set() total = len(self) entries = list(self.allEntriesWithPages()) if entries: macro = Lib.Command.Macro(description) for i, entry in enumerate(entries, 1): percent = int(min(100, i * 100 // total)) if percent not in percents: # report every 1% done reportProgress("Renumbering {}%".format(percent)) percents.add(percent) pages = Pages.renumbered(entry.pages, options) command = Command.EditEntry(entry, entry.saf, entry.sortas, entry.term, pages, entry.notes) macro.append(command) self._stack.push(macro) # Store, then do description = self._xix.doCommand(macro) self.changed.emit(command.eid, description) else: say("No pages in range for renumbering")
def openXix(self, filename=None): if self.isReadOnly( filename if filename is not None else self.filename): return self.closeXix() if filename is not None: self.filename = filename try: self.recent_files.remove(self.filename) self.updateRecentFilesMenu() except ValueError: pass # No problem if it isn't there to be removed self.state.indexPath = os.path.dirname(self.filename) QApplication.setOverrideCursor(Qt.WaitCursor) try: say("Opening {}…".format(os.path.normpath(self.filename))) QApplication.processEvents() self.state.entryPanel.clearForm() self._openModel("Opened") self.state.entryPanel.termEdit.setFocus() self.state.updateUi() self.updateWorkTime() self.updateLanguageIndicator() self.state.setMode(ModeKind.VIEW) self.refreshBookmarks() finally: QApplication.restoreOverrideCursor()
def combinePages(self): widget = QApplication.focusWidget() say("Looking for overlapping pages…") with Lib.Qt.DisableUI(*self.window.widgets(), forModalDialog=True): form = Forms.CombinePages.Form(self.state, self.window) form.exec_() Lib.restoreFocus(widget)
def initialize(self): self.setWindowTitle("{}".format(QApplication.applicationName())) self.state.updateDisplayFonts() self.filename = None if len(sys.argv) > 1: filename = sys.argv[1] if (filename.lower().endswith(EXTENSION) and os.path.exists(filename)): self.filename = filename if self.filename is None: settings = QSettings() filename = settings.value(Gopt.Key.MainForm_Filename, Gopt.Default.MainForm_Filename) if (filename and filename.lower().endswith(EXTENSION) and os.path.exists(filename)): self.filename = filename if self.filename is None: say("Click File→New or File→Open to create or open an index") self.updateWorkTime() self.state.updateUi() else: say("Opening {}".format(os.path.normpath(self.filename))) QTimer.singleShot(5, self.openXix) self.updateRecentFilesMenu() self.updateToolTips() Lib.maybe_register_filetype(self.debug)
def copy(self): self.state.maybeSave() eid = self.selectedEntry.eid peid = None if self.copyToTopRadioButton.isChecked(): peid = ROOT description = "copy “{}” to be main entry" elif self.subentryRadioButton.isChecked(): peid = eid description = "copy “{}” to be subentry of itself" elif self.siblingRadioButton.isChecked(): peid = self.selectedEntry.peid description = "copy “{}” to be sibling of itself" elif self.filteredRadioButton.isChecked(): peid = self.filteredEntry.eid description = "copy “{}” under filtered" elif self.circledRadioButton.isChecked(): peid = self.circledEntry.eid description = "copy “{}” under circled" elif self.recentRadioButton.isChecked(): peid = self.recentComboBox.itemData( self.recentComboBox.currentIndex()) description = "copy “{}” under recently visited" if peid is not None: # Should always be True description = description.format( Lib.elidePatchHtml(self.selectedEntry.term, self.state)) self.state.model.copyEntry( Lib.CopyInfo(eid, peid, self.copyXrefsCheckBox.isChecked(), self.copyGroupsCheckBox.isChecked(), self.copySubentriesCheckBox.isChecked(), self.linkPagesCheckBox.isChecked(), self.withSeeCheckBox.isChecked(), description)) say(re.sub(r"^copy", "Copied", Lib.htmlToPlainText(description)), SAY_TIMEOUT) self.accept()
def backup(self): model = self.state.model if not model: return widget = QApplication.focusWidget() filename = Lib.incrementedFilename(model.filename) with Lib.Qt.DisableUI(*self.window.widgets(), forModalDialog=True): filename, _ = QFileDialog.getSaveFileName( self.window, "Backup Index — {}".format(QApplication.applicationName()), filename, "{} index (*{})".format(QApplication.applicationName(), EXTENSION)) if filename: with Lib.Qt.DisableUI(*self.window.widgets(), forModalDialog=True): self.state.saving = True try: self.state.maybeSave() if not filename.endswith(EXTENSION): filename += EXTENSION say("Backing up to “{}”...".format( QDir.toNativeSeparators(filename))) model.optimize() model.backup(filename, "Backing up", self.window.reportProgress) say("Backed up to “{}”".format( QDir.toNativeSeparators(filename))) finally: self.state.saving = False Lib.restoreFocus(widget)
def removeBookmark(self): self.state.maybeSave() eid = self.state.viewAllPanel.view.selectedEid if eid is not None: self.state.model.removeBookmark(eid) self.window.refreshBookmarks() say("Removed bookmark", SAY_TIMEOUT)
def printIndex(self): widget = QApplication.focusWidget() if self.state.printer is None: self.state.printer = QPrinter(QPrinter.HighResolution) self.state.printer.setColorMode(QPrinter.GrayScale) settings = QSettings() size = PaperSizeKind( int(settings.value(Gopt.Key.PaperSize, Gopt.Default.PaperSize))) self.state.printer.setPaperSize( QPrinter.Letter if size is PaperSizeKind.LETTER else QPrinter. A4) with Lib.Qt.DisableUI(*self.window.widgets(), forModalDialog=True): form = QPrintDialog(self.state.printer, self.state.window) form.setWindowTitle("Print Index") if form.exec_(): try: with Lib.DisableUI(*self.window.widgets()): config = self.state.model.configs().copy() config.Filename = "print.$$$" config.Printer = self.state.printer Output.outputEntries(self.state.model, config, "Printing", self.window.reportProgress) say("Printed") except Output.Error as err: say("Failed to print: {}".format(err)) logging.error("printIndex failed: {}".format(err)) Lib.restoreFocus(widget)
def move(self): self.state.maybeSave() eid = self.selectedEntry.eid if self.moveToTopRadioButton.isChecked(): self.state.model.moveToTop(eid) else: peid = None if self.grandParentRadioButton.isChecked(): peid = self.grandParentEntry.eid message = "move up" elif self.filteredRadioButton.isChecked(): peid = self.filteredEntry.eid message = "move under filtered" elif self.circledRadioButton.isChecked(): peid = self.circledEntry.eid message = "move under circled" elif self.recentRadioButton.isChecked(): peid = self.recentComboBox.itemData( self.recentComboBox.currentIndex()) message = "move under recently visited" if peid is not None: # Should always be True self.state.model.moveUnder(eid, peid, message) term = Lib.htmlToPlainText(self.state.model.term(eid)) if peid == ROOT: message = "Moved “{}” to be a main entry".format(term) else: message = "Moved “{}” under “{}”".format( term, Lib.htmlToPlainText(self.state.model.term(peid))) say(message, SAY_TIMEOUT) self.accept()
def addXRef(self): from_eid = self.selectedEntry.eid assert from_eid is not None kind = (XrefKind.SEE if self.seeRadioButton.isChecked() else XrefKind.SEE_ALSO) if self.groupRadioButton.isChecked(): gid = int( self.groupComboBox.itemData(self.groupComboBox.currentIndex())) for to_eid in tuple(self.state.model.eidsForGid(gid)): self.state.model.addXRef(from_eid, to_eid, kind) elif not self.genericTermRadioButton.isChecked(): to_eid = None if self.filteredRadioButton.isChecked(): to_eid = self.filteredEntry.eid elif self.circledRadioButton.isChecked(): to_eid = self.circledEntry.eid elif self.recentRadioButton.isChecked(): to_eid = self.recentComboBox.itemData( self.recentComboBox.currentIndex()) assert to_eid is not None self.state.model.addXRef(from_eid, to_eid, kind) else: term = self.genericTermLineEdit.toHtml() kind = (XrefKind.SEE_GENERIC if self.seeRadioButton.isChecked() else XrefKind.SEE_ALSO_GENERIC) self.state.model.addGenericXRef(from_eid, term, kind) say("Added cross-reference", SAY_TIMEOUT) self.accept()
def saveAs(self): # No need to restore focus widget filename = self._getSaveAsFilename() if filename: # Do a backup to the new filename, then open it say("Saving to “{}”...".format(QDir.toNativeSeparators(filename))) self.state.model.backup(filename, "Saving", self.window.reportProgress) self.window.openXix(filename)
def accept(self): clipboard = QApplication.clipboard() text = self.copyTextLineEdit.text() or self.panel.currentChar clipboard.setText(text, QClipboard.Clipboard) clipboard.setText(text, QClipboard.Selection) if text: say("Copied “{}” to the clipboard".format(text), SAY_TIMEOUT) super().accept()
def recreate(self): # No need to restore focus widget item = self.listWidget.currentItem() if item: eid = item.data(Qt.UserRole) subentries = self.recreateSubentriesCheckBox.isChecked() with Lib.DisableUI(self): self.state.model.recreateEntry(eid, subentries) say("Recreated", SAY_TIMEOUT) self.accept()
def redo(self): widget = QApplication.focusWidget() if self.model.isRedoMacro: say("Redoing…") eid = self.viewAllPanel.view.selectedEid with Lib.DisableUI(*self.window.widgets()): self.model.redo() say("Redone", SAY_TIMEOUT) else: eid = self.model.redo() self._postUndoOrRedoRefresh(eid, widget)
def updateNavigationStatus(self): if (self.viewFilteredPanel.view.selectedEid is None or not self.viewFilteredPanel.view.lineCount): message = "No entries match the filter" elif (self.viewAllPanel.view.selectedEid != self.viewFilteredPanel.view.selectedEid): message = ("The filtered entry isn't the current entry: " "press F3 to navigate") else: message = "The filtered entry is the current entry" say(message, SAY_TIMEOUT)
def updateTitle(self): if self.filename is None: self.setWindowTitle(QApplication.applicationName()) say("Click File→New or File→Open to create or open an index") else: filename = os.path.basename(self.filename) dirname = os.path.abspath(os.path.dirname(self.filename)) dirname = (" [{}]".format(os.path.normcase(dirname)) if dirname != os.getcwd() else "") self.setWindowTitle("{}{} — {}".format( filename, dirname, QApplication.applicationName()))
def start(self): if self.regexRadioButton.isChecked(): try: re.compile(self.searchLineEdit.text()) except re.error as err: say("Invalid regex pattern: {}".format(err)) return self.stopped = False say("Searching…") self.searcher.prepare(self.state.model, self.options()) self.updateUi() self._search(started=True)
def _reportOnOutput(self, monitor, goodMessage, badTitle, badMessage): if monitor.changed: say(goodMessage) else: QMessageBox.warning( self.window, "{} — {}".format(badTitle, QApplication.applicationName()), """\ <p>{}</p><p>{} cannot write a file if the file is open in another application or if you don't have access permission to write the file (or to write in the file's folder).</p> <p><b>Try using a different filename and/or folder.</b></p>""".format( badMessage, QApplication.applicationName()))
def allChecks(self): if bool(self.state.model): self.doingAllChecks = True self.checkCount = 5 # Number of checks using temp file filename = self._checkCreateTempFile() self.check(FilterKind.SAME_TERM_TEXTS, filename=filename) self.check(FilterKind.HAS_OVERLAPPING_PAGES, filename=filename) self.check(FilterKind.TOO_HIGH_PAGE, filename=filename) self.check(FilterKind.TOO_LARGE_PAGE_RANGE, filename=filename) self.check(FilterKind.TOO_MANY_PAGES, filename=filename) self.unindexedPages() say("Doing All the Checks — will report each one when done", SAY_TIMEOUT)
def _importIndex(self, filename, inFilename): language, sortAsRules, pageRangeRules = self._getLanguageAndRules() with Lib.Timer("Imported in", 0.2): if self.state.model.importIndex(inFilename, self.filename, language, sortAsRules, pageRangeRules): say( "Imported “{}” from “{}”".format(self.filename, inFilename), self.state.showMessageTime) else: say( "Failed to import “{}” from “{}”".format( self.filename, inFilename), self.state.showMessageTime) self.updateTitle()
def check(self, filter, match="", filename=None): if bool(self.state.model): uuid = self.state.model.config(UUID) if filename is None: filename = self._checkCreateTempFile() say("Checking “{}” — will report when done".format(filter.text), SAY_TIMEOUT) asyncResult = Check.filteredEntries(filename=filename, filter=filter, match=match) slot = functools.partial(self.checkDone, asyncResult=asyncResult, filename=filename, uuid=uuid, name=filter.text) QTimer.singleShot(CHECK_TIMEOUT, slot)
def _openModel(self, word): self.state.viewAllPanel.clear() self.state.viewFilteredPanel.clear() language, sortAsRules, pageRangeRules = self._getLanguageAndRules() with Lib.Timer("Opened in", 0.2): self.state.model.open(self.filename, language, sortAsRules, pageRangeRules) say("{} “{}”".format(word, os.path.normpath(self.filename)), self.state.showMessageTime) rules = SortAs.RulesForName[self.state.model.sortAsRules()] self.sortAsRuleLabel.setText(LABEL_TEMPLATE.format(rules.abbrev)) self.sortAsRuleLabel.setToolTip(Lib.rulesTip(rules.tip)) rules = Pages.RulesForName[self.state.model.pageRangeRules()] self.pageRangeRulesLabel.setText(LABEL_TEMPLATE.format(rules.abbrev)) self.pageRangeRulesLabel.setToolTip(Lib.rulesTip(rules.tip, False)) self.updateTitle() self.state.viewAllPanel.view.goHome()
def newXix(self, filename): if self.isReadOnly(filename): return self.closeXix() self.filename = filename QApplication.setOverrideCursor(Qt.WaitCursor) try: say("Creating {}…".format(os.path.normpath(self.filename)), SAY_TIMEOUT) QApplication.processEvents() self._openModel("Created") self.state.updateUi() self.updateWorkTime() self.state.entryPanel.clearForm() self.updateLanguageIndicator() self.state.setMode(ModeKind.VIEW) finally: QApplication.restoreOverrideCursor()
def renumber(self): # No need to restore focus widget options = RenumberOptions(self.romanStartSpinBox.value(), self.romanEndSpinBox.value(), self.romanChangeSpinBox.value(), self.decimalStartSpinBox.value(), self.decimalEndSpinBox.value(), self.decimalChangeSpinBox.value()) with Lib.DisableUI(self): self.state.model.renumber(options, self.state.window.reportProgress) message = "Renumber pages" if self.state.model.canUndo: self.state.model.can_undo.emit(True, message) if self.state.model.canRedo: self.state.model.can_redo.emit(True, message) self.state.updateUi() say("Renumbered pages", SAY_TIMEOUT) self.accept()
def importIndex(self, filename, inFilename): if self.isReadOnly(filename): return self.closeXix() Lib.remove_file(filename) # Don't want to merge! self.filename = filename QApplication.setOverrideCursor(Qt.WaitCursor) try: say("Importing {}…".format(os.path.normpath(self.filename)), SAY_TIMEOUT) QApplication.processEvents() self._importIndex(filename, inFilename) self.state.entryPanel.termEdit.setFocus() self.state.updateUi() self.updateWorkTime() self.updateLanguageIndicator() self.state.setMode(ModeKind.VIEW) finally: QApplication.restoreOverrideCursor()
def _search(self, *, started=False): self.searchMatch = self.searcher.search() if self.stopped: return # Already handled if self.searchMatch is None: self.stop("None found" if started else "No (more) found") else: if self.filteredEntriesRadioButton.isChecked(): self.state.viewFilteredPanel.view.gotoEid(self.searchMatch.eid) self.state.viewAllPanel.view.gotoEid(self.searchMatch.eid) editor = self._searchEditor() if editor is not None: cursor = editor.textCursor() cursor.setPosition(self.searchMatch.start) cursor.setPosition(self.searchMatch.end, QTextCursor.KeepAnchor) editor.setTextCursor(cursor) say("Found: Click Replace or Skip or Stop Search") self.updateUi()
def setSortAsRules(self, name, prefix=None, reportProgress=None): rules = SortAs.RulesForName[name] say("Updating Sort As texts for “{}” rules…".format(rules.name)) self.setMode(ModeKind.CHANGE) QApplication.sendPostedEvents(None, 0) QApplication.processEvents() try: eid = self.viewAllPanel.view.selectedEid self.model.setSortAsRules(name, prefix, reportProgress) self.window.sortAsRuleLabel.setText( LABEL_TEMPLATE.format(rules.abbrev)) self.window.sortAsRuleLabel.setToolTip(Lib.rulesTip(rules.tip)) self.viewAllPanel.view.gotoEid(eid) finally: say("Updated Sort As texts for “{}” rules".format(rules.name), SAY_TIMEOUT) self.setMode(ModeKind.VIEW) QApplication.sendPostedEvents(None, 0) QApplication.processEvents()
def _outputIndex(self, filename, widget): monitor = Lib.MonitorFile(filename) self.state.outputPath = os.path.dirname(filename) nativeFilename = QDir.toNativeSeparators(filename) try: say("Outputting to “{}”…".format(nativeFilename)) with Lib.DisableUI(*self.state.window.widgets()): config = self.state.model.configs().copy() config.Filename = filename Output.outputEntries(self.state.model, config, "Outputting", self.window.reportProgress) self._reportOnOutput( monitor, "Output to “{}”".format(nativeFilename), "Output Failed", "Failed to output to “{}”".format(nativeFilename)) except Output.Error as err: self._reportOnOutput( monitor, "Output to “{}”".format(nativeFilename), "Output Failed", "Failed to output to “{}”: {}".format(nativeFilename, err)) Lib.restoreFocus(widget)
def __init__(self, state, parent=None): super().__init__(parent) Lib.prepareModalDialog(self) self.state = state self.setWindowTitle("Combine Overlapping Pages — {}".format( QApplication.applicationName())) self.createWidgets() self.layoutWidgets() self.createConnections() self.entry = None QApplication.setOverrideCursor(Qt.WaitCursor) try: self.eids = list( self.state.model.filteredEntries( filter=FilterKind.HAS_OVERLAPPING_PAGES, match=None, offset=0, limit=UNLIMITED, entryData=EntryDataKind.EID)) finally: QApplication.restoreOverrideCursor() self.eidIndex = -1 if self.eids else None if self.eids: self.skip() self.combineButton.setFocus() say( "Found {:,} entries with overlapping pages".format( len(self.eids)), SAY_TIMEOUT) else: say("No overlapping pages found", SAY_TIMEOUT) self.updateUi() settings = QSettings() self.updateToolTips( bool( int( settings.value(Gopt.Key.ShowDialogToolTips, Gopt.Default.ShowDialogToolTips))))