From a231721bdbcda2e797f44a0cd084bd428b32951e Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 9 Nov 2017 15:18:21 +0100 Subject: [PATCH] Adds: ability to split scenes at custom points. #200 --- manuskript/importer/__init__.py | 4 +- manuskript/importer/markdownImporter.py | 12 ++-- manuskript/importer/opmlImporter.py | 8 ++- manuskript/importer/pandocImporters.py | 8 ++- manuskript/models/outlineModel.py | 59 ++++++++++++++++--- manuskript/ui/importers/generalSettings.py | 19 ++++++ manuskript/ui/importers/generalSettings_ui.py | 6 +- manuskript/ui/importers/generalSettings_ui.ui | 12 ++-- manuskript/ui/importers/importer.py | 28 +++++---- 9 files changed, 115 insertions(+), 41 deletions(-) diff --git a/manuskript/importer/__init__.py b/manuskript/importer/__init__.py index 196784c..d5a0aef 100644 --- a/manuskript/importer/__init__.py +++ b/manuskript/importer/__init__.py @@ -10,9 +10,9 @@ from manuskript.importer.pandocImporters import markdownPandocImporter, \ importers = [ # Internal - opmlImporter, - folderImporter, markdownImporter, + folderImporter, + opmlImporter, # Pandoc markdownPandocImporter, diff --git a/manuskript/importer/markdownImporter.py b/manuskript/importer/markdownImporter.py index 2e60f85..a57b6be 100644 --- a/manuskript/importer/markdownImporter.py +++ b/manuskript/importer/markdownImporter.py @@ -88,8 +88,8 @@ class markdownImporter(abstractImporter): return child ATXHeader = re.compile(r"(\#+)\s*(.+?)\s*\#*$") - setextHeader1 = re.compile(r"(.+)\n===+$", re.MULTILINE) - setextHeader2 = re.compile(r"(.+)\n---+$", re.MULTILINE) + setextHeader1 = re.compile(r"([^\#-=].+)\n(===+)$", re.MULTILINE) + setextHeader2 = re.compile(r"([^\#-=].+)\n(---+)$", re.MULTILINE) # We store the level of each item in a temporary var parent.__miLevel = 0 # markdown importer header level @@ -117,14 +117,15 @@ class markdownImporter(abstractImporter): # Check setext header m = setextHeader1.match(l2) - if not header and m: + + if not header and m and len(m.group(1)) == len(m.group(2)): header = True level = 1 name = m.group(1) skipNextLine = True m = setextHeader2.match(l2) - if not header and m: + if not header and m and len(m.group(1)) == len(m.group(2)): header = True level = 2 name = m.group(1) @@ -160,7 +161,8 @@ class markdownImporter(abstractImporter): # So we make it a text item i._data[Outline.type] = "md" i._data[Outline.text] = i.children()[0].text() - i.removeChild(0) + c = i.removeChild(0) + items.remove(c) return items diff --git a/manuskript/importer/opmlImporter.py b/manuskript/importer/opmlImporter.py index 2fce7bc..b7b8b99 100644 --- a/manuskript/importer/opmlImporter.py +++ b/manuskript/importer/opmlImporter.py @@ -57,7 +57,7 @@ class opmlImporter(abstractImporter): if outlineEls is not None: for element in outlineEls: - items.append(cls.parseItems(element, parentItem)) + items.extend(cls.parseItems(element, parentItem)) ret = True if not ret: @@ -72,10 +72,12 @@ class opmlImporter(abstractImporter): @classmethod def parseItems(cls, underElement, parentItem=None): + items = [] title = underElement.get('text') if title is not None: card = outlineItem(parent=parentItem, title=title) + items.append(card) body = "" note = underElement.get('_note') @@ -86,12 +88,12 @@ class opmlImporter(abstractImporter): children = underElement.findall('outline') if children is not None and len(children) > 0: for el in children: - cls.parseItems(el, card) + items.extend(cls.parseItems(el, card)) else: card.setData(Outline.type.value, 'md') card.setData(Outline.text.value, body) - return card + return items @classmethod def saveNewlines(cls, inString): diff --git a/manuskript/importer/pandocImporters.py b/manuskript/importer/pandocImporters.py index bbdfa9c..023a4b0 100644 --- a/manuskript/importer/pandocImporters.py +++ b/manuskript/importer/pandocImporters.py @@ -12,6 +12,7 @@ class pandocImporter(abstractImporter): formatFrom = "" engine = "Pandoc" + extraArgs = [] @classmethod def isValid(cls): @@ -27,10 +28,14 @@ class pandocImporter(abstractImporter): "--from={}".format(self.formatFrom), filePath, "--to={}".format(formatTo), - "--standalone", "--wrap={}".format(wrap), ] + if formatTo == "opml": + args.append("--standalone") + + args += self.extraArgs + r = pandocExporter().run(args) if formatTo == "opml": @@ -149,4 +154,3 @@ class OPMLPandocImporter(pandocImporter): - diff --git a/manuskript/models/outlineModel.py b/manuskript/models/outlineModel.py index ca9eaec..a20c2e0 100644 --- a/manuskript/models/outlineModel.py +++ b/manuskript/models/outlineModel.py @@ -816,10 +816,56 @@ class outlineItem(): return qApp.translate("outlineModel", "{} words").format( locale.format("%d", wc, grouping=True)) + def copy(self): + """ + Returns a copy of item, with no parent, and no ID. + """ + item = outlineItem(xml=self.toXML()) + item.setData(Outline.ID.value, None) + return item - ############################################################################### - # XML - ############################################################################### + def split(self, splitMark, recursive=True): + """ + Split scene at splitMark. If multiple splitMark, multiple splits. + + If called on a folder and recursive is True, then it is recursively + applied to every children. + """ + if self.isFolder() and recursive: + for c in self.children(): + c.split(splitMark) + + else: + txt = self.text().split(splitMark) + + if len(txt) == 1: + # Mark not found + return False + + else: + + # Stores the new text + self.setData(Outline.text.value, txt[0]) + + k = 1 + for subTxt in txt[1:]: + # Create a copy + item = self.copy() + + # Change title adding _k + item.setData(Outline.title.value, + "{}_{}".format(item.title(), k+1)) + + # Set text + item.setData(Outline.text.value, subTxt) + + # Inserting item + self.parent().insertChild(self.row()+k, item) + k += 1 + + ############################################################################### + # XML + ############################################################################### def toXML(self): item = ET.Element("outlineItem") @@ -879,10 +925,9 @@ class outlineItem(): elif child.tag == "revision": self.appendRevision(child.attrib["timestamp"], child.attrib["text"]) - - ############################################################################### - # IDS - ############################################################################### + ############################################################################### + # IDS + ############################################################################### def getUniqueID(self): self.setData(Outline.ID.value, self._model.rootItem.findUniqueID()) diff --git a/manuskript/ui/importers/generalSettings.py b/manuskript/ui/importers/generalSettings.py index 874a3fc..3940aab 100644 --- a/manuskript/ui/importers/generalSettings.py +++ b/manuskript/ui/importers/generalSettings.py @@ -69,3 +69,22 @@ class generalSettings(QWidget, Ui_generalSettings): def trimLongTitles(self): return self.chkGeneralTrimTitles.isChecked() + def splitScenes(self): + """ + Return wheter the user wants to split scenes. + If unchecked, returns False. + If checked, returns the escaped split mark, or default (in placeholderText). + """ + if self.chkGeneralSplitScenes.isChecked(): + split = self.txtGeneralSplitScenes.text() + + if not split: + split = self.txtGeneralSplitScenes.placeholderText() + + split = split.replace("\\n", "\n") + split = split.replace("\\t", "\t") + return split + + else: + return False + diff --git a/manuskript/ui/importers/generalSettings_ui.py b/manuskript/ui/importers/generalSettings_ui.py index 0c4d36b..6ca528a 100644 --- a/manuskript/ui/importers/generalSettings_ui.py +++ b/manuskript/ui/importers/generalSettings_ui.py @@ -37,11 +37,10 @@ class Ui_generalSettings(object): self.formLayout_4.setRowWrapPolicy(QtWidgets.QFormLayout.WrapLongRows) self.formLayout_4.setObjectName("formLayout_4") self.chkGeneralSplitScenes = QtWidgets.QCheckBox(self.general) - self.chkGeneralSplitScenes.setEnabled(False) self.chkGeneralSplitScenes.setObjectName("chkGeneralSplitScenes") self.formLayout_4.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.chkGeneralSplitScenes) self.txtGeneralSplitScenes = QtWidgets.QLineEdit(self.general) - self.txtGeneralSplitScenes.setEnabled(False) + self.txtGeneralSplitScenes.setText("") self.txtGeneralSplitScenes.setObjectName("txtGeneralSplitScenes") self.formLayout_4.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.txtGeneralSplitScenes) self.chkGeneralTrimTitles = QtWidgets.QCheckBox(self.general) @@ -56,7 +55,7 @@ class Ui_generalSettings(object): self.formLayout_4.setWidget(0, QtWidgets.QFormLayout.SpanningRole, self.chkGeneralParent) self.chkGeneralTopLevel = QtWidgets.QCheckBox(self.general) self.chkGeneralTopLevel.setObjectName("chkGeneralTopLevel") - self.formLayout_4.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.chkGeneralTopLevel) + self.formLayout_4.setWidget(2, QtWidgets.QFormLayout.SpanningRole, self.chkGeneralTopLevel) self.verticalLayout_5.addLayout(self.formLayout_4) self.toolBox.addItem(self.general, "") self.verticalLayout_2.addWidget(self.toolBox) @@ -70,6 +69,7 @@ class Ui_generalSettings(object): _translate = QtCore.QCoreApplication.translate generalSettings.setWindowTitle(_translate("generalSettings", "Form")) self.chkGeneralSplitScenes.setText(_translate("generalSettings", "Split scenes at:")) + self.txtGeneralSplitScenes.setPlaceholderText(_translate("generalSettings", "\\n---\\n")) self.chkGeneralTrimTitles.setText(_translate("generalSettings", "Trim long titles (> 32 chars)")) self.chkGeneralParent.setText(_translate("generalSettings", "Import under:")) self.chkGeneralTopLevel.setText(_translate("generalSettings", "Import in a top-level folder")) diff --git a/manuskript/ui/importers/generalSettings_ui.ui b/manuskript/ui/importers/generalSettings_ui.ui index a1ea203..955fc21 100644 --- a/manuskript/ui/importers/generalSettings_ui.ui +++ b/manuskript/ui/importers/generalSettings_ui.ui @@ -80,9 +80,6 @@ QToolBox::tab:selected, QToolBox::tab:hover{ - - false - Split scenes at: @@ -90,8 +87,11 @@ QToolBox::tab:selected, QToolBox::tab:hover{ - - false + + + + + \n---\n @@ -116,7 +116,7 @@ QToolBox::tab:selected, QToolBox::tab:hover{ - + Import in a top-level folder diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py index fff1b6f..f09ae44 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -64,8 +64,10 @@ class importerDialog(QWidget, Ui_importer): def populateImportList(self): - def addFormat(name, icon): - self.cmbImporters.addItem(QIcon.fromTheme(icon), name) + def addFormat(name, icon, identifier): + # Identifier serves to distingues 2 importers that would have the + # same name. + self.cmbImporters.addItem(QIcon.fromTheme(icon), name, identifier) def addHeader(name): self.cmbImporters.addItem(name, "header") @@ -82,7 +84,7 @@ class importerDialog(QWidget, Ui_importer): addHeader(f.engine) lastEngine = f.engine - addFormat(f.name, f.icon) + addFormat(f.name, f.icon, "{}:{}".format(f.engine, f.name)) if not f.isValid(): item = self.cmbImporters.model().item(self.cmbImporters.count() - 1) item.setFlags(Qt.NoItemFlags) @@ -96,12 +98,13 @@ class importerDialog(QWidget, Ui_importer): self.cmbImporters.setCurrentIndex(1) def currentFormat(self): - formatName = self.cmbImporters.currentText() + formatIdentifier = self.cmbImporters.currentData() - if self.cmbImporters.currentData() == "header": + if formatIdentifier == "header": return None - F = [F for F in self.importers if F.name == formatName][0] + F = [F for F in self.importers + if formatIdentifier == "{}:{}".format(F.engine, F.name)][0] # We instantiate the class return F() @@ -303,15 +306,14 @@ class importerDialog(QWidget, Ui_importer): # Trim long titles if self.settingsWidget.trimLongTitles(): - def trim(item): + for item in items: if len(item.title()) > 32: item.setData(Outline.title.value, item.title()[:32]) - # I think it's overkill to do it recursively, since items - # is supposed to contain every imported items. - for c in item.children(): - trim(c) - for i in items: - trim(i) + + # Split at + if self.settingsWidget.splitScenes(): + for item in items: + item.split(self.settingsWidget.splitScenes(), recursive=False) return items