From 982a96021b9128d5a5b1fff1a1cc58f2ed842f51 Mon Sep 17 00:00:00 2001 From: Cam Stevenson Date: Sun, 15 Oct 2017 17:25:09 -0400 Subject: [PATCH 01/23] Checkpoint: OPML import --- manuskript/import_export/__init__.py | 0 manuskript/import_export/opml.py | 44 ++++++++++++++++++++++++++++ manuskript/mainWindow.py | 12 ++++++-- manuskript/ui/mainWindow.py | 5 ++++ 4 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 manuskript/import_export/__init__.py create mode 100644 manuskript/import_export/opml.py diff --git a/manuskript/import_export/__init__.py b/manuskript/import_export/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manuskript/import_export/opml.py b/manuskript/import_export/opml.py new file mode 100644 index 00000000..77e6d311 --- /dev/null +++ b/manuskript/import_export/opml.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +# Import/export outline cards in OPML format + +from manuskript.models.outlineModel import outlineItem +from manuskript.enums import Outline +import xmltodict +from manuskript.functions import mainWindow +import xml.etree.ElementTree as ET + + +def handleCard(_, card): + print(card['title']) + + +def exportOpml(): + return True + + +def importOpml(opmlFilePath): + with open(opmlFilePath, 'r') as opmlFile: + opmlContent = opmlFile.read() + + mw = mainWindow() + mdl = mw.mdlOutline + + dict = xmltodict.parse(opmlContent, item_callback=handleCard) + + opmlNode = dict['opml'] + bodyNode = opmlNode['body'] + + outline = bodyNode['outline'] + + for element in outline: + if '@text' in element: + card = outlineItem(parent=mdl.rootItem) + card.title = element['@text'] + card.ID = card.title + card.path = '' + if '@_note' in element: + card.setData(Outline.text.value, element['@_note']) + + return True diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index 3ab3b18c..67d178b7 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -26,6 +26,7 @@ from manuskript.ui.mainWindow import Ui_MainWindow from manuskript.ui.tools.frequencyAnalyzer import frequencyAnalyzer from manuskript.ui.views.outlineDelegates import outlineCharacterDelegate from manuskript.ui.views.plotDelegate import plotDelegate +from manuskript.import_export import opml as opmlInputExport # Spellcheck support from manuskript.ui.views.textEditView import textEditView @@ -94,13 +95,14 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.cmbSummary.currentIndexChanged.emit(0) # Main Menu - for i in [self.actSave, self.actSaveAs, self.actCloseProject, + for i in [self.actSave, self.actSaveAs, self.actImport, self.actCloseProject, self.menuEdit, self.menuView, self.menuTools, self.menuHelp]: i.setEnabled(False) self.actOpen.triggered.connect(self.welcome.openFile) self.actSave.triggered.connect(self.saveDatas) self.actSaveAs.triggered.connect(self.welcome.saveAsFile) + self.actImport.triggered.connect(self.importOutline) self.actCompile.triggered.connect(self.doCompile) self.actLabels.triggered.connect(self.settingsLabel) self.actStatus.triggered.connect(self.settingsStatus) @@ -386,7 +388,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): # UI for i in [self.actOpen, self.menuRecents]: i.setEnabled(False) - for i in [self.actSave, self.actSaveAs, self.actCloseProject, + for i in [self.actSave, self.actSaveAs, self.actImport, self.actCloseProject, self.menuEdit, self.menuView, self.menuTools, self.menuHelp]: i.setEnabled(True) @@ -429,7 +431,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): # UI for i in [self.actOpen, self.menuRecents]: i.setEnabled(True) - for i in [self.actSave, self.actSaveAs, self.actCloseProject, + for i in [self.actSave, self.actSaveAs, self.actImport, self.actCloseProject, self.menuEdit, self.menuView, self.menuTools, self.menuHelp]: i.setEnabled(False) @@ -548,6 +550,10 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.statusBar().showMessage( self.tr("Project {} loaded with some errors.").format(project), 5000) + def importOutline(self, project): + opmlInputExport.importOpml('/home/cstevenson/End Plan 2.opml') + return True + ############################################################################### # MAIN CONNECTIONS ############################################################################### diff --git a/manuskript/ui/mainWindow.py b/manuskript/ui/mainWindow.py index 9c13e469..8b4ef492 100644 --- a/manuskript/ui/mainWindow.py +++ b/manuskript/ui/mainWindow.py @@ -1123,6 +1123,9 @@ class Ui_MainWindow(object): self.actSaveAs.setObjectName("actSaveAs") self.actQuit = QtWidgets.QAction(MainWindow) icon = QtGui.QIcon.fromTheme("application-exit") + self.actImport = QtWidgets.QAction(MainWindow) + self.actImport.setIcon(icon) + self.actImport.setObjectName("actImport") self.actQuit.setIcon(icon) self.actQuit.setObjectName("actQuit") self.actShowHelp = QtWidgets.QAction(MainWindow) @@ -1175,6 +1178,7 @@ class Ui_MainWindow(object): self.menuFile.addAction(self.menuRecents.menuAction()) self.menuFile.addAction(self.actSave) self.menuFile.addAction(self.actSaveAs) + self.menuFile.addAction(self.actImport) self.menuFile.addAction(self.actCloseProject) self.menuFile.addSeparator() self.menuFile.addAction(self.actCompile) @@ -1316,6 +1320,7 @@ class Ui_MainWindow(object): self.actSave.setShortcut(_translate("MainWindow", "Ctrl+S")) self.actSaveAs.setText(_translate("MainWindow", "Sa&ve as...")) self.actSaveAs.setShortcut(_translate("MainWindow", "Ctrl+Shift+S")) + self.actImport.setText("Import") self.actQuit.setText(_translate("MainWindow", "&Quit")) self.actQuit.setShortcut(_translate("MainWindow", "Ctrl+Q")) self.actShowHelp.setText(_translate("MainWindow", "&Show help texts")) From bdc6a096f2c1267b9703f0397aaee2d1e792eb40 Mon Sep 17 00:00:00 2001 From: Cam Stevenson Date: Sat, 21 Oct 2017 16:29:17 -0400 Subject: [PATCH 02/23] Checkpoint: OPML import --- manuskript/import_export/opml.py | 49 +++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/manuskript/import_export/opml.py b/manuskript/import_export/opml.py index 77e6d311..fe90fc6e 100644 --- a/manuskript/import_export/opml.py +++ b/manuskript/import_export/opml.py @@ -7,7 +7,6 @@ from manuskript.models.outlineModel import outlineItem from manuskript.enums import Outline import xmltodict from manuskript.functions import mainWindow -import xml.etree.ElementTree as ET def handleCard(_, card): @@ -20,12 +19,12 @@ def exportOpml(): def importOpml(opmlFilePath): with open(opmlFilePath, 'r') as opmlFile: - opmlContent = opmlFile.read() + opmlContent = saveNewlines(opmlFile.read()) mw = mainWindow() mdl = mw.mdlOutline - dict = xmltodict.parse(opmlContent, item_callback=handleCard) + dict = xmltodict.parse(opmlContent, strip_whitespace=False) opmlNode = dict['opml'] bodyNode = opmlNode['body'] @@ -33,12 +32,42 @@ def importOpml(opmlFilePath): outline = bodyNode['outline'] for element in outline: - if '@text' in element: - card = outlineItem(parent=mdl.rootItem) - card.title = element['@text'] - card.ID = card.title - card.path = '' - if '@_note' in element: - card.setData(Outline.text.value, element['@_note']) + parseItems(underElement=element, parentItem=mdl.rootItem) return True + +def parseItems(underElement, parentItem): + if '@text' in underElement: + card = outlineItem(parent=parentItem, title=underElement['@text']) + + text = "" + summary = "" + if '@_note' in underElement: + text = restoreNewLines(underElement['@_note']) + summary = text[0:128] + + card.setData(Outline.summaryFull.value, summary) + + if 'outline' in underElement: + elements = underElement['outline'] + + for el in elements: + parseItems(el, card) + else: + card.setData(Outline.type.value, 'md') + card.setData(Outline.text.value, text) + + # I assume I don't have to do the following + # parentItem.appendChild(card) + + return + +def saveNewlines(inString): + inString = inString.replace("\r\n", "\n") + inString = inString.replace("\n", "{{lf}}") + + return inString + +def restoreNewLines(inString): + return inString.replace("{{lf}}", "\n") + From 44db1a59893d8a0f5baa7b3682fa9618ce4ed795 Mon Sep 17 00:00:00 2001 From: Cam Stevenson Date: Sun, 22 Oct 2017 10:21:39 -0400 Subject: [PATCH 03/23] Checkpoint: functional OPML import --- manuskript/import_export/opml.py | 14 +++++++++----- manuskript/mainWindow.py | 6 +++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/manuskript/import_export/opml.py b/manuskript/import_export/opml.py index fe90fc6e..e178ddc3 100644 --- a/manuskript/import_export/opml.py +++ b/manuskript/import_export/opml.py @@ -7,10 +7,7 @@ from manuskript.models.outlineModel import outlineItem from manuskript.enums import Outline import xmltodict from manuskript.functions import mainWindow - - -def handleCard(_, card): - print(card['title']) +from PyQt5.QtCore import QModelIndex def exportOpml(): @@ -32,10 +29,15 @@ def importOpml(opmlFilePath): outline = bodyNode['outline'] for element in outline: - parseItems(underElement=element, parentItem=mdl.rootItem) + parseItems(element, mdl.rootItem) + + mdl.layoutChanged.emit() + + mw.treeRedacOutline.viewport().update() return True + def parseItems(underElement, parentItem): if '@text' in underElement: card = outlineItem(parent=parentItem, title=underElement['@text']) @@ -62,12 +64,14 @@ def parseItems(underElement, parentItem): return + def saveNewlines(inString): inString = inString.replace("\r\n", "\n") inString = inString.replace("\n", "{{lf}}") return inString + def restoreNewLines(inString): return inString.replace("{{lf}}", "\n") diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index 9401c614..92850ff8 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -98,7 +98,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): # Main Menu for i in [self.actSave, self.actSaveAs, self.actCloseProject, self.menuEdit, self.menuView, self.menuTools, self.menuHelp, - self.actCompile, self.actSettings]: + self.actCompile, self.actImport, self.actSettings]: i.setEnabled(False) self.actOpen.triggered.connect(self.welcome.openFile) @@ -447,7 +447,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): i.setEnabled(False) for i in [self.actSave, self.actSaveAs, self.actCloseProject, self.menuEdit, self.menuView, self.menuTools, self.menuHelp, - self.actCompile, self.actSettings]: + self.actCompile, self.actImport, self.actSettings]: i.setEnabled(True) # Add project name to Window's name @@ -491,7 +491,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): i.setEnabled(True) for i in [self.actSave, self.actSaveAs, self.actCloseProject, self.menuEdit, self.menuView, self.menuTools, self.menuHelp, - self.actCompile, self.actSettings]: + self.actCompile, self.actImport, self.actSettings]: i.setEnabled(False) # Set Window's name - no project loaded From 1fa86ddd73959cc6fb958ba8c462a33333b90744 Mon Sep 17 00:00:00 2001 From: Cam Stevenson Date: Sun, 29 Oct 2017 11:26:43 -0400 Subject: [PATCH 04/23] Finish OPML Import --- manuskript/import_export/opml.py | 125 ++++++++++++++++++------- manuskript/mainWindow.py | 12 +-- manuskript/ui/editors/mainEditor.py | 20 ++++ manuskript/ui/editors/mainEditor_ui.py | 10 ++ manuskript/ui/editors/mainEditor_ui.ui | 17 ++++ manuskript/ui/mainWindow.py | 5 - 6 files changed, 142 insertions(+), 47 deletions(-) diff --git a/manuskript/import_export/opml.py b/manuskript/import_export/opml.py index e178ddc3..9dc63277 100644 --- a/manuskript/import_export/opml.py +++ b/manuskript/import_export/opml.py @@ -2,69 +2,112 @@ # --!-- coding: utf8 --!-- # Import/export outline cards in OPML format - +from PyQt5.QtWidgets import QMessageBox from manuskript.models.outlineModel import outlineItem from manuskript.enums import Outline -import xmltodict +from lxml import etree as ET from manuskript.functions import mainWindow -from PyQt5.QtCore import QModelIndex -def exportOpml(): - return True - - -def importOpml(opmlFilePath): - with open(opmlFilePath, 'r') as opmlFile: - opmlContent = saveNewlines(opmlFile.read()) - +def importOpml(opmlFilePath, idx): + ret = False mw = mainWindow() + + try: + with open(opmlFilePath, 'r') as opmlFile: + opmlContent = saveNewlines(opmlFile.read()) + except: + # TODO: Translation + QMessageBox.critical(mw, mw.tr("OPML Import"), + mw.tr("File open failed.")) + return False + mdl = mw.mdlOutline - dict = xmltodict.parse(opmlContent, strip_whitespace=False) + if idx.internalPointer() is not None: + parentItem = idx.internalPointer() + else: + parentItem = mdl.rootItem - opmlNode = dict['opml'] - bodyNode = opmlNode['body'] + try: + parsed = ET.fromstring(bytes(opmlContent, 'utf-8')) - outline = bodyNode['outline'] + opmlNode = parsed + bodyNode = opmlNode.find("body") - for element in outline: - parseItems(element, mdl.rootItem) + if bodyNode is not None: + outlineEls = bodyNode.findall("outline") - mdl.layoutChanged.emit() + if outlineEls is not None: + for element in outlineEls: + parseItems(element, parentItem) - mw.treeRedacOutline.viewport().update() + mdl.layoutChanged.emit() + mw.treeRedacOutline.viewport().update() + ret = True + except: + pass - return True + # TODO: Translation + if ret: + QMessageBox.information(mw, mw.tr("OPML Import"), + mw.tr("Import Complete.")) + else: + QMessageBox.critical(mw, mw.tr("OPML Import"), + mw.tr("This does not appear to be a valid OPML file.")) + + return ret def parseItems(underElement, parentItem): - if '@text' in underElement: - card = outlineItem(parent=parentItem, title=underElement['@text']) + text = underElement.get('text') + if text is not None: + """ + In the case where the title is exceptionally long, trim it so it isn't + distracting in the tab label + """ + title = text[0:32] + if len(title) < len(text): + title += '...' - text = "" + card = outlineItem(parent=parentItem, title=title) + + body = "" summary = "" - if '@_note' in underElement: - text = restoreNewLines(underElement['@_note']) - summary = text[0:128] + note = underElement.get('_note') + if note is not None and not isWhitespaceOnly(note): + body = restoreNewLines(note) + summary = body[0:128] + else: + """ + There's no note (body), but there is a title. Fill the + body with the title to support cards that consist only + of a title. + """ + body = text card.setData(Outline.summaryFull.value, summary) - if 'outline' in underElement: - elements = underElement['outline'] - - for el in elements: + children = underElement.findall('outline') + if children is not None and len(children) > 0: + for el in children: parseItems(el, card) else: card.setData(Outline.type.value, 'md') - card.setData(Outline.text.value, text) + card.setData(Outline.text.value, body) - # I assume I don't have to do the following - # parentItem.appendChild(card) + # I assume I don't have to do the following + # parentItem.appendChild(card) return +""" +Since XML parsers are notorious for stripping out significant newlines, +save them in a form we can restore after the parse. +""" + + def saveNewlines(inString): inString = inString.replace("\r\n", "\n") inString = inString.replace("\n", "{{lf}}") @@ -72,6 +115,22 @@ def saveNewlines(inString): return inString +""" +Restore any significant newlines +""" + + def restoreNewLines(inString): return inString.replace("{{lf}}", "\n") + +""" +Determine whether or not a string only contains whitespace. +""" + + +def isWhitespaceOnly(inString): + str = restoreNewLines(inString) + str = ''.join(str.split()) + + return len(str) is 0 diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index 92850ff8..36d40e17 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -27,7 +27,6 @@ from manuskript.ui.mainWindow import Ui_MainWindow from manuskript.ui.tools.frequencyAnalyzer import frequencyAnalyzer from manuskript.ui.views.outlineDelegates import outlineCharacterDelegate from manuskript.ui.views.plotDelegate import plotDelegate -from manuskript.import_export import opml as opmlInputExport # Spellcheck support from manuskript.ui.views.textEditView import textEditView @@ -98,13 +97,12 @@ class MainWindow(QMainWindow, Ui_MainWindow): # Main Menu for i in [self.actSave, self.actSaveAs, self.actCloseProject, self.menuEdit, self.menuView, self.menuTools, self.menuHelp, - self.actCompile, self.actImport, self.actSettings]: + self.actCompile, self.actSettings]: i.setEnabled(False) self.actOpen.triggered.connect(self.welcome.openFile) self.actSave.triggered.connect(self.saveDatas) self.actSaveAs.triggered.connect(self.welcome.saveAsFile) - self.actImport.triggered.connect(self.importOutline) self.actCompile.triggered.connect(self.doCompile) self.actLabels.triggered.connect(self.settingsLabel) self.actStatus.triggered.connect(self.settingsStatus) @@ -447,7 +445,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): i.setEnabled(False) for i in [self.actSave, self.actSaveAs, self.actCloseProject, self.menuEdit, self.menuView, self.menuTools, self.menuHelp, - self.actCompile, self.actImport, self.actSettings]: + self.actCompile, self.actSettings]: i.setEnabled(True) # Add project name to Window's name @@ -491,7 +489,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): i.setEnabled(True) for i in [self.actSave, self.actSaveAs, self.actCloseProject, self.menuEdit, self.menuView, self.menuTools, self.menuHelp, - self.actCompile, self.actImport, self.actSettings]: + self.actCompile, self.actSettings]: i.setEnabled(False) # Set Window's name - no project loaded @@ -627,10 +625,6 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.statusBar().showMessage( self.tr("Project {} loaded with some errors.").format(project), 5000) - def importOutline(self, project): - opmlInputExport.importOpml('/home/cstevenson/End Plan 2.opml') - return True - ############################################################################### # MAIN CONNECTIONS ############################################################################### diff --git a/manuskript/ui/editors/mainEditor.py b/manuskript/ui/editors/mainEditor.py index ab5c1b60..71571e7d 100644 --- a/manuskript/ui/editors/mainEditor.py +++ b/manuskript/ui/editors/mainEditor.py @@ -14,6 +14,7 @@ from manuskript.ui import style from manuskript.ui.editors.editorWidget import editorWidget from manuskript.ui.editors.fullScreenEditor import fullScreenEditor from manuskript.ui.editors.mainEditor_ui import Ui_mainEditor +from manuskript.import_export import opml as opmlInputExport locale.setlocale(locale.LC_ALL, '') @@ -44,6 +45,10 @@ class mainEditor(QWidget, Ui_mainEditor): self.btnRedacFullscreen.clicked.connect( self.showFullScreen, AUC) + self.btnImport.clicked.connect( + lambda v: self.importOPML() + ) + # self.tab.setDocumentMode(False) # Bug in Qt < 5.5: doesn't always load icons from custom theme. @@ -217,6 +222,7 @@ class mainEditor(QWidget, Ui_mainEditor): self.btnRedacFolderText.setVisible(visible) self.btnRedacFolderCork.setVisible(visible) self.btnRedacFolderOutline.setVisible(visible) + self.btnImport.setVisible(visible) self.sldCorkSizeFactor.setVisible(visible and self.btnRedacFolderCork.isChecked()) self.btnRedacFullscreen.setVisible(not visible) @@ -296,6 +302,20 @@ class mainEditor(QWidget, Ui_mainEditor): if self.currentEditor(): self._fullScreen = fullScreenEditor(self.currentEditor().currentIndex) + def importOPML(self): + from PyQt5.QtWidgets import QFileDialog + options = QFileDialog.Options() + options |= QFileDialog.DontUseNativeDialog + fileName, _ = QFileDialog.getOpenFileName(self, "Import OPML", "", + "OPML Files (*.opml)", options=options) + if fileName: + if len(self.mw.treeRedacOutline.selectionModel(). + selection().indexes()) == 0: + idx = QModelIndex() + else: + idx = self.mw.treeRedacOutline.currentIndex() + opmlInputExport.importOpml(fileName, idx) + ############################################################################### # DICT AND STUFF LIKE THAT ############################################################################### diff --git a/manuskript/ui/editors/mainEditor_ui.py b/manuskript/ui/editors/mainEditor_ui.py index 269f0fc1..22fd1f0c 100644 --- a/manuskript/ui/editors/mainEditor_ui.py +++ b/manuskript/ui/editors/mainEditor_ui.py @@ -55,6 +55,14 @@ class Ui_mainEditor(object): self.btnRedacFolderOutline.setObjectName("btnRedacFolderOutline") self.buttonGroup.addButton(self.btnRedacFolderOutline) self.horizontalLayout_19.addWidget(self.btnRedacFolderOutline) + self.btnImport = QtWidgets.QPushButton(mainEditor) + self.btnImport.setText("") + icon = QtGui.QIcon.fromTheme("document-open") + self.btnImport.setIcon(icon) + self.btnImport.setFlat(True) + self.btnImport.setObjectName("btnImport") + self.buttonGroup.addButton(self.btnImport) + self.horizontalLayout_19.addWidget(self.btnImport) self.sldCorkSizeFactor = QtWidgets.QSlider(mainEditor) self.sldCorkSizeFactor.setMinimumSize(QtCore.QSize(100, 0)) self.sldCorkSizeFactor.setMaximumSize(QtCore.QSize(200, 16777215)) @@ -109,6 +117,8 @@ class Ui_mainEditor(object): self.btnRedacFolderCork.setText(_translate("mainEditor", "Index cards")) self.btnRedacFolderOutline.setText(_translate("mainEditor", "Outline")) self.btnRedacFullscreen.setShortcut(_translate("mainEditor", "F11")) + # TODO: Translation + self.btnImport.setToolTip(_translate("mainEditor", "Import items from an OPML file into the current folder")) from manuskript.ui.editors.tabSplitter import tabSplitter from manuskript.ui.editors.textFormat import textFormat diff --git a/manuskript/ui/editors/mainEditor_ui.ui b/manuskript/ui/editors/mainEditor_ui.ui index 68a3a68f..ce73c973 100644 --- a/manuskript/ui/editors/mainEditor_ui.ui +++ b/manuskript/ui/editors/mainEditor_ui.ui @@ -111,6 +111,23 @@ + + + + + Import items from an OPML file into the current folder + + + + + + + + + true + + + diff --git a/manuskript/ui/mainWindow.py b/manuskript/ui/mainWindow.py index ad5d0933..a2a9a74e 100644 --- a/manuskript/ui/mainWindow.py +++ b/manuskript/ui/mainWindow.py @@ -1123,9 +1123,6 @@ class Ui_MainWindow(object): self.actSaveAs.setObjectName("actSaveAs") self.actQuit = QtWidgets.QAction(MainWindow) icon = QtGui.QIcon.fromTheme("application-exit") - self.actImport = QtWidgets.QAction(MainWindow) - self.actImport.setIcon(icon) - self.actImport.setObjectName("actImport") self.actQuit.setIcon(icon) self.actQuit.setObjectName("actQuit") self.actShowHelp = QtWidgets.QAction(MainWindow) @@ -1186,7 +1183,6 @@ class Ui_MainWindow(object): self.menuFile.addAction(self.menuRecents.menuAction()) self.menuFile.addAction(self.actSave) self.menuFile.addAction(self.actSaveAs) - self.menuFile.addAction(self.actImport) self.menuFile.addAction(self.actCloseProject) self.menuFile.addSeparator() self.menuFile.addAction(self.actCompile) @@ -1328,7 +1324,6 @@ class Ui_MainWindow(object): self.actSave.setShortcut(_translate("MainWindow", "Ctrl+S")) self.actSaveAs.setText(_translate("MainWindow", "Sa&ve as...")) self.actSaveAs.setShortcut(_translate("MainWindow", "Ctrl+Shift+S")) - self.actImport.setText("Import") self.actQuit.setText(_translate("MainWindow", "&Quit")) self.actQuit.setShortcut(_translate("MainWindow", "Ctrl+Q")) self.actShowHelp.setText(_translate("MainWindow", "&Show help texts")) From 572feb54091c4e464278c9ddd721492f19e77fdc Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Mon, 6 Nov 2017 09:16:44 +0100 Subject: [PATCH 05/23] Creates empty import window --- .../{import_export => importer}/__init__.py | 0 .../{import_export => importer}/opml.py | 0 manuskript/mainWindow.py | 18 ++- manuskript/ui/editors/mainEditor.py | 2 +- manuskript/ui/importers/__init__.py | 0 manuskript/ui/importers/importer.py | 23 +++ manuskript/ui/importers/importer_ui.py | 72 ++++++++++ manuskript/ui/importers/importer_ui.ui | 133 ++++++++++++++++++ manuskript/ui/mainWindow.py | 6 + manuskript/ui/mainWindow.ui | 9 ++ 10 files changed, 258 insertions(+), 5 deletions(-) rename manuskript/{import_export => importer}/__init__.py (100%) rename manuskript/{import_export => importer}/opml.py (100%) create mode 100644 manuskript/ui/importers/__init__.py create mode 100644 manuskript/ui/importers/importer.py create mode 100644 manuskript/ui/importers/importer_ui.py create mode 100644 manuskript/ui/importers/importer_ui.ui diff --git a/manuskript/import_export/__init__.py b/manuskript/importer/__init__.py similarity index 100% rename from manuskript/import_export/__init__.py rename to manuskript/importer/__init__.py diff --git a/manuskript/import_export/opml.py b/manuskript/importer/opml.py similarity index 100% rename from manuskript/import_export/opml.py rename to manuskript/importer/opml.py diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index b47892e5..6a01d1bb 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -21,6 +21,7 @@ from manuskript.settingsWindow import settingsWindow from manuskript.ui import style from manuskript.ui.about import aboutDialog from manuskript.ui.collapsibleDockWidgets import collapsibleDockWidgets +from manuskript.ui.importers.importer import importerDialog from manuskript.ui.exporters.exporter import exporterDialog from manuskript.ui.helpLabel import helpLabel from manuskript.ui.mainWindow import Ui_MainWindow @@ -97,12 +98,13 @@ class MainWindow(QMainWindow, Ui_MainWindow): # Main Menu for i in [self.actSave, self.actSaveAs, self.actCloseProject, self.menuEdit, self.menuView, self.menuTools, self.menuHelp, - self.actCompile, self.actSettings]: + self.actImport, self.actCompile, self.actSettings]: i.setEnabled(False) self.actOpen.triggered.connect(self.welcome.openFile) self.actSave.triggered.connect(self.saveDatas) self.actSaveAs.triggered.connect(self.welcome.saveAsFile) + self.actImport.triggered.connect(self.doImport) self.actCompile.triggered.connect(self.doCompile) self.actLabels.triggered.connect(self.settingsLabel) self.actStatus.triggered.connect(self.settingsStatus) @@ -460,7 +462,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): i.setEnabled(False) for i in [self.actSave, self.actSaveAs, self.actCloseProject, self.menuEdit, self.menuView, self.menuTools, self.menuHelp, - self.actCompile, self.actSettings]: + self.actImport, self.actCompile, self.actSettings]: i.setEnabled(True) # Add project name to Window's name @@ -504,7 +506,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): i.setEnabled(True) for i in [self.actSave, self.actSaveAs, self.actCloseProject, self.menuEdit, self.menuView, self.menuTools, self.menuHelp, - self.actCompile, self.actSettings]: + self.actImport, self.actCompile, self.actSettings]: i.setEnabled(False) # Set Window's name - no project loaded @@ -1306,9 +1308,17 @@ class MainWindow(QMainWindow, Ui_MainWindow): # POV in settings / views ############################################################################### - # COMPILE + # IMPORT / EXPORT ############################################################################### + def doImport(self): + self.dialog = importerDialog(mw=self) + self.dialog.show() + + r = self.dialog.geometry() + r2 = self.geometry() + self.dialog.move(r2.center() - r.center()) + def doCompile(self): self.dialog = exporterDialog(mw=self) self.dialog.show() diff --git a/manuskript/ui/editors/mainEditor.py b/manuskript/ui/editors/mainEditor.py index d5dcc678..8791bfd9 100644 --- a/manuskript/ui/editors/mainEditor.py +++ b/manuskript/ui/editors/mainEditor.py @@ -14,7 +14,7 @@ from manuskript.ui import style from manuskript.ui.editors.editorWidget import editorWidget from manuskript.ui.editors.fullScreenEditor import fullScreenEditor from manuskript.ui.editors.mainEditor_ui import Ui_mainEditor -from manuskript.import_export import opml as opmlInputExport +from manuskript.importer import opml as opmlInputExport locale.setlocale(locale.LC_ALL, '') diff --git a/manuskript/ui/importers/__init__.py b/manuskript/ui/importers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py new file mode 100644 index 00000000..24c62b16 --- /dev/null +++ b/manuskript/ui/importers/importer.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- +import json +import os + +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QBrush, QColor, QIcon +from PyQt5.QtWidgets import QWidget + +from manuskript import exporter +from manuskript.functions import lightBlue, writablePath +from manuskript.ui.importers.importer_ui import Ui_importer + + +class importerDialog(QWidget, Ui_importer): + def __init__(self, parent=None, mw=None): + QWidget.__init__(self, parent) + self.setupUi(self) + + # Var + self.mw = mw + + #TODO diff --git a/manuskript/ui/importers/importer_ui.py b/manuskript/ui/importers/importer_ui.py new file mode 100644 index 00000000..c5ccc28a --- /dev/null +++ b/manuskript/ui/importers/importer_ui.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'manuskript/ui/importers/importer_ui.ui' +# +# Created by: PyQt5 UI code generator 5.9 +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore, QtGui, QtWidgets + +class Ui_importer(object): + def setupUi(self, importer): + importer.setObjectName("importer") + importer.resize(694, 489) + self.verticalLayout = QtWidgets.QVBoxLayout(importer) + self.verticalLayout.setObjectName("verticalLayout") + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.label = QtWidgets.QLabel(importer) + self.label.setObjectName("label") + self.horizontalLayout.addWidget(self.label) + self.cmbImporters = QtWidgets.QComboBox(importer) + self.cmbImporters.setObjectName("cmbImporters") + self.horizontalLayout.addWidget(self.cmbImporters) + self.btnManageImporters = QtWidgets.QPushButton(importer) + icon = QtGui.QIcon.fromTheme("preferences-system") + self.btnManageImporters.setIcon(icon) + self.btnManageImporters.setObjectName("btnManageImporters") + self.horizontalLayout.addWidget(self.btnManageImporters) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem) + self.btnChoseFile = QtWidgets.QPushButton(importer) + icon = QtGui.QIcon.fromTheme("document-import") + self.btnChoseFile.setIcon(icon) + self.btnChoseFile.setObjectName("btnChoseFile") + self.horizontalLayout.addWidget(self.btnChoseFile) + self.btnPreview = QtWidgets.QPushButton(importer) + icon = QtGui.QIcon.fromTheme("document-print-preview") + self.btnPreview.setIcon(icon) + self.btnPreview.setObjectName("btnPreview") + self.horizontalLayout.addWidget(self.btnPreview) + self.verticalLayout.addLayout(self.horizontalLayout) + self.splitter = QtWidgets.QSplitter(importer) + self.splitter.setOrientation(QtCore.Qt.Horizontal) + self.splitter.setObjectName("splitter") + self.grpSettings = QtWidgets.QGroupBox(self.splitter) + self.grpSettings.setObjectName("grpSettings") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.grpSettings) + self.verticalLayout_3.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_3.setSpacing(0) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.grpPreview = QtWidgets.QGroupBox(self.splitter) + self.grpPreview.setObjectName("grpPreview") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.grpPreview) + self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_2.setSpacing(0) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.verticalLayout.addWidget(self.splitter) + + self.retranslateUi(importer) + QtCore.QMetaObject.connectSlotsByName(importer) + + def retranslateUi(self, importer): + _translate = QtCore.QCoreApplication.translate + importer.setWindowTitle(_translate("importer", "Import")) + self.label.setText(_translate("importer", "Import from:")) + self.btnManageImporters.setText(_translate("importer", "Manage importers")) + self.btnChoseFile.setText(_translate("importer", "Chose file")) + self.btnPreview.setText(_translate("importer", "Preview")) + self.grpSettings.setTitle(_translate("importer", "Settings")) + self.grpPreview.setTitle(_translate("importer", "Preview")) + diff --git a/manuskript/ui/importers/importer_ui.ui b/manuskript/ui/importers/importer_ui.ui new file mode 100644 index 00000000..2d3ce526 --- /dev/null +++ b/manuskript/ui/importers/importer_ui.ui @@ -0,0 +1,133 @@ + + + importer + + + + 0 + 0 + 694 + 489 + + + + Import + + + + + + + + Import from: + + + + + + + + + + Manage importers + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Chose file + + + + + + + + + + Preview + + + + + + + + + + + + + + Qt::Horizontal + + + + Settings + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + Preview + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + diff --git a/manuskript/ui/mainWindow.py b/manuskript/ui/mainWindow.py index 73237964..91bc55d1 100644 --- a/manuskript/ui/mainWindow.py +++ b/manuskript/ui/mainWindow.py @@ -1189,12 +1189,17 @@ class Ui_MainWindow(object): icon = QtGui.QIcon.fromTheme("stock_view-details") self.actAbout.setIcon(icon) self.actAbout.setObjectName("actAbout") + self.actImport = QtWidgets.QAction(MainWindow) + icon = QtGui.QIcon.fromTheme("document-import") + self.actImport.setIcon(icon) + self.actImport.setObjectName("actImport") self.menuFile.addAction(self.actOpen) self.menuFile.addAction(self.menuRecents.menuAction()) self.menuFile.addAction(self.actSave) self.menuFile.addAction(self.actSaveAs) self.menuFile.addAction(self.actCloseProject) self.menuFile.addSeparator() + self.menuFile.addAction(self.actImport) self.menuFile.addAction(self.actCompile) self.menuFile.addSeparator() self.menuFile.addAction(self.actQuit) @@ -1360,6 +1365,7 @@ class Ui_MainWindow(object): self.actToolFrequency.setText(_translate("MainWindow", "&Frequency Analyzer")) self.actAbout.setText(_translate("MainWindow", "&About")) self.actAbout.setToolTip(_translate("MainWindow", "About Manuskript")) + self.actImport.setText(_translate("MainWindow", "Import…")) from manuskript.ui.cheatSheet import cheatSheet from manuskript.ui.editors.mainEditor import mainEditor diff --git a/manuskript/ui/mainWindow.ui b/manuskript/ui/mainWindow.ui index 05063d73..f341bd68 100644 --- a/manuskript/ui/mainWindow.ui +++ b/manuskript/ui/mainWindow.ui @@ -2116,6 +2116,7 @@ + @@ -2482,6 +2483,14 @@ QListView::item:hover { About Manuskript + + + + + + Import… + + From 72b44fe90d39eac4cc4fc38de6554f7516873ac0 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Mon, 6 Nov 2017 09:30:33 +0100 Subject: [PATCH 06/23] Fixes docstrings --- manuskript/importer/opml.py | 49 +++++++++++++++---------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/manuskript/importer/opml.py b/manuskript/importer/opml.py index 9dc63277..51ff9369 100644 --- a/manuskript/importer/opml.py +++ b/manuskript/importer/opml.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # --!-- coding: utf8 --!-- -# Import/export outline cards in OPML format from PyQt5.QtWidgets import QMessageBox from manuskript.models.outlineModel import outlineItem from manuskript.enums import Outline @@ -10,6 +9,9 @@ from manuskript.functions import mainWindow def importOpml(opmlFilePath, idx): + """ + Import/export outline cards in OPML format. + """ ret = False mw = mainWindow() @@ -62,10 +64,9 @@ def importOpml(opmlFilePath, idx): def parseItems(underElement, parentItem): text = underElement.get('text') if text is not None: - """ - In the case where the title is exceptionally long, trim it so it isn't - distracting in the tab label - """ + + # In the case where the title is exceptionally long, trim it so it isn't + # distracting in the tab label title = text[0:32] if len(title) < len(text): title += '...' @@ -79,11 +80,10 @@ def parseItems(underElement, parentItem): body = restoreNewLines(note) summary = body[0:128] else: - """ - There's no note (body), but there is a title. Fill the - body with the title to support cards that consist only - of a title. - """ + + # There's no note (body), but there is a title. Fill the + # body with the title to support cards that consist only + # of a title. body = text card.setData(Outline.summaryFull.value, summary) @@ -101,35 +101,26 @@ def parseItems(underElement, parentItem): return - -""" -Since XML parsers are notorious for stripping out significant newlines, -save them in a form we can restore after the parse. -""" - - def saveNewlines(inString): + """ + Since XML parsers are notorious for stripping out significant newlines, + save them in a form we can restore after the parse. + """ inString = inString.replace("\r\n", "\n") inString = inString.replace("\n", "{{lf}}") return inString - -""" -Restore any significant newlines -""" - - def restoreNewLines(inString): + """ + Restore any significant newlines + """ return inString.replace("{{lf}}", "\n") - -""" -Determine whether or not a string only contains whitespace. -""" - - def isWhitespaceOnly(inString): + """ + Determine whether or not a string only contains whitespace. + """ str = restoreNewLines(inString) str = ''.join(str.split()) From 340fceeda3c7aef756536e276e4a39a7089649a1 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Mon, 6 Nov 2017 18:21:40 +0100 Subject: [PATCH 07/23] Checkpoint: selecting file working, have to preview and import now. --- manuskript/ui/importers/importer.py | 119 ++++++++++++++++++++++++- manuskript/ui/importers/importer_ui.py | 26 +++++- manuskript/ui/importers/importer_ui.ui | 55 +++++++++++- manuskript/ui/mainWindow.py | 1 + manuskript/ui/mainWindow.ui | 3 + 5 files changed, 196 insertions(+), 8 deletions(-) diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py index 24c62b16..3254a926 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -5,19 +5,132 @@ import os from PyQt5.QtCore import Qt from PyQt5.QtGui import QBrush, QColor, QIcon -from PyQt5.QtWidgets import QWidget +from PyQt5.QtWidgets import QWidget, QFileDialog from manuskript import exporter -from manuskript.functions import lightBlue, writablePath +from manuskript.functions import lightBlue, writablePath, appPath from manuskript.ui.importers.importer_ui import Ui_importer +class importFormat: + def __init__(self, name, icon, fileFormat): + self.name = name + self.icon = icon + self.fileFormat = fileFormat class importerDialog(QWidget, Ui_importer): + + formatsIcon = { + ".epub": "application-epub+zip", + ".odt": "application-vnd.oasis.opendocument.text", + ".docx": "application-vnd.openxmlformats-officedocument.wordprocessingml.document", + ".md": "text-x-markdown", + ".rst": "text-plain", + ".tex": "text-x-tex", + ".opml": "text-x-opml+xml", + ".html": "text-html", + } + def __init__(self, parent=None, mw=None): QWidget.__init__(self, parent) self.setupUi(self) # Var self.mw = mw + self.fileName = "" - #TODO + # Register importFormats: + self.formats = [] + self.formats.append(importFormat("OPML", "text-x-opml+xml", + "OPML Files (*.opml)")) + self.formats.append(importFormat("Markdown", "text-x-markdown", + "Markdown files (*.md; *.txt; *)")) + self.formats.append(importFormat("Folder", "folder", + "<>")) + + # Populate combo box with formats + self.populateImportList() + + # Connections + self.btnChoseFile.clicked.connect(self.selectFile) + self.btnClearFileName.clicked.connect(self.setFileName) + self.btnPreview.clicked.connect(self.preview) + self.setFileName("") + + ############################################################################ + # Combobox / Formats + ############################################################################ + + def populateImportList(self): + + def addFormat(name, icon): + self.cmbImporters.addItem(QIcon.fromTheme(icon), name) + + for f in self.formats: + addFormat(f.name, f.icon) + + ############################################################################ + # Import file + ############################################################################ + + def selectFile(self): + + # We find the current selected format + formatName = self.cmbImporters.currentText() + F = [F for F in self.formats if F.name == formatName][0] + + options = QFileDialog.Options() + options |= QFileDialog.DontUseNativeDialog + if F.fileFormat == "<>": + options = QFileDialog.DontUseNativeDialog | QFileDialog.ShowDirsOnly + fileName = QFileDialog.getExistingDirectory(self, "Select import folder", + "", options=options) + else: + fileName, _ = QFileDialog.getOpenFileName(self, "Import from file", "", + F.fileFormat, options=options) + self.setFileName(fileName) + + def setFileName(self, fileName): + if fileName: + self.fileName = fileName + self.btnPreview.setEnabled(True) + self.lblFileName.setText(os.path.basename(fileName)) + self.lblFileName.setToolTip(fileName) + self.btnClearFileName.setVisible(True) + ext = os.path.splitext(fileName)[1] + if ext and ext in self.formatsIcon: + self.lblIcon.setVisible(True) + h = self.lblFileName.height() + self.lblIcon.setPixmap( + QIcon.fromTheme(self.formatsIcon[ext]).pixmap(h, h) + ) + elif os.path.isdir(fileName): + self.lblIcon.setVisible(True) + h = self.lblFileName.height() + self.lblIcon.setPixmap(QIcon.fromTheme("folder").pixmap(h, h)) + + else: + self.fileName = None + self.btnPreview.setEnabled(False) + self.lblFileName.setText("") + self.btnClearFileName.setVisible(False) + self.lblIcon.setVisible(False) + + ############################################################################ + # Preview + ############################################################################ + + def preview(self): + # TODO + pass + + ############################################################################ + # + ############################################################################ + + def getParentIndex(self): + if len(self.mw.treeRedacOutline.selectionModel(). + selection().indexes()) == 0: + idx = QModelIndex() + else: + idx = self.mw.treeRedacOutline.currentIndex() + return idx diff --git a/manuskript/ui/importers/importer_ui.py b/manuskript/ui/importers/importer_ui.py index c5ccc28a..b6730a58 100644 --- a/manuskript/ui/importers/importer_ui.py +++ b/manuskript/ui/importers/importer_ui.py @@ -23,6 +23,7 @@ class Ui_importer(object): self.cmbImporters.setObjectName("cmbImporters") self.horizontalLayout.addWidget(self.cmbImporters) self.btnManageImporters = QtWidgets.QPushButton(importer) + self.btnManageImporters.setText("") icon = QtGui.QIcon.fromTheme("preferences-system") self.btnManageImporters.setIcon(icon) self.btnManageImporters.setObjectName("btnManageImporters") @@ -34,6 +35,27 @@ class Ui_importer(object): self.btnChoseFile.setIcon(icon) self.btnChoseFile.setObjectName("btnChoseFile") self.horizontalLayout.addWidget(self.btnChoseFile) + self.lblIcon = QtWidgets.QLabel(importer) + self.lblIcon.setText("") + self.lblIcon.setObjectName("lblIcon") + self.horizontalLayout.addWidget(self.lblIcon) + self.lblFileName = QtWidgets.QLabel(importer) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.lblFileName.setFont(font) + self.lblFileName.setText("") + self.lblFileName.setObjectName("lblFileName") + self.horizontalLayout.addWidget(self.lblFileName) + self.btnClearFileName = QtWidgets.QPushButton(importer) + self.btnClearFileName.setText("") + icon = QtGui.QIcon.fromTheme("edit-clear") + self.btnClearFileName.setIcon(icon) + self.btnClearFileName.setFlat(True) + self.btnClearFileName.setObjectName("btnClearFileName") + self.horizontalLayout.addWidget(self.btnClearFileName) + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem1) self.btnPreview = QtWidgets.QPushButton(importer) icon = QtGui.QIcon.fromTheme("document-print-preview") self.btnPreview.setIcon(icon) @@ -63,8 +85,8 @@ class Ui_importer(object): def retranslateUi(self, importer): _translate = QtCore.QCoreApplication.translate importer.setWindowTitle(_translate("importer", "Import")) - self.label.setText(_translate("importer", "Import from:")) - self.btnManageImporters.setText(_translate("importer", "Manage importers")) + self.label.setText(_translate("importer", "Format:")) + self.btnManageImporters.setToolTip(_translate("importer", "Manage importers")) self.btnChoseFile.setText(_translate("importer", "Chose file")) self.btnPreview.setText(_translate("importer", "Preview")) self.grpSettings.setTitle(_translate("importer", "Settings")) diff --git a/manuskript/ui/importers/importer_ui.ui b/manuskript/ui/importers/importer_ui.ui index 2d3ce526..c7877abd 100644 --- a/manuskript/ui/importers/importer_ui.ui +++ b/manuskript/ui/importers/importer_ui.ui @@ -19,7 +19,7 @@ - Import from: + Format: @@ -28,9 +28,12 @@ - + Manage importers + + + @@ -39,7 +42,7 @@ - + Qt::Horizontal @@ -61,6 +64,52 @@ + + + + + + + + + + + + 75 + true + + + + + + + + + + + + + + + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + diff --git a/manuskript/ui/mainWindow.py b/manuskript/ui/mainWindow.py index 91bc55d1..26b92db6 100644 --- a/manuskript/ui/mainWindow.py +++ b/manuskript/ui/mainWindow.py @@ -1366,6 +1366,7 @@ class Ui_MainWindow(object): self.actAbout.setText(_translate("MainWindow", "&About")) self.actAbout.setToolTip(_translate("MainWindow", "About Manuskript")) self.actImport.setText(_translate("MainWindow", "Import…")) + self.actImport.setShortcut(_translate("MainWindow", "F7")) from manuskript.ui.cheatSheet import cheatSheet from manuskript.ui.editors.mainEditor import mainEditor diff --git a/manuskript/ui/mainWindow.ui b/manuskript/ui/mainWindow.ui index f341bd68..d13d8752 100644 --- a/manuskript/ui/mainWindow.ui +++ b/manuskript/ui/mainWindow.ui @@ -2490,6 +2490,9 @@ QListView::item:hover { Import… + + F7 + From 221c7a181d6ea2985b57fee02e08f69e6a37983a Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 7 Nov 2017 11:25:19 +0100 Subject: [PATCH 08/23] Checkpoint: basic mechanics of settings implemented --- manuskript/importer/__init__.py | 12 ++ manuskript/importer/abstractImporter.py | 39 +++++ manuskript/importer/folderImporter.py | 12 ++ manuskript/importer/markdownImporter.py | 12 ++ manuskript/importer/opml.py | 127 ----------------- manuskript/importer/opmlImporter.py | 134 ++++++++++++++++++ manuskript/ui/editors/mainEditor.py | 20 --- manuskript/ui/editors/mainEditor_ui.py | 15 +- manuskript/ui/editors/mainEditor_ui.ui | 21 +-- .../manuskript/plainTextSettings_ui.py | 14 +- .../manuskript/plainTextSettings_ui.ui | 10 +- manuskript/ui/importers/generalSettings.py | 46 ++++++ manuskript/ui/importers/generalSettings_ui.py | 67 +++++++++ manuskript/ui/importers/generalSettings_ui.ui | 103 ++++++++++++++ manuskript/ui/importers/importer.py | 112 +++++++++------ manuskript/ui/importers/importer_ui.py | 19 ++- manuskript/ui/importers/importer_ui.ui | 47 +++--- 17 files changed, 552 insertions(+), 258 deletions(-) create mode 100644 manuskript/importer/abstractImporter.py create mode 100644 manuskript/importer/folderImporter.py create mode 100644 manuskript/importer/markdownImporter.py delete mode 100644 manuskript/importer/opml.py create mode 100644 manuskript/importer/opmlImporter.py create mode 100644 manuskript/ui/importers/generalSettings.py create mode 100644 manuskript/ui/importers/generalSettings_ui.py create mode 100644 manuskript/ui/importers/generalSettings_ui.ui diff --git a/manuskript/importer/__init__.py b/manuskript/importer/__init__.py index e69de29b..d2f0bc1a 100644 --- a/manuskript/importer/__init__.py +++ b/manuskript/importer/__init__.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +from manuskript.importer.folderImporter import folderImporter +from manuskript.importer.markdownImporter import markdownImporter +from manuskript.importer.opmlImporter import opmlImporter + +importers = [ + markdownImporter, + opmlImporter, + folderImporter, + ] diff --git a/manuskript/importer/abstractImporter.py b/manuskript/importer/abstractImporter.py new file mode 100644 index 00000000..822ebe58 --- /dev/null +++ b/manuskript/importer/abstractImporter.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- +import os +import shutil +import subprocess + +from PyQt5.QtCore import QSettings +from PyQt5.QtWidgets import QWidget + + +class abstractImporter: + """ + abstractImporter is used to import documents into manuskript. + + The startImport function must be subclassed. It takes a filePath (str to + the document to import), and must return `outlineItem`s. + """ + + name = "" + description = "" + fileFormat = "" # File format accepted. For example: "OPML Files (*.opml)" + # For folder, use "<>" + icon = "" + + @classmethod + def startImport(cls, filePath): + """ + Takes a str path to the file/folder to import, and return `outlineItem`s. + """ + pass + + @classmethod + def settingsWidget(cls): + """ + Returns a QWidget if needed for settings. + """ + return None + + diff --git a/manuskript/importer/folderImporter.py b/manuskript/importer/folderImporter.py new file mode 100644 index 00000000..a9618880 --- /dev/null +++ b/manuskript/importer/folderImporter.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +from manuskript.importer.abstractImporter import abstractImporter + + +class folderImporter(abstractImporter): + + name = "Folder" + description = "" + fileFormat = "<>" + icon = "folder" diff --git a/manuskript/importer/markdownImporter.py b/manuskript/importer/markdownImporter.py new file mode 100644 index 00000000..0a3a5cd2 --- /dev/null +++ b/manuskript/importer/markdownImporter.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +from manuskript.importer.abstractImporter import abstractImporter + + +class markdownImporter(abstractImporter): + + name = "Markdown" + description = "" + fileFormat = "Markdown files (*.md; *.txt; *)" + icon = "text-x-markdown" diff --git a/manuskript/importer/opml.py b/manuskript/importer/opml.py deleted file mode 100644 index 51ff9369..00000000 --- a/manuskript/importer/opml.py +++ /dev/null @@ -1,127 +0,0 @@ -#!/usr/bin/env python -# --!-- coding: utf8 --!-- - -from PyQt5.QtWidgets import QMessageBox -from manuskript.models.outlineModel import outlineItem -from manuskript.enums import Outline -from lxml import etree as ET -from manuskript.functions import mainWindow - - -def importOpml(opmlFilePath, idx): - """ - Import/export outline cards in OPML format. - """ - ret = False - mw = mainWindow() - - try: - with open(opmlFilePath, 'r') as opmlFile: - opmlContent = saveNewlines(opmlFile.read()) - except: - # TODO: Translation - QMessageBox.critical(mw, mw.tr("OPML Import"), - mw.tr("File open failed.")) - return False - - mdl = mw.mdlOutline - - if idx.internalPointer() is not None: - parentItem = idx.internalPointer() - else: - parentItem = mdl.rootItem - - try: - parsed = ET.fromstring(bytes(opmlContent, 'utf-8')) - - opmlNode = parsed - bodyNode = opmlNode.find("body") - - if bodyNode is not None: - outlineEls = bodyNode.findall("outline") - - if outlineEls is not None: - for element in outlineEls: - parseItems(element, parentItem) - - mdl.layoutChanged.emit() - mw.treeRedacOutline.viewport().update() - ret = True - except: - pass - - # TODO: Translation - if ret: - QMessageBox.information(mw, mw.tr("OPML Import"), - mw.tr("Import Complete.")) - else: - QMessageBox.critical(mw, mw.tr("OPML Import"), - mw.tr("This does not appear to be a valid OPML file.")) - - return ret - - -def parseItems(underElement, parentItem): - text = underElement.get('text') - if text is not None: - - # In the case where the title is exceptionally long, trim it so it isn't - # distracting in the tab label - title = text[0:32] - if len(title) < len(text): - title += '...' - - card = outlineItem(parent=parentItem, title=title) - - body = "" - summary = "" - note = underElement.get('_note') - if note is not None and not isWhitespaceOnly(note): - body = restoreNewLines(note) - summary = body[0:128] - else: - - # There's no note (body), but there is a title. Fill the - # body with the title to support cards that consist only - # of a title. - body = text - - card.setData(Outline.summaryFull.value, summary) - - children = underElement.findall('outline') - if children is not None and len(children) > 0: - for el in children: - parseItems(el, card) - else: - card.setData(Outline.type.value, 'md') - card.setData(Outline.text.value, body) - - # I assume I don't have to do the following - # parentItem.appendChild(card) - - return - -def saveNewlines(inString): - """ - Since XML parsers are notorious for stripping out significant newlines, - save them in a form we can restore after the parse. - """ - inString = inString.replace("\r\n", "\n") - inString = inString.replace("\n", "{{lf}}") - - return inString - -def restoreNewLines(inString): - """ - Restore any significant newlines - """ - return inString.replace("{{lf}}", "\n") - -def isWhitespaceOnly(inString): - """ - Determine whether or not a string only contains whitespace. - """ - str = restoreNewLines(inString) - str = ''.join(str.split()) - - return len(str) is 0 diff --git a/manuskript/importer/opmlImporter.py b/manuskript/importer/opmlImporter.py new file mode 100644 index 00000000..636d7b40 --- /dev/null +++ b/manuskript/importer/opmlImporter.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +from PyQt5.QtWidgets import QMessageBox +from manuskript.models.outlineModel import outlineItem +from manuskript.enums import Outline +from lxml import etree as ET +from manuskript.functions import mainWindow +from manuskript.importer.abstractImporter import abstractImporter + +class opmlImporter(abstractImporter): + + name = "OPML" + description = "" + fileFormat = "OPML Files (*.opml)" + icon = "text-x-opml+xml" + + def importOpml(opmlFilePath, idx): + """ + Import/export outline cards in OPML format. + """ + ret = False + mw = mainWindow() + + try: + with open(opmlFilePath, 'r') as opmlFile: + opmlContent = saveNewlines(opmlFile.read()) + except: + # TODO: Translation + QMessageBox.critical(mw, mw.tr("OPML Import"), + mw.tr("File open failed.")) + return False + + mdl = mw.mdlOutline + + if idx.internalPointer() is not None: + parentItem = idx.internalPointer() + else: + parentItem = mdl.rootItem + + try: + parsed = ET.fromstring(bytes(opmlContent, 'utf-8')) + + opmlNode = parsed + bodyNode = opmlNode.find("body") + + if bodyNode is not None: + outlineEls = bodyNode.findall("outline") + + if outlineEls is not None: + for element in outlineEls: + parseItems(element, parentItem) + + mdl.layoutChanged.emit() + mw.treeRedacOutline.viewport().update() + ret = True + except: + pass + + # TODO: Translation + if ret: + QMessageBox.information(mw, mw.tr("OPML Import"), + mw.tr("Import Complete.")) + else: + QMessageBox.critical(mw, mw.tr("OPML Import"), + mw.tr("This does not appear to be a valid OPML file.")) + + return ret + + + def parseItems(underElement, parentItem): + text = underElement.get('text') + if text is not None: + + # In the case where the title is exceptionally long, trim it so it isn't + # distracting in the tab label + title = text[0:32] + if len(title) < len(text): + title += '...' + + card = outlineItem(parent=parentItem, title=title) + + body = "" + summary = "" + note = underElement.get('_note') + if note is not None and not isWhitespaceOnly(note): + body = restoreNewLines(note) + summary = body[0:128] + else: + + # There's no note (body), but there is a title. Fill the + # body with the title to support cards that consist only + # of a title. + body = text + + card.setData(Outline.summaryFull.value, summary) + + children = underElement.findall('outline') + if children is not None and len(children) > 0: + for el in children: + parseItems(el, card) + else: + card.setData(Outline.type.value, 'md') + card.setData(Outline.text.value, body) + + # I assume I don't have to do the following + # parentItem.appendChild(card) + + return + + def saveNewlines(inString): + """ + Since XML parsers are notorious for stripping out significant newlines, + save them in a form we can restore after the parse. + """ + inString = inString.replace("\r\n", "\n") + inString = inString.replace("\n", "{{lf}}") + + return inString + + def restoreNewLines(inString): + """ + Restore any significant newlines + """ + return inString.replace("{{lf}}", "\n") + + def isWhitespaceOnly(inString): + """ + Determine whether or not a string only contains whitespace. + """ + str = restoreNewLines(inString) + str = ''.join(str.split()) + + return len(str) is 0 diff --git a/manuskript/ui/editors/mainEditor.py b/manuskript/ui/editors/mainEditor.py index 8791bfd9..16358b08 100644 --- a/manuskript/ui/editors/mainEditor.py +++ b/manuskript/ui/editors/mainEditor.py @@ -14,7 +14,6 @@ from manuskript.ui import style from manuskript.ui.editors.editorWidget import editorWidget from manuskript.ui.editors.fullScreenEditor import fullScreenEditor from manuskript.ui.editors.mainEditor_ui import Ui_mainEditor -from manuskript.importer import opml as opmlInputExport locale.setlocale(locale.LC_ALL, '') @@ -84,10 +83,6 @@ class mainEditor(QWidget, Ui_mainEditor): self.btnRedacFullscreen.clicked.connect( self.showFullScreen, AUC) - self.btnImport.clicked.connect( - lambda v: self.importOPML() - ) - # self.tab.setDocumentMode(False) # Bug in Qt < 5.5: doesn't always load icons from custom theme. @@ -264,7 +259,6 @@ class mainEditor(QWidget, Ui_mainEditor): self.btnRedacFolderText.setVisible(visible) self.btnRedacFolderCork.setVisible(visible) self.btnRedacFolderOutline.setVisible(visible) - self.btnImport.setVisible(visible) self.sldCorkSizeFactor.setVisible(visible and self.btnRedacFolderCork.isChecked()) self.btnRedacFullscreen.setVisible(not visible) @@ -344,20 +338,6 @@ class mainEditor(QWidget, Ui_mainEditor): if self.currentEditor(): self._fullScreen = fullScreenEditor(self.currentEditor().currentIndex) - def importOPML(self): - from PyQt5.QtWidgets import QFileDialog - options = QFileDialog.Options() - options |= QFileDialog.DontUseNativeDialog - fileName, _ = QFileDialog.getOpenFileName(self, "Import OPML", "", - "OPML Files (*.opml)", options=options) - if fileName: - if len(self.mw.treeRedacOutline.selectionModel(). - selection().indexes()) == 0: - idx = QModelIndex() - else: - idx = self.mw.treeRedacOutline.currentIndex() - opmlInputExport.importOpml(fileName, idx) - ############################################################################### # DICT AND STUFF LIKE THAT ############################################################################### diff --git a/manuskript/ui/editors/mainEditor_ui.py b/manuskript/ui/editors/mainEditor_ui.py index 22fd1f0c..45c8e6ff 100644 --- a/manuskript/ui/editors/mainEditor_ui.py +++ b/manuskript/ui/editors/mainEditor_ui.py @@ -2,8 +2,7 @@ # Form implementation generated from reading ui file 'manuskript/ui/editors/mainEditor_ui.ui' # -# Created: Sat Oct 14 21:30:36 2017 -# by: PyQt5 UI code generator 5.2.1 +# Created by: PyQt5 UI code generator 5.9 # # WARNING! All changes made in this file will be lost! @@ -14,8 +13,8 @@ class Ui_mainEditor(object): mainEditor.setObjectName("mainEditor") mainEditor.resize(791, 319) self.verticalLayout = QtWidgets.QVBoxLayout(mainEditor) - self.verticalLayout.setSpacing(0) self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setSpacing(0) self.verticalLayout.setObjectName("verticalLayout") self.tabSplitter = tabSplitter(mainEditor) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) @@ -55,14 +54,6 @@ class Ui_mainEditor(object): self.btnRedacFolderOutline.setObjectName("btnRedacFolderOutline") self.buttonGroup.addButton(self.btnRedacFolderOutline) self.horizontalLayout_19.addWidget(self.btnRedacFolderOutline) - self.btnImport = QtWidgets.QPushButton(mainEditor) - self.btnImport.setText("") - icon = QtGui.QIcon.fromTheme("document-open") - self.btnImport.setIcon(icon) - self.btnImport.setFlat(True) - self.btnImport.setObjectName("btnImport") - self.buttonGroup.addButton(self.btnImport) - self.horizontalLayout_19.addWidget(self.btnImport) self.sldCorkSizeFactor = QtWidgets.QSlider(mainEditor) self.sldCorkSizeFactor.setMinimumSize(QtCore.QSize(100, 0)) self.sldCorkSizeFactor.setMaximumSize(QtCore.QSize(200, 16777215)) @@ -117,8 +108,6 @@ class Ui_mainEditor(object): self.btnRedacFolderCork.setText(_translate("mainEditor", "Index cards")) self.btnRedacFolderOutline.setText(_translate("mainEditor", "Outline")) self.btnRedacFullscreen.setShortcut(_translate("mainEditor", "F11")) - # TODO: Translation - self.btnImport.setToolTip(_translate("mainEditor", "Import items from an OPML file into the current folder")) from manuskript.ui.editors.tabSplitter import tabSplitter from manuskript.ui.editors.textFormat import textFormat diff --git a/manuskript/ui/editors/mainEditor_ui.ui b/manuskript/ui/editors/mainEditor_ui.ui index ce73c973..1110a34f 100644 --- a/manuskript/ui/editors/mainEditor_ui.ui +++ b/manuskript/ui/editors/mainEditor_ui.ui @@ -50,7 +50,9 @@ - + + + Alt+Up @@ -111,23 +113,6 @@ - - - - - Import items from an OPML file into the current folder - - - - - - - - - true - - - diff --git a/manuskript/ui/exporters/manuskript/plainTextSettings_ui.py b/manuskript/ui/exporters/manuskript/plainTextSettings_ui.py index 77598ba5..a6c8f98c 100644 --- a/manuskript/ui/exporters/manuskript/plainTextSettings_ui.py +++ b/manuskript/ui/exporters/manuskript/plainTextSettings_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'manuskript/ui/exporters/manuskript/plainTextSettings_ui.ui' # -# Created by: PyQt5 UI code generator 5.4.2 +# Created by: PyQt5 UI code generator 5.9 # # WARNING! All changes made in this file will be lost! @@ -28,9 +28,10 @@ class Ui_exporterSettings(object): "}") self.toolBox.setObjectName("toolBox") self.content = QtWidgets.QWidget() - self.content.setGeometry(QtCore.QRect(0, 0, 491, 842)) + self.content.setGeometry(QtCore.QRect(0, 0, 497, 834)) self.content.setObjectName("content") self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.content) + self.verticalLayout_5.setContentsMargins(0, 0, 0, 0) self.verticalLayout_5.setObjectName("verticalLayout_5") self.label = QtWidgets.QLabel(self.content) self.label.setObjectName("label") @@ -111,9 +112,10 @@ class Ui_exporterSettings(object): self.verticalLayout_5.addItem(spacerItem1) self.toolBox.addItem(self.content, "") self.separations = QtWidgets.QWidget() - self.separations.setGeometry(QtCore.QRect(0, 0, 511, 522)) + self.separations.setGeometry(QtCore.QRect(0, 0, 511, 534)) self.separations.setObjectName("separations") self.verticalLayout_8 = QtWidgets.QVBoxLayout(self.separations) + self.verticalLayout_8.setContentsMargins(0, 0, 0, 0) self.verticalLayout_8.setObjectName("verticalLayout_8") self.label_3 = QtWidgets.QLabel(self.separations) font = QtGui.QFont() @@ -319,10 +321,11 @@ class Ui_exporterSettings(object): self.verticalLayout_8.addItem(spacerItem6) self.toolBox.addItem(self.separations, "") self.transformations = QtWidgets.QWidget() - self.transformations.setGeometry(QtCore.QRect(0, 0, 511, 522)) + self.transformations.setGeometry(QtCore.QRect(0, 0, 511, 534)) self.transformations.setStyleSheet("QGroupBox{font-weight:bold;}") self.transformations.setObjectName("transformations") self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.transformations) + self.verticalLayout_6.setContentsMargins(0, 0, 0, 0) self.verticalLayout_6.setObjectName("verticalLayout_6") self.grpTransTypo = collapsibleGroupBox2(self.transformations) self.grpTransTypo.setStyleSheet("") @@ -481,10 +484,11 @@ class Ui_exporterSettings(object): self.verticalLayout_6.addItem(spacerItem10) self.toolBox.addItem(self.transformations, "") self.preview = QtWidgets.QWidget() - self.preview.setGeometry(QtCore.QRect(0, 0, 511, 522)) + self.preview.setGeometry(QtCore.QRect(0, 0, 511, 534)) self.preview.setStyleSheet("QGroupBox{font-weight:bold;}") self.preview.setObjectName("preview") self.verticalLayout_11 = QtWidgets.QVBoxLayout(self.preview) + self.verticalLayout_11.setContentsMargins(0, 0, 0, 0) self.verticalLayout_11.setObjectName("verticalLayout_11") self.groupBox = QtWidgets.QGroupBox(self.preview) self.groupBox.setObjectName("groupBox") diff --git a/manuskript/ui/exporters/manuskript/plainTextSettings_ui.ui b/manuskript/ui/exporters/manuskript/plainTextSettings_ui.ui index 80649472..15c1a5e4 100644 --- a/manuskript/ui/exporters/manuskript/plainTextSettings_ui.ui +++ b/manuskript/ui/exporters/manuskript/plainTextSettings_ui.ui @@ -53,8 +53,8 @@ QToolBox::tab:selected, QToolBox::tab:hover{ 0 0 - 491 - 842 + 497 + 834 @@ -233,7 +233,7 @@ QToolBox::tab:selected, QToolBox::tab:hover{ 0 0 511 - 522 + 534 @@ -773,7 +773,7 @@ QToolBox::tab:selected, QToolBox::tab:hover{ 0 0 511 - 522 + 534 @@ -1162,7 +1162,7 @@ QToolBox::tab:selected, QToolBox::tab:hover{ 0 0 511 - 522 + 534 diff --git a/manuskript/ui/importers/generalSettings.py b/manuskript/ui/importers/generalSettings.py new file mode 100644 index 00000000..7dd96fc0 --- /dev/null +++ b/manuskript/ui/importers/generalSettings.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- +import json +import os + +from PyQt5.QtCore import Qt, QSize, QSortFilterProxyModel +from PyQt5.QtGui import QIcon, QFontMetrics, QFont +from PyQt5.QtWidgets import QWidget, QTableWidgetItem, QListWidgetItem, QTreeView + +from manuskript.functions import mainWindow, writablePath +from manuskript.ui.importers.generalSettings_ui import Ui_generalSettings +from manuskript.enums import Outline + +class generalSettings(QWidget, Ui_generalSettings): + def __init__(self, parent=None): + QWidget.__init__(self, parent) + self.setupUi(self) + + self.mw = mainWindow() + + # TreeView to select parent + # We use a proxy to display only folders + proxy = QSortFilterProxyModel() + proxy.setFilterKeyColumn(Outline.type.value) + proxy.setFilterFixedString("folder") + proxy.setSourceModel(self.mw.mdlOutline) + self.treeGeneralParent.setModel(proxy) + for i in range(1, self.mw.mdlOutline.columnCount()): + self.treeGeneralParent.hideColumn(i) + self.treeGeneralParent.setCurrentIndex(self.getParentIndex()) + self.chkGeneralParent.toggled.connect(self.treeGeneralParent.setVisible) + self.treeGeneralParent.hide() + self.treeGeneralParent.selectionModel().currentChanged.connect( + lambda: print(self.treeGeneralParent.currentIndex().row())) + + def getParentIndex(self): + """ + Returns the currently selected index in the mainWindow. + """ + if len(self.mw.treeRedacOutline.selectionModel(). + selection().indexes()) == 0: + idx = QModelIndex() + else: + idx = self.mw.treeRedacOutline.currentIndex() + return idx + diff --git a/manuskript/ui/importers/generalSettings_ui.py b/manuskript/ui/importers/generalSettings_ui.py new file mode 100644 index 00000000..078ca2ba --- /dev/null +++ b/manuskript/ui/importers/generalSettings_ui.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'manuskript/ui/importers/generalSettings_ui.ui' +# +# Created by: PyQt5 UI code generator 5.9 +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore, QtGui, QtWidgets + +class Ui_generalSettings(object): + def setupUi(self, generalSettings): + generalSettings.setObjectName("generalSettings") + generalSettings.resize(289, 396) + self.verticalLayout_2 = QtWidgets.QVBoxLayout(generalSettings) + self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_2.setSpacing(10) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.toolBox = QtWidgets.QToolBox(generalSettings) + self.toolBox.setStyleSheet("QToolBox::tab{\n" +" background-color: #BBB;\n" +" padding: 2px;\n" +" border: none;\n" +"}\n" +"\n" +"QToolBox::tab:selected, QToolBox::tab:hover{\n" +" background-color:skyblue;\n" +"}") + self.toolBox.setObjectName("toolBox") + self.general = QtWidgets.QWidget() + self.general.setGeometry(QtCore.QRect(0, 0, 289, 373)) + self.general.setObjectName("general") + self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.general) + self.verticalLayout_5.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_5.setObjectName("verticalLayout_5") + self.formLayout_4 = QtWidgets.QFormLayout() + self.formLayout_4.setRowWrapPolicy(QtWidgets.QFormLayout.WrapLongRows) + self.formLayout_4.setObjectName("formLayout_4") + self.chkGeneralParent = QtWidgets.QCheckBox(self.general) + self.chkGeneralParent.setObjectName("chkGeneralParent") + self.formLayout_4.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.chkGeneralParent) + self.chkGeneralSplitScenes = QtWidgets.QCheckBox(self.general) + self.chkGeneralSplitScenes.setObjectName("chkGeneralSplitScenes") + self.formLayout_4.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.chkGeneralSplitScenes) + self.txtGeneralSplitScenes = QtWidgets.QLineEdit(self.general) + self.txtGeneralSplitScenes.setObjectName("txtGeneralSplitScenes") + self.formLayout_4.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.txtGeneralSplitScenes) + self.treeGeneralParent = QtWidgets.QTreeView(self.general) + self.treeGeneralParent.setHeaderHidden(True) + self.treeGeneralParent.setObjectName("treeGeneralParent") + self.formLayout_4.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.treeGeneralParent) + self.verticalLayout_5.addLayout(self.formLayout_4) + self.toolBox.addItem(self.general, "") + self.verticalLayout_2.addWidget(self.toolBox) + + self.retranslateUi(generalSettings) + self.toolBox.setCurrentIndex(0) + self.toolBox.layout().setSpacing(0) + QtCore.QMetaObject.connectSlotsByName(generalSettings) + + def retranslateUi(self, generalSettings): + _translate = QtCore.QCoreApplication.translate + generalSettings.setWindowTitle(_translate("generalSettings", "Form")) + self.chkGeneralParent.setText(_translate("generalSettings", "Import under:")) + self.chkGeneralSplitScenes.setText(_translate("generalSettings", "Split scenes at:")) + self.toolBox.setItemText(self.toolBox.indexOf(self.general), _translate("generalSettings", "General")) + diff --git a/manuskript/ui/importers/generalSettings_ui.ui b/manuskript/ui/importers/generalSettings_ui.ui new file mode 100644 index 00000000..53446d2b --- /dev/null +++ b/manuskript/ui/importers/generalSettings_ui.ui @@ -0,0 +1,103 @@ + + + generalSettings + + + + 0 + 0 + 289 + 396 + + + + Form + + + + 10 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QToolBox::tab{ + background-color: #BBB; + padding: 2px; + border: none; +} + +QToolBox::tab:selected, QToolBox::tab:hover{ + background-color:skyblue; +} + + + 0 + + + 0 + + + + + 0 + 0 + 289 + 373 + + + + General + + + + + + QFormLayout::WrapLongRows + + + + + Import under: + + + + + + + Split scenes at: + + + + + + + + + + true + + + + + + + + + + + + + + diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py index 3254a926..4aaad994 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -7,15 +7,11 @@ from PyQt5.QtCore import Qt from PyQt5.QtGui import QBrush, QColor, QIcon from PyQt5.QtWidgets import QWidget, QFileDialog -from manuskript import exporter from manuskript.functions import lightBlue, writablePath, appPath from manuskript.ui.importers.importer_ui import Ui_importer - -class importFormat: - def __init__(self, name, icon, fileFormat): - self.name = name - self.icon = icon - self.fileFormat = fileFormat +from manuskript.ui.importers.generalSettings import generalSettings +from manuskript.ui import style +from manuskript import importer class importerDialog(QWidget, Ui_importer): @@ -37,15 +33,10 @@ class importerDialog(QWidget, Ui_importer): # Var self.mw = mw self.fileName = "" + self.setStyleSheet(style.mainWindowSS()) # Register importFormats: - self.formats = [] - self.formats.append(importFormat("OPML", "text-x-opml+xml", - "OPML Files (*.opml)")) - self.formats.append(importFormat("Markdown", "text-x-markdown", - "Markdown files (*.md; *.txt; *)")) - self.formats.append(importFormat("Folder", "folder", - "<>")) + self.importers = importer.importers # Populate combo box with formats self.populateImportList() @@ -54,7 +45,10 @@ class importerDialog(QWidget, Ui_importer): self.btnChoseFile.clicked.connect(self.selectFile) self.btnClearFileName.clicked.connect(self.setFileName) self.btnPreview.clicked.connect(self.preview) - self.setFileName("") + self.cmbImporters.currentTextChanged.connect(self.updateSettings) + + #self.setFileName("") + self.setFileName("/home/olivier/Dropbox/Documents/Travail/Geekeries/Python/PyCharmProjects/manuskript/test-projects/IMPORTS/End Plan 2.opml") ############################################################################ # Combobox / Formats @@ -65,18 +59,25 @@ class importerDialog(QWidget, Ui_importer): def addFormat(name, icon): self.cmbImporters.addItem(QIcon.fromTheme(icon), name) - for f in self.formats: + for f in self.importers: addFormat(f.name, f.icon) + def currentFormat(self): + formatName = self.cmbImporters.currentText() + F = [F for F in self.importers if F.name == formatName][0] + return F + ############################################################################ # Import file ############################################################################ def selectFile(self): + """ + Called to select a file in the file system. Uses QFileDialog. + """ # We find the current selected format - formatName = self.cmbImporters.currentText() - F = [F for F in self.formats if F.name == formatName][0] + F = self.currentFormat() options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog @@ -90,47 +91,70 @@ class importerDialog(QWidget, Ui_importer): self.setFileName(fileName) def setFileName(self, fileName): + """ + Updates Ui with given filename. Filename can be empty. + """ if fileName: self.fileName = fileName - self.btnPreview.setEnabled(True) self.lblFileName.setText(os.path.basename(fileName)) self.lblFileName.setToolTip(fileName) - self.btnClearFileName.setVisible(True) ext = os.path.splitext(fileName)[1] if ext and ext in self.formatsIcon: - self.lblIcon.setVisible(True) - h = self.lblFileName.height() - self.lblIcon.setPixmap( - QIcon.fromTheme(self.formatsIcon[ext]).pixmap(h, h) - ) + icon = QIcon.fromTheme(self.formatsIcon[ext]) elif os.path.isdir(fileName): - self.lblIcon.setVisible(True) - h = self.lblFileName.height() - self.lblIcon.setPixmap(QIcon.fromTheme("folder").pixmap(h, h)) + icon = QIcon.fromTheme("folder") + + #self.lblIcon.setVisible(True) + h = self.lblFileName.height() + self.lblIcon.setPixmap(icon.pixmap(h, h)) else: self.fileName = None - self.btnPreview.setEnabled(False) self.lblFileName.setText("") - self.btnClearFileName.setVisible(False) - self.lblIcon.setVisible(False) + + hasFile = True if fileName else False + + self.btnClearFileName.setVisible(hasFile) + self.lblIcon.setVisible(hasFile) + self.btnChoseFile.setVisible(not hasFile) + self.btnPreview.setEnabled(hasFile) + self.btnImport.setEnabled(hasFile) ############################################################################ - # Preview + # UI + ############################################################################ + + def updateSettings(self): + """ + When the current format change (through the combobox), we update the + settings widget using the current format provided settings widget. + """ + + F = self.currentFormat() + self.settingsWidget = generalSettings() + self.setGroupWidget(self.grpSettings, self.settingsWidget) + + #TODO: custom format widget + + def setGroupWidget(self, group, widget): + """ + Sets the given widget as main widget for QGroupBox group. + """ + + # Removes every items from given layout. + l = group.layout() + while l.count(): + item = l.itemAt(0) + l.removeItem(item) + item.widget().deleteLater() + + l.addWidget(widget) + widget.setParent(group) + + ############################################################################ + # Preview / Import ############################################################################ def preview(self): # TODO pass - - ############################################################################ - # - ############################################################################ - - def getParentIndex(self): - if len(self.mw.treeRedacOutline.selectionModel(). - selection().indexes()) == 0: - idx = QModelIndex() - else: - idx = self.mw.treeRedacOutline.currentIndex() - return idx diff --git a/manuskript/ui/importers/importer_ui.py b/manuskript/ui/importers/importer_ui.py index b6730a58..7d393291 100644 --- a/manuskript/ui/importers/importer_ui.py +++ b/manuskript/ui/importers/importer_ui.py @@ -22,12 +22,6 @@ class Ui_importer(object): self.cmbImporters = QtWidgets.QComboBox(importer) self.cmbImporters.setObjectName("cmbImporters") self.horizontalLayout.addWidget(self.cmbImporters) - self.btnManageImporters = QtWidgets.QPushButton(importer) - self.btnManageImporters.setText("") - icon = QtGui.QIcon.fromTheme("preferences-system") - self.btnManageImporters.setIcon(icon) - self.btnManageImporters.setObjectName("btnManageImporters") - self.horizontalLayout.addWidget(self.btnManageImporters) spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) self.btnChoseFile = QtWidgets.QPushButton(importer) @@ -59,8 +53,14 @@ class Ui_importer(object): self.btnPreview = QtWidgets.QPushButton(importer) icon = QtGui.QIcon.fromTheme("document-print-preview") self.btnPreview.setIcon(icon) + self.btnPreview.setFlat(True) self.btnPreview.setObjectName("btnPreview") self.horizontalLayout.addWidget(self.btnPreview) + self.btnImport = QtWidgets.QPushButton(importer) + icon = QtGui.QIcon.fromTheme("document-import") + self.btnImport.setIcon(icon) + self.btnImport.setObjectName("btnImport") + self.horizontalLayout.addWidget(self.btnImport) self.verticalLayout.addLayout(self.horizontalLayout) self.splitter = QtWidgets.QSplitter(importer) self.splitter.setOrientation(QtCore.Qt.Horizontal) @@ -77,6 +77,10 @@ class Ui_importer(object): self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) self.verticalLayout_2.setSpacing(0) self.verticalLayout_2.setObjectName("verticalLayout_2") + self.splitter_2 = QtWidgets.QSplitter(self.grpPreview) + self.splitter_2.setOrientation(QtCore.Qt.Horizontal) + self.splitter_2.setObjectName("splitter_2") + self.verticalLayout_2.addWidget(self.splitter_2) self.verticalLayout.addWidget(self.splitter) self.retranslateUi(importer) @@ -86,9 +90,10 @@ class Ui_importer(object): _translate = QtCore.QCoreApplication.translate importer.setWindowTitle(_translate("importer", "Import")) self.label.setText(_translate("importer", "Format:")) - self.btnManageImporters.setToolTip(_translate("importer", "Manage importers")) self.btnChoseFile.setText(_translate("importer", "Chose file")) + self.btnClearFileName.setToolTip(_translate("importer", "Clear file")) self.btnPreview.setText(_translate("importer", "Preview")) + self.btnImport.setText(_translate("importer", "Import")) self.grpSettings.setTitle(_translate("importer", "Settings")) self.grpPreview.setTitle(_translate("importer", "Preview")) diff --git a/manuskript/ui/importers/importer_ui.ui b/manuskript/ui/importers/importer_ui.ui index c7877abd..404db62e 100644 --- a/manuskript/ui/importers/importer_ui.ui +++ b/manuskript/ui/importers/importer_ui.ui @@ -26,21 +26,6 @@ - - - - Manage importers - - - - - - - - - - - @@ -60,7 +45,8 @@ Chose file - + + ../../../../../../../../../../.designer/backup../../../../../../../../../../.designer/backup @@ -86,11 +72,15 @@ + + Clear file + - + + ../../../../../../../../../../.designer/backup../../../../../../../../../../.designer/backup true @@ -117,8 +107,20 @@ - - + ../../../../../../../../../../.designer/backup../../../../../../../../../../.designer/backup + + + true + + + + + + + Import + + + @@ -171,6 +173,13 @@ 0 + + + + Qt::Horizontal + + + From d51233ebba295d1f1a0a99af22d8e27706a5bec2 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 7 Nov 2017 12:02:02 +0100 Subject: [PATCH 09/23] Cleans treeView of some call to mainWindow that should be here --- manuskript/mainWindow.py | 3 ++ manuskript/ui/importers/importer.py | 3 +- manuskript/ui/importers/importer_ui.py | 6 ++++ manuskript/ui/importers/importer_ui.ui | 15 ++++++++ manuskript/ui/views/outlineBasics.py | 49 ++++++++++++++++++++++---- manuskript/ui/views/treeView.py | 24 ++----------- 6 files changed, 71 insertions(+), 29 deletions(-) diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index 6a01d1bb..f72bd8b5 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -383,6 +383,9 @@ class MainWindow(QMainWindow, Ui_MainWindow): def openIndex(self, index): self.treeRedacOutline.setCurrentIndex(index) + def openIndexes(self, indexes, newTab=True): + self.mainEditor.openIndexes(indexes, newTab=True) + ############################################################################### # LOAD AND SAVE ############################################################################### diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py index 4aaad994..72152f36 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -12,6 +12,7 @@ from manuskript.ui.importers.importer_ui import Ui_importer from manuskript.ui.importers.generalSettings import generalSettings from manuskript.ui import style from manuskript import importer +from manuskript.models.outlineModel import outlineModel class importerDialog(QWidget, Ui_importer): @@ -157,4 +158,4 @@ class importerDialog(QWidget, Ui_importer): def preview(self): # TODO - pass + previewModel = outlineModel(self) diff --git a/manuskript/ui/importers/importer_ui.py b/manuskript/ui/importers/importer_ui.py index 7d393291..44afddc1 100644 --- a/manuskript/ui/importers/importer_ui.py +++ b/manuskript/ui/importers/importer_ui.py @@ -80,6 +80,10 @@ class Ui_importer(object): self.splitter_2 = QtWidgets.QSplitter(self.grpPreview) self.splitter_2.setOrientation(QtCore.Qt.Horizontal) self.splitter_2.setObjectName("splitter_2") + self.tree = treeView(self.splitter_2) + self.tree.setObjectName("tree") + self.editor = mainEditor(self.splitter_2) + self.editor.setObjectName("editor") self.verticalLayout_2.addWidget(self.splitter_2) self.verticalLayout.addWidget(self.splitter) @@ -97,3 +101,5 @@ class Ui_importer(object): self.grpSettings.setTitle(_translate("importer", "Settings")) self.grpPreview.setTitle(_translate("importer", "Preview")) +from manuskript.ui.editors.mainEditor import mainEditor +from manuskript.ui.views.treeView import treeView diff --git a/manuskript/ui/importers/importer_ui.ui b/manuskript/ui/importers/importer_ui.ui index 404db62e..afb053e9 100644 --- a/manuskript/ui/importers/importer_ui.ui +++ b/manuskript/ui/importers/importer_ui.ui @@ -178,6 +178,8 @@ Qt::Horizontal + + @@ -186,6 +188,19 @@ + + + treeView + QTreeView +
manuskript.ui.views.treeView.h
+
+ + mainEditor + QWidget +
manuskript.ui.editors.mainEditor.h
+ 1 +
+
diff --git a/manuskript/ui/views/outlineBasics.py b/manuskript/ui/views/outlineBasics.py index 8c21a8ec..7b12f794 100644 --- a/manuskript/ui/views/outlineBasics.py +++ b/manuskript/ui/views/outlineBasics.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # --!-- coding: utf8 --!-- from PyQt5.QtCore import Qt, QSignalMapper, QSize -from PyQt5.QtGui import QIcon +from PyQt5.QtGui import QIcon, QCursor from PyQt5.QtWidgets import QAbstractItemView, qApp, QMenu, QAction from PyQt5.QtWidgets import QListWidget, QWidgetAction, QListWidgetItem, QLineEdit @@ -14,7 +14,7 @@ from manuskript.models.outlineModel import outlineItem class outlineBasics(QAbstractItemView): def __init__(self, parent=None): - pass + self._indexesToOpen = None def getSelection(self): sel = [] @@ -39,11 +39,44 @@ class outlineBasics(QAbstractItemView): menu = QMenu(self) - # Open items - self.actOpen = QAction(QIcon.fromTheme("go-right"), qApp.translate("outlineBasics", "Open Item"), menu) + # 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 = self.tr("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 = self.tr("Open {} items in new tabs").format(len(sel)) + self._indexesToOpen = sel + else: + actionTitle = self.tr("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() # Rename / add / remove items @@ -185,7 +218,6 @@ class outlineBasics(QAbstractItemView): self.actAddText.setEnabled(False) if len(sel) == 0: - self.actOpen.setEnabled(False) self.actCopy.setEnabled(False) self.actCut.setEnabled(False) self.actRename.setEnabled(False) @@ -201,10 +233,15 @@ class outlineBasics(QAbstractItemView): return menu def openItem(self): - idx = self.currentIndex() + #idx = self.currentIndex() + idx = self._indexesToOpen[0] from manuskript.functions import MW MW.openIndex(idx) + def openItemsInNewTabs(self): + from manuskript.functions import MW + MW.openIndexes(self._indexesToOpen) + def rename(self): if len(self.getSelection()) == 1: index = self.currentIndex() diff --git a/manuskript/ui/views/treeView.py b/manuskript/ui/views/treeView.py index 53a063f7..4e28d1c9 100644 --- a/manuskript/ui/views/treeView.py +++ b/manuskript/ui/views/treeView.py @@ -31,30 +31,13 @@ class treeView(QTreeView, dndView, outlineBasics): def makePopupMenu(self): menu = outlineBasics.makePopupMenu(self) - first = menu.actions()[0] + first = menu.actions()[3] # Open item in new tab - sel = self.selectedIndexes() + #sel = self.selectedIndexes() pos = self.viewport().mapFromGlobal(QCursor.pos()) mouseIndex = self.indexAt(pos) - if mouseIndex.isValid(): - mouseTitle = mouseIndex.internalPointer().title() - else: - mouseTitle = self.tr("Root") - - if mouseIndex in sel and len(sel) > 1: - actionTitle = self.tr("Open {} items in new tabs").format(len(sel)) - self._indexesToOpen = sel - else: - actionTitle = self.tr("Open {} in a new tab").format(mouseTitle) - self._indexesToOpen = [mouseIndex] - - self.actNewTab = QAction(actionTitle, menu) - self.actNewTab.triggered.connect(self.openNewTab) - menu.insertAction(first, self.actNewTab) - menu.insertSeparator(first) - # Expand /collapse item if mouseIndex.isValid(): # index = self.currentIndex() @@ -83,9 +66,6 @@ class treeView(QTreeView, dndView, outlineBasics): return menu - def openNewTab(self): - mainWindow().mainEditor.openIndexes(self._indexesToOpen, newTab=True) - def expandCurrentIndex(self, index=None): if index is None or type(index) == bool: index = self._indexesToOpen[0] # self.currentIndex() From 316651245c866898a4550adcfe61ca17763683fd Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 7 Nov 2017 12:50:40 +0100 Subject: [PATCH 10/23] Checkpoint: opml import can be previewed in tree view --- manuskript/importer/abstractImporter.py | 7 +- manuskript/importer/opmlImporter.py | 100 ++++++++++++------ manuskript/ui/importers/generalSettings_ui.py | 4 + manuskript/ui/importers/generalSettings_ui.ui | 7 ++ manuskript/ui/importers/importer.py | 27 ++++- manuskript/ui/importers/importer_ui.py | 5 +- manuskript/ui/importers/importer_ui.ui | 14 +-- 7 files changed, 117 insertions(+), 47 deletions(-) diff --git a/manuskript/importer/abstractImporter.py b/manuskript/importer/abstractImporter.py index 822ebe58..7c75a681 100644 --- a/manuskript/importer/abstractImporter.py +++ b/manuskript/importer/abstractImporter.py @@ -22,10 +22,11 @@ class abstractImporter: # For folder, use "<>" icon = "" - @classmethod - def startImport(cls, filePath): + def startImport(self, filePath, settingsWidget): """ - Takes a str path to the file/folder to import, and return `outlineItem`s. + Takes a str path to the file/folder to import, and the settingsWidget + returnend by `self.settingsWidget()` containing the user set settings, + and return `outlineItem`s. """ pass diff --git a/manuskript/importer/opmlImporter.py b/manuskript/importer/opmlImporter.py index 636d7b40..aff37282 100644 --- a/manuskript/importer/opmlImporter.py +++ b/manuskript/importer/opmlImporter.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # --!-- coding: utf8 --!-- -from PyQt5.QtWidgets import QMessageBox +from PyQt5.QtWidgets import qApp, QMessageBox from manuskript.models.outlineModel import outlineItem from manuskript.enums import Outline from lxml import etree as ET @@ -15,9 +15,56 @@ class opmlImporter(abstractImporter): fileFormat = "OPML Files (*.opml)" icon = "text-x-opml+xml" + @classmethod + def startImport(cls, filePath, parentItem, settingsWidget): + """ + Import/export outline cards in OPML format. + """ + ret = False + + try: + with open(filePath, 'r') as opmlFile: + opmlContent = cls.saveNewlines(opmlFile.read()) + except: + QMessageBox.critical(settingsWidget, + qApp.translate("Import", "OPML Import"), + qApp.translate("Import", "File open failed.")) + return None + + parsed = ET.fromstring(bytes(opmlContent, 'utf-8')) + + opmlNode = parsed + bodyNode = opmlNode.find("body") + items = [] + + if bodyNode is not None: + outlineEls = bodyNode.findall("outline") + + if outlineEls is not None: + for element in outlineEls: + items.append(cls.parseItems(element, parentItem)) + ret = True + + if ret: + #QMessageBox.information( + #settingsWidget, + #qApp.translate("Import", "OPML Import"), + #qApp.translate("Import", "Import Complete.")) + pass + else: + QMessageBox.critical( + settingsWidget, + qApp.translate("Import", "OPML Import"), + qApp.translate("Import", "This does not appear to be a valid OPML file.")) + + return None + + return items + def importOpml(opmlFilePath, idx): """ Import/export outline cards in OPML format. + #FIXME: delete me when done with startImport """ ret = False mw = mainWindow() @@ -26,7 +73,6 @@ class opmlImporter(abstractImporter): with open(opmlFilePath, 'r') as opmlFile: opmlContent = saveNewlines(opmlFile.read()) except: - # TODO: Translation QMessageBox.critical(mw, mw.tr("OPML Import"), mw.tr("File open failed.")) return False @@ -67,48 +113,30 @@ class opmlImporter(abstractImporter): return ret - - def parseItems(underElement, parentItem): - text = underElement.get('text') - if text is not None: - - # In the case where the title is exceptionally long, trim it so it isn't - # distracting in the tab label - title = text[0:32] - if len(title) < len(text): - title += '...' + @classmethod + def parseItems(cls, underElement, parentItem=None): + title = underElement.get('text') + if title is not None: card = outlineItem(parent=parentItem, title=title) body = "" - summary = "" note = underElement.get('_note') - if note is not None and not isWhitespaceOnly(note): - body = restoreNewLines(note) - summary = body[0:128] - else: - - # There's no note (body), but there is a title. Fill the - # body with the title to support cards that consist only - # of a title. - body = text - - card.setData(Outline.summaryFull.value, summary) + if note is not None and not cls.isWhitespaceOnly(note): + body = cls.restoreNewLines(note) children = underElement.findall('outline') if children is not None and len(children) > 0: for el in children: - parseItems(el, card) + cls.parseItems(el, card) else: card.setData(Outline.type.value, 'md') card.setData(Outline.text.value, body) - # I assume I don't have to do the following - # parentItem.appendChild(card) + return card - return - - def saveNewlines(inString): + @classmethod + def saveNewlines(cls, inString): """ Since XML parsers are notorious for stripping out significant newlines, save them in a form we can restore after the parse. @@ -118,17 +146,19 @@ class opmlImporter(abstractImporter): return inString - def restoreNewLines(inString): + @classmethod + def restoreNewLines(cls, inString): """ Restore any significant newlines """ return inString.replace("{{lf}}", "\n") - def isWhitespaceOnly(inString): + @classmethod + def isWhitespaceOnly(cls, inString): """ Determine whether or not a string only contains whitespace. """ - str = restoreNewLines(inString) - str = ''.join(str.split()) + s = cls.restoreNewLines(inString) + s = ''.join(s.split()) - return len(str) is 0 + return len(s) is 0 diff --git a/manuskript/ui/importers/generalSettings_ui.py b/manuskript/ui/importers/generalSettings_ui.py index 078ca2ba..0faed4cd 100644 --- a/manuskript/ui/importers/generalSettings_ui.py +++ b/manuskript/ui/importers/generalSettings_ui.py @@ -49,6 +49,9 @@ class Ui_generalSettings(object): self.treeGeneralParent.setHeaderHidden(True) self.treeGeneralParent.setObjectName("treeGeneralParent") self.formLayout_4.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.treeGeneralParent) + self.chkGeneralTrimTitles = QtWidgets.QCheckBox(self.general) + self.chkGeneralTrimTitles.setObjectName("chkGeneralTrimTitles") + self.formLayout_4.setWidget(2, QtWidgets.QFormLayout.SpanningRole, self.chkGeneralTrimTitles) self.verticalLayout_5.addLayout(self.formLayout_4) self.toolBox.addItem(self.general, "") self.verticalLayout_2.addWidget(self.toolBox) @@ -63,5 +66,6 @@ class Ui_generalSettings(object): generalSettings.setWindowTitle(_translate("generalSettings", "Form")) self.chkGeneralParent.setText(_translate("generalSettings", "Import under:")) self.chkGeneralSplitScenes.setText(_translate("generalSettings", "Split scenes at:")) + self.chkGeneralTrimTitles.setText(_translate("generalSettings", "Trim long titles (> 32 chars)")) self.toolBox.setItemText(self.toolBox.indexOf(self.general), _translate("generalSettings", "General")) diff --git a/manuskript/ui/importers/generalSettings_ui.ui b/manuskript/ui/importers/generalSettings_ui.ui index 53446d2b..45fe6efd 100644 --- a/manuskript/ui/importers/generalSettings_ui.ui +++ b/manuskript/ui/importers/generalSettings_ui.ui @@ -90,6 +90,13 @@ QToolBox::tab:selected, QToolBox::tab:hover{ + + + + Trim long titles (> 32 chars) + + + diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py index 72152f36..ea527c4e 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -157,5 +157,30 @@ class importerDialog(QWidget, Ui_importer): ############################################################################ def preview(self): - # TODO + + # We find the current selected format + F = self.currentFormat() + + # Temporary outlineModel previewModel = outlineModel(self) + + # Calling the importer in a temporary model + items = F.startImport(self.fileName, + previewModel.rootItem, + self.settingsWidget) + + # Do transformations + # TODO + + if items: + self.tree.setModel(previewModel) + for i in range(1, previewModel.columnCount()): + self.tree.hideColumn(i) + + def startImport(self): + pass + + # Note: dont forget to emit: mdl.layoutChanged.emit() + # Maybe: mw.treeRedacOutline.viewport().update() + + diff --git a/manuskript/ui/importers/importer_ui.py b/manuskript/ui/importers/importer_ui.py index 44afddc1..67f49db2 100644 --- a/manuskript/ui/importers/importer_ui.py +++ b/manuskript/ui/importers/importer_ui.py @@ -80,7 +80,9 @@ class Ui_importer(object): self.splitter_2 = QtWidgets.QSplitter(self.grpPreview) self.splitter_2.setOrientation(QtCore.Qt.Horizontal) self.splitter_2.setObjectName("splitter_2") - self.tree = treeView(self.splitter_2) + self.tree = QtWidgets.QTreeView(self.splitter_2) + self.tree.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + self.tree.setHeaderHidden(True) self.tree.setObjectName("tree") self.editor = mainEditor(self.splitter_2) self.editor.setObjectName("editor") @@ -102,4 +104,3 @@ class Ui_importer(object): self.grpPreview.setTitle(_translate("importer", "Preview")) from manuskript.ui.editors.mainEditor import mainEditor -from manuskript.ui.views.treeView import treeView diff --git a/manuskript/ui/importers/importer_ui.ui b/manuskript/ui/importers/importer_ui.ui index afb053e9..0281cd06 100644 --- a/manuskript/ui/importers/importer_ui.ui +++ b/manuskript/ui/importers/importer_ui.ui @@ -178,7 +178,14 @@ Qt::Horizontal - + + + QAbstractItemView::NoEditTriggers + + + true + + @@ -189,11 +196,6 @@ - - treeView - QTreeView -
manuskript.ui.views.treeView.h
-
mainEditor QWidget From 543d5a232ac28f259bf9fe84db24dfa8d72febbd Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 7 Nov 2017 14:25:47 +0100 Subject: [PATCH 11/23] Checkpoint: preview seems to work --- manuskript/ui/editors/editorWidget.py | 28 ++++++----- manuskript/ui/importers/generalSettings.py | 21 +++++++-- manuskript/ui/importers/generalSettings_ui.py | 26 +++++------ manuskript/ui/importers/generalSettings_ui.ui | 46 ++++++++++++------- manuskript/ui/importers/importer.py | 41 +++++++++++++++-- manuskript/ui/importers/importer_ui.py | 17 +++---- manuskript/ui/importers/importer_ui.ui | 15 +++--- 7 files changed, 132 insertions(+), 62 deletions(-) diff --git a/manuskript/ui/editors/editorWidget.py b/manuskript/ui/editors/editorWidget.py index 75abb568..60ee4008 100644 --- a/manuskript/ui/editors/editorWidget.py +++ b/manuskript/ui/editors/editorWidget.py @@ -55,6 +55,8 @@ class editorWidget(QWidget, Ui_editorWidget_ui): self.mw = mainWindow() self._tabWidget = None # set by mainEditor on creation + self._model = None + # def setModel(self, model): # self._model = model # self.setView() @@ -83,8 +85,10 @@ class editorWidget(QWidget, Ui_editorWidget_ui): if r.isValid(): count = r.internalPointer().childCount() + elif self._model: + count = self._model.rootItem.childCount() else: - count = self.mw.mdlOutline.rootItem.childCount() + count = 0 for c in range(count): self.corkView.itemDelegate().sizeHintChanged.emit(r.child(c, 0)) @@ -102,8 +106,10 @@ class editorWidget(QWidget, Ui_editorWidget_ui): if self.currentIndex.isValid(): item = self.currentIndex.internalPointer() + elif self._model: + item = self._model.rootItem else: - item = self.mw.mdlOutline.rootItem + return i = self._tabWidget.indexOf(self) self._tabWidget.setTabText(i, item.title()) @@ -202,7 +208,7 @@ class editorWidget(QWidget, Ui_editorWidget_ui): self.txtEdits = [] - if item != self.mw.mdlOutline.rootItem: + if item != self._model.rootItem: addTitle(item) addChildren(item) @@ -211,7 +217,7 @@ class editorWidget(QWidget, Ui_editorWidget_ui): elif item and item.isFolder() and self.folderView == "cork": self.stack.setCurrentIndex(2) - self.corkView.setModel(self.mw.mdlOutline) + self.corkView.setModel(self._model) self.corkView.setRootIndex(self.currentIndex) try: self.corkView.selectionModel().selectionChanged.connect(mainWindow().redacMetadata.selectionChanged, AUC) @@ -225,7 +231,7 @@ class editorWidget(QWidget, Ui_editorWidget_ui): self.outlineView.setModelCharacters(mainWindow().mdlCharacter) self.outlineView.setModelLabels(mainWindow().mdlLabels) self.outlineView.setModelStatus(mainWindow().mdlStatus) - self.outlineView.setModel(self.mw.mdlOutline) + self.outlineView.setModel(self._model) self.outlineView.setRootIndex(self.currentIndex) try: @@ -242,9 +248,9 @@ class editorWidget(QWidget, Ui_editorWidget_ui): 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._model.dataChanged.connect(self.modelDataChanged, AUC) + self._model.rowsInserted.connect(self.updateIndexFromID, AUC) + self._model.rowsRemoved.connect(self.updateIndexFromID, AUC) #self.mw.mdlOutline.rowsAboutToBeRemoved.connect(self.rowsAboutToBeRemoved, AUC) except TypeError: pass @@ -254,8 +260,8 @@ class editorWidget(QWidget, Ui_editorWidget_ui): def setCurrentModelIndex(self, index=None): if index.isValid(): self.currentIndex = index - self.currentID = self.mw.mdlOutline.ID(index) - # self._model = index.model() + self._model = index.model() + self.currentID = self._model.ID(index) else: self.currentIndex = QModelIndex() self.currentID = None @@ -267,7 +273,7 @@ class editorWidget(QWidget, Ui_editorWidget_ui): Index might have changed (through drag an drop), so we keep current item's ID and update index. Item might have been deleted too. """ - idx = self.mw.mdlOutline.getIndexByID(self.currentID) + idx = self._model.getIndexByID(self.currentID) # If we have an ID but the ID does not exist, it has been deleted if self.currentID and idx == QModelIndex(): diff --git a/manuskript/ui/importers/generalSettings.py b/manuskript/ui/importers/generalSettings.py index 7dd96fc0..17d7bcd6 100644 --- a/manuskript/ui/importers/generalSettings.py +++ b/manuskript/ui/importers/generalSettings.py @@ -3,7 +3,7 @@ import json import os -from PyQt5.QtCore import Qt, QSize, QSortFilterProxyModel +from PyQt5.QtCore import Qt, QSize, QSortFilterProxyModel, QModelIndex from PyQt5.QtGui import QIcon, QFontMetrics, QFont from PyQt5.QtWidgets import QWidget, QTableWidgetItem, QListWidgetItem, QTreeView @@ -30,8 +30,6 @@ class generalSettings(QWidget, Ui_generalSettings): self.treeGeneralParent.setCurrentIndex(self.getParentIndex()) self.chkGeneralParent.toggled.connect(self.treeGeneralParent.setVisible) self.treeGeneralParent.hide() - self.treeGeneralParent.selectionModel().currentChanged.connect( - lambda: print(self.treeGeneralParent.currentIndex().row())) def getParentIndex(self): """ @@ -44,3 +42,20 @@ class generalSettings(QWidget, Ui_generalSettings): idx = self.mw.treeRedacOutline.currentIndex() return idx + def importUnderID(self): + """ + Returns the ID of the item selected in treeGeneralParent, if checked. + """ + if self.chkGeneralParent.isChecked(): + idx = self.treeGeneralParent.currentIndex() + # We used a filter proxy model, so we have to map back to source + # to get an index from mdlOutline + idx = self.treeGeneralParent.model().mapToSource(idx) + if idx.isValid(): + return idx.internalPointer().ID() + + return "0" # 0 is root's ID + + def trimLongTitles(self): + return self.chkGeneralTrimTitles.isChecked() + diff --git a/manuskript/ui/importers/generalSettings_ui.py b/manuskript/ui/importers/generalSettings_ui.py index 0faed4cd..a07c172d 100644 --- a/manuskript/ui/importers/generalSettings_ui.py +++ b/manuskript/ui/importers/generalSettings_ui.py @@ -11,7 +11,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_generalSettings(object): def setupUi(self, generalSettings): generalSettings.setObjectName("generalSettings") - generalSettings.resize(289, 396) + generalSettings.resize(267, 401) self.verticalLayout_2 = QtWidgets.QVBoxLayout(generalSettings) self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) self.verticalLayout_2.setSpacing(10) @@ -28,30 +28,30 @@ class Ui_generalSettings(object): "}") self.toolBox.setObjectName("toolBox") self.general = QtWidgets.QWidget() - self.general.setGeometry(QtCore.QRect(0, 0, 289, 373)) + self.general.setGeometry(QtCore.QRect(0, 0, 267, 378)) self.general.setObjectName("general") self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.general) - self.verticalLayout_5.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_5.setContentsMargins(6, 6, 6, 6) self.verticalLayout_5.setObjectName("verticalLayout_5") self.formLayout_4 = QtWidgets.QFormLayout() self.formLayout_4.setRowWrapPolicy(QtWidgets.QFormLayout.WrapLongRows) self.formLayout_4.setObjectName("formLayout_4") - self.chkGeneralParent = QtWidgets.QCheckBox(self.general) - self.chkGeneralParent.setObjectName("chkGeneralParent") - self.formLayout_4.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.chkGeneralParent) self.chkGeneralSplitScenes = QtWidgets.QCheckBox(self.general) self.chkGeneralSplitScenes.setObjectName("chkGeneralSplitScenes") - self.formLayout_4.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.chkGeneralSplitScenes) + self.formLayout_4.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.chkGeneralSplitScenes) self.txtGeneralSplitScenes = QtWidgets.QLineEdit(self.general) self.txtGeneralSplitScenes.setObjectName("txtGeneralSplitScenes") - self.formLayout_4.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.txtGeneralSplitScenes) + self.formLayout_4.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.txtGeneralSplitScenes) + self.chkGeneralTrimTitles = QtWidgets.QCheckBox(self.general) + self.chkGeneralTrimTitles.setObjectName("chkGeneralTrimTitles") + self.formLayout_4.setWidget(3, QtWidgets.QFormLayout.SpanningRole, self.chkGeneralTrimTitles) self.treeGeneralParent = QtWidgets.QTreeView(self.general) self.treeGeneralParent.setHeaderHidden(True) self.treeGeneralParent.setObjectName("treeGeneralParent") - self.formLayout_4.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.treeGeneralParent) - self.chkGeneralTrimTitles = QtWidgets.QCheckBox(self.general) - self.chkGeneralTrimTitles.setObjectName("chkGeneralTrimTitles") - self.formLayout_4.setWidget(2, QtWidgets.QFormLayout.SpanningRole, self.chkGeneralTrimTitles) + self.formLayout_4.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.treeGeneralParent) + self.chkGeneralParent = QtWidgets.QCheckBox(self.general) + self.chkGeneralParent.setObjectName("chkGeneralParent") + self.formLayout_4.setWidget(0, QtWidgets.QFormLayout.SpanningRole, self.chkGeneralParent) self.verticalLayout_5.addLayout(self.formLayout_4) self.toolBox.addItem(self.general, "") self.verticalLayout_2.addWidget(self.toolBox) @@ -64,8 +64,8 @@ class Ui_generalSettings(object): def retranslateUi(self, generalSettings): _translate = QtCore.QCoreApplication.translate generalSettings.setWindowTitle(_translate("generalSettings", "Form")) - self.chkGeneralParent.setText(_translate("generalSettings", "Import under:")) self.chkGeneralSplitScenes.setText(_translate("generalSettings", "Split scenes at:")) self.chkGeneralTrimTitles.setText(_translate("generalSettings", "Trim long titles (> 32 chars)")) + self.chkGeneralParent.setText(_translate("generalSettings", "Import under:")) self.toolBox.setItemText(self.toolBox.indexOf(self.general), _translate("generalSettings", "General")) diff --git a/manuskript/ui/importers/generalSettings_ui.ui b/manuskript/ui/importers/generalSettings_ui.ui index 45fe6efd..4bc2c4ad 100644 --- a/manuskript/ui/importers/generalSettings_ui.ui +++ b/manuskript/ui/importers/generalSettings_ui.ui @@ -6,8 +6,8 @@ 0 0 - 289 - 396 + 267 + 401 @@ -53,47 +53,59 @@ QToolBox::tab:selected, QToolBox::tab:hover{ 0 0 - 289 - 373 + 267 + 378 General + + 6 + + + 6 + + + 6 + + + 6 + QFormLayout::WrapLongRows - - - - Import under: - - - - + Split scenes at: - + - + + + + Trim long titles (> 32 chars) + + + + true - - + + - Trim long titles (> 32 chars) + Import under: diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py index ea527c4e..721d0458 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -3,7 +3,7 @@ import json import os -from PyQt5.QtCore import Qt +from PyQt5.QtCore import Qt, QTimer from PyQt5.QtGui import QBrush, QColor, QIcon from PyQt5.QtWidgets import QWidget, QFileDialog @@ -13,6 +13,7 @@ from manuskript.ui.importers.generalSettings import generalSettings from manuskript.ui import style from manuskript import importer from manuskript.models.outlineModel import outlineModel +from manuskript.enums import Outline class importerDialog(QWidget, Ui_importer): @@ -35,6 +36,8 @@ class importerDialog(QWidget, Ui_importer): self.mw = mw self.fileName = "" self.setStyleSheet(style.mainWindowSS()) + self.tree.setStyleSheet("QTreeView{background:transparent;}") + self.editor.setStyleSheet("QWidget{background:transparent;}") # Register importFormats: self.importers = importer.importers @@ -49,7 +52,11 @@ class importerDialog(QWidget, Ui_importer): self.cmbImporters.currentTextChanged.connect(self.updateSettings) #self.setFileName("") - self.setFileName("/home/olivier/Dropbox/Documents/Travail/Geekeries/Python/PyCharmProjects/manuskript/test-projects/IMPORTS/End Plan 2.opml") + QTimer.singleShot(50, lambda: + self.cmbImporters.setCurrentText("OPML")) + QTimer.singleShot(50, lambda: + self.setFileName("/home/olivier/Dropbox/Documents/Travail/Geekeries/Python/PyCharmProjects/manuskript/test-projects/IMPORTS/End Plan 2.opml") + ) ############################################################################ # Combobox / Formats @@ -134,8 +141,13 @@ class importerDialog(QWidget, Ui_importer): F = self.currentFormat() self.settingsWidget = generalSettings() self.setGroupWidget(self.grpSettings, self.settingsWidget) + self.grpSettings.setMinimumWidth(200) #TODO: custom format widget + #toolBox = self.settingsWidget.toolBox + #w = QWidget() + #toolBox.insertItem(toolBox.count(), w, "Pandoc") + #See pandoc's abstractPlainText def setGroupWidget(self, group, widget): """ @@ -163,19 +175,40 @@ class importerDialog(QWidget, Ui_importer): # Temporary outlineModel previewModel = outlineModel(self) + previewModel.loadFromXML( + self.mw.mdlOutline.saveToXML(), + fromString=True) + + # Parent item + ID = self.settingsWidget.importUnderID() + parentItem = previewModel.getItemByID(ID) # Calling the importer in a temporary model items = F.startImport(self.fileName, - previewModel.rootItem, + parentItem, self.settingsWidget) # Do transformations - # TODO + # ------------------ + + # Trim long titles + if self.settingsWidget.trimLongTitles(): + def trim(item): + if len(item.title()) > 32: + item.setData(Outline.title.value, item.title()[:32]) + for c in item.children(): + trim(c) + for i in items: + trim(i) if items: self.tree.setModel(previewModel) for i in range(1, previewModel.columnCount()): self.tree.hideColumn(i) + self.tree.selectionModel().currentChanged.connect(self.editor.setCurrentModelIndex) + self.previewSplitter.setStretchFactor(0, 10) + self.previewSplitter.setStretchFactor(1, 40) + def startImport(self): pass diff --git a/manuskript/ui/importers/importer_ui.py b/manuskript/ui/importers/importer_ui.py index 67f49db2..d23c8e4e 100644 --- a/manuskript/ui/importers/importer_ui.py +++ b/manuskript/ui/importers/importer_ui.py @@ -11,7 +11,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_importer(object): def setupUi(self, importer): importer.setObjectName("importer") - importer.resize(694, 489) + importer.resize(867, 560) self.verticalLayout = QtWidgets.QVBoxLayout(importer) self.verticalLayout.setObjectName("verticalLayout") self.horizontalLayout = QtWidgets.QHBoxLayout() @@ -64,6 +64,7 @@ class Ui_importer(object): self.verticalLayout.addLayout(self.horizontalLayout) self.splitter = QtWidgets.QSplitter(importer) self.splitter.setOrientation(QtCore.Qt.Horizontal) + self.splitter.setChildrenCollapsible(False) self.splitter.setObjectName("splitter") self.grpSettings = QtWidgets.QGroupBox(self.splitter) self.grpSettings.setObjectName("grpSettings") @@ -77,16 +78,16 @@ class Ui_importer(object): self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) self.verticalLayout_2.setSpacing(0) self.verticalLayout_2.setObjectName("verticalLayout_2") - self.splitter_2 = QtWidgets.QSplitter(self.grpPreview) - self.splitter_2.setOrientation(QtCore.Qt.Horizontal) - self.splitter_2.setObjectName("splitter_2") - self.tree = QtWidgets.QTreeView(self.splitter_2) + self.previewSplitter = QtWidgets.QSplitter(self.grpPreview) + self.previewSplitter.setOrientation(QtCore.Qt.Horizontal) + self.previewSplitter.setObjectName("previewSplitter") + self.tree = QtWidgets.QTreeView(self.previewSplitter) self.tree.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) self.tree.setHeaderHidden(True) self.tree.setObjectName("tree") - self.editor = mainEditor(self.splitter_2) + self.editor = editorWidget(self.previewSplitter) self.editor.setObjectName("editor") - self.verticalLayout_2.addWidget(self.splitter_2) + self.verticalLayout_2.addWidget(self.previewSplitter) self.verticalLayout.addWidget(self.splitter) self.retranslateUi(importer) @@ -103,4 +104,4 @@ class Ui_importer(object): self.grpSettings.setTitle(_translate("importer", "Settings")) self.grpPreview.setTitle(_translate("importer", "Preview")) -from manuskript.ui.editors.mainEditor import mainEditor +from manuskript.ui.editors.editorWidget import editorWidget diff --git a/manuskript/ui/importers/importer_ui.ui b/manuskript/ui/importers/importer_ui.ui index 0281cd06..a7f2f907 100644 --- a/manuskript/ui/importers/importer_ui.ui +++ b/manuskript/ui/importers/importer_ui.ui @@ -6,8 +6,8 @@ 0 0 - 694 - 489 + 867 + 560 @@ -131,6 +131,9 @@ Qt::Horizontal + + false + Settings @@ -174,7 +177,7 @@ 0 - + Qt::Horizontal @@ -186,7 +189,7 @@ true - + @@ -197,9 +200,9 @@ - mainEditor + editorWidget QWidget -
manuskript.ui.editors.mainEditor.h
+
manuskript.ui.editors.editorWidget.h
1
From fb50d423484b1e8c6d5687c652398f0119a4ea2d Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 7 Nov 2017 14:40:46 +0100 Subject: [PATCH 12/23] Checkpoint: import now works. --- manuskript/importer/opmlImporter.py | 60 +------------------------ manuskript/ui/importers/importer.py | 69 ++++++++++++++++++++--------- 2 files changed, 48 insertions(+), 81 deletions(-) diff --git a/manuskript/importer/opmlImporter.py b/manuskript/importer/opmlImporter.py index aff37282..6bb372b5 100644 --- a/manuskript/importer/opmlImporter.py +++ b/manuskript/importer/opmlImporter.py @@ -45,13 +45,7 @@ class opmlImporter(abstractImporter): items.append(cls.parseItems(element, parentItem)) ret = True - if ret: - #QMessageBox.information( - #settingsWidget, - #qApp.translate("Import", "OPML Import"), - #qApp.translate("Import", "Import Complete.")) - pass - else: + if not ret: QMessageBox.critical( settingsWidget, qApp.translate("Import", "OPML Import"), @@ -61,58 +55,6 @@ class opmlImporter(abstractImporter): return items - def importOpml(opmlFilePath, idx): - """ - Import/export outline cards in OPML format. - #FIXME: delete me when done with startImport - """ - ret = False - mw = mainWindow() - - try: - with open(opmlFilePath, 'r') as opmlFile: - opmlContent = saveNewlines(opmlFile.read()) - except: - QMessageBox.critical(mw, mw.tr("OPML Import"), - mw.tr("File open failed.")) - return False - - mdl = mw.mdlOutline - - if idx.internalPointer() is not None: - parentItem = idx.internalPointer() - else: - parentItem = mdl.rootItem - - try: - parsed = ET.fromstring(bytes(opmlContent, 'utf-8')) - - opmlNode = parsed - bodyNode = opmlNode.find("body") - - if bodyNode is not None: - outlineEls = bodyNode.findall("outline") - - if outlineEls is not None: - for element in outlineEls: - parseItems(element, parentItem) - - mdl.layoutChanged.emit() - mw.treeRedacOutline.viewport().update() - ret = True - except: - pass - - # TODO: Translation - if ret: - QMessageBox.information(mw, mw.tr("OPML Import"), - mw.tr("Import Complete.")) - else: - QMessageBox.critical(mw, mw.tr("OPML Import"), - mw.tr("This does not appear to be a valid OPML file.")) - - return ret - @classmethod def parseItems(cls, underElement, parentItem=None): title = underElement.get('text') diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py index 721d0458..5984fa40 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -5,7 +5,7 @@ import os from PyQt5.QtCore import Qt, QTimer from PyQt5.QtGui import QBrush, QColor, QIcon -from PyQt5.QtWidgets import QWidget, QFileDialog +from PyQt5.QtWidgets import QWidget, QFileDialog, QMessageBox from manuskript.functions import lightBlue, writablePath, appPath from manuskript.ui.importers.importer_ui import Ui_importer @@ -49,6 +49,7 @@ class importerDialog(QWidget, Ui_importer): self.btnChoseFile.clicked.connect(self.selectFile) self.btnClearFileName.clicked.connect(self.setFileName) self.btnPreview.clicked.connect(self.preview) + self.btnImport.clicked.connect(self.doImport) self.cmbImporters.currentTextChanged.connect(self.updateSettings) #self.setFileName("") @@ -170,26 +171,63 @@ class importerDialog(QWidget, Ui_importer): def preview(self): - # We find the current selected format - F = self.currentFormat() - - # Temporary outlineModel + # Creating a temporary outlineModel previewModel = outlineModel(self) previewModel.loadFromXML( self.mw.mdlOutline.saveToXML(), fromString=True) + # Inserting elements + result = self.startImport(previewModel) + + if result: + self.tree.setModel(previewModel) + for i in range(1, previewModel.columnCount()): + self.tree.hideColumn(i) + self.tree.selectionModel().currentChanged.connect(self.editor.setCurrentModelIndex) + self.previewSplitter.setStretchFactor(0, 10) + self.previewSplitter.setStretchFactor(1, 40) + + def doImport(self): + """ + Called by the Import button. + """ + self.startImport(self.mw.mdlOutline) + + QMessageBox.information(self, self.tr("Import status"), + self.tr("Import Complete.")) + + self.close() + + def startImport(self, outlineModel): + """ + Where most of the magic happens. + Is used by preview and by doImport (actual import). + + `outlineModel` is the model where the imported items are added. + """ + + # We find the current selected format + F = self.currentFormat() + # Parent item ID = self.settingsWidget.importUnderID() - parentItem = previewModel.getItemByID(ID) + parentItem = outlineModel.getItemByID(ID) - # Calling the importer in a temporary model + # Calling the importer items = F.startImport(self.fileName, parentItem, self.settingsWidget) # Do transformations - # ------------------ + items = self.doTransformations(items) + + return True + + def doTransformations(self, items): + """ + Do general transformations. + """ # Trim long titles if self.settingsWidget.trimLongTitles(): @@ -201,19 +239,6 @@ class importerDialog(QWidget, Ui_importer): for i in items: trim(i) - if items: - self.tree.setModel(previewModel) - for i in range(1, previewModel.columnCount()): - self.tree.hideColumn(i) - self.tree.selectionModel().currentChanged.connect(self.editor.setCurrentModelIndex) - self.previewSplitter.setStretchFactor(0, 10) - self.previewSplitter.setStretchFactor(1, 40) - - - def startImport(self): - pass - - # Note: dont forget to emit: mdl.layoutChanged.emit() - # Maybe: mw.treeRedacOutline.viewport().update() + return items From a29eddabeaf34b9def74eaee807ddad2e79eb108 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 7 Nov 2017 15:33:18 +0100 Subject: [PATCH 13/23] Adds: OPML import with preview (using @camstevenson's importer) #200 --- manuskript/importer/__init__.py | 4 ++-- manuskript/importer/markdownImporter.py | 2 +- manuskript/importer/opmlImporter.py | 12 ++++++----- manuskript/ui/importers/generalSettings_ui.py | 2 ++ manuskript/ui/importers/generalSettings_ui.ui | 9 ++++++++- manuskript/ui/importers/importer.py | 20 ++++++++++--------- 6 files changed, 31 insertions(+), 18 deletions(-) diff --git a/manuskript/importer/__init__.py b/manuskript/importer/__init__.py index d2f0bc1a..8ca62308 100644 --- a/manuskript/importer/__init__.py +++ b/manuskript/importer/__init__.py @@ -6,7 +6,7 @@ from manuskript.importer.markdownImporter import markdownImporter from manuskript.importer.opmlImporter import opmlImporter importers = [ - markdownImporter, + #markdownImporter, opmlImporter, - folderImporter, + #folderImporter, ] diff --git a/manuskript/importer/markdownImporter.py b/manuskript/importer/markdownImporter.py index 0a3a5cd2..2567f30d 100644 --- a/manuskript/importer/markdownImporter.py +++ b/manuskript/importer/markdownImporter.py @@ -8,5 +8,5 @@ class markdownImporter(abstractImporter): name = "Markdown" description = "" - fileFormat = "Markdown files (*.md; *.txt; *)" + fileFormat = "Markdown files (*.md *.txt *)" icon = "text-x-markdown" diff --git a/manuskript/importer/opmlImporter.py b/manuskript/importer/opmlImporter.py index 6bb372b5..e6474f5e 100644 --- a/manuskript/importer/opmlImporter.py +++ b/manuskript/importer/opmlImporter.py @@ -12,7 +12,7 @@ class opmlImporter(abstractImporter): name = "OPML" description = "" - fileFormat = "OPML Files (*.opml)" + fileFormat = "OPML Files (*.opml *.xml)" icon = "text-x-opml+xml" @classmethod @@ -23,15 +23,16 @@ class opmlImporter(abstractImporter): ret = False try: - with open(filePath, 'r') as opmlFile: - opmlContent = cls.saveNewlines(opmlFile.read()) + with open(filePath, 'rb') as opmlFile: + #opmlContent = cls.saveNewlines(opmlFile.read()) + opmlContent = opmlFile.read() except: QMessageBox.critical(settingsWidget, qApp.translate("Import", "OPML Import"), qApp.translate("Import", "File open failed.")) return None - parsed = ET.fromstring(bytes(opmlContent, 'utf-8')) + parsed = ET.fromstring(opmlContent) opmlNode = parsed bodyNode = opmlNode.find("body") @@ -65,7 +66,8 @@ class opmlImporter(abstractImporter): body = "" note = underElement.get('_note') if note is not None and not cls.isWhitespaceOnly(note): - body = cls.restoreNewLines(note) + #body = cls.restoreNewLines(note) + body = note children = underElement.findall('outline') if children is not None and len(children) > 0: diff --git a/manuskript/ui/importers/generalSettings_ui.py b/manuskript/ui/importers/generalSettings_ui.py index a07c172d..1843ee0e 100644 --- a/manuskript/ui/importers/generalSettings_ui.py +++ b/manuskript/ui/importers/generalSettings_ui.py @@ -37,9 +37,11 @@ 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(2, QtWidgets.QFormLayout.LabelRole, self.chkGeneralSplitScenes) self.txtGeneralSplitScenes = QtWidgets.QLineEdit(self.general) + self.txtGeneralSplitScenes.setEnabled(False) self.txtGeneralSplitScenes.setObjectName("txtGeneralSplitScenes") self.formLayout_4.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.txtGeneralSplitScenes) self.chkGeneralTrimTitles = QtWidgets.QCheckBox(self.general) diff --git a/manuskript/ui/importers/generalSettings_ui.ui b/manuskript/ui/importers/generalSettings_ui.ui index 4bc2c4ad..21c4f95e 100644 --- a/manuskript/ui/importers/generalSettings_ui.ui +++ b/manuskript/ui/importers/generalSettings_ui.ui @@ -80,13 +80,20 @@ QToolBox::tab:selected, QToolBox::tab:hover{ + + false + Split scenes at: - + + + false + + diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py index 5984fa40..915149a7 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -25,6 +25,7 @@ class importerDialog(QWidget, Ui_importer): ".rst": "text-plain", ".tex": "text-x-tex", ".opml": "text-x-opml+xml", + ".xml": "text-x-opml+xml", ".html": "text-html", } @@ -38,6 +39,7 @@ class importerDialog(QWidget, Ui_importer): self.setStyleSheet(style.mainWindowSS()) self.tree.setStyleSheet("QTreeView{background:transparent;}") self.editor.setStyleSheet("QWidget{background:transparent;}") + self.editor.toggleSpellcheck(False) # Register importFormats: self.importers = importer.importers @@ -52,12 +54,8 @@ class importerDialog(QWidget, Ui_importer): self.btnImport.clicked.connect(self.doImport) self.cmbImporters.currentTextChanged.connect(self.updateSettings) - #self.setFileName("") - QTimer.singleShot(50, lambda: - self.cmbImporters.setCurrentText("OPML")) - QTimer.singleShot(50, lambda: - self.setFileName("/home/olivier/Dropbox/Documents/Travail/Geekeries/Python/PyCharmProjects/manuskript/test-projects/IMPORTS/End Plan 2.opml") - ) + self.setFileName("") + self.updateSettings() ############################################################################ # Combobox / Formats @@ -108,14 +106,18 @@ class importerDialog(QWidget, Ui_importer): self.lblFileName.setText(os.path.basename(fileName)) self.lblFileName.setToolTip(fileName) ext = os.path.splitext(fileName)[1] + icon = None if ext and ext in self.formatsIcon: icon = QIcon.fromTheme(self.formatsIcon[ext]) elif os.path.isdir(fileName): icon = QIcon.fromTheme("folder") - #self.lblIcon.setVisible(True) - h = self.lblFileName.height() - self.lblIcon.setPixmap(icon.pixmap(h, h)) + if icon: + self.lblIcon.setVisible(True) + h = self.lblFileName.height() + self.lblIcon.setPixmap(icon.pixmap(h, h)) + else: + self.lblIcon.hide() else: self.fileName = None From 9eb1402613bf70a4d3e8132ad424c0fdcd80efe6 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 7 Nov 2017 16:22:59 +0100 Subject: [PATCH 14/23] Fixes two small bugs #200. --- manuskript/ui/importers/importer.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py index 915149a7..f98c66f9 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -196,8 +196,14 @@ class importerDialog(QWidget, Ui_importer): """ self.startImport(self.mw.mdlOutline) - QMessageBox.information(self, self.tr("Import status"), - self.tr("Import Complete.")) + # Signal every views that important model changes have happened. + self.mw.mdlOutline.layoutChanged.emit() + + # I'm getting seg fault over this message sometimes... + # Using status bar message instead... + #QMessageBox.information(self, self.tr("Import status"), + #self.tr("Import Complete.")) + self.mw.statusBar().showMessage("Import complete!", 5000) self.close() From ccf33b3ccfeaef84f02ca173c759b31624230df5 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 7 Nov 2017 20:30:39 +0100 Subject: [PATCH 15/23] Adds: import from many formats using pandoc. #200 --- manuskript/exporter/basic.py | 2 +- manuskript/importer/__init__.py | 17 ++++- manuskript/importer/abstractImporter.py | 7 +- manuskript/importer/opmlImporter.py | 32 ++++++--- manuskript/importer/pandocImporters.py | 91 +++++++++++++++++++++++++ manuskript/ui/importers/importer.py | 27 +++++++- 6 files changed, 160 insertions(+), 16 deletions(-) create mode 100644 manuskript/importer/pandocImporters.py diff --git a/manuskript/exporter/basic.py b/manuskript/exporter/basic.py index 5acbe9e0..bc1c1fc0 100644 --- a/manuskript/exporter/basic.py +++ b/manuskript/exporter/basic.py @@ -129,7 +129,7 @@ class basicFormat: @classmethod def isValid(cls): return True - + @classmethod def projectPath(cls): return os.path.dirname(os.path.abspath(mainWindow().currentProject)) diff --git a/manuskript/importer/__init__.py b/manuskript/importer/__init__.py index 8ca62308..3edfeba9 100644 --- a/manuskript/importer/__init__.py +++ b/manuskript/importer/__init__.py @@ -4,9 +4,22 @@ from manuskript.importer.folderImporter import folderImporter from manuskript.importer.markdownImporter import markdownImporter from manuskript.importer.opmlImporter import opmlImporter +from manuskript.importer.pandocImporters import markdownPandocImporter, \ + odtPandocImporter, ePubPandocImporter, docXPandocImporter, HTMLPandocImporter, \ + rstPandocImporter, LaTeXPandocImporter importers = [ - #markdownImporter, + # Internal opmlImporter, - #folderImporter, + folderImporter, + markdownImporter, + + # Pandoc + markdownPandocImporter, + odtPandocImporter, + ePubPandocImporter, + docXPandocImporter, + HTMLPandocImporter, + rstPandocImporter, + LaTeXPandocImporter, ] diff --git a/manuskript/importer/abstractImporter.py b/manuskript/importer/abstractImporter.py index 7c75a681..6f146e74 100644 --- a/manuskript/importer/abstractImporter.py +++ b/manuskript/importer/abstractImporter.py @@ -22,7 +22,8 @@ class abstractImporter: # For folder, use "<>" icon = "" - def startImport(self, filePath, settingsWidget): + @classmethod + def startImport(cls, filePath, parentItem, settingsWidget): """ Takes a str path to the file/folder to import, and the settingsWidget returnend by `self.settingsWidget()` containing the user set settings, @@ -37,4 +38,8 @@ class abstractImporter: """ return None + @classmethod + def isValid(cls): + return False + diff --git a/manuskript/importer/opmlImporter.py b/manuskript/importer/opmlImporter.py index e6474f5e..2fce7bc4 100644 --- a/manuskript/importer/opmlImporter.py +++ b/manuskript/importer/opmlImporter.py @@ -16,22 +16,36 @@ class opmlImporter(abstractImporter): icon = "text-x-opml+xml" @classmethod - def startImport(cls, filePath, parentItem, settingsWidget): + def isValid(cls): + return True + + @classmethod + def startImport(cls, filePath, parentItem, settingsWidget, fromString=None): """ Import/export outline cards in OPML format. """ ret = False - try: - with open(filePath, 'rb') as opmlFile: - #opmlContent = cls.saveNewlines(opmlFile.read()) - opmlContent = opmlFile.read() - except: - QMessageBox.critical(settingsWidget, - qApp.translate("Import", "OPML Import"), - qApp.translate("Import", "File open failed.")) + if filePath != "": + # We have a filePath, so we read the file + try: + with open(filePath, 'rb') as opmlFile: + #opmlContent = cls.saveNewlines(opmlFile.read()) + opmlContent = opmlFile.read() + except: + QMessageBox.critical(settingsWidget, + qApp.translate("Import", "OPML Import"), + qApp.translate("Import", "File open failed.")) + return None + + elif fromString == "": + # We have neither filePath nor fromString, so we leave return None + else: + # We load from string + opmlContent = bytes(fromString, "utf-8") + parsed = ET.fromstring(opmlContent) opmlNode = parsed diff --git a/manuskript/importer/pandocImporters.py b/manuskript/importer/pandocImporters.py new file mode 100644 index 00000000..c23eefc2 --- /dev/null +++ b/manuskript/importer/pandocImporters.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +from manuskript.importer.abstractImporter import abstractImporter +from manuskript.exporter.pandoc import pandocExporter +from manuskript.importer.opmlImporter import opmlImporter + +class pandocImporter(abstractImporter): + + formatFrom = "" + + @classmethod + def isValid(cls): + return pandocExporter().isValid() + + @classmethod + def startImport(cls, filePath, parentItem, settingsWidget): + + # pandoc --from=markdown filename --to=opml --standalone + args = [ + "--from={}".format(cls.formatFrom), + filePath, + "--to=opml", + "--standalone" + ] + + r = pandocExporter().run(args) + + return opmlImporter.startImport("", parentItem, + settingsWidget, fromString=r) + + +class markdownPandocImporter(pandocImporter): + + name = "Markdown (pandoc)" + description = "Markdown, using pandoc" + fileFormat = "Markdown files (*.md *.txt *)" + icon = "text-x-markdown" + formatFrom = "markdown" + +class ePubPandocImporter(pandocImporter): + + name = "ePub (pandoc)" + description = "" + fileFormat = "ePub files (*.epub)" + icon = "application-epub+zip" + formatFrom = "epub" + +class docXPandocImporter(pandocImporter): + + name = "DocX (pandoc)" + description = "" + fileFormat = "DocX files (*.docx)" + icon = "application-vnd.openxmlformats-officedocument.wordprocessingml.document" + formatFrom = "docx" + +class odtPandocImporter(pandocImporter): + + name = "ODT (pandoc)" + description = "" + fileFormat = "Open Document files (*.odt)" + icon = "application-vnd.oasis.opendocument.text" + formatFrom = "odt" + +class rstPandocImporter(pandocImporter): + + name = "reStructuredText (pandoc)" + description = "" + fileFormat = "reStructuredText files (*.rst)" + icon = "text-plain" + formatFrom = "rst" + +class HTMLPandocImporter(pandocImporter): + + name = "HTML (pandoc)" + description = "" + fileFormat = "HTML files (*.htm *.html)" + icon = "text-html" + formatFrom = "html" + +class LaTeXPandocImporter(pandocImporter): + + name = "LaTeX (pandoc)" + description = "" + fileFormat = "LaTeX files (*.tex)" + icon = "text-x-tex" + formatFrom = "latex" + + + + diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py index f98c66f9..c8ecf785 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -3,9 +3,9 @@ import json import os -from PyQt5.QtCore import Qt, QTimer -from PyQt5.QtGui import QBrush, QColor, QIcon -from PyQt5.QtWidgets import QWidget, QFileDialog, QMessageBox +from PyQt5.QtCore import Qt, QTimer, QUrl +from PyQt5.QtGui import QBrush, QColor, QIcon, QDesktopServices +from PyQt5.QtWidgets import QWidget, QFileDialog, QMessageBox, QStyle from manuskript.functions import lightBlue, writablePath, appPath from manuskript.ui.importers.importer_ui import Ui_importer @@ -14,6 +14,7 @@ from manuskript.ui import style from manuskript import importer from manuskript.models.outlineModel import outlineModel from manuskript.enums import Outline +from manuskript.exporter.pandoc import pandocExporter class importerDialog(QWidget, Ui_importer): @@ -68,6 +69,15 @@ class importerDialog(QWidget, Ui_importer): for f in self.importers: addFormat(f.name, f.icon) + if not f.isValid(): + item = self.cmbImporters.model().item(self.cmbImporters.count() - 1) + item.setFlags(Qt.NoItemFlags) + + if not pandocExporter().isValid(): + self.cmbImporters.addItem( + self.style().standardIcon(QStyle.SP_MessageBoxWarning), + "Install pandoc to import from much more formats", + "::URL::http://pandoc.org/installing.html") def currentFormat(self): formatName = self.cmbImporters.currentText() @@ -141,7 +151,15 @@ class importerDialog(QWidget, Ui_importer): settings widget using the current format provided settings widget. """ + # We check if we have to open an URL + data = self.cmbImporters.currentData() + if data and data[:7] == "::URL::" and data[7:]: + # FIXME: use functions.openURL after merge with feature/Exporters + QDesktopServices.openUrl(QUrl(data[7:])) + return + F = self.currentFormat() + self.settingsWidget = generalSettings() self.setGroupWidget(self.grpSettings, self.settingsWidget) self.grpSettings.setMinimumWidth(200) @@ -152,6 +170,9 @@ class importerDialog(QWidget, Ui_importer): #toolBox.insertItem(toolBox.count(), w, "Pandoc") #See pandoc's abstractPlainText + # Clear file name + self.setFileName("") + def setGroupWidget(self, group, widget): """ Sets the given widget as main widget for QGroupBox group. From c6391e976cdcfdd1864e407e0559ebad077f1b18 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 8 Nov 2017 21:35:26 +0100 Subject: [PATCH 16/23] Adds: ability to import from folder structure. #200 --- manuskript/importer/abstractImporter.py | 171 +++++++++++++++++++-- manuskript/importer/folderImporter.py | 123 +++++++++++++++ manuskript/models/outlineModel.py | 5 +- manuskript/ui/importers/generalSettings.py | 3 + manuskript/ui/importers/importer.py | 23 ++- 5 files changed, 307 insertions(+), 18 deletions(-) diff --git a/manuskript/importer/abstractImporter.py b/manuskript/importer/abstractImporter.py index 6f146e74..0e315f74 100644 --- a/manuskript/importer/abstractImporter.py +++ b/manuskript/importer/abstractImporter.py @@ -5,7 +5,10 @@ import shutil import subprocess from PyQt5.QtCore import QSettings -from PyQt5.QtWidgets import QWidget +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QCheckBox, QHBoxLayout, \ + QLabel, QSpinBox, QComboBox, QLineEdit +from manuskript.ui.collapsibleGroupBox2 import collapsibleGroupBox2 +from manuskript.ui import style class abstractImporter: @@ -22,8 +25,10 @@ class abstractImporter: # For folder, use "<>" icon = "" - @classmethod - def startImport(cls, filePath, parentItem, settingsWidget): + def __init__(self): + self.settings = {} + + def startImport(self, filePath, parentItem, settingsWidget): """ Takes a str path to the file/folder to import, and the settingsWidget returnend by `self.settingsWidget()` containing the user set settings, @@ -31,15 +36,161 @@ class abstractImporter: """ pass - @classmethod - def settingsWidget(cls): - """ - Returns a QWidget if needed for settings. - """ - return None - @classmethod def isValid(cls): return False + def settingsWidget(self, widget): + """ + Takes a QWidget that can be modified and must be returned. + """ + return widget + + def addPage(self, widget, title): + """ + Convenience function to add a page to the settingsWidget `widget`, at + the end. + + Returns the page widget. + """ + w = QWidget(widget) + w.setLayout(QVBoxLayout()) + widget.toolBox.insertItem(widget.toolBox.count(), w, title) + widget.toolBox.layout().setSpacing(0) + return w + + def addGroup(self, parent, title): + """ + Adds a collapsible group to the given widget. + """ + g = collapsibleGroupBox2(title=title) + parent.layout().addWidget(g) + g.setLayout(QVBoxLayout()) + return g + + def addSetting(self, name, type, label, widget=None, default=None, + tooltip=None, min=None, max=None, vals=None, suffix=""): + + self.settings[name] = self.setting(name, type, label, widget, default, + tooltip, min, max, vals, suffix) + + def widget(self, name): + if name in self.settings: + return self.settings[name].widget() + + def getSetting(self, name): + if name in self.settings: + return self.settings[name] + + + class setting: + """ + A class used to store setting, and display a widget for the user to + modify it. + """ + def __init__(self, name, type, label, widget=None, default=None, + tooltip=None, min=None, max=None, vals=None, suffix=""): + self.name = name + self.type = type + self.label = label + self._widget = widget + self.default = default + self.min = min + self.max = max + self.vals = vals.split("|") if vals else [] + self.suffix = suffix + self.tooltip = tooltip + + def widget(self, parent=None): + """ + Returns the widget used, or creates it if not done yet. If parent + is given, widget is inserted in parent's layout. + """ + if self._widget: + return self._widget + + else: + + if "checkbox" in self.type: + self._widget = QCheckBox(self.label) + if self.default: + self._widget.setChecked(True) + if parent: + parent.layout().addWidget(self._widget) + + elif "number" in self.type: + l = QHBoxLayout() + label = QLabel(self.label, parent) + label.setWordWrap(True) + l.addWidget(label, 8) + self._widget = QSpinBox() + self._widget.setValue(self.default if self.default else 0) + if self.min: + self._widget.setMinimum(self.min) + if self.max: + self._widget.setMaximum(self.max) + if self.suffix: + self._widget.setSuffix(self.suffix) + l.addWidget(self._widget, 2) + if parent: + parent.layout().addLayout(l) + + elif "combo" in self.type: + l = QHBoxLayout() + label = QLabel(self.label, parent) + label.setWordWrap(True) + l.addWidget(label, 6) + self._widget = QComboBox() + self._widget.addItems(self.vals) + l.addWidget(self._widget, 2) + if parent: + parent.layout().addLayout(l) + + elif "text" in self.type: + l = QHBoxLayout() + label = QLabel(self.label, parent) + label.setWordWrap(True) + l.addWidget(label, 5) + self._widget = QLineEdit() + self._widget.setStyleSheet(style.lineEditSS()) + if self.default: + self._widget.setText(self.default) + l.addWidget(self._widget, 3) + if parent: + parent.layout().addLayout(l) + + elif "label" in self.type: + self._widget = QLabel(self.label, parent) + self._widget.setWordWrap(True) + if parent: + parent.layout().addWidget(self._widget) + + if self.tooltip: + self._widget.setToolTip(self.tooltip) + + return self._widget + + def value(self): + """ + Return the value contained in the widget. + """ + if not self._widget: + return self.default + + else: + + if "checkbox" in self.type: + return self._widget.isChecked() + + elif "number" in self.type: + return self._widget.value() + + elif "combo" in self.type: + return self._widget.currentText() + + elif "text" in self.type: + return self._widget.text() + + + diff --git a/manuskript/importer/folderImporter.py b/manuskript/importer/folderImporter.py index a9618880..a06195b4 100644 --- a/manuskript/importer/folderImporter.py +++ b/manuskript/importer/folderImporter.py @@ -1,7 +1,11 @@ #!/usr/bin/env python # --!-- coding: utf8 --!-- +import os from manuskript.importer.abstractImporter import abstractImporter +from manuskript.models.outlineModel import outlineItem +from manuskript.enums import Outline +from PyQt5.QtWidgets import qApp class folderImporter(abstractImporter): @@ -10,3 +14,122 @@ class folderImporter(abstractImporter): description = "" fileFormat = "<>" icon = "folder" + + @classmethod + def isValid(cls): + return True + + def startImport(self, filePath, parentItem, settingsWidget, fromString=None): + """ + Imports from a folder. + """ + ext = self.getSetting("ext").value() + ext = [e.strip().replace("*", "") for e in ext.split(",")] + + sorting = self.getSetting("sortItems").value() + + items = [] + stack = {} + + if self.getSetting("topLevelFolder").value(): + parent = outlineItem(title=os.path.basename(filePath), + parent=parentItem) + items.append(parent) + stack[filePath] = parent + + for dirpath, dirnames, filenames in os.walk(filePath): + + if dirpath in stack: + item = stack[dirpath] + else: + # It's the parent folder, and we are not including it + # so every item is attached to parentItem + item = parentItem + + def addFile(f): + fName, fExt = os.path.splitext(f) + if fExt in ext: + child = outlineItem(title=fName, _type="md", parent=item) + with open(os.path.join(dirpath, f), "r") as fr: + child._data[Outline.text] = fr.read() + items.append(child) + + def addFolder(d): + child = outlineItem(title=d, parent=item) + items.append(child) + stack[os.path.join(dirpath, d)] = child + + if not self.getSetting("separateFolderFiles").value(): + # Import folder and files together (only makes differences if + # they are sorted, really) + allFiles = dirnames + filenames + if sorting: + allFiles = sorted(allFiles) + + for f in allFiles: + if f in dirnames: + addFolder(f) + else: + addFile(f) + + else: + # Import first folders, then files + if sorting: + dirnames = sorted(dirnames) + filenames = sorted(filenames) + + # Import folders + for d in dirnames: + addFolder(d) + + # Import files + for f in filenames: + addFile(f) + + return items + + + def settingsWidget(self, widget): + """ + Takes a QWidget that can be modified and must be returned. + """ + + # Add group + group = self.addGroup(widget.toolBox.widget(0), + qApp.translate("Import", "Folder import")) + #group = cls.addPage(widget, "Folder import") + + self.addSetting("info", "label", + qApp.translate("Import", """Info: Imports a whole + directory structure. Folders are added as folders, and + plaintext documents within (you chose which ones by extension) + are added as scene.
 """)) + + self.addSetting("ext", "text", + qApp.translate("Import", "Include only those extensions:"), + default="*.txt, *.md", + tooltip=qApp.translate("Import", "Coma separated values")), + + self.addSetting("topLevelFolder", "checkbox", + qApp.translate("Import", "Include top-level folder"), + default=False), + + self.addSetting("sortItems", "checkbox", + qApp.translate("Import", "Sort items by name"), + default=True), + + self.addSetting("separateFolderFiles", "checkbox", + qApp.translate("Import", "Import folder then files"), + default=True), + + + + + for s in self.settings: + self.settings[s].widget(group) + + return widget + + + + diff --git a/manuskript/models/outlineModel.py b/manuskript/models/outlineModel.py index 9b4f766f..1391ba33 100644 --- a/manuskript/models/outlineModel.py +++ b/manuskript/models/outlineModel.py @@ -895,7 +895,7 @@ class outlineItem(): self.IDs = self.listAllIDs() if max([self.IDs.count(i) for i in self.IDs if i]) != 1: - print("There are some doublons:", [i for i in self.IDs if i and self.IDs.count(i) != 1]) + print("WARNING ! There are some items with same IDs:", [i for i in self.IDs if i and self.IDs.count(i) != 1]) def checkChildren(item): for c in item.children(): @@ -913,8 +913,9 @@ class outlineItem(): return IDs def findUniqueID(self): + IDs = [int(i) for i in self.IDs] k = 0 - while str(k) in self.IDs: + while k in self.IDs: k += 1 self.IDs.append(str(k)) return str(k) diff --git a/manuskript/ui/importers/generalSettings.py b/manuskript/ui/importers/generalSettings.py index 17d7bcd6..e7dc43e5 100644 --- a/manuskript/ui/importers/generalSettings.py +++ b/manuskript/ui/importers/generalSettings.py @@ -10,6 +10,8 @@ from PyQt5.QtWidgets import QWidget, QTableWidgetItem, QListWidgetItem, QTreeVie from manuskript.functions import mainWindow, writablePath from manuskript.ui.importers.generalSettings_ui import Ui_generalSettings from manuskript.enums import Outline +from manuskript.ui import style + class generalSettings(QWidget, Ui_generalSettings): def __init__(self, parent=None): @@ -17,6 +19,7 @@ class generalSettings(QWidget, Ui_generalSettings): self.setupUi(self) self.mw = mainWindow() + self.txtGeneralSplitScenes.setStyleSheet(style.lineEditSS()) # TreeView to select parent # We use a proxy to display only folders diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py index c8ecf785..6314ac66 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -82,7 +82,8 @@ class importerDialog(QWidget, Ui_importer): def currentFormat(self): formatName = self.cmbImporters.currentText() F = [F for F in self.importers if F.name == formatName][0] - return F + # We instantiate the class + return F() ############################################################################ # Import file @@ -94,7 +95,7 @@ class importerDialog(QWidget, Ui_importer): """ # We find the current selected format - F = self.currentFormat() + F = self._format options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog @@ -159,17 +160,21 @@ class importerDialog(QWidget, Ui_importer): return F = self.currentFormat() + self._format = F self.settingsWidget = generalSettings() - self.setGroupWidget(self.grpSettings, self.settingsWidget) - self.grpSettings.setMinimumWidth(200) - #TODO: custom format widget + self.settingsWidget = F.settingsWidget(self.settingsWidget) + #toolBox = self.settingsWidget.toolBox #w = QWidget() #toolBox.insertItem(toolBox.count(), w, "Pandoc") #See pandoc's abstractPlainText + # Set the settings widget in place + self.setGroupWidget(self.grpSettings, self.settingsWidget) + self.grpSettings.setMinimumWidth(200) + # Clear file name self.setFileName("") @@ -234,10 +239,14 @@ class importerDialog(QWidget, Ui_importer): Is used by preview and by doImport (actual import). `outlineModel` is the model where the imported items are added. + + FIXME: Optimisation: when adding many outlineItems, outlineItem.updateWordCount + is a bottleneck. It gets called a crazy number of time, and its not + necessary. """ # We find the current selected format - F = self.currentFormat() + F = self._format # Parent item ID = self.settingsWidget.importUnderID() @@ -263,6 +272,8 @@ class importerDialog(QWidget, Ui_importer): def trim(item): 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: From bc5c53fe6d6a517d9bccc6de1afd8034f99ade80 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 8 Nov 2017 22:54:31 +0100 Subject: [PATCH 17/23] Adds: internal markdown importer. #200 --- manuskript/importer/folderImporter.py | 4 - manuskript/importer/markdownImporter.py | 146 ++++++++++++++++++++++++ 2 files changed, 146 insertions(+), 4 deletions(-) diff --git a/manuskript/importer/folderImporter.py b/manuskript/importer/folderImporter.py index a06195b4..a8e3f25f 100644 --- a/manuskript/importer/folderImporter.py +++ b/manuskript/importer/folderImporter.py @@ -88,7 +88,6 @@ class folderImporter(abstractImporter): return items - def settingsWidget(self, widget): """ Takes a QWidget that can be modified and must be returned. @@ -122,9 +121,6 @@ class folderImporter(abstractImporter): qApp.translate("Import", "Import folder then files"), default=True), - - - for s in self.settings: self.settings[s].widget(group) diff --git a/manuskript/importer/markdownImporter.py b/manuskript/importer/markdownImporter.py index 2567f30d..ca4e2462 100644 --- a/manuskript/importer/markdownImporter.py +++ b/manuskript/importer/markdownImporter.py @@ -2,6 +2,11 @@ # --!-- coding: utf8 --!-- from manuskript.importer.abstractImporter import abstractImporter +from manuskript.importer.abstractImporter import abstractImporter +from manuskript.models.outlineModel import outlineItem +from manuskript.enums import Outline +from PyQt5.QtWidgets import qApp +import re, os class markdownImporter(abstractImporter): @@ -10,3 +15,144 @@ class markdownImporter(abstractImporter): description = "" fileFormat = "Markdown files (*.md *.txt *)" icon = "text-x-markdown" + + @classmethod + def isValid(cls): + return True + + def startImport(self, filePath, parentItem, settingsWidget, fromString=None): + """ + Very simple import from markdown. We just look at ATX headers (we + ignore setext for the sake of simplicity, for now.) + + **A difficulty:** in the following example, we can do things with + markdown headers (like go from level 1 to level 4 and back to level 2) + that we cannot do in an outline. + + ``` + # Level 1 + # Level 1 + ## Level 2 + ### Level 3 + #### Level 4 + ##### Level 5 + ### Level 3 + # Level 1 + #### Level 4? → Level 2 + ### Level 3? → Level 2 + ## Level 2 → Level 2 + #### Level 4? → Level 3 + ``` + + I think the current version of the imported manages that quite well. + + **A question:** In the following sample, the first Level 1 becomes a + text element, because it has no other sub elements. But the content of + second Level 1 becomes a text element, with no name. What name should + we give it? + + ``` + # Level 1 + Some texte content. + Level 1 will become a text element. + # Level 1 + This content has no name. + ## Level 2 + ... + ``` + """ + + # Read file + with open(filePath, "r") as f: + txt = f.read() + + items = [] + + parent = parentItem + lastLevel = 0 + content = "" + + def saveContent(content, parent): + if content.strip(): + child = outlineItem(title=parent.title(), parent=parent, _type="md") + child._data[Outline.text] = content + items.append(child) + return "" + + def addTitle(name, parent, level): + child = outlineItem(title=name, parent=parent) + child.__miLevel = level + items.append(child) + return child + + header = re.compile(r"(\#+)\s*(.+?)\s*\#*$") + + # Import in top level folder? + if self.getSetting("topLevelFolder").value(): + parent = addTitle(os.path.basename(filePath), parentItem, 0) + + # We store the level of each item in a temporary var + parent.__miLevel = 0 # markdown importer header level + + for l in txt.split("\n"): + m = header.match(l) + if m: + # Header ! + level = len(m.group(1)) + name = m.group(2) + + # save content + content = saveContent(content, parent) + + # get parent level + while parent.__miLevel >= level: + parent = parent.parent() + + # create title + child = addTitle(name, parent, level) + child.__miLevel = level + + # title becomes the new parent + parent = child + + lastLevel = level + + else: + content += l + "\n" + + saveContent(content, parent) + + # Clean up + for i in items: + if i.childCount() == 1 and i.children()[0].isText(): + # We have a folder with only one text item + # So we make it a text item + i._data[Outline.type] = "md" + i._data[Outline.text] = i.children()[0].text() + i.removeChild(0) + + return items + + def settingsWidget(self, widget): + """ + Takes a QWidget that can be modified and must be returned. + """ + + # Add group + group = self.addGroup(widget.toolBox.widget(0), + qApp.translate("Import", "Markdown import")) + #group = cls.addPage(widget, "Folder import") + + self.addSetting("info", "label", + qApp.translate("Import", """WARNING: Current + importer only knows ATX-header (# Header), and + not setext headers (underlined with ========).
 """)) + + self.addSetting("topLevelFolder", "checkbox", + qApp.translate("Import", "Import in a top-level folder."), + default=False), + + for s in self.settings: + self.settings[s].widget(group) + + return widget From 34b55b511c545fb30c418be106600dfd4adbfb6d Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 8 Nov 2017 23:20:13 +0100 Subject: [PATCH 18/23] Adds: support of setext-style headers in markdown import. #200 --- manuskript/importer/markdownImporter.py | 47 +++++++++++++++++++++---- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/manuskript/importer/markdownImporter.py b/manuskript/importer/markdownImporter.py index ca4e2462..69df32cc 100644 --- a/manuskript/importer/markdownImporter.py +++ b/manuskript/importer/markdownImporter.py @@ -85,7 +85,9 @@ class markdownImporter(abstractImporter): items.append(child) return child - header = re.compile(r"(\#+)\s*(.+?)\s*\#*$") + ATXHeader = re.compile(r"(\#+)\s*(.+?)\s*\#*$") + setextHeader1 = re.compile(r"(.+)\n===+$", re.MULTILINE) + setextHeader2 = re.compile(r"(.+)\n---+$", re.MULTILINE) # Import in top level folder? if self.getSetting("topLevelFolder").value(): @@ -94,13 +96,44 @@ class markdownImporter(abstractImporter): # We store the level of each item in a temporary var parent.__miLevel = 0 # markdown importer header level - for l in txt.split("\n"): - m = header.match(l) + txt = txt.split("\n") + skipNextLine = False + for i in range(len(txt)): + + l = txt[i] + l2 = "\n".join(txt[i:i+2]) + + header = False + + if skipNextLine: + # Last line was a setext-style header. + skipNextLine = False + continue + + # Check ATX Header + m = ATXHeader.match(l) if m: - # Header ! + header = True level = len(m.group(1)) name = m.group(2) + # Check setext header + m = setextHeader1.match(l2) + if not header and m: + header = True + level = 1 + name = m.group(1) + skipNextLine = True + + m = setextHeader2.match(l2) + if not header and m: + header = True + level = 2 + name = m.group(1) + skipNextLine = True + + if header: + # save content content = saveContent(content, parent) @@ -144,9 +177,9 @@ class markdownImporter(abstractImporter): #group = cls.addPage(widget, "Folder import") self.addSetting("info", "label", - qApp.translate("Import", """WARNING: Current - importer only knows ATX-header (# Header), and - not setext headers (underlined with ========).
 """)) + qApp.translate("Import", """Info: A very simple + parser that will go through a markdown document and + create items for each titles.
 """)) self.addSetting("topLevelFolder", "checkbox", qApp.translate("Import", "Import in a top-level folder."), From 9c99d186e54c550f7ab0c3d76f31fb4d0ae0ccec Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 8 Nov 2017 23:46:18 +0100 Subject: [PATCH 19/23] Adds: import with pandoc through either OPML or markdown. #200 --- manuskript/importer/markdownImporter.py | 10 +++-- manuskript/importer/pandocImporters.py | 51 ++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/manuskript/importer/markdownImporter.py b/manuskript/importer/markdownImporter.py index 69df32cc..089aec06 100644 --- a/manuskript/importer/markdownImporter.py +++ b/manuskript/importer/markdownImporter.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # --!-- coding: utf8 --!-- -from manuskript.importer.abstractImporter import abstractImporter from manuskript.importer.abstractImporter import abstractImporter from manuskript.models.outlineModel import outlineItem from manuskript.enums import Outline @@ -62,9 +61,12 @@ class markdownImporter(abstractImporter): ``` """ - # Read file - with open(filePath, "r") as f: - txt = f.read() + if not fromString: + # Read file + with open(filePath, "r") as f: + txt = f.read() + else: + txt = fromString items = [] diff --git a/manuskript/importer/pandocImporters.py b/manuskript/importer/pandocImporters.py index c23eefc2..c81c8c74 100644 --- a/manuskript/importer/pandocImporters.py +++ b/manuskript/importer/pandocImporters.py @@ -4,6 +4,9 @@ from manuskript.importer.abstractImporter import abstractImporter from manuskript.exporter.pandoc import pandocExporter from manuskript.importer.opmlImporter import opmlImporter +from manuskript.importer.markdownImporter import markdownImporter +from PyQt5.QtWidgets import qApp + class pandocImporter(abstractImporter): @@ -13,21 +16,57 @@ class pandocImporter(abstractImporter): def isValid(cls): return pandocExporter().isValid() - @classmethod - def startImport(cls, filePath, parentItem, settingsWidget): + def startImport(self, filePath, parentItem, settingsWidget): + + formatTo = self.getSetting("formatTo").value().lower() # pandoc --from=markdown filename --to=opml --standalone args = [ - "--from={}".format(cls.formatFrom), + "--from={}".format(self.formatFrom), filePath, - "--to=opml", + "--to={}".format(formatTo), "--standalone" ] r = pandocExporter().run(args) - return opmlImporter.startImport("", parentItem, - settingsWidget, fromString=r) + if formatTo == "opml": + return self.opmlImporter.startImport("", parentItem, + settingsWidget, fromString=r) + elif formatTo == "markdown": + return self.mdImporter.startImport(filePath, parentItem, + settingsWidget, fromString=r) + + def settingsWidget(self, widget): + """ + Takes a QWidget that can be modified and must be returned. + """ + + # Add group + group = self.addGroup(widget.toolBox.widget(0), + qApp.translate("Import", "Pandoc import")) + + self.addSetting("info", "label", + qApp.translate("Import", """Info: Manuskript can + import from markdown or OPML. Pandoc will + convert your document to either (see option below), and + then it will be imported in manuskript. One or the other + might give better result depending on your document. +
 """)) + + self.addSetting("formatTo", "combo", + qApp.translate("Import", "Import using:"), + vals="markdown|OPML") + + for s in self.settings: + self.settings[s].widget(group) + + self.mdImporter = markdownImporter() + widget = self.mdImporter.settingsWidget(widget) + self.opmlImporter = opmlImporter() + widget = self.opmlImporter.settingsWidget(widget) + + return widget class markdownPandocImporter(pandocImporter): From 271f467d0ea38be9bb120e8ab330dea8871f16b8 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 9 Nov 2017 08:46:31 +0100 Subject: [PATCH 20/23] Adds pandoc wrap option to manage non-semantic linebreaks in imports --- manuskript/importer/pandocImporters.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/manuskript/importer/pandocImporters.py b/manuskript/importer/pandocImporters.py index c81c8c74..89548dc5 100644 --- a/manuskript/importer/pandocImporters.py +++ b/manuskript/importer/pandocImporters.py @@ -19,13 +19,15 @@ class pandocImporter(abstractImporter): def startImport(self, filePath, parentItem, settingsWidget): formatTo = self.getSetting("formatTo").value().lower() + wrap = self.getSetting("wrap").value().lower() # pandoc --from=markdown filename --to=opml --standalone args = [ "--from={}".format(self.formatFrom), filePath, "--to={}".format(formatTo), - "--standalone" + "--standalone", + "--wrap={}".format(wrap), ] r = pandocExporter().run(args) @@ -58,6 +60,19 @@ class pandocImporter(abstractImporter): qApp.translate("Import", "Import using:"), vals="markdown|OPML") + self.addSetting("wrap", "combo", + qApp.translate("Import", "Wrap lines:"), + vals="auto|none|preserve") + + self.addSetting("infoWrap", "label", + qApp.translate("Import", """(auto: wraps at + 72 characters.
+ none: no line wrap.
+ preserve: tries to preserves line wrap from + the original document. +
 """), + default="none") + for s in self.settings: self.settings[s].widget(group) From 3cef130bc6e0e9c5509fc00fe2c8ff36fbf24c6e Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 9 Nov 2017 10:40:54 +0100 Subject: [PATCH 21/23] Fixes a strupid bug recentrly introduced in outlineItem.findUniqueID. --- manuskript/mainWindow.py | 2 +- manuskript/models/outlineModel.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index f72bd8b5..4429d01c 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -413,7 +413,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.makeConnections() # Load settings - if settings.openIndexes: + if settings.openIndexes and settings.openIndexes != [""]: self.mainEditor.tabSplitter.restoreOpenIndexes(settings.openIndexes) self.generateViewMenu() self.mainEditor.sldCorkSizeFactor.setValue(settings.corkSizeFactor) diff --git a/manuskript/models/outlineModel.py b/manuskript/models/outlineModel.py index 1391ba33..ca9eaec1 100644 --- a/manuskript/models/outlineModel.py +++ b/manuskript/models/outlineModel.py @@ -914,8 +914,8 @@ class outlineItem(): def findUniqueID(self): IDs = [int(i) for i in self.IDs] - k = 0 - while k in self.IDs: + k = 1 + while k in IDs: k += 1 self.IDs.append(str(k)) return str(k) From 24607bca59f1174b797b51f67f5228ca79b6b921 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 9 Nov 2017 11:25:24 +0100 Subject: [PATCH 22/23] Adds setting to manage word wrap, and enhances UI. #200 --- manuskript/importer/__init__.py | 3 +- manuskript/importer/abstractImporter.py | 3 ++ manuskript/importer/folderImporter.py | 10 ---- manuskript/importer/markdownImporter.py | 8 ---- manuskript/importer/pandocImporters.py | 41 +++++++++------- manuskript/ui/importers/generalSettings.py | 7 +++ manuskript/ui/importers/generalSettings_ui.py | 10 ++-- manuskript/ui/importers/generalSettings_ui.ui | 13 +++-- manuskript/ui/importers/importer.py | 48 ++++++++++++++++--- 9 files changed, 94 insertions(+), 49 deletions(-) diff --git a/manuskript/importer/__init__.py b/manuskript/importer/__init__.py index 3edfeba9..196784ca 100644 --- a/manuskript/importer/__init__.py +++ b/manuskript/importer/__init__.py @@ -6,7 +6,7 @@ from manuskript.importer.markdownImporter import markdownImporter from manuskript.importer.opmlImporter import opmlImporter from manuskript.importer.pandocImporters import markdownPandocImporter, \ odtPandocImporter, ePubPandocImporter, docXPandocImporter, HTMLPandocImporter, \ - rstPandocImporter, LaTeXPandocImporter + rstPandocImporter, LaTeXPandocImporter, OPMLPandocImporter importers = [ # Internal @@ -22,4 +22,5 @@ importers = [ HTMLPandocImporter, rstPandocImporter, LaTeXPandocImporter, + OPMLPandocImporter, ] diff --git a/manuskript/importer/abstractImporter.py b/manuskript/importer/abstractImporter.py index 0e315f74..440068b7 100644 --- a/manuskript/importer/abstractImporter.py +++ b/manuskript/importer/abstractImporter.py @@ -24,6 +24,7 @@ class abstractImporter: fileFormat = "" # File format accepted. For example: "OPML Files (*.opml)" # For folder, use "<>" icon = "" + engine = "Internal" def __init__(self): self.settings = {} @@ -143,6 +144,8 @@ class abstractImporter: l.addWidget(label, 6) self._widget = QComboBox() self._widget.addItems(self.vals) + if self.default: + self._widget.setCurrentText(self.default) l.addWidget(self._widget, 2) if parent: parent.layout().addLayout(l) diff --git a/manuskript/importer/folderImporter.py b/manuskript/importer/folderImporter.py index a8e3f25f..4a3929cc 100644 --- a/manuskript/importer/folderImporter.py +++ b/manuskript/importer/folderImporter.py @@ -31,12 +31,6 @@ class folderImporter(abstractImporter): items = [] stack = {} - if self.getSetting("topLevelFolder").value(): - parent = outlineItem(title=os.path.basename(filePath), - parent=parentItem) - items.append(parent) - stack[filePath] = parent - for dirpath, dirnames, filenames in os.walk(filePath): if dirpath in stack: @@ -109,10 +103,6 @@ class folderImporter(abstractImporter): default="*.txt, *.md", tooltip=qApp.translate("Import", "Coma separated values")), - self.addSetting("topLevelFolder", "checkbox", - qApp.translate("Import", "Include top-level folder"), - default=False), - self.addSetting("sortItems", "checkbox", qApp.translate("Import", "Sort items by name"), default=True), diff --git a/manuskript/importer/markdownImporter.py b/manuskript/importer/markdownImporter.py index 089aec06..2e60f859 100644 --- a/manuskript/importer/markdownImporter.py +++ b/manuskript/importer/markdownImporter.py @@ -91,10 +91,6 @@ class markdownImporter(abstractImporter): setextHeader1 = re.compile(r"(.+)\n===+$", re.MULTILINE) setextHeader2 = re.compile(r"(.+)\n---+$", re.MULTILINE) - # Import in top level folder? - if self.getSetting("topLevelFolder").value(): - parent = addTitle(os.path.basename(filePath), parentItem, 0) - # We store the level of each item in a temporary var parent.__miLevel = 0 # markdown importer header level @@ -183,10 +179,6 @@ class markdownImporter(abstractImporter): parser that will go through a markdown document and create items for each titles.
 """)) - self.addSetting("topLevelFolder", "checkbox", - qApp.translate("Import", "Import in a top-level folder."), - default=False), - for s in self.settings: self.settings[s].widget(group) diff --git a/manuskript/importer/pandocImporters.py b/manuskript/importer/pandocImporters.py index 89548dc5..bbdfa9c9 100644 --- a/manuskript/importer/pandocImporters.py +++ b/manuskript/importer/pandocImporters.py @@ -11,6 +11,7 @@ from PyQt5.QtWidgets import qApp class pandocImporter(abstractImporter): formatFrom = "" + engine = "Pandoc" @classmethod def isValid(cls): @@ -62,16 +63,14 @@ class pandocImporter(abstractImporter): self.addSetting("wrap", "combo", qApp.translate("Import", "Wrap lines:"), - vals="auto|none|preserve") - - self.addSetting("infoWrap", "label", - qApp.translate("Import", """(auto: wraps at - 72 characters.
- none: no line wrap.
- preserve: tries to preserves line wrap from - the original document. -
 """), - default="none") + vals="auto|none|preserve", + default="none", + tooltip=qApp.translate("Import", """

Should pandoc create + cosmetic / non-semantic line-breaks?

+ auto: wraps at 72 characters.
+ none: no line wrap.
+ preserve: tries to preserves line wrap from the + original document.

""")) for s in self.settings: self.settings[s].widget(group) @@ -86,7 +85,7 @@ class pandocImporter(abstractImporter): class markdownPandocImporter(pandocImporter): - name = "Markdown (pandoc)" + name = "Markdown" description = "Markdown, using pandoc" fileFormat = "Markdown files (*.md *.txt *)" icon = "text-x-markdown" @@ -94,7 +93,7 @@ class markdownPandocImporter(pandocImporter): class ePubPandocImporter(pandocImporter): - name = "ePub (pandoc)" + name = "ePub" description = "" fileFormat = "ePub files (*.epub)" icon = "application-epub+zip" @@ -102,7 +101,7 @@ class ePubPandocImporter(pandocImporter): class docXPandocImporter(pandocImporter): - name = "DocX (pandoc)" + name = "DocX" description = "" fileFormat = "DocX files (*.docx)" icon = "application-vnd.openxmlformats-officedocument.wordprocessingml.document" @@ -110,7 +109,7 @@ class docXPandocImporter(pandocImporter): class odtPandocImporter(pandocImporter): - name = "ODT (pandoc)" + name = "ODT" description = "" fileFormat = "Open Document files (*.odt)" icon = "application-vnd.oasis.opendocument.text" @@ -118,7 +117,7 @@ class odtPandocImporter(pandocImporter): class rstPandocImporter(pandocImporter): - name = "reStructuredText (pandoc)" + name = "reStructuredText" description = "" fileFormat = "reStructuredText files (*.rst)" icon = "text-plain" @@ -126,7 +125,7 @@ class rstPandocImporter(pandocImporter): class HTMLPandocImporter(pandocImporter): - name = "HTML (pandoc)" + name = "HTML" description = "" fileFormat = "HTML files (*.htm *.html)" icon = "text-html" @@ -134,12 +133,20 @@ class HTMLPandocImporter(pandocImporter): class LaTeXPandocImporter(pandocImporter): - name = "LaTeX (pandoc)" + name = "LaTeX" description = "" fileFormat = "LaTeX files (*.tex)" icon = "text-x-tex" formatFrom = "latex" +class OPMLPandocImporter(pandocImporter): + + name = "OPML" + description = "" + fileFormat = "OPML files (*.opml *.xml)" + icon = "text-x-opml+xml" + formatFrom = "opml" + diff --git a/manuskript/ui/importers/generalSettings.py b/manuskript/ui/importers/generalSettings.py index e7dc43e5..874a3fc9 100644 --- a/manuskript/ui/importers/generalSettings.py +++ b/manuskript/ui/importers/generalSettings.py @@ -59,6 +59,13 @@ class generalSettings(QWidget, Ui_generalSettings): return "0" # 0 is root's ID + def importInTopLevelFolder(self): + """ + Should the import be flat in the parent folder, or create a top-level + folder? + """ + return self.chkGeneralTopLevel.isChecked() + def trimLongTitles(self): return self.chkGeneralTrimTitles.isChecked() diff --git a/manuskript/ui/importers/generalSettings_ui.py b/manuskript/ui/importers/generalSettings_ui.py index 1843ee0e..0c4d36b7 100644 --- a/manuskript/ui/importers/generalSettings_ui.py +++ b/manuskript/ui/importers/generalSettings_ui.py @@ -39,14 +39,14 @@ class Ui_generalSettings(object): self.chkGeneralSplitScenes = QtWidgets.QCheckBox(self.general) self.chkGeneralSplitScenes.setEnabled(False) self.chkGeneralSplitScenes.setObjectName("chkGeneralSplitScenes") - self.formLayout_4.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.chkGeneralSplitScenes) + self.formLayout_4.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.chkGeneralSplitScenes) self.txtGeneralSplitScenes = QtWidgets.QLineEdit(self.general) self.txtGeneralSplitScenes.setEnabled(False) self.txtGeneralSplitScenes.setObjectName("txtGeneralSplitScenes") - self.formLayout_4.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.txtGeneralSplitScenes) + self.formLayout_4.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.txtGeneralSplitScenes) self.chkGeneralTrimTitles = QtWidgets.QCheckBox(self.general) self.chkGeneralTrimTitles.setObjectName("chkGeneralTrimTitles") - self.formLayout_4.setWidget(3, QtWidgets.QFormLayout.SpanningRole, self.chkGeneralTrimTitles) + self.formLayout_4.setWidget(4, QtWidgets.QFormLayout.SpanningRole, self.chkGeneralTrimTitles) self.treeGeneralParent = QtWidgets.QTreeView(self.general) self.treeGeneralParent.setHeaderHidden(True) self.treeGeneralParent.setObjectName("treeGeneralParent") @@ -54,6 +54,9 @@ class Ui_generalSettings(object): self.chkGeneralParent = QtWidgets.QCheckBox(self.general) self.chkGeneralParent.setObjectName("chkGeneralParent") 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.verticalLayout_5.addLayout(self.formLayout_4) self.toolBox.addItem(self.general, "") self.verticalLayout_2.addWidget(self.toolBox) @@ -69,5 +72,6 @@ class Ui_generalSettings(object): self.chkGeneralSplitScenes.setText(_translate("generalSettings", "Split scenes at:")) 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")) self.toolBox.setItemText(self.toolBox.indexOf(self.general), _translate("generalSettings", "General")) diff --git a/manuskript/ui/importers/generalSettings_ui.ui b/manuskript/ui/importers/generalSettings_ui.ui index 21c4f95e..a1ea203e 100644 --- a/manuskript/ui/importers/generalSettings_ui.ui +++ b/manuskript/ui/importers/generalSettings_ui.ui @@ -78,7 +78,7 @@ QToolBox::tab:selected, QToolBox::tab:hover{ QFormLayout::WrapLongRows - + false @@ -88,14 +88,14 @@ QToolBox::tab:selected, QToolBox::tab:hover{ - + false - + Trim long titles (> 32 chars) @@ -116,6 +116,13 @@ 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 6314ac66..fff1b6fe 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -12,7 +12,7 @@ from manuskript.ui.importers.importer_ui import Ui_importer from manuskript.ui.importers.generalSettings import generalSettings from manuskript.ui import style from manuskript import importer -from manuskript.models.outlineModel import outlineModel +from manuskript.models.outlineModel import outlineModel, outlineItem from manuskript.enums import Outline from manuskript.exporter.pandoc import pandocExporter @@ -67,7 +67,21 @@ class importerDialog(QWidget, Ui_importer): def addFormat(name, icon): self.cmbImporters.addItem(QIcon.fromTheme(icon), name) + def addHeader(name): + self.cmbImporters.addItem(name, "header") + self.cmbImporters.setItemData(self.cmbImporters.count() - 1, QBrush(QColor(Qt.darkBlue)), Qt.ForegroundRole) + self.cmbImporters.setItemData(self.cmbImporters.count() - 1, QBrush(lightBlue()), Qt.BackgroundRole) + item = self.cmbImporters.model().item(self.cmbImporters.count() - 1) + item.setFlags(Qt.ItemIsEnabled) + + lastEngine = "" + for f in self.importers: + # Header + if f.engine != lastEngine: + addHeader(f.engine) + lastEngine = f.engine + addFormat(f.name, f.icon) if not f.isValid(): item = self.cmbImporters.model().item(self.cmbImporters.count() - 1) @@ -79,8 +93,14 @@ class importerDialog(QWidget, Ui_importer): "Install pandoc to import from much more formats", "::URL::http://pandoc.org/installing.html") + self.cmbImporters.setCurrentIndex(1) + def currentFormat(self): formatName = self.cmbImporters.currentText() + + if self.cmbImporters.currentData() == "header": + return None + F = [F for F in self.importers if F.name == formatName][0] # We instantiate the class return F() @@ -162,15 +182,18 @@ class importerDialog(QWidget, Ui_importer): F = self.currentFormat() self._format = F + # Checking if we have a valid importer (otherwise a header) + if not F: + self.grpSettings.setEnabled(False) + self.grpPreview.setEnabled(False) + return + self.grpSettings.setEnabled(True) + self.grpPreview.setEnabled(True) + self.settingsWidget = generalSettings() #TODO: custom format widget self.settingsWidget = F.settingsWidget(self.settingsWidget) - #toolBox = self.settingsWidget.toolBox - #w = QWidget() - #toolBox.insertItem(toolBox.count(), w, "Pandoc") - #See pandoc's abstractPlainText - # Set the settings widget in place self.setGroupWidget(self.grpSettings, self.settingsWidget) self.grpSettings.setMinimumWidth(200) @@ -245,6 +268,8 @@ class importerDialog(QWidget, Ui_importer): necessary. """ + items = [] + # We find the current selected format F = self._format @@ -252,11 +277,20 @@ class importerDialog(QWidget, Ui_importer): ID = self.settingsWidget.importUnderID() parentItem = outlineModel.getItemByID(ID) + # Import in top-level folder? + if self.settingsWidget.importInTopLevelFolder(): + parent = outlineItem(title=os.path.basename(self.fileName), + parent=parentItem) + parentItem = parent + items.append(parent) + # Calling the importer - items = F.startImport(self.fileName, + rItems = F.startImport(self.fileName, parentItem, self.settingsWidget) + items.extend(rItems) + # Do transformations items = self.doTransformations(items) From a231721bdbcda2e797f44a0cd084bd428b32951e Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 9 Nov 2017 15:18:21 +0100 Subject: [PATCH 23/23] 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 196784ca..d5a0aef7 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 2e60f859..a57b6be2 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 2fce7bc4..b7b8b994 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 bbdfa9c9..023a4b08 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 ca9eaec1..a20c2e05 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 874a3fc9..3940aab7 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 0c4d36b7..6ca528a9 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 a1ea203e..955fc21a 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 fff1b6fe..f09ae44b 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