def refToLink(ref): """Transforms the reference ``ref`` in a link displaying useful infos about that reference. For character, character's name. For text item, item's name, etc. """ match = re.fullmatch(RegEx, ref) if match: _type = match.group(1) _ref = match.group(2) text = "" if _type == TextLetter: m = mainWindow().mdlOutline idx = m.getIndexByID(_ref) if idx.isValid(): item = idx.internalPointer() text = item.title() elif _type == PersoLetter: m = mainWindow().mdlPersos text = m.item(int(_ref), Perso.name.value).text() elif _type == PlotLetter: m = mainWindow().mdlPlots text = m.getPlotNameByID(_ref) elif _type == WorldLetter: m = mainWindow().mdlWorld text = m.itemByID(_ref).text() if text: return "<a href='{ref}'>{text}</a>".format( ref=ref, text=text) else: return ref
def open(ref): """Identify ``ref`` and open it.""" match = re.fullmatch(RegEx, ref) if not match: return _type = match.group(1) _ref = match.group(2) if _type == CharacterLetter: mw = mainWindow() item = mw.lstCharacters.getItemByID(_ref) if item: mw.tabMain.setCurrentIndex(mw.TabPersos) mw.lstCharacters.setCurrentItem(item) return True print("Error: Ref {} not found".format(ref)) return False elif _type == TextLetter: mw = mainWindow() index = mw.mdlOutline.getIndexByID(_ref) if index.isValid(): mw.tabMain.setCurrentIndex(mw.TabRedac) mw.mainEditor.setCurrentModelIndex(index, newTab=True) return True else: print("Ref not found") return False elif _type == PlotLetter: mw = mainWindow() item = mw.lstPlots.getItemByID(_ref) if item: mw.tabMain.setCurrentIndex(mw.TabPlots) mw.lstPlots.setCurrentItem(item) return True print("Ref not found") return False elif _type == WorldLetter: mw = mainWindow() item = mw.mdlWorld.itemByID(_ref) if item: mw.tabMain.setCurrentIndex(mw.TabWorld) mw.treeWorld.setCurrentIndex( mw.mdlWorld.indexFromItem(item)) return True print("Ref not found") return False print("Ref not implemented") return False
def addCharacterInfo(self, ID): c = self.getCharacterByID(ID) self.beginInsertRows(c.index(), len(c.infos), len(c.infos)) c.infos.append(CharacterInfo(c, description="Description", value="Value")) self.endInsertRows() mainWindow().updatePersoInfoView()
def output(self, settingsWidget): settings = settingsWidget.getSettings() try: return self.concatenate(mainWindow().mdlOutline.rootItem, settings) except re.error as e: QMessageBox.warning(mainWindow().dialog, qApp.translate("Export", "Error"), qApp.translate("Export", "Error processing regular expression : \n{}").format(str(e))) return ""
def setAppFontSize(self, val): """ Set application default font point size. """ f = qApp.font() f.setPointSize(val) qApp.setFont(f) mainWindow().setFont(f) sttgs = QSettings(qApp.organizationName(), qApp.applicationName()) sttgs.setValue("appFontSize", val)
def choseCharacterColor(self): ID = self.currentCharacterID() c = self._model.getCharacterByID(ID) if c: color = iconColor(c.icon) else: color = Qt.white self.colorDialog = QColorDialog(color, mainWindow()) color = self.colorDialog.getColor(color) if color.isValid(): c.setColor(color) mainWindow().updateCharacterColor(ID)
def updateAllWidgets(self): # Update font and defaultBlockFormat to all textEditView. Drastically. for w in mainWindow().findChildren(textEditView, QRegExp(".*")): w.loadFontSettings() # Update background color in all tabSplitter (tabs) for w in mainWindow().findChildren(tabSplitter, QRegExp(".*")): w.updateStyleSheet() # Update background color in all folder text view: for w in mainWindow().findChildren(QWidget, QRegExp("editorWidgetFolderText")): w.setStyleSheet("background: {};".format(settings.textEditor["background"]))
def tooltip(ref): """Returns a tooltip in HTML for the reference ``ref``.""" match = re.fullmatch(RegEx, ref) if not match: return qApp.translate("references", "Not a reference: {}.").format(ref) _type = match.group(1) _ref = match.group(2) if _type == TextLetter: m = mainWindow().mdlOutline idx = m.getIndexByID(_ref) if not idx.isValid(): return qApp.translate("references", "Unknown reference: {}.").format(ref) item = idx.internalPointer() if item.isFolder(): tt = qApp.translate("references", "Folder: <b>{}</b>").format(item.title()) else: tt = qApp.translate("references", "Text: <b>{}</b>").format(item.title()) tt += "<br><i>{}</i>".format(item.path()) return tt elif _type == PersoLetter: m = mainWindow().mdlPersos item = m.item(int(_ref), Perso.name.value) if item: return qApp.translate("references", "Character: <b>{}</b>").format(item.text()) elif _type == PlotLetter: m = mainWindow().mdlPlots name = m.getPlotNameByID(_ref) if name: return qApp.translate("references", "Plot: <b>{}</b>").format(name) elif _type == WorldLetter: m = mainWindow().mdlWorld item = m.itemByID(_ref) if item: name = item.text() path = m.path(item) return qApp.translate("references", "World: <b>{name}</b>{path}").format( name=name, path=" <span style='color:gray;'>({})</span>".format(path) if path else "") return qApp.translate("references", "<b>Unknown reference:</b> {}.").format(ref)
def convert(self, src, args, outputfile=None): args = [self.cmd] + args if outputfile: args.append("--output={}".format(outputfile)) for name, col, var in [ ("Title", 0, "title"), ("Subtitle", 1, "subtitle"), ("Serie", 2, ""), ("Volume", 3, ""), ("Genre", 4, ""), ("License", 5, ""), ("Author", 6, "author"), ("Email", 7, ""), ]: item = mainWindow().mdlFlatData.item(0, col) if var and item and item.text().strip(): args.append("--variable={}:{}".format(var, item.text().strip())) # Add title metatadata required for pandoc >= 2.x args.append("--metadata=title:{}".format(mainWindow().mdlFlatData.item(0, 0).text().strip())) qApp.setOverrideCursor(QCursor(Qt.WaitCursor)) p = subprocess.Popen( args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) if not type(src) == bytes: src = src.encode("utf-8") # assumes utf-8 stdout, stderr = p.communicate(src) qApp.restoreOverrideCursor() if stderr or p.returncode != 0: err = "ERROR on export" + "\n" \ + "Return code" + ": %d\n" % (p.returncode) \ + "Command and parameters" + ":\n%s\n" % (p.args) \ + "Stderr content" + ":\n" + stderr.decode("utf-8") print(err) QMessageBox.critical(mainWindow().dialog, qApp.translate("Export", "Error"), err) return None return stdout.decode("utf-8")
def doCompile(self, filename): mw = mainWindow() root = mw.mdlOutline.rootItem doc = QTextDocument() cursor = QTextCursor(doc) def appendItem(item): if item.isFolder(): cursor.setPosition(doc.characterCount() - 1) title = "<h{l}>{t}</h{l}><br>\n".format( l=str(item.level() + 1), t=item.title()) cursor.insertHtml(title) for c in item.children(): appendItem(c) else: text = self.formatText(item.text(), item.type()) cursor.setPosition(doc.characterCount() - 1) cursor.insertHtml(text) for c in root.children(): appendItem(c) dw = QTextDocumentWriter(filename, "odt") dw.write(doc)
def doCompile(self, path): # FIXME: overwrites when items have identical names mw = mainWindow() root = mw.mdlOutline.rootItem def writeItem(item, path): if item.isFolder(): path2 = os.path.join(path, item.title()) try: os.mkdir(path2) except FileExistsError: pass for c in item.children(): writeItem(c, path2) else: ext = ".t2t" if item.isT2T() else \ ".html" if item.isHTML() else \ ".txt" path2 = os.path.join(path, item.title() + ext) f = open(path2, "w") text = self.formatText(item.text(), item.type()) f.write(text) for c in root.children(): writeItem(c, path)
def saveProject(): """ Saves the whole project. Call this function to save the project in Version 0 format. """ files = [] mw = mainWindow() files.append((saveStandardItemModelXML(mw.mdlFlatData), "flatModel.xml")) print("ERROR: file format 0 does not save characters !") # files.append((saveStandardItemModelXML(mw.mdlCharacter), # "perso.xml")) files.append((saveStandardItemModelXML(mw.mdlWorld), "world.xml")) files.append((saveStandardItemModelXML(mw.mdlLabels), "labels.xml")) files.append((saveStandardItemModelXML(mw.mdlStatus), "status.xml")) files.append((saveStandardItemModelXML(mw.mdlPlots), "plots.xml")) files.append((mw.mdlOutline.saveToXML(), "outline.xml")) files.append((settings.save(), "settings.pickle")) saveFilesToZip(files, mw.currentProject)
def itemContains(self, text, columns, mainWindow=mainWindow(), caseSensitive=False): lst = [] text = text.lower() if not caseSensitive else text for c in columns: if c == Outline.POV.value and self.POV(): c = mainWindow.mdlCharacter.getCharacterByID(self.POV()) if c: searchIn = c.name() else: searchIn = "" print("Character POV not found:", self.POV()) elif c == Outline.status.value: searchIn = mainWindow.mdlStatus.item(toInt(self.status()), 0).text() elif c == Outline.label.value: searchIn = mainWindow.mdlLabels.item(toInt(self.label()), 0).text() else: searchIn = self.data(c) searchIn = searchIn.lower() if not caseSensitive else searchIn if text in searchIn: if not self.ID() in lst: lst.append(self.ID()) return lst
def __init__(self, parent=None): QWidget.__init__(self, parent) self.setupUi(self) self._updating = False self.mw = mainWindow() self.tab.tabCloseRequested.connect(self.closeTab) self.tab.currentChanged.connect(self.tabChanged) # UI try: self.tab.setTabBarAutoHide(True) except AttributeError: print("Info: install Qt 5.4 or higher to use tabbar auto-hide in editor.") # Connections -------------------------------------------------------- self.sldCorkSizeFactor.valueChanged.connect( self.setCorkSizeFactor, AUC) self.btnRedacFolderCork.toggled.connect( self.sldCorkSizeFactor.setVisible, AUC) self.btnRedacFolderText.clicked.connect( lambda v: self.setFolderView("text"), AUC) self.btnRedacFolderCork.clicked.connect( lambda v: self.setFolderView("cork"), AUC) self.btnRedacFolderOutline.clicked.connect( lambda v: self.setFolderView("outline"), AUC) self.btnRedacFullscreen.clicked.connect( self.showFullScreen, AUC)
def callMainTreeView(self, functionName): """ The tree view in main window must have same index as the text edit that has focus. So we can pass it the call for documents edits like: duplicate, move up, etc. """ if self._index and self._column == Outline.text: function = getattr(F.mainWindow().treeRedacOutline, functionName) function()
def updateStatusBar(self): # Update progress # if self.currentIndex and self.currentIndex.isValid(): # if self._model: mw = mainWindow() if not mw: return mw.mainEditor.updateStats()
def MW(): """ Returns the mainWindow """ from manuskript import functions as F MW = F.mainWindow() assert MW is not None assert MW == F.MW return MW
def test_mainWindow(): from PyQt5.QtWidgets import QWidget, QLCDNumber assert F.mainWindow() is not None assert F.MW is not None F.statusMessage("Test") F.printObjects() assert len(F.findWidgetsOfClass(QWidget)) > 0 assert len(F.findWidgetsOfClass(QLCDNumber)) == 0
def findItemsContaining(self, text, columns, mainWindow=mainWindow(), caseSensitive=False, recursive=True): """Returns a list if IDs of all subitems containing ``text`` in columns ``columns`` (being a list of int). """ lst = self.itemContains(text, columns, mainWindow, caseSensitive) if recursive: for c in self.children(): lst.extend(c.findItemsContaining(text, columns, mainWindow, caseSensitive)) return lst
def loadProject(project): files = loadFilesFromZip(project) mw = mainWindow() errors = [] if "flatModel.xml" in files: loadStandardItemModelXML(mw.mdlFlatData, files["flatModel.xml"], fromString=True) else: errors.append("flatModel.xml") if "perso.xml" in files: loadStandardItemModelXMLForCharacters(mw.mdlCharacter, files["perso.xml"]) else: errors.append("perso.xml") if "world.xml" in files: loadStandardItemModelXML(mw.mdlWorld, files["world.xml"], fromString=True) else: errors.append("world.xml") if "labels.xml" in files: loadStandardItemModelXML(mw.mdlLabels, files["labels.xml"], fromString=True) else: errors.append("labels.xml") if "status.xml" in files: loadStandardItemModelXML(mw.mdlStatus, files["status.xml"], fromString=True) else: errors.append("status.xml") if "plots.xml" in files: loadStandardItemModelXML(mw.mdlPlots, files["plots.xml"], fromString=True) else: errors.append("plots.xml") if "outline.xml" in files: mw.mdlOutline.loadFromXML(files["outline.xml"], fromString=True) else: errors.append("outline.xml") if "settings.pickle" in files: settings.load(files["settings.pickle"], fromString=True) else: errors.append("settings.pickle") return errors
def launch(app, MW = None): if MW is None: from manuskript.functions import mainWindow MW = mainWindow() MW.show() # Support for IPython Jupyter QT Console as a debugging aid. # Last argument must be --console to enable it # Code reference : # https://github.com/ipython/ipykernel/blob/master/examples/embedding/ipkernel_qtapp.py # https://github.com/ipython/ipykernel/blob/master/examples/embedding/internal_ipkernel.py if len(sys.argv) > 1 and sys.argv[-1] == "--console": try: from IPython.lib.kernel import connect_qtconsole from ipykernel.kernelapp import IPKernelApp # Only to ensure matplotlib QT mainloop integration is available import matplotlib # Create IPython kernel within our application kernel = IPKernelApp.instance() # Initialize it and use matplotlib for main event loop integration with QT kernel.initialize(['python', '--matplotlib=qt']) # Create the console in a new process and connect console = connect_qtconsole(kernel.abs_connection_file, profile=kernel.profile) # Export MW and app variable to the console's namespace kernel.shell.user_ns['MW'] = MW kernel.shell.user_ns['app'] = app kernel.shell.user_ns['kernel'] = kernel kernel.shell.user_ns['console'] = console # When we close manuskript, make sure we close the console process and stop the # IPython kernel's mainloop, otherwise the app will never finish. def console_cleanup(): app.quit() console.kill() kernel.io_loop.stop() app.lastWindowClosed.connect(console_cleanup) # Very important, IPython-specific step: this gets GUI event loop # integration going, and it replaces calling app.exec_() kernel.start() except Exception as e: print("Console mode requested but error initializing IPython : %s" % str(e)) print("To make use of the Interactive IPython QT Console, make sure you install : ") print("$ pip3 install ipython qtconsole matplotlib") qApp.exec_() else: qApp.exec_() qApp.deleteLater()
def setModels(self): mw = mainWindow() self.outlineModel = mw.mdlOutline self.persoModel = mw.mdlPersos self.plotModel = mw.mdlPlots self.worldModel = mw.mdlWorld self.outlineModel.dataChanged.connect(self.populateTimer.start) self.persoModel.dataChanged.connect(self.populateTimer.start) self.plotModel.dataChanged.connect(self.populateTimer.start) self.worldModel.dataChanged.connect(self.populateTimer.start) self.populate()
def removeCharacterInfo(self, ID): c = self.getCharacterByID(ID) rm = [] for idx in mainWindow().tblPersoInfos.selectedIndexes(): if not idx.row() in rm: rm.append(idx.row()) rm.sort() rm.reverse() for r in rm: self.beginRemoveRows(c.index(), r, r) c.infos.pop(r) self.endRemoveRows()
def __init__(self, parent): QWidget.__init__(self, parent) self.setupUi(self) self.currentIndex = QModelIndex() self.currentID = None self.txtEdits = [] self.scroll.setBackgroundRole(QPalette.Base) self.toggledSpellcheck.connect(self.txtRedacText.toggleSpellcheck, AUC) self.dictChanged.connect(self.txtRedacText.setDict, AUC) self.txtRedacText.setHighlighting(True) self.currentDict = "" self.spellcheck = True self.folderView = "cork" self.mw = mainWindow()
def listReferences(ref, title=qApp.translate("references", "Referenced in:")): oM = mainWindow().mdlOutline listRefs = "" lst = findReferencesTo(ref) for t in lst: idx = oM.getIndexByID(t) listRefs += "<li><a href='{link}'>{text}</a></li>".format( link=textReference(t), text=oM.data(idx, Outline.title)) return "<h2>{title}</h2><ul>{ref}</ul>".format( title=title, ref=listRefs) if listRefs else ""
def updateListFromData(self): data = mainWindow().cheatSheet.data self.list.clear() for cat in data: filtered = [i for i in data[cat] if self.text.text().lower() in i[0].lower()] if filtered: self.addCategory(cat[0]) for item in filtered: i = QListWidgetItem(item[0]) i.setData(Qt.UserRole, Ref.EmptyRef.format(cat[1], item[1], item[0])) i.setData(Qt.UserRole + 1, item[2]) self.list.addItem(i) self.list.setCurrentRow(1) self.text.setFocus(Qt.PopupFocusReason)
def setModels(self): mw = mainWindow() self.outlineModel = mw.mdlOutline self.characterModel = mw.mdlCharacter self.plotModel = mw.mdlPlots self.worldModel = mw.mdlWorld self.outlineModel.dataChanged.connect(self.populateTimer.start) self.characterModel.dataChanged.connect(self.populateTimer.start) self.characterModel.rowsInserted.connect(self.populateTimer.start) self.characterModel.rowsRemoved.connect(self.populateTimer.start) self.plotModel.dataChanged.connect(self.populateTimer.start) self.worldModel.dataChanged.connect(self.populateTimer.start) self.populate()
def restoreOpenIndexes(self, openIndexes): try: if openIndexes[1]: self.split(state=openIndexes[0]) for i in openIndexes[1]: idx = mainWindow().mdlOutline.getIndexByID(i) self.mainEditor.setCurrentModelIndex(idx, newTab=True) if openIndexes[2]: self.focusTab = 2 self.secondTab.restoreOpenIndexes(openIndexes[2]) except: # Cannot load open indexes. Let's simply open root. self.mainEditor.setCurrentModelIndex(QModelIndex(), newTab=True)
def __init__(self, parent=None): QWidget.__init__(self, parent) self.setupUi(self) self.template = [] self.mw = mainWindow() self.btnOpen.clicked.connect(self.openFile) self.btnCreate.clicked.connect(self.createFile) self.chkLoadLastProject.toggled.connect(self.setAutoLoad) self.tree.itemClicked.connect(self.changeTemplate) self.btnAddLevel.clicked.connect(self.templateAddLevel) self.btnAddWC.clicked.connect(self.templateAddWordCount) self.btnCreateText = self.btnCreate.text() self.populateTemplates() self._templates = self.templates()
def findReferencesTo(ref, parent=None, recursive=True): """List of text items containing references ref, and returns IDs. Starts from item parent. If None, starts from root.""" oM = mainWindow().mdlOutline if parent == None: parent = oM.rootItem # Removes everything after the second ':': '{L:ID:random text}' → '{L:ID:' ref = ref[:ref.index(":", ref.index(":") + 1)+1] # Bare form '{L:ID}' ref2 = ref[:-1] + "}" # Since it's a simple search (no regex), we search for both. lst = parent.findItemsContaining(ref, [Outline.notes], recursive=recursive) lst += parent.findItemsContaining(ref2, [Outline.notes], recursive=recursive) return lst
def setView(self): # index = mainWindow().treeRedacOutline.currentIndex() # Couting the number of other selected items # sel = [] # for i in mainWindow().treeRedacOutline.selectionModel().selection().indexes(): # if i.column() != 0: continue # if i not in sel: sel.append(i) # if len(sel) != 0: # item = index.internalPointer() # else: # index = QModelIndex() # item = self.mw.mdlOutline.rootItem # self.currentIndex = index if self.currentIndex.isValid(): item = self.currentIndex.internalPointer() else: item = self.mw.mdlOutline.rootItem self.updateTabTitle() def addTitle(itm): edt = textEditView(self, html="<h{l}>{t}</h{l}>".format(l=min( itm.level() + 1, 5), t=itm.title()), autoResize=True) edt.setFrameShape(QFrame.NoFrame) self.txtEdits.append(edt) l.addWidget(edt) def addLine(): line = QFrame(self.text) line.setFrameShape(QFrame.HLine) line.setFrameShadow(QFrame.Sunken) l.addWidget(line) def addText(itm): edt = textEditView(self, index=itm.index(), spellcheck=self.spellcheck, dict=settings.dict, highlighting=True, autoResize=True) edt.setFrameShape(QFrame.NoFrame) edt.setStatusTip("{}".format(itm.path())) self.toggledSpellcheck.connect(edt.toggleSpellcheck, AUC) self.dictChanged.connect(edt.setDict, AUC) # edt.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.txtEdits.append(edt) l.addWidget(edt) def addChildren(itm): for c in range(itm.childCount()): child = itm.child(c) if child.isFolder(): addTitle(child) addChildren(child) else: addText(child) addLine() def addSpacer(): l.addItem( QSpacerItem(10, 1000, QSizePolicy.Minimum, QSizePolicy.Expanding)) # Display multiple selected items # if len(sel) > 1 and False: # Buggy and not very useful, skip # self.stack.setCurrentIndex(1) # w = QWidget() # l = QVBoxLayout(w) # self.txtEdits = [] # for idx in sel: # sItem = idx.internalPointer() # addTitle(sItem) # if sItem.isFolder(): # addChildren(sItem) # else: # addText(sItem) # addLine() # addSpacer() # self.scroll.setWidget(w) if item and item.isFolder() and self.folderView == "text": self.stack.setCurrentIndex(1) w = QWidget() w.setObjectName("editorWidgetFolderText") l = QVBoxLayout(w) w.setStyleSheet("background: {};".format( settings.textEditor["background"])) # self.scroll.setWidgetResizable(False) self.txtEdits = [] if item != self.mw.mdlOutline.rootItem: addTitle(item) addChildren(item) addSpacer() self.scroll.setWidget(w) elif item and item.isFolder() and self.folderView == "cork": self.stack.setCurrentIndex(2) self.corkView.setModel(self.mw.mdlOutline) self.corkView.setRootIndex(self.currentIndex) try: self.corkView.selectionModel().selectionChanged.connect( mainWindow().redacMetadata.selectionChanged, AUC) self.corkView.clicked.connect( mainWindow().redacMetadata.selectionChanged, AUC) self.corkView.clicked.connect( mainWindow().mainEditor.updateTargets, AUC) except TypeError: pass elif item and item.isFolder() and self.folderView == "outline": self.stack.setCurrentIndex(3) self.outlineView.setModelCharacters(mainWindow().mdlCharacter) self.outlineView.setModelLabels(mainWindow().mdlLabels) self.outlineView.setModelStatus(mainWindow().mdlStatus) self.outlineView.setModel(self.mw.mdlOutline) self.outlineView.setRootIndex(self.currentIndex) try: self.outlineView.selectionModel().selectionChanged.connect( mainWindow().redacMetadata.selectionChanged, AUC) self.outlineView.clicked.connect( mainWindow().redacMetadata.selectionChanged, AUC) self.outlineView.clicked.connect( mainWindow().mainEditor.updateTargets, AUC) except TypeError: pass if item and item.isText(): self.txtRedacText.setCurrentModelIndex(self.currentIndex) self.stack.setCurrentIndex(0) # Single text item else: self.txtRedacText.setCurrentModelIndex(QModelIndex()) try: self.mw.mdlOutline.dataChanged.connect(self.modelDataChanged, AUC) self.mw.mdlOutline.rowsInserted.connect(self.updateIndexFromID, AUC) self.mw.mdlOutline.rowsRemoved.connect(self.updateIndexFromID, AUC) #self.mw.mdlOutline.rowsAboutToBeRemoved.connect(self.rowsAboutToBeRemoved, AUC) except TypeError: pass self.updateStatusBar()
def __init__(self, _format, parent=None): QWidget.__init__(self, parent) self.setupUi(self) self.toolBox.setStyleSheet(S.toolBoxSS()) self.mw = mainWindow() self._format = _format self.settings = {} ################################################################# # Content self.grpContentFilters.button.setChecked(False) h = self.tblContent.horizontalHeader() h.setSectionResizeMode(h.ResizeToContents) h.setSectionResizeMode(0, h.Stretch) self.contentUpdateTable() self.chkContentMore.toggled.connect(self.contentTableToggle) self.contentTableToggle(False) # Labels self.lstContentLabels.clear() h = QFontMetrics(self.font()).height() for i in range(0, self.mw.mdlLabels.rowCount()): item = self.mw.mdlLabels.item(i, 0) if item: item = QListWidgetItem(item.icon(), item.text()) item.setFlags(item.flags() | Qt.ItemIsUserCheckable) item.setCheckState(Qt.Checked) item.setSizeHint(QSize(100, h)) self.lstContentLabels.addItem(item) self.chkContentLabels.toggled.connect(self.lstContentLabels.setVisible) self.chkContentLabels.toggled.connect(lambda: self.listWidgetAdjustToContent(self.lstContentLabels)) self.lstContentLabels.setVisible(False) # Status self.lstContentStatus.clear() h = QFontMetrics(self.font()).height() for i in range(0, self.mw.mdlStatus.rowCount()): item = self.mw.mdlStatus.item(i, 0) if item: item = QListWidgetItem(item.icon(), item.text()) item.setFlags(item.flags() | Qt.ItemIsUserCheckable) item.setCheckState(Qt.Checked) item.setSizeHint(QSize(100, h)) self.lstContentStatus.addItem(item) self.chkContentStatus.toggled.connect(self.lstContentStatus.setVisible) self.chkContentStatus.toggled.connect(lambda: self.listWidgetAdjustToContent(self.lstContentStatus)) self.lstContentStatus.setVisible(False) # Root item self.cmbContentParent.setModel(self.mw.mdlOutline) v = QTreeView() self.cmbContentParent.setView(v) v.setHeaderHidden(True) for i in range(1, self.mw.mdlOutline.columnCount()): v.hideColumn(i) self.chkContentParent.toggled.connect(self.cmbContentParent.setVisible) self.cmbContentParent.hide() ################################################################# # Separations for cmb in [self.cmbSepFF, self.cmbSepTT, self.cmbSepFT, self.cmbSepTF]: cmb.clear() cmb.addItem(self.tr("Empty line"), "empty") cmb.addItem(self.tr("Custom"), "custom") cmb.currentIndexChanged.connect(self.sepCmbChanged) ################################################################# # Transformations h = self.tblReplacements.horizontalHeader() h.setSectionResizeMode(h.ResizeToContents) h.setSectionResizeMode(1, h.Stretch) h.setSectionResizeMode(2, h.Stretch) # Cf. https://en.wikipedia.org/wiki/Quotation_mark self.cmbTransDoubleQuotes.clear() self.cmbTransDoubleQuotes.addItems(["”___“", "“___”", "«___»"]) self.cmbTransSingleQuote.clear() self.cmbTransSingleQuote.addItems(["‘___’", "‹___›"]) for cmb in [self.cmbTransDoubleQuotes, self.cmbTransSingleQuote]: cmb.addItem(self.tr("Custom"), "custom") cmb.currentIndexChanged.connect(self.transCmbChanged) cmb.currentIndexChanged.emit(0) self.btnTransAdd.clicked.connect(lambda: self.transAddTableRow(checked=True)) self.btnTransRemove.clicked.connect(self.transRemoveTableRow) self.tableWidgetAdjustToContent(self.tblReplacements) ################################################################# # Preview self.cmbPreviewFont.setCurrentFont(self.font()) self.spnPreviewSize.setValue(self.font().pointSize()) ################################################################# # Final stuff self.toolBox.setCurrentIndex(0)
def load(string, fromString=False, protocol=None): """Load settings from 'string'. 'string' is the filename of the pickle dump. If fromString=True, string is the data of the pickle dumps.""" global allSettings if not fromString: try: f = open(string, "rb") allSettings = pickle.load(f) except: print("{} doesn't exist, cannot load settings.".format(string)) return else: if protocol == 0: allSettings = json.loads(string) else: allSettings = pickle.loads(string) #pp=pprint.PrettyPrinter(indent=4, compact=False) #print("Loading:") #pp.pprint(allSettings) # FIXME: use dict.update(dict) to update settings in newer versions. if "viewSettings" in allSettings: global viewSettings viewSettings = allSettings["viewSettings"] for cat, name, default in [ ("Tree", "iconSize", 24), # Added in 0.6.0 ]: if not name in viewSettings[cat]: viewSettings[cat][name] = default if "fullscreenSettings" in allSettings: global fullscreenSettings fullscreenSettings = allSettings["fullscreenSettings"] if "dict" in allSettings: global dict dict = allSettings["dict"] if "spellcheck" in allSettings: global spellcheck spellcheck = allSettings["spellcheck"] if "corkSizeFactor" in allSettings: global corkSizeFactor corkSizeFactor = allSettings["corkSizeFactor"] if "folderView" in allSettings: global folderView folderView = allSettings["folderView"] if "lastTab" in allSettings: global lastTab lastTab = allSettings["lastTab"] if "openIndexes" in allSettings: global openIndexes openIndexes = allSettings["openIndexes"] if "autoSave" in allSettings: global autoSave autoSave = allSettings["autoSave"] if "autoSaveDelay" in allSettings: global autoSaveDelay autoSaveDelay = allSettings["autoSaveDelay"] if "saveOnQuit" in allSettings: global saveOnQuit saveOnQuit = allSettings["saveOnQuit"] if "autoSaveNoChanges" in allSettings: global autoSaveNoChanges autoSaveNoChanges = allSettings["autoSaveNoChanges"] if "autoSaveNoChangesDelay" in allSettings: global autoSaveNoChangesDelay autoSaveNoChangesDelay = allSettings["autoSaveNoChangesDelay"] if "outlineViewColumns" in allSettings: global outlineViewColumns outlineViewColumns = allSettings["outlineViewColumns"] if "corkBackground" in allSettings: global corkBackground corkBackground = allSettings["corkBackground"] if "corkStyle" in allSettings: global corkStyle corkStyle = allSettings["corkStyle"] if "fullScreenTheme" in allSettings: global fullScreenTheme fullScreenTheme = allSettings["fullScreenTheme"] if "defaultTextType" in allSettings: global defaultTextType defaultTextType = allSettings["defaultTextType"] if "textEditor" in allSettings: global textEditor textEditor = allSettings["textEditor"] added = { "textAlignment": 0, # Added in 0.5.0 "cursorWidth": 1, "cursorNotBlinking": False, # Added in 0.6.0 "maxWidth": 600, "marginsLR": 0, "marginsTB": 20, "backgroundTransparent": False, # Added in 0.6.0 "alwaysCenter": False, # Added in 0.7.0 "focusMode": False, } for k in added: if not k in textEditor: textEditor[k] = added[k] if textEditor["cursorNotBlinking"]: qApp.setCursorFlashTime(0) else: from manuskript.functions import mainWindow qApp.setCursorFlashTime(mainWindow()._defaultCursorFlashTime) if "revisions" in allSettings: global revisions revisions = allSettings["revisions"] # With JSON we had to convert int keys to str, and None to "null", so we roll back. r = {} for i in revisions["rules"]: if i == "null": r[None] = revisions["rules"]["null"] elif i == None: r[None] = revisions["rules"][None] else: r[int(i)] = revisions["rules"][i] revisions["rules"] = r if "frequencyAnalyzer" in allSettings: global frequencyAnalyzer frequencyAnalyzer = allSettings["frequencyAnalyzer"] if "viewMode" in allSettings: global viewMode viewMode = allSettings["viewMode"] if "saveToZip" in allSettings: global saveToZip saveToZip = allSettings["saveToZip"] if "dontShowDeleteWarning" in allSettings: global dontShowDeleteWarning dontShowDeleteWarning = allSettings["dontShowDeleteWarning"]
def projectPath(cls): return os.path.dirname(os.path.abspath(mainWindow().currentProject))
def loadProject(project, zip=None): """ Loads a project. @param project: the filename of the project to open. @param zip: whether the project is a zipped or not. @return: an array of errors, empty if None. """ mw = mainWindow() errors = [] #################################################################################################################### # Read and store everything in a dict log("\nLoading {} ({})".format(project, "ZIP" if zip else "not zip")) if zip: files = loadFilesFromZip(project) # Decode files for f in files: if f[-4:] not in [".xml", "opml"]: files[f] = files[f].decode("utf-8") else: # Project path dir = os.path.dirname(project) # Folder containing file: name of the project file (without .msk extension) folder = os.path.splitext(os.path.basename(project))[0] # The full path towards the folder containing files path = os.path.join(dir, folder, "") files = {} for dirpath, dirnames, filenames in os.walk(path): p = dirpath.replace(path, "") # Skip directories that begin with a period if p[:1] == ".": continue for f in filenames: # Skip filenames that begin with a period if f[:1] == ".": continue # mode = "r" + ("b" if f[-4:] in [".xml", "opml"] else "") if f[-4:] in [".xml", "opml"]: with open(os.path.join(dirpath, f), "rb") as fo: files[os.path.join(p, f)] = fo.read() else: with open(os.path.join(dirpath, f), "r", encoding="utf8") as fo: files[os.path.join(p, f)] = fo.read() # Saves to cache (only if we loaded from disk and not zip) global cache cache = files # FIXME: watch directory for changes # Sort files by keys files = OrderedDict(sorted(files.items())) #################################################################################################################### # Settings if "settings.txt" in files: settings.load(files["settings.txt"], fromString=True, protocol=0) else: errors.append("settings.txt") # Just to be sure settings.saveToZip = zip settings.defaultTextType = "md" #################################################################################################################### # Labels mdl = mw.mdlLabels mdl.appendRow(QStandardItem("")) # Empty = No labels if "labels.txt" in files: log("\nReading labels:") for s in files["labels.txt"].split("\n"): if not s: continue m = re.search(r"^(.*?):\s*(.*)$", s) txt = m.group(1) col = m.group(2) log("* Add status: {} ({})".format(txt, col)) icon = iconFromColorString(col) mdl.appendRow(QStandardItem(icon, txt)) else: errors.append("labels.txt") #################################################################################################################### # Status mdl = mw.mdlStatus mdl.appendRow(QStandardItem("")) # Empty = No status if "status.txt" in files: log("\nReading Status:") for s in files["status.txt"].split("\n"): if not s: continue log("* Add status:", s) mdl.appendRow(QStandardItem(s)) else: errors.append("status.txt") #################################################################################################################### # Infos mdl = mw.mdlFlatData if "infos.txt" in files: md, body = parseMMDFile(files["infos.txt"], asDict=True) row = [] for name in [ "Title", "Subtitle", "Serie", "Volume", "Genre", "License", "Author", "Email" ]: row.append(QStandardItem(md.get(name, ""))) mdl.appendRow(row) else: errors.append("infos.txt") #################################################################################################################### # Summary mdl = mw.mdlFlatData if "summary.txt" in files: md, body = parseMMDFile(files["summary.txt"], asDict=True) row = [] for name in ["Situation", "Sentence", "Paragraph", "Page", "Full"]: row.append(QStandardItem(md.get(name, ""))) mdl.appendRow(row) else: errors.append("summary.txt") #################################################################################################################### # Plots mdl = mw.mdlPlots if "plots.xml" in files: log("\nReading plots:") # xml = bytearray(files["plots.xml"], "utf-8") root = ET.fromstring(files["plots.xml"]) for plot in root: # Create row row = getStandardItemRowFromXMLEnum(plot, Plot) # Log log("* Add plot: ", row[0].text()) # Characters if row[Plot.characters].text(): IDs = row[Plot.characters].text().split(",") item = QStandardItem() for ID in IDs: item.appendRow(QStandardItem(ID.strip())) row[Plot.characters] = item # Subplots for step in plot: row[Plot.steps].appendRow( getStandardItemRowFromXMLEnum(step, PlotStep)) # Add row to the model mdl.appendRow(row) else: errors.append("plots.xml") #################################################################################################################### # World mdl = mw.mdlWorld if "world.opml" in files: log("\nReading World:") # xml = bytearray(files["plots.xml"], "utf-8") root = ET.fromstring(files["world.opml"]) body = root.find("body") for outline in body: row = getOutlineItem(outline, World) mdl.appendRow(row) else: errors.append("world.opml") #################################################################################################################### # Characters mdl = mw.mdlCharacter log("\nReading Characters:") for f in [f for f in files if "characters" in f]: md, body = parseMMDFile(files[f]) c = mdl.addCharacter() c.lastPath = f color = False for desc, val in md: # Base infos if desc in characterMap.values(): key = [ key for key, value in characterMap.items() if value == desc ][0] index = c.index(key.value) mdl.setData(index, val) # Character color elif desc == "Color" and not color: c.setColor(QColor(val)) # We remember the first time we found "Color": it is the icon color. # If "Color" comes a second time, it is a Character's info. color = True # Character's infos else: c.infos.append(CharacterInfo(c, desc, val)) log("* Adds {} ({})".format(c.name(), c.ID())) #################################################################################################################### # Texts # We read outline form the outline folder. If revisions are saved, then there's also a revisions.xml which contains # everything, but the outline folder takes precedence (in cases it's been edited outside of manuskript. mdl = mw.mdlOutline log("\nReading outline:") paths = [f for f in files if "outline" in f] outline = OrderedDict() # We create a structure of imbricated OrderedDict to store the whole tree. for f in paths: split = f.split(os.path.sep)[1:] # log("* ", split) last = "" parent = outline parentLastPath = "outline" for i in split: if last: parent = parent[last] parentLastPath = os.path.join(parentLastPath, last) last = i if not i in parent: # If not last item, then it is a folder if i != split[-1]: parent[i] = OrderedDict() # If file, we store it else: parent[i] = files[f] # We store f to add it later as lastPath parent[i + ":lastPath"] = os.path.join(parentLastPath, i) # We now just have to recursively add items. addTextItems(mdl, outline) # Adds revisions if "revisions.xml" in files: root = ET.fromstring(files["revisions.xml"]) appendRevisions(mdl, root) # Check IDS mdl.rootItem.checkIDs() return errors
def tabOpenIndexes(self): sel = [] for i in range(self.tab.count()): sel.append(mainWindow().mdlOutline.ID(self.tab.widget(i).currentIndex)) return sel
def openView(self, searchResult): mainWindow().tabMain.setCurrentIndex(mainWindow().TabSummary)
def __init__(self, parent): QStandardItemModel.__init__(self, 0, 3, parent) self.setHorizontalHeaderLabels([i.name for i in Plot]) self.mw = mainWindow() self.updatePlotPersoButton()
def shortInfos(ref): """Returns infos about reference ``ref``. Returns -1 if ``ref`` is not a valid reference, and None if it is valid but unknown.""" match = re.fullmatch(RegEx, ref) if not match: return -1 _type = match.group(1) _ref = match.group(2) infos = {} infos["ID"] = _ref if _type == TextLetter: infos["type"] = TextLetter m = mainWindow().mdlOutline idx = m.getIndexByID(_ref) if not idx.isValid(): return None item = idx.internalPointer() if item.isFolder(): infos["text_type"] = "folder" else: infos["text_type"] = "text" infos["title"] = item.title() infos["path"] = item.path() return infos elif _type == CharacterLetter: infos["type"] = CharacterLetter m = mainWindow().mdlCharacter c = m.getCharacterByID(_ref) if c: infos["title"] = c.name() infos["name"] = c.name() return infos elif _type == PlotLetter: infos["type"] = PlotLetter m = mainWindow().mdlPlots name = m.getPlotNameByID(_ref) if name: infos["title"] = name return infos elif _type == WorldLetter: infos["type"] = WorldLetter m = mainWindow().mdlWorld item = m.itemByID(_ref) if item: name = item.text() path = m.path(item) infos["title"] = name infos["path"] = path return infos return None
def saveProject(zip=None): """ Saves the project. If zip is False, the project is saved as a multitude of plain-text files for the most parts and some XML or zip? for settings and stuff. If zip is True, everything is saved as a single zipped file. Easier to carry around, but does not allow collaborative work, versioning, or third-party editing. @param zip: if True, saves as a single file. If False, saves as plain-text. If None, tries to determine based on settings. @return: True if successful, False otherwise. """ if zip is None: zip = settings.saveToZip log("\n\nSaving to:", "zip" if zip else "folder") # List of files to be written files = [] # List of files to be removed removes = [] # List of files to be moved moves = [] # MainWindow interaction things. mw = mainWindow() project = mw.currentProject # Sanity check (see PR-583): make sure we actually have a current project. if project is None: print( "Error: cannot save project because there is no current project in the UI." ) return False # File format version files.append(("MANUSKRIPT", "1")) # General infos (book and author) # Saved in plain text, in infos.txt path = "infos.txt" content = "" for name, col in [ ("Title", 0), ("Subtitle", 1), ("Serie", 2), ("Volume", 3), ("Genre", 4), ("License", 5), ("Author", 6), ("Email", 7), ]: item = mw.mdlFlatData.item(0, col) if item: val = item.text().strip() else: val = "" if val: content += "{name}:{spaces}{value}\n".format(name=name, spaces=" " * (15 - len(name)), value=val) files.append((path, content)) #################################################################################################################### # Summary # In plain text, in summary.txt path = "summary.txt" content = "" for name, col in [ ("Situation", 0), ("Sentence", 1), ("Paragraph", 2), ("Page", 3), ("Full", 4), ]: item = mw.mdlFlatData.item(1, col) if item: val = item.text().strip() else: val = "" if val: content += formatMetaData(name, val, 12) files.append((path, content)) #################################################################################################################### # Label & Status # In plain text for mdl, path in [(mw.mdlStatus, "status.txt"), (mw.mdlLabels, "labels.txt")]: content = "" # We skip the first row, which is empty and transparent for i in range(1, mdl.rowCount()): color = "" if mdl.data(mdl.index(i, 0), Qt.DecorationRole) is not None: color = iconColor(mdl.data(mdl.index( i, 0), Qt.DecorationRole)).name(QColor.HexRgb) color = color if color != "#ff000000" else "#00000000" text = mdl.data(mdl.index(i, 0)) if text: content += "{name}{color}\n".format( name=text, color="" if color == "" else ":" + " " * (20 - len(text)) + color) files.append((path, content)) #################################################################################################################### # Characters # In a character folder path = os.path.join("characters", "{name}.txt") mdl = mw.mdlCharacter # Review characters for c in mdl.characters: # Generates file's content content = "" for m in characterMap: val = mdl.data(c.index(m.value)).strip() if val: content += formatMetaData(characterMap[m], val, 20) # Character's color: content += formatMetaData("Color", c.color().name(QColor.HexRgb), 20) # Character's infos for info in c.infos: content += formatMetaData(info.description, info.value, 20) # generate file's path cpath = path.format(name="{ID}-{slugName}".format( ID=c.ID(), slugName=slugify(c.name()))) # Has the character been renamed? if c.lastPath and cpath != c.lastPath: moves.append((c.lastPath, cpath)) # Update character's path c.lastPath = cpath files.append((cpath, content)) #################################################################################################################### # Texts # In an outline folder mdl = mw.mdlOutline # Go through the tree f, m, r = exportOutlineItem(mdl.rootItem) files += f moves += m removes += r # Writes revisions (if asked for) if settings.revisions["keep"]: files.append(("revisions.xml", mdl.saveToXML())) #################################################################################################################### # World # Either in an XML file, or in lots of plain texts? # More probably text, since there might be writing done in third-party. path = "world.opml" mdl = mw.mdlWorld root = ET.Element("opml") root.attrib["version"] = "1.0" body = ET.SubElement(root, "body") addWorldItem(body, mdl) content = ET.tostring(root, encoding="UTF-8", xml_declaration=True, pretty_print=True) files.append((path, content)) #################################################################################################################### # Plots (mw.mdlPlots) # Either in XML or lots of plain texts? # More probably XML since there is not really a lot if writing to do (third-party) path = "plots.xml" mdl = mw.mdlPlots root = ET.Element("root") addPlotItem(root, mdl) content = ET.tostring(root, encoding="UTF-8", xml_declaration=True, pretty_print=True) files.append((path, content)) #################################################################################################################### # Settings # Saved in readable text (json) for easier versioning. But they mustn't be shared, it seems. # Maybe include them only if zipped? # Well, for now, we keep them here... files.append(("settings.txt", settings.save(protocol=0))) # We check if the file exist and we have write access. If the file does # not exist, we check the parent folder, because it might be a new project. if os.path.exists(project) and not os.access(project, os.W_OK) or \ not os.path.exists(project) and not os.access(os.path.dirname(project), os.W_OK): print("Error: you don't have write access to save this project there.") return False #################################################################################################################### # Save to zip if zip: # project = os.path.join( # os.path.dirname(project), # "_" + os.path.basename(project) # ) zf = zipfile.ZipFile(project, mode="w") for filename, content in files: zf.writestr(filename, content, compress_type=compression) zf.close() return True #################################################################################################################### # Save to plain text else: global cache # Project path dir = os.path.dirname(project) # Folder containing file: name of the project file (without .msk extension) folder = os.path.splitext(os.path.basename(project))[0] # Debug log("\nSaving to folder", folder) # If cache is empty (meaning we haven't loaded from disk), we wipe folder, just to be sure. if not cache: if os.path.exists(os.path.join(dir, folder)): shutil.rmtree(os.path.join(dir, folder)) # Moving files that have been renamed for old, new in moves: # Get full path oldPath = os.path.join(dir, folder, old) newPath = os.path.join(dir, folder, new) # Move the old file to the new place try: os.replace(oldPath, newPath) log("* Renaming/moving {} to {}".format(old, new)) except FileNotFoundError: # Maybe parent folder has been renamed pass # Update cache cache2 = {} for f in cache: f2 = f.replace(old, new) if f2 != f: log(" * Updating cache:", f, f2) cache2[f2] = cache[f] cache = cache2 # Writing files for path, content in files: filename = os.path.join(dir, folder, path) os.makedirs(os.path.dirname(filename), exist_ok=True) # Check if content is in cache, and write if necessary if path not in cache or cache[path] != content: log("* Writing file {} ({})".format( path, "not in cache" if path not in cache else "different")) # mode = "w" + ("b" if type(content) == bytes else "") if type(content) == bytes: with open(filename, "wb") as f: f.write(content) else: with open(filename, "w", encoding='utf8') as f: f.write(content) cache[path] = content # Removing phantoms for path in [p for p in cache if p not in [p for p, c in files]]: filename = os.path.join(dir, folder, path) log("* Removing", path) if os.path.isdir(filename): shutil.rmtree(filename) else: # elif os.path.exists(filename) os.remove(filename) # Clear cache cache.pop(path, 0) # Removing empty directories for root, dirs, files in os.walk(os.path.join(dir, folder, "outline")): for dir in dirs: newDir = os.path.join(root, dir) try: os.removedirs(newDir) log("* Removing empty directory:", newDir) except: # Directory not empty, we don't remove. pass # Write the project file's content with open(project, "w", encoding='utf8') as f: f.write("1") # Format number return True
def paint(self, p, option, index): # QStyledItemDelegate.paint(self, p, option, index) if not index.isValid(): return item = index.internalPointer() self.updateRects(option, index) colors = outlineItemColors(item) style = qApp.style() def _rotate(angle): p.translate(self.mainRect.center()) p.rotate(angle) p.translate(-self.mainRect.center()) # Draw background cg = QPalette.ColorGroup(QPalette.Normal if option.state & QStyle.State_Enabled else QPalette.Disabled) if cg == QPalette.Normal and not option.state & QStyle.State_Active: cg = QPalette.Inactive # Selection if option.state & QStyle.State_Selected: p.save() p.setBrush(option.palette.brush(cg, QPalette.Highlight)) p.setPen(Qt.NoPen) p.drawRoundedRect(option.rect, 12, 12) p.restore() # Stack if item.isFolder() and item.childCount() > 0: p.save() p.setBrush(Qt.white) for i in reversed(range(3)): p.drawRoundedRect( self.itemRect.adjusted(2 * i, 2 * i, -2 * i, 2 * i), 10, 10) p.restore() # Background itemRect = self.itemRect p.save() if settings.viewSettings["Cork"]["Background"] != "Nothing": c = colors[settings.viewSettings["Cork"]["Background"]] col = mixColors(c, QColor(Qt.white), .2) p.setBrush(col) else: p.setBrush(Qt.white) pen = p.pen() pen.setWidth(2) p.setPen(pen) p.drawRoundedRect(itemRect, 10, 10) p.restore() # Title bar topRect = self.topRect p.save() if item.isFolder(): color = QColor(Qt.darkGreen) else: color = QColor(Qt.blue).lighter(175) p.setPen(Qt.NoPen) p.setBrush(color) p.setClipRegion(QRegion(topRect)) p.drawRoundedRect(itemRect, 10, 10) # p.drawRect(topRect) p.restore() # Label color if settings.viewSettings["Cork"]["Corner"] != "Nothing": p.save() color = colors[settings.viewSettings["Cork"]["Corner"]] p.setPen(Qt.NoPen) p.setBrush(color) p.setClipRegion(QRegion(self.labelRect)) p.drawRoundedRect(itemRect, 10, 10) # p.drawRect(topRect) p.restore() p.drawLine(self.labelRect.topLeft(), self.labelRect.bottomLeft()) # One line summary background lineSummary = item.data(Outline.summarySentence.value) fullSummary = item.data(Outline.summaryFull.value) if lineSummary or not fullSummary: m = self.margin r = self.mainLineRect.adjusted(-m, -m, m, m / 2) p.save() p.setPen(Qt.NoPen) p.setBrush(QColor("#EEE")) p.drawRect(r) p.restore() # Border p.save() p.setBrush(Qt.NoBrush) pen = p.pen() pen.setWidth(2) if settings.viewSettings["Cork"]["Border"] != "Nothing": col = colors[settings.viewSettings["Cork"]["Border"]] if col == Qt.transparent: col = Qt.black pen.setColor(col) p.setPen(pen) p.drawRoundedRect(itemRect, 10, 10) p.restore() # Draw the icon iconRect = self.iconRect mode = QIcon.Normal if not option.state & style.State_Enabled: mode = QIcon.Disabled elif option.state & style.State_Selected: mode = QIcon.Selected # index.data(Qt.DecorationRole).paint(p, iconRect, option.decorationAlignment, mode) icon = index.data(Qt.DecorationRole).pixmap(iconRect.size()) if settings.viewSettings["Cork"]["Icon"] != "Nothing": color = colors[settings.viewSettings["Cork"]["Icon"]] colorifyPixmap(icon, color) QIcon(icon).paint(p, iconRect, option.decorationAlignment, mode) # Draw title p.save() text = index.data() titleRect = self.titleRect if text: if settings.viewSettings["Cork"]["Text"] != "Nothing": col = colors[settings.viewSettings["Cork"]["Text"]] if col == Qt.transparent: col = Qt.black p.setPen(col) f = QFont(option.font) # f.setPointSize(f.pointSize() + 1) f.setBold(True) p.setFont(f) fm = QFontMetrics(f) elidedText = fm.elidedText(text, Qt.ElideRight, titleRect.width()) p.drawText(titleRect, Qt.AlignCenter, elidedText) p.restore() # Draw the line bottomRect = self.bottomRect p.save() # p.drawLine(itemRect.x(), iconRect.bottom() + margin, # itemRect.right(), iconRect.bottom() + margin) p.drawLine(bottomRect.topLeft(), bottomRect.topRight()) p.restore() # Lines if True: p.save() p.setPen(QColor("#EEE")) fm = QFontMetrics(option.font) h = fm.lineSpacing() l = self.mainTextRect.topLeft() + QPoint(0, h) while self.mainTextRect.contains(l): p.drawLine(l, QPoint(self.mainTextRect.right(), l.y())) l.setY(l.y() + h) p.restore() # Draw status mainRect = self.mainRect status = item.data(Outline.status.value) if status: it = mainWindow().mdlStatus.item(int(status), 0) if it != None: p.save() p.setClipRegion(QRegion(mainRect)) f = p.font() f.setPointSize(f.pointSize() + 12) f.setBold(True) p.setFont(f) p.setPen(QColor(Qt.red).lighter(175)) _rotate(-35) p.drawText(mainRect, Qt.AlignCenter, it.text()) p.restore() # Draw Summary # One line if lineSummary: p.save() f = QFont(option.font) f.setItalic(True) p.setFont(f) fm = QFontMetrics(f) elidedText = fm.elidedText(lineSummary, Qt.ElideRight, self.mainLineRect.width()) p.drawText(self.mainLineRect, Qt.AlignCenter, elidedText) p.restore() # Full summary if fullSummary: p.setFont(option.font) p.drawText(self.mainTextRect, Qt.TextWordWrap, fullSummary)
def output(self, settingsWidget): settings = settingsWidget.getSettings() return self.concatenate(mainWindow().mdlOutline.rootItem, settings)
def infos(ref): """Returns a full paragraph in HTML format containing detailed infos about the reference ``ref``. """ match = re.fullmatch(RegEx, ref) if not match: return qApp.translate("references", "Not a reference: {}.").format(ref) _type = match.group(1) _ref = match.group(2) # A text or outine item if _type == TextLetter: m = mainWindow().mdlOutline idx = m.getIndexByID(_ref) if not idx.isValid(): return qApp.translate("references", "Unknown reference: {}.").format(ref) item = idx.internalPointer() # Titles pathTitle = qApp.translate("references", "Path:") statsTitle = qApp.translate("references", "Stats:") POVTitle = qApp.translate("references", "POV:") statusTitle = qApp.translate("references", "Status:") labelTitle = qApp.translate("references", "Label:") ssTitle = qApp.translate("references", "Short summary:") lsTitle = qApp.translate("references", "Long summary:") notesTitle = qApp.translate("references", "Notes:") # The POV of the scene POV = "" if item.POV(): POV = "<a href='{ref}'>{text}</a>".format( ref=characterReference(item.POV()), text=mainWindow().mdlCharacter.getCharacterByID( item.POV()).name()) # The status of the scene status = item.status() if status: status = mainWindow().mdlStatus.item(int(status), 0).text() else: status = "" # The label of the scene label = item.label() if label: label = mainWindow().mdlLabels.item(int(label), 0).text() else: label = "" # The path of the scene path = item.pathID() pathStr = [] for _id, title in path: pathStr.append("<a href='{ref}'>{text}</a>".format( ref=textReference(_id), text=title)) path = " > ".join(pathStr) # Summaries and notes ss = item.data(Outline.summarySentence.value) ls = item.data(Outline.summaryFull.value) notes = item.data(Outline.notes.value) text = """<h1>{title}</h1> <p><b>{pathTitle}</b> {path}</p> <p><b>{statsTitle}</b> {stats}<br> {POV} {status} {label}</p> {ss} {ls} {notes} {references} """.format( title=item.title(), pathTitle=pathTitle, path=path, statsTitle=statsTitle, stats=item.stats(), POV="<b>{POVTitle}</b> {POV}<br>".format(POVTitle=POVTitle, POV=POV) if POV else "", status="<b>{statusTitle}</b> {status}<br>".format( statusTitle=statusTitle, status=status) if status else "", label="<b>{labelTitle}</b> {label}</p>".format( labelTitle=labelTitle, label=label) if label else "", ss="<p><b>{ssTitle}</b> {ss}</p>".format( ssTitle=ssTitle, ss=ss.replace("\n", "<br>")) if ss.strip() else "", ls="<p><b>{lsTitle}</b><br>{ls}</p>".format( lsTitle=lsTitle, ls=ls.replace("\n", "<br>")) if ls.strip() else "", notes="<p><b>{notesTitle}</b><br>{notes}</p>".format( notesTitle=notesTitle, notes=linkifyAllRefs(notes)) if notes.strip() else "", references=listReferences(ref)) return text # A character elif _type == CharacterLetter: m = mainWindow().mdlCharacter c = m.getCharacterByID(int(_ref)) index = c.index() name = c.name() # Titles basicTitle = qApp.translate("references", "Basic infos") detailedTitle = qApp.translate("references", "Detailed infos") POVof = qApp.translate("references", "POV of:") # Goto (link) goto = qApp.translate("references", "Go to {}.") goto = goto.format(refToLink(ref)) # basic infos basic = [] for i in [ (Character.motivation, qApp.translate("references", "Motivation"), False), (Character.goal, qApp.translate("references", "Goal"), False), (Character.conflict, qApp.translate("references", "Conflict"), False), (Character.epiphany, qApp.translate("references", "Epiphany"), False), (Character.summarySentence, qApp.translate("references", "Short summary"), True), (Character.summaryPara, qApp.translate("references", "Longer summary"), True), ]: val = m.data(index.sibling(index.row(), i[0].value)) if val: basic.append("<b>{title}:</b>{n}{val}".format( title=i[1], n="\n" if i[2] else " ", val=val)) basic = "<br>".join(basic) # detailed infos detailed = [] for _name, _val in c.listInfos(): detailed.append("<b>{}:</b> {}".format(_name, _val)) detailed = "<br>".join(detailed) # list scenes of which it is POV oM = mainWindow().mdlOutline lst = oM.findItemsByPOV(_ref) listPOV = "" for t in lst: idx = oM.getIndexByID(t) listPOV += "<li><a href='{link}'>{text}</a></li>".format( link=textReference(t), text=oM.data(idx, Outline.title.value)) text = """<h1>{name}</h1> {goto} {basicInfos} {detailedInfos} {POV} {references} """.format(name=name, goto=goto, basicInfos="<h2>{basicTitle}</h2>{basic}".format( basicTitle=basicTitle, basic=basic) if basic else "", detailedInfos="<h2>{detailedTitle}</h2>{detailed}".format( detailedTitle=detailedTitle, detailed=detailed) if detailed else "", POV="<h2>{POVof}</h2><ul>{listPOV}</ul>".format( POVof=POVof, listPOV=listPOV) if listPOV else "", references=listReferences(ref)) return text # A plot elif _type == PlotLetter: m = mainWindow().mdlPlots index = m.getIndexFromID(_ref) name = m.getPlotNameByID(_ref) # Titles descriptionTitle = qApp.translate("references", "Description") resultTitle = qApp.translate("references", "Result") charactersTitle = qApp.translate("references", "Characters") stepsTitle = qApp.translate("references", "Resolution steps") # Goto (link) goto = qApp.translate("references", "Go to {}.") goto = goto.format(refToLink(ref)) # Description description = m.data(index.sibling(index.row(), Plot.description.value)) # Result result = m.data(index.sibling(index.row(), Plot.result.value)) # Characters pM = mainWindow().mdlCharacter item = m.item(index.row(), Plot.characters.value) characters = "" if item: for r in range(item.rowCount()): ID = item.child(r, 0).text() characters += "<li><a href='{link}'>{text}</a>".format( link=characterReference(ID), text=pM.getCharacterByID(ID).name()) # Resolution steps steps = "" item = m.item(index.row(), Plot.steps.value) if item: for r in range(item.rowCount()): title = item.child(r, PlotStep.name.value).text() summary = item.child(r, PlotStep.summary.value).text() meta = item.child(r, PlotStep.meta.value).text() if meta: meta = " <span style='color:gray;'>({})</span>".format( meta) steps += "<li><b>{title}</b>{summary}{meta}</li>".format( title=title, summary=": {}".format(summary) if summary else "", meta=meta if meta else "") text = """<h1>{name}</h1> {goto} {characters} {description} {result} {steps} {references} """.format( name=name, goto=goto, description="<h2>{title}</h2>{text}".format(title=descriptionTitle, text=description) if description else "", result="<h2>{title}</h2>{text}".format( title=resultTitle, text=result) if result else "", characters="<h2>{title}</h2><ul>{lst}</ul>".format( title=charactersTitle, lst=characters) if characters else "", steps="<h2>{title}</h2><ul>{steps}</ul>".format( title=stepsTitle, steps=steps) if steps else "", references=listReferences(ref)) return text # A World item elif _type == WorldLetter: m = mainWindow().mdlWorld index = m.indexByID(_ref) name = m.name(index) # Titles descriptionTitle = qApp.translate("references", "Description") passionTitle = qApp.translate("references", "Passion") conflictTitle = qApp.translate("references", "Conflict") # Goto (link) goto = qApp.translate("references", "Go to {}.") goto = goto.format(refToLink(ref)) # Description description = basicFormat(m.description(index)) # Passion passion = basicFormat(m.passion(index)) # Conflict conflict = basicFormat(m.conflict(index)) text = """<h1>{name}</h1> {goto} {description} {passion} {conflict} {references} """.format(name=name, goto=goto, description="<h2>{title}</h2>{text}".format( title=descriptionTitle, text=description) if description else "", passion="<h2>{title}</h2>{text}".format( title=passionTitle, text=passion) if passion else "", conflict="<h2>{title}</h2><ul>{lst}</ul>".format( title=conflictTitle, lst=conflict) if conflict else "", references=listReferences(ref)) return text else: return qApp.translate("references", "Unknown reference: {}.").format(ref)
def makePopupMenu(self): index = self.currentIndex() sel = self.getSelection() clipboard = qApp.clipboard() menu = QMenu(self) # Get index under cursor pos = self.viewport().mapFromGlobal(QCursor.pos()) mouseIndex = self.indexAt(pos) # Get index's title if mouseIndex.isValid(): title = mouseIndex.internalPointer().title() elif self.rootIndex().parent().isValid(): # mouseIndex is the background of an item, so we check the parent mouseIndex = self.rootIndex().parent() title = mouseIndex.internalPointer().title() else: title = qApp.translate("outlineBasics", "Root") if len(title) > 25: title = title[:25] + "…" # Open Item action self.actOpen = QAction(QIcon.fromTheme("go-right"), qApp.translate("outlineBasics", "Open {}".format(title)), menu) self.actOpen.triggered.connect(self.openItem) menu.addAction(self.actOpen) # Open item(s) in new tab if mouseIndex in sel and len(sel) > 1: actionTitle = qApp.translate("outlineBasics", "Open {} items in new tabs").format(len(sel)) self._indexesToOpen = sel else: actionTitle = qApp.translate("outlineBasics", "Open {} in a new tab").format(title) self._indexesToOpen = [mouseIndex] self.actNewTab = QAction(QIcon.fromTheme("go-right"), actionTitle, menu) self.actNewTab.triggered.connect(self.openItemsInNewTabs) menu.addAction(self.actNewTab) menu.addSeparator() # Add text / folder self.actAddFolder = QAction(QIcon.fromTheme("folder-new"), qApp.translate("outlineBasics", "New &Folder"), menu) self.actAddFolder.triggered.connect(self.addFolder) menu.addAction(self.actAddFolder) self.actAddText = QAction(QIcon.fromTheme("document-new"), qApp.translate("outlineBasics", "New &Text"), menu) self.actAddText.triggered.connect(self.addText) menu.addAction(self.actAddText) menu.addSeparator() # Copy, cut, paste, duplicate self.actCut = QAction(QIcon.fromTheme("edit-cut"), qApp.translate("outlineBasics", "C&ut"), menu) self.actCut.triggered.connect(self.cut) menu.addAction(self.actCut) self.actCopy = QAction(QIcon.fromTheme("edit-copy"), qApp.translate("outlineBasics", "&Copy"), menu) self.actCopy.triggered.connect(self.copy) menu.addAction(self.actCopy) self.actPaste = QAction(QIcon.fromTheme("edit-paste"), qApp.translate("outlineBasics", "&Paste"), menu) self.actPaste.triggered.connect(self.paste) menu.addAction(self.actPaste) # Rename / duplicate / remove items self.actDelete = QAction(QIcon.fromTheme("edit-delete"), qApp.translate("outlineBasics", "&Delete"), menu) self.actDelete.triggered.connect(self.delete) menu.addAction(self.actDelete) self.actRename = QAction(QIcon.fromTheme("edit-rename"), qApp.translate("outlineBasics", "&Rename"), menu) self.actRename.triggered.connect(self.rename) menu.addAction(self.actRename) menu.addSeparator() # POV self.menuPOV = QMenu(qApp.translate("outlineBasics", "Set POV"), menu) mw = mainWindow() a = QAction(QIcon.fromTheme("dialog-no"), qApp.translate("outlineBasics", "None"), self.menuPOV) a.triggered.connect(lambda: self.setPOV("")) self.menuPOV.addAction(a) self.menuPOV.addSeparator() menus = [] for i in [qApp.translate("outlineBasics", "Main"), qApp.translate("outlineBasics", "Secondary"), qApp.translate("outlineBasics", "Minor")]: m = QMenu(i, self.menuPOV) menus.append(m) self.menuPOV.addMenu(m) mpr = QSignalMapper(self.menuPOV) for i in range(mw.mdlCharacter.rowCount()): a = QAction(mw.mdlCharacter.icon(i), mw.mdlCharacter.name(i), self.menuPOV) a.triggered.connect(mpr.map) mpr.setMapping(a, int(mw.mdlCharacter.ID(i))) imp = toInt(mw.mdlCharacter.importance(i)) menus[2 - imp].addAction(a) mpr.mapped.connect(self.setPOV) menu.addMenu(self.menuPOV) # Status self.menuStatus = QMenu(qApp.translate("outlineBasics", "Set Status"), menu) # a = QAction(QIcon.fromTheme("dialog-no"), qApp.translate("outlineBasics", "None"), self.menuStatus) # a.triggered.connect(lambda: self.setStatus("")) # self.menuStatus.addAction(a) # self.menuStatus.addSeparator() mpr = QSignalMapper(self.menuStatus) for i in range(mw.mdlStatus.rowCount()): a = QAction(mw.mdlStatus.item(i, 0).text(), self.menuStatus) a.triggered.connect(mpr.map) mpr.setMapping(a, i) self.menuStatus.addAction(a) mpr.mapped.connect(self.setStatus) menu.addMenu(self.menuStatus) # Labels self.menuLabel = QMenu(qApp.translate("outlineBasics", "Set Label"), menu) mpr = QSignalMapper(self.menuLabel) for i in range(mw.mdlLabels.rowCount()): a = QAction(mw.mdlLabels.item(i, 0).icon(), mw.mdlLabels.item(i, 0).text(), self.menuLabel) a.triggered.connect(mpr.map) mpr.setMapping(a, i) self.menuLabel.addAction(a) mpr.mapped.connect(self.setLabel) menu.addMenu(self.menuLabel) menu.addSeparator() # Custom icons if self.menuCustomIcons: menu.addMenu(self.menuCustomIcons) else: self.menuCustomIcons = QMenu(qApp.translate("outlineBasics", "Set Custom Icon"), menu) a = QAction(qApp.translate("outlineBasics", "Restore to default"), self.menuCustomIcons) a.triggered.connect(lambda: self.setCustomIcon("")) self.menuCustomIcons.addAction(a) self.menuCustomIcons.addSeparator() txt = QLineEdit() txt.textChanged.connect(self.filterLstIcons) txt.setPlaceholderText("Filter icons") txt.setStyleSheet("QLineEdit { background: transparent; border: none; }") act = QWidgetAction(self.menuCustomIcons) act.setDefaultWidget(txt) self.menuCustomIcons.addAction(act) self.lstIcons = QListWidget() for i in customIcons(): item = QListWidgetItem() item.setIcon(QIcon.fromTheme(i)) item.setData(Qt.UserRole, i) item.setToolTip(i) self.lstIcons.addItem(item) self.lstIcons.itemClicked.connect(self.setCustomIconFromItem) self.lstIcons.setViewMode(self.lstIcons.IconMode) self.lstIcons.setUniformItemSizes(True) self.lstIcons.setResizeMode(self.lstIcons.Adjust) self.lstIcons.setMovement(self.lstIcons.Static) self.lstIcons.setStyleSheet("background: transparent; background: none;") self.filterLstIcons("") act = QWidgetAction(self.menuCustomIcons) act.setDefaultWidget(self.lstIcons) self.menuCustomIcons.addAction(act) menu.addMenu(self.menuCustomIcons) # Disabling stuff if not clipboard.mimeData().hasFormat("application/xml"): self.actPaste.setEnabled(False) if len(sel) == 0: self.actCopy.setEnabled(False) self.actCut.setEnabled(False) self.actRename.setEnabled(False) self.actDelete.setEnabled(False) self.menuPOV.setEnabled(False) self.menuStatus.setEnabled(False) self.menuLabel.setEnabled(False) self.menuCustomIcons.setEnabled(False) if len(sel) > 1: self.actRename.setEnabled(False) return menu
def __init__(self, parent): QStandardItemModel.__init__(self, 0, 3, parent) self.mw = mainWindow()
def openNewTab(self): mainWindow().mainEditor.openIndexes(self._indexesToOpen, newTab=True)
def getItem(self, index): if index.isValid(): return index.internalPointer() else: return mainWindow().mdlOutline.rootItem
def findItemsContaining(self, text, columns, caseSensitive=False): """Returns a list of IDs of all items containing ``text`` in columns ``columns`` (being a list of int).""" return self.rootItem.findItemsContaining(text, columns, mainWindow(), caseSensitive)
def openView(self, searchResult): r = Ref.plotReference(searchResult.id()) Ref.open(r) mainWindow().tabPlot.setEnabled(True)