From f40a43b6ed4899622f2347cd98cf0c4cd7879449 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Mon, 29 Feb 2016 23:25:23 +0100 Subject: [PATCH 001/103] Story line can display characters --- manuskript/models/references.py | 97 ++++++++++++++++++++---- manuskript/ui/views/storylineView.py | 107 +++++++++++++++++++-------- 2 files changed, 161 insertions(+), 43 deletions(-) diff --git a/manuskript/models/references.py b/manuskript/models/references.py index ac29a06c..d8f87e7a 100644 --- a/manuskript/models/references.py +++ b/manuskript/models/references.py @@ -373,56 +373,125 @@ def infos(ref): return qApp.translate("references", "Unknown reference: {}.").format(ref) -def tooltip(ref): - """Returns a tooltip in HTML for the reference ``ref``.""" +def shortInfos(ref): + """Returns infos about reference ``ref``. + Returns -1 if ``ref`` is not a valid reference, and None if it is valid but unknown.""" match = re.fullmatch(RegEx, ref) if not match: - return qApp.translate("references", "Not a reference: {}.").format(ref) + return -1 _type = match.group(1) _ref = match.group(2) + infos = {} + infos["ID"] = _ref + if _type == TextLetter: + + infos["type"] = TextLetter + m = mainWindow().mdlOutline idx = m.getIndexByID(_ref) if not idx.isValid(): - return qApp.translate("references", "Unknown reference: {}.").format(ref) + return None item = idx.internalPointer() if item.isFolder(): - tt = qApp.translate("references", "Folder: {}").format(item.title()) + infos["text_type"] = "folder" else: - tt = qApp.translate("references", "Text: {}").format(item.title()) - tt += "
{}".format(item.path()) + infos["text_type"] = "text" - return tt + infos["title"] = item.title() + infos["path"] = item.path() + return infos elif _type == PersoLetter: + + infos["type"] = PersoLetter + m = mainWindow().mdlPersos item = m.item(int(_ref), Perso.name.value) if item: - return qApp.translate("references", "Character: {}").format(item.text()) + infos["title"] = item.text() + infos["name"] = item.text() + return infos elif _type == PlotLetter: + + infos["type"] = PlotLetter + m = mainWindow().mdlPlots name = m.getPlotNameByID(_ref) if name: - return qApp.translate("references", "Plot: {}").format(name) + infos["title"] = name + return infos elif _type == WorldLetter: + + infos["type"] = WorldLetter + m = mainWindow().mdlWorld item = m.itemByID(_ref) if item: name = item.text() path = m.path(item) - return qApp.translate("references", "World: {name}{path}").format( - name=name, - path=" ({})".format(path) if path else "") + infos["title"] = name + infos["path"] = path + return infos - return qApp.translate("references", "Unknown reference: {}.").format(ref) + return None + + +def title(ref): + """Returns a the title (or name) for the reference ``ref``.""" + infos = shortInfos(ref) + if infos and infos != -1 and "title" in infos: + return infos["title"] + else: + return None + +def type(ref): + infos = shortInfos(ref) + if infos and infos != -1: + return infos["type"] + +def ID(ref): + infos = shortInfos(ref) + if infos and infos != -1: + return infos["ID"] + +def tooltip(ref): + """Returns a tooltip in HTML for the reference ``ref``.""" + infos = shortInfos(ref) + + if not infos: + return qApp.translate("references", "Unknown reference: {}.").format(ref) + + if infos == -1: + return qApp.translate("references", "Not a reference: {}.").format(ref) + + + if infos["type"] == TextLetter: + if infos["text_type"] == "folder": + tt = qApp.translate("references", "Folder: {}").format(infos["title"]) + else: + tt = qApp.translate("references", "Text: {}").format(infos["title"]) + tt += "
{}".format(infos["path"]) + return tt + + elif infos["type"] == PersoLetter: + return qApp.translate("references", "Character: {}").format(infos["title"]) + + elif infos["type"] == PlotLetter: + return qApp.translate("references", "Plot: {}").format(infos["title"]) + + elif infos["type"] == WorldLetter: + return qApp.translate("references", "World: {name}{path}").format( + name=infos["title"], + path=" ({})".format(infos["path"]) if infos["path"] else "") ############################################################################### diff --git a/manuskript/ui/views/storylineView.py b/manuskript/ui/views/storylineView.py index ac149f70..a5f81362 100644 --- a/manuskript/ui/views/storylineView.py +++ b/manuskript/ui/views/storylineView.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # --!-- coding: utf8 --!-- from PyQt5.QtCore import Qt, QTimer, QRectF -from PyQt5.QtGui import QBrush, QPen, QFontMetrics, QFontMetricsF +from PyQt5.QtGui import QBrush, QPen, QFontMetrics, QFontMetricsF, QColor from PyQt5.QtWidgets import QWidget, QGraphicsScene, QGraphicsSimpleTextItem, QMenu, QAction, QGraphicsRectItem, \ QGraphicsLineItem, QGraphicsEllipseItem @@ -32,15 +32,17 @@ class storylineView(QWidget, Ui_storylineView): def generateMenu(self): m = QMenu() - for i in [ - self.tr("Show Plots"), - self.tr("Show Characters"), - self.tr("Show Objects"), - ]: - a = QAction(i, m) - a.setCheckable(True) - a.setEnabled(False) - m.addAction(a) + self.actPlots = QAction(self.tr("Show Plots"), m) + self.actPlots.setCheckable(True) + self.actPlots.setChecked(True) + self.actPlots.toggled.connect(self.reloadTimer.start) + m.addAction(self.actPlots) + + self.actCharacters = QAction(self.tr("Show Characters"), m) + self.actCharacters.setCheckable(True) + self.actCharacters.setChecked(False) + self.actCharacters.toggled.connect(self.reloadTimer.start) + m.addAction(self.actCharacters) self.btnSettings.setMenu(m) @@ -55,6 +57,33 @@ class storylineView(QWidget, Ui_storylineView): self._mdlPersos = mdlPersos self._mdlPersos.dataChanged.connect(self.reloadTimer.start) + def plotReferences(self): + "Returns a list of plot references" + if not self._mdlPlots: + pass + + plotsID = self._mdlPlots.getPlotsByImportance() + r = [] + for importance in plotsID: + for ID in importance: + ref = references.plotReference(ID) + r.append(ref) + + return r + + def persosReferences(self): + "Returns a list of character references" + if not self._mdlPersos: + pass + + IDs = self._mdlPersos.getPersosByImportance() + r = [] + for importance in IDs: + for ID in importance: + ref = references.persoReference(ID) + r.append(ref) + + return r def refresh(self): if not self._mdlPlots or not self._mdlOutline or not self._mdlPersos: @@ -82,24 +111,24 @@ class storylineView(QWidget, Ui_storylineView): MAX_LEVEL = maxLevel(root) - # Generate left entries - # (As of now, plot only) - plotsID = self._mdlPlots.getPlotsByImportance() + # Get the list of tracked items (array of references) trackedItems = [] - fm = QFontMetrics(s.font()) - max_name = 0 - for importance in plotsID: - for ID in importance: - name = self._mdlPlots.getPlotNameByID(ID) - ref = references.plotReference(ID, searchable=True) + if self.actPlots.isChecked(): + trackedItems += self.plotReferences() - trackedItems.append((ID, ref, name)) - max_name = max(fm.width(name), max_name) + if self.actCharacters.isChecked(): + trackedItems += self.persosReferences() ROWS_HEIGHT = len(trackedItems) * (LINE_HEIGHT + SPACING ) - TITLE_WIDTH = max_name + 2 * SPACING + fm = QFontMetrics(s.font()) + max_name = 0 + for ref in trackedItems: + name = references.title(ref) + max_name = max(fm.width(name), max_name) + + TITLE_WIDTH = max_name + 2 * SPACING # Add Folders and Texts outline = OutlineRect(0, 0, 0, ROWS_HEIGHT + SPACING + MAX_LEVEL * LEVEL_HEIGHT) @@ -150,9 +179,22 @@ class storylineView(QWidget, Ui_storylineView): rectChild.setToolTip(references.tooltip(references.textReference(child.ID()))) # Find tracked references in that scene (or parent folders) - for ID, ref, name in trackedItems: + for ref in trackedItems: result = [] + + # Tests if POV + scenePOV = False # Will hold true of character is POV of the current text, not containing folder + if references.type(ref) == references.PersoLetter: + ID = references.ID(ref) + c = child + while c: + if c.POV() == ID: + result.append(c.ID()) + if c == child: scenePOV = True + c = c.parent() + + # Search in notes/references c = child while c: result += references.findReferencesTo(ref, c, recursive=False) @@ -162,7 +204,7 @@ class storylineView(QWidget, Ui_storylineView): ref2 = result[0] # Create a RefCircle with the reference - c = RefCircle(TEXT_WIDTH / 2, - CIRCLE_WIDTH / 2, CIRCLE_WIDTH, ID=ref2) + c = RefCircle(TEXT_WIDTH / 2, - CIRCLE_WIDTH / 2, CIRCLE_WIDTH, ID=ref2, important=scenePOV) # Store it, with the position of that item, to display it on the line later on refCircles.append((ref, c, rect.mapToItem(outline, rectChild.pos()))) @@ -173,22 +215,27 @@ class storylineView(QWidget, Ui_storylineView): OUTLINE_WIDTH = itemWidth(root) - # Add Plots + # Add Tracked items i = 0 itemsRect = s.addRect(0, 0, 0, 0) itemsRect.setPos(0, MAX_LEVEL * LEVEL_HEIGHT + SPACING) - for ID, ref, name in trackedItems: - color = randomColor() + for ref in trackedItems: + if references.type(ref) == references.PersoLetter: + color = QColor(self._mdlPersos.getPersoColorByID(references.ID(ref))) + else: + color = randomColor() # Rect r = QGraphicsRectItem(0, 0, TITLE_WIDTH, LINE_HEIGHT, itemsRect) r.setPen(QPen(Qt.NoPen)) r.setBrush(QBrush(color)) r.setPos(0, i * LINE_HEIGHT + i * SPACING) + r.setToolTip(references.tooltip(ref)) i += 1 # Text + name = references.title(ref) txt = QGraphicsSimpleTextItem(name, r) txt.setPos(r.boundingRect().center() - txt.boundingRect().center()) @@ -198,7 +245,7 @@ class storylineView(QWidget, Ui_storylineView): line.setPos(TITLE_WIDTH, r.mapToScene(r.rect().center()).y()) s.addItem(line) line.setPen(QPen(color, 5)) - line.setToolTip(self.tr("Plot: ") + name) + line.setToolTip(references.tooltip(ref)) # We add the circles / references to text, on the line for ref2, circle, pos in refCircles: @@ -225,13 +272,15 @@ class OutlineRect(QGraphicsRectItem): class RefCircle(QGraphicsEllipseItem): - def __init__(self, x, y, diameter, parent=None, ID=None): + def __init__(self, x, y, diameter, parent=None, ID=None, important=False): QGraphicsEllipseItem.__init__(self, x, y, diameter, diameter, parent) self.setBrush(Qt.white) self._ref = references.textReference(ID) self.setToolTip(references.tooltip(self._ref)) self.setPen(QPen(Qt.black, 2)) self.setAcceptHoverEvents(True) + if important: + self.setBrush(Qt.black) def multiplyDiameter(self, factor): r1 = self.rect() From ba25abe6a508c0566d8d755132f62a0d1e50b654 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Mon, 29 Feb 2016 23:26:04 +0100 Subject: [PATCH 002/103] Sample projet 'Book of Acts' has more references to showcase storyline --- sample-projects/book-of-acts.msk | Bin 30596 -> 30732 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/sample-projects/book-of-acts.msk b/sample-projects/book-of-acts.msk index 0bc4d5b2941d67bf74b5461027eb7ab4d80b0b4d..2f939bee1c5172dcede70535acf19aad02d5ca01 100644 GIT binary patch delta 29319 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 z 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 From 008edd073f7d72ee1ca90597e1eaac42ae705509 Mon Sep 17 00:00:00 2001 From: Tim D'Annecy Date: Tue, 1 Mar 2016 17:32:07 -0500 Subject: [PATCH 003/103] Update basicItemView.py Changed "sentance" to "sentence". --- manuskript/ui/views/basicItemView.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manuskript/ui/views/basicItemView.py b/manuskript/ui/views/basicItemView.py index 7b73c00f..77a47b2a 100644 --- a/manuskript/ui/views/basicItemView.py +++ b/manuskript/ui/views/basicItemView.py @@ -10,13 +10,13 @@ class basicItemView(QWidget, Ui_basicItemView): def __init__(self, parent=None): QWidget.__init__(self) self.setupUi(self) - self.txtSummarySentance.setColumn(Outline.summarySentance.value) + self.txtSummarySentence.setColumn(Outline.summarySentence.value) self.txtSummaryFull.setColumn(Outline.summaryFull.value) self.txtGoal.setColumn(Outline.setGoal.value) def setModels(self, mdlOutline, mdlPersos, mdlLabels, mdlStatus): self.cmbPOV.setModels(mdlPersos, mdlOutline) - self.txtSummarySentance.setModel(mdlOutline) + self.txtSummarySentence.setModel(mdlOutline) self.txtSummaryFull.setModel(mdlOutline) self.txtGoal.setModel(mdlOutline) From 01e80e2f6ef65d75cbbdb928832573c330f8ddb5 Mon Sep 17 00:00:00 2001 From: Tim D'Annecy Date: Tue, 1 Mar 2016 17:32:50 -0500 Subject: [PATCH 004/103] Update enums.py Changes "sentance" to "sentence". --- manuskript/enums.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manuskript/enums.py b/manuskript/enums.py index 7c434fbb..8cc17e91 100644 --- a/manuskript/enums.py +++ b/manuskript/enums.py @@ -17,7 +17,7 @@ class Perso(Enum): goal = 4 conflict = 5 epiphany = 6 - summarySentance = 7 + summarySentence = 7 summaryPara = 8 summaryFull = 9 notes = 10 From f75ea7bc38f53a0e9b4595fa66dbfaf1711dfd7c Mon Sep 17 00:00:00 2001 From: Tim D'Annecy Date: Tue, 1 Mar 2016 17:33:35 -0500 Subject: [PATCH 005/103] Update metadataView.py Changes "sentance" to "sentence". --- manuskript/ui/views/metadataView.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manuskript/ui/views/metadataView.py b/manuskript/ui/views/metadataView.py index 78916d9e..52affbac 100644 --- a/manuskript/ui/views/metadataView.py +++ b/manuskript/ui/views/metadataView.py @@ -11,14 +11,14 @@ class metadataView(QWidget, Ui_metadataView): QWidget.__init__(self, parent) self.setupUi(self) self._lastIndexes = None - self.txtSummarySentance.setColumn(Outline.summarySentance.value) + self.txtSummarySentence.setColumn(Outline.summarySentence.value) self.txtSummaryFull.setColumn(Outline.summaryFull.value) self.txtNotes.setColumn(Outline.notes.value) self.revisions.setEnabled(False) def setModels(self, mdlOutline, mdlPersos, mdlLabels, mdlStatus): self.properties.setModels(mdlOutline, mdlPersos, mdlLabels, mdlStatus) - self.txtSummarySentance.setModel(mdlOutline) + self.txtSummarySentence.setModel(mdlOutline) self.txtSummaryFull.setModel(mdlOutline) self.txtNotes.setModel(mdlOutline) self.revisions.setModel(mdlOutline) From d2cc05fe69d8e8ada2d7ce8021078200caca70d9 Mon Sep 17 00:00:00 2001 From: Tim D'Annecy Date: Tue, 1 Mar 2016 17:36:06 -0500 Subject: [PATCH 006/103] Update metadataView_ui.py Changes "sentance" to "sentence". --- manuskript/ui/views/metadataView_ui.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/manuskript/ui/views/metadataView_ui.py b/manuskript/ui/views/metadataView_ui.py index 3930f9c2..a5c759d7 100644 --- a/manuskript/ui/views/metadataView_ui.py +++ b/manuskript/ui/views/metadataView_ui.py @@ -43,11 +43,11 @@ class Ui_metadataView(object): self.verticalLayout_22.setSpacing(0) self.verticalLayout_22.setContentsMargins(0, 0, 0, 0) self.verticalLayout_22.setObjectName("verticalLayout_22") - self.txtSummarySentance = lineEditView(self.grpSummary) - self.txtSummarySentance.setInputMask("") - self.txtSummarySentance.setFrame(False) - self.txtSummarySentance.setObjectName("txtSummarySentance") - self.verticalLayout_22.addWidget(self.txtSummarySentance) + self.txtSummarySentence = lineEditView(self.grpSummary) + self.txtSummarySentence.setInputMask("") + self.txtSummarySentence.setFrame(False) + self.txtSummarySentence.setObjectName("txtSummarySentence") + self.verticalLayout_22.addWidget(self.txtSummarySentence) self.line = QtWidgets.QFrame(self.grpSummary) self.line.setFrameShadow(QtWidgets.QFrame.Plain) self.line.setLineWidth(0) @@ -95,7 +95,7 @@ class Ui_metadataView(object): metadataView.setWindowTitle(_translate("metadataView", "Form")) self.grpProperties.setTitle(_translate("metadataView", "Properties")) self.grpSummary.setTitle(_translate("metadataView", "Summary")) - self.txtSummarySentance.setPlaceholderText(_translate("metadataView", "One line summary")) + self.txtSummarySentence.setPlaceholderText(_translate("metadataView", "One line summary")) self.txtSummaryFull.setPlaceholderText(_translate("metadataView", "Full summary")) self.grpNotes.setTitle(_translate("metadataView", "Notes / References")) self.txtNotes.setPlaceholderText(_translate("metadataView", "Notes / References")) From f46f4f50c1300d830383e91500d9ced881f8bb71 Mon Sep 17 00:00:00 2001 From: Tim D'Annecy Date: Tue, 1 Mar 2016 17:37:49 -0500 Subject: [PATCH 007/103] Update corkDelegate.py Changes "sentance" to "sentence". --- manuskript/ui/views/corkDelegate.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/manuskript/ui/views/corkDelegate.py b/manuskript/ui/views/corkDelegate.py index c8aadae1..5f28d2eb 100644 --- a/manuskript/ui/views/corkDelegate.py +++ b/manuskript/ui/views/corkDelegate.py @@ -38,7 +38,7 @@ class corkDelegate(QStyledItemDelegate): if self.mainLineRect.contains(self.lastPos): # One line summary - self.editing = Outline.summarySentance + self.editing = Outline.summarySentence edt = QLineEdit(parent) edt.setFocusPolicy(Qt.StrongFocus) edt.setFrame(False) @@ -74,7 +74,7 @@ class corkDelegate(QStyledItemDelegate): def updateEditorGeometry(self, editor, option, index): - if self.editing == Outline.summarySentance: + if self.editing == Outline.summarySentence: # One line summary editor.setGeometry(self.mainLineRect) @@ -89,9 +89,9 @@ class corkDelegate(QStyledItemDelegate): def setEditorData(self, editor, index): item = index.internalPointer() - if self.editing == Outline.summarySentance: + if self.editing == Outline.summarySentence: # One line summary - editor.setText(item.data(Outline.summarySentance.value)) + editor.setText(item.data(Outline.summarySentence.value)) elif self.editing == Outline.title: # Title @@ -103,9 +103,9 @@ class corkDelegate(QStyledItemDelegate): def setModelData(self, editor, model, index): - if self.editing == Outline.summarySentance: + if self.editing == Outline.summarySentence: # One line summary - model.setData(index.sibling(index.row(), Outline.summarySentance.value), editor.text()) + model.setData(index.sibling(index.row(), Outline.summarySentence.value), editor.text()) elif self.editing == Outline.title: # Title @@ -133,7 +133,7 @@ class corkDelegate(QStyledItemDelegate): self.mainRect.topRight() + QPoint(0, iconSize)) self.mainTextRect = QRect(self.mainLineRect.bottomLeft() + QPoint(0, margin), self.mainRect.bottomRight()) - if not item.data(Outline.summarySentance.value): + if not item.data(Outline.summarySentence.value): self.mainTextRect.setTopLeft(self.mainLineRect.topLeft()) if item.data(Outline.label.value) in ["", "0"]: self.titleRect.setBottomRight(self.labelRect.bottomRight() - QPoint(self.margin, self.margin)) @@ -218,7 +218,7 @@ class corkDelegate(QStyledItemDelegate): p.drawLine(self.labelRect.topLeft(), self.labelRect.bottomLeft()) # One line summary background - lineSummary = item.data(Outline.summarySentance.value) + lineSummary = item.data(Outline.summarySentence.value) fullSummary = item.data(Outline.summaryFull.value) if lineSummary or not fullSummary: m = self.margin From c2302ac70b12ed3675872930401fce679fed374a Mon Sep 17 00:00:00 2001 From: Tim D'Annecy Date: Tue, 1 Mar 2016 17:38:40 -0500 Subject: [PATCH 008/103] Update basicItemView_ui.py Changed "sentance" to "sentence". --- manuskript/ui/views/basicItemView_ui.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/manuskript/ui/views/basicItemView_ui.py b/manuskript/ui/views/basicItemView_ui.py index 8ba33dd3..94da0b0b 100644 --- a/manuskript/ui/views/basicItemView_ui.py +++ b/manuskript/ui/views/basicItemView_ui.py @@ -43,10 +43,10 @@ class Ui_basicItemView(object): self.txtGoal.setObjectName("txtGoal") self.horizontalLayout_11.addWidget(self.txtGoal) self.verticalLayout.addLayout(self.horizontalLayout_11) - self.txtSummarySentance = lineEditView(basicItemView) - self.txtSummarySentance.setText("") - self.txtSummarySentance.setObjectName("txtSummarySentance") - self.verticalLayout.addWidget(self.txtSummarySentance) + self.txtSummarySentence = lineEditView(basicItemView) + self.txtSummarySentence.setText("") + self.txtSummarySentence.setObjectName("txtSummarySentence") + self.verticalLayout.addWidget(self.txtSummarySentence) self.label_9 = QtWidgets.QLabel(basicItemView) self.label_9.setObjectName("label_9") self.verticalLayout.addWidget(self.label_9) @@ -63,7 +63,7 @@ class Ui_basicItemView(object): self.lblPlanPOV.setText(_translate("basicItemView", "POV:")) self.lblGoal.setText(_translate("basicItemView", "Goal:")) self.txtGoal.setPlaceholderText(_translate("basicItemView", "Word count")) - self.txtSummarySentance.setPlaceholderText(_translate("basicItemView", "One line summary")) + self.txtSummarySentence.setPlaceholderText(_translate("basicItemView", "One line summary")) self.label_9.setText(_translate("basicItemView", "Few sentences summary:")) from manuskript.ui.views.cmbOutlinePersoChoser import cmbOutlinePersoChoser From ef2eeb5f938135598715ce62ca2354c229680cc5 Mon Sep 17 00:00:00 2001 From: Tim D'Annecy Date: Tue, 1 Mar 2016 17:39:25 -0500 Subject: [PATCH 009/103] Update treeDelegates.py Changed "sentance" to "sentence". --- manuskript/ui/views/treeDelegates.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manuskript/ui/views/treeDelegates.py b/manuskript/ui/views/treeDelegates.py index 23a5c5e2..ed78fb44 100644 --- a/manuskript/ui/views/treeDelegates.py +++ b/manuskript/ui/views/treeDelegates.py @@ -105,7 +105,7 @@ class treeTitleDelegate(QStyledItemDelegate): if extraText: extraText = " ({}%)".format(extraText) elif settings.viewSettings["Tree"]["InfoFolder"] == "Summary": - extraText = item.data(Outline.summarySentance.value) + extraText = item.data(Outline.summarySentence.value) if extraText: extraText = " - {}".format(extraText) @@ -118,7 +118,7 @@ class treeTitleDelegate(QStyledItemDelegate): if extraText: extraText = " ({}%)".format(extraText) elif settings.viewSettings["Tree"]["InfoText"] == "Summary": - extraText = item.data(Outline.summarySentance.value) + extraText = item.data(Outline.summarySentence.value) if extraText: extraText = " - {}".format(extraText) From 0ed89d2ec87ae0bc6cebdabb4b9fb1abbf96c52b Mon Sep 17 00:00:00 2001 From: Tim D'Annecy Date: Tue, 1 Mar 2016 17:41:00 -0500 Subject: [PATCH 010/103] Update search.py Changed "sentance" to "sentence". --- manuskript/ui/search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manuskript/ui/search.py b/manuskript/ui/search.py index a75b5ad5..bc388f07 100644 --- a/manuskript/ui/search.py +++ b/manuskript/ui/search.py @@ -84,7 +84,7 @@ class search(QWidget, Ui_search): lstColumns = [ ("Title", Outline.title.value), ("Text", Outline.text.value), - ("Summary", Outline.summarySentance.value), + ("Summary", Outline.summarySentence.value), ("Summary", Outline.summaryFull.value), ("Notes", Outline.notes.value), ("POV", Outline.POV.value), From c83a2ba0cb65ca51cc440e8a3165d4f1c218d1bf Mon Sep 17 00:00:00 2001 From: Tim D'Annecy Date: Tue, 1 Mar 2016 17:41:55 -0500 Subject: [PATCH 011/103] Update mainWindow.py Changed "sentance" to "sentence". --- manuskript/mainWindow.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index ed6bc4ca..9fadcf25 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -66,7 +66,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): # Word count self.mprWordCount = QSignalMapper(self) for t, i in [ - (self.txtSummarySentance, 0), + (self.txtSummarySentence, 0), (self.txtSummaryPara, 1), (self.txtSummaryPage, 2), (self.txtSummaryFull, 3) @@ -161,7 +161,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.txtPersoGoal, self.txtPersoConflict, self.txtPersoEpiphany, - self.txtPersoSummarySentance, + self.txtPersoSummarySentence, self.txtPersoSummaryPara, self.txtPersoSummaryFull, self.txtPersoNotes, @@ -592,8 +592,8 @@ class MainWindow(QMainWindow, Ui_MainWindow): # Flat datas (Summary and general infos) for widget, col in [ (self.txtSummarySituation, 0), - (self.txtSummarySentance, 1), - (self.txtSummarySentance_2, 1), + (self.txtSummarySentence, 1), + (self.txtSummarySentence_2, 1), (self.txtSummaryPara, 2), (self.txtSummaryPara_2, 2), (self.txtPlotSummaryPara, 2), @@ -639,7 +639,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): (self.txtPersoGoal, Perso.goal.value), (self.txtPersoConflict, Perso.conflict.value), (self.txtPersoEpiphany, Perso.epiphany.value), - (self.txtPersoSummarySentance, Perso.summarySentance.value), + (self.txtPersoSummarySentence, Perso.summarySentence.value), (self.txtPersoSummaryPara, Perso.summaryPara.value), (self.txtPersoSummaryFull, Perso.summaryFull.value), (self.txtPersoNotes, Perso.notes.value) @@ -787,14 +787,14 @@ class MainWindow(QMainWindow, Ui_MainWindow): def wordCount(self, i): src = { - 0: self.txtSummarySentance, + 0: self.txtSummarySentence, 1: self.txtSummaryPara, 2: self.txtSummaryPage, 3: self.txtSummaryFull }[i] lbl = { - 0: self.lblSummaryWCSentance, + 0: self.lblSummaryWCSentence, 1: self.lblSummaryWCPara, 2: self.lblSummaryWCPage, 3: self.lblSummaryWCFull @@ -881,7 +881,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): 1), (self.lytSummary, self.tr( - """Take time to think about a one sentance (~50 words) summary of your book. Then expand it to + """Take time to think about a one sentence (~50 words) summary of your book. Then expand it to a paragraph, then to a page, then to a full summary."""), 1), (self.lytTabPersos, From cbc42dcb64fb5a3de09829258d608eb26934c150 Mon Sep 17 00:00:00 2001 From: Tim D'Annecy Date: Tue, 1 Mar 2016 17:42:44 -0500 Subject: [PATCH 012/103] Update mainWindow.py Changed "sentance" to "sentence". --- manuskript/ui/mainWindow.py | 44 ++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/manuskript/ui/mainWindow.py b/manuskript/ui/mainWindow.py index 8eac15f3..fd4b7f94 100644 --- a/manuskript/ui/mainWindow.py +++ b/manuskript/ui/mainWindow.py @@ -165,18 +165,18 @@ class Ui_MainWindow(object): self.label = QtWidgets.QLabel(self.tabSummaryPage1) self.label.setObjectName("label") self.verticalLayout_5.addWidget(self.label) - self.txtSummarySentance = textEditView(self.tabSummaryPage1) + self.txtSummarySentence = textEditView(self.tabSummaryPage1) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.txtSummarySentance.sizePolicy().hasHeightForWidth()) - self.txtSummarySentance.setSizePolicy(sizePolicy) - self.txtSummarySentance.setObjectName("txtSummarySentance") - self.verticalLayout_5.addWidget(self.txtSummarySentance) - self.lblSummaryWCSentance = QtWidgets.QLabel(self.tabSummaryPage1) - self.lblSummaryWCSentance.setText("") - self.lblSummaryWCSentance.setObjectName("lblSummaryWCSentance") - self.verticalLayout_5.addWidget(self.lblSummaryWCSentance) + sizePolicy.setHeightForWidth(self.txtSummarySentence.sizePolicy().hasHeightForWidth()) + self.txtSummarySentence.setSizePolicy(sizePolicy) + self.txtSummarySentence.setObjectName("txtSummarySentence") + self.verticalLayout_5.addWidget(self.txtSummarySentence) + self.lblSummaryWCSentence = QtWidgets.QLabel(self.tabSummaryPage1) + self.lblSummaryWCSentence.setText("") + self.lblSummaryWCSentence.setObjectName("lblSummaryWCSentence") + self.verticalLayout_5.addWidget(self.lblSummaryWCSentence) spacerItem5 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.verticalLayout_5.addItem(spacerItem5) self.tabSummary.addWidget(self.tabSummaryPage1) @@ -189,15 +189,15 @@ class Ui_MainWindow(object): self.label_21 = QtWidgets.QLabel(self.tabSummaryPage2) self.label_21.setObjectName("label_21") self.verticalLayout.addWidget(self.label_21) - self.txtSummarySentance_2 = textEditView(self.tabSummaryPage2) + self.txtSummarySentence_2 = textEditView(self.tabSummaryPage2) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.txtSummarySentance_2.sizePolicy().hasHeightForWidth()) - self.txtSummarySentance_2.setSizePolicy(sizePolicy) - self.txtSummarySentance_2.setReadOnly(True) - self.txtSummarySentance_2.setObjectName("txtSummarySentance_2") - self.verticalLayout.addWidget(self.txtSummarySentance_2) + sizePolicy.setHeightForWidth(self.txtSummarySentence_2.sizePolicy().hasHeightForWidth()) + self.txtSummarySentence_2.setSizePolicy(sizePolicy) + self.txtSummarySentence_2.setReadOnly(True) + self.txtSummarySentence_2.setObjectName("txtSummarySentence_2") + self.verticalLayout.addWidget(self.txtSummarySentence_2) spacerItem6 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.verticalLayout.addItem(spacerItem6) self.horizontalLayout_4.addLayout(self.verticalLayout) @@ -433,9 +433,9 @@ class Ui_MainWindow(object): self.txtPersoEpiphany = textEditView(self.infos) self.txtPersoEpiphany.setObjectName("txtPersoEpiphany") self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.txtPersoEpiphany) - self.txtPersoSummarySentance = textEditView(self.infos) - self.txtPersoSummarySentance.setObjectName("txtPersoSummarySentance") - self.formLayout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.txtPersoSummarySentance) + self.txtPersoSummarySentence = textEditView(self.infos) + self.txtPersoSummarySentence.setObjectName("txtPersoSummarySentence") + self.formLayout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.txtPersoSummarySentence) self.txtPersoSummaryPara = textEditView(self.infos) self.txtPersoSummaryPara.setObjectName("txtPersoSummaryPara") self.formLayout.setWidget(7, QtWidgets.QFormLayout.FieldRole, self.txtPersoSummaryPara) @@ -1196,12 +1196,12 @@ class Ui_MainWindow(object): self.tabMain.setTabText(self.tabMain.indexOf(self.lytTabOverview), _translate("MainWindow", "General")) self.label_9.setText(_translate("MainWindow", "Situation:")) self.label_29.setText(_translate("MainWindow", "Summary:")) - self.cmbSummary.setItemText(0, _translate("MainWindow", "One sentance")) + self.cmbSummary.setItemText(0, _translate("MainWindow", "One sentence")) self.cmbSummary.setItemText(1, _translate("MainWindow", "One paragraph")) self.cmbSummary.setItemText(2, _translate("MainWindow", "One page")) self.cmbSummary.setItemText(3, _translate("MainWindow", "Full")) - self.label.setText(_translate("MainWindow", "One sentance summary")) - self.label_21.setText(_translate("MainWindow", "One sentance summary")) + self.label.setText(_translate("MainWindow", "One sentence summary")) + self.label_21.setText(_translate("MainWindow", "One sentence summary")) self.label_2.setText(_translate("MainWindow", "One paragraph summary")) self.label_22.setText(_translate("MainWindow", "One paragraph summary")) self.label_17.setText(_translate("MainWindow", "Expand each sentence of your one paragraph summary to a paragraph")) @@ -1221,7 +1221,7 @@ class Ui_MainWindow(object): self.label_5.setText(_translate("MainWindow", "Goal")) self.label_6.setText(_translate("MainWindow", "Conflict")) self.label_7.setText(_translate("MainWindow", "Epiphany")) - self.label_24.setText(_translate("MainWindow", "

One sentance
summary

")) + self.label_24.setText(_translate("MainWindow", "

One sentence
summary

")) self.label_8.setText(_translate("MainWindow", "

One paragraph
summary

")) self.btnStepFour.setText(_translate("MainWindow", "Next")) self.tabPersos.setTabText(self.tabPersos.indexOf(self.infos), _translate("MainWindow", "Basic infos")) From dabc566ae9b7a18d2fec161c47117aa12f9cfbf2 Mon Sep 17 00:00:00 2001 From: Tim D'Annecy Date: Tue, 1 Mar 2016 17:52:02 -0500 Subject: [PATCH 013/103] Update metadataView_ui.ui Changed "sentance" to "sentence". --- manuskript/ui/views/metadataView_ui.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manuskript/ui/views/metadataView_ui.ui b/manuskript/ui/views/metadataView_ui.ui index 96d12b41..1656d0f9 100644 --- a/manuskript/ui/views/metadataView_ui.ui +++ b/manuskript/ui/views/metadataView_ui.ui @@ -103,7 +103,7 @@ 0 - + From a47715e3de4a02e4bbf14d3a102105f029484a97 Mon Sep 17 00:00:00 2001 From: Tim D'Annecy Date: Tue, 1 Mar 2016 17:54:22 -0500 Subject: [PATCH 014/103] Update mainWindow.ui Changed "sentance" to "sentence". --- manuskript/ui/mainWindow.ui | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/manuskript/ui/mainWindow.ui b/manuskript/ui/mainWindow.ui index 2720d370..1a5390d1 100644 --- a/manuskript/ui/mainWindow.ui +++ b/manuskript/ui/mainWindow.ui @@ -342,7 +342,7 @@ - One sentance + One sentence @@ -374,12 +374,12 @@ - One sentance summary + One sentence summary - + 0 @@ -389,7 +389,7 @@ - + @@ -417,12 +417,12 @@ - One sentance summary + One sentence summary - + 0 @@ -823,7 +823,7 @@ - <html><head/><body><p align="right">One sentance<br/>summary</p></body></html> + <html><head/><body><p align="right">One sentence<br/>summary</p></body></html> @@ -899,7 +899,7 @@ - + From 7934f5cbb8ca55d39547879ba113455342f47730 Mon Sep 17 00:00:00 2001 From: Tim D'Annecy Date: Tue, 1 Mar 2016 17:55:56 -0500 Subject: [PATCH 015/103] Update references.py Changed "sentance" to "sentence". --- manuskript/models/references.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manuskript/models/references.py b/manuskript/models/references.py index ac29a06c..b9b54361 100644 --- a/manuskript/models/references.py +++ b/manuskript/models/references.py @@ -130,7 +130,7 @@ def infos(ref): path = " > ".join(pathStr) # Summaries and notes - ss = item.data(Outline.summarySentance.value) + ss = item.data(Outline.summarySentence.value) ls = item.data(Outline.summaryFull.value) notes = item.data(Outline.notes.value) @@ -195,7 +195,7 @@ def infos(ref): (Perso.goal, qApp.translate("references", "Goal"), False), (Perso.conflict, qApp.translate("references", "Conflict"), False), (Perso.epiphany, qApp.translate("references", "Epiphany"), False), - (Perso.summarySentance, qApp.translate("references", "Short summary"), True), + (Perso.summarySentence, qApp.translate("references", "Short summary"), True), (Perso.summaryPara, qApp.translate("references", "Longer summary"), True), ]: val = m.data(index.sibling(index.row(), i[0].value)) From f137f029c0a9174108b1e8fe9d87c523c2caf8c3 Mon Sep 17 00:00:00 2001 From: Tim D'Annecy Date: Tue, 1 Mar 2016 17:57:01 -0500 Subject: [PATCH 016/103] Update manuskript_fr.ts Changed "sentance" to "sentence". --- i18n/manuskript_fr.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/i18n/manuskript_fr.ts b/i18n/manuskript_fr.ts index ebb209f6..d4e27417 100644 --- a/i18n/manuskript_fr.ts +++ b/i18n/manuskript_fr.ts @@ -65,12 +65,12 @@ - One sentance + One sentence Une phrase - One sentance summary + One sentence summary Résumé en une phrase @@ -160,7 +160,7 @@ - <html><head/><body><p align="right">One sentance<br/>summary</p></body></html> + <html><head/><body><p align="right">One sentence<br/>summary</p></body></html> <html><head/><body><p align="right">Résumé<br/>en une phrase</p></body></html> @@ -515,7 +515,7 @@ - Take time to think about a one sentance (~50 words) summary of your book. Then expand it to a paragraph, then to a page, then to a full summary. + Take time to think about a one sentence (~50 words) summary of your book. Then expand it to a paragraph, then to a page, then to a full summary. Prenez le temps de réfléchir à un résumé de votre livre, en une phrase (~50 mots). Puis augmentez cette phrase en un paragraphe, puis en une page, puis en un résumé complet. From ca3d9691ee5fa055846d27686d02b34f6d571cf0 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 2 Mar 2016 00:36:51 +0100 Subject: [PATCH 017/103] Some more 'sentance' caught --- manuskript/enums.py | 2 +- manuskript/ui/compileDialog_ui.py | 5 +++-- manuskript/ui/mainWindow.py | 22 +++++++++++----------- manuskript/ui/revisions_ui.py | 2 +- manuskript/ui/settings_ui.py | 4 +++- manuskript/ui/views/basicItemView.py | 4 ++-- manuskript/ui/views/basicItemView_ui.py | 5 +++-- manuskript/ui/views/basicItemView_ui.ui | 2 +- manuskript/ui/views/metadataView.py | 4 ++-- manuskript/ui/views/metadataView_ui.py | 6 +++--- manuskript/ui/views/propertiesView_ui.py | 11 ++++++----- 11 files changed, 36 insertions(+), 31 deletions(-) diff --git a/manuskript/enums.py b/manuskript/enums.py index 8cc17e91..8fa68a58 100644 --- a/manuskript/enums.py +++ b/manuskript/enums.py @@ -51,7 +51,7 @@ class Outline(Enum): title = 0 ID = 1 type = 2 - summarySentance = 3 + summarySentence = 3 summaryFull = 4 POV = 5 notes = 6 diff --git a/manuskript/ui/compileDialog_ui.py b/manuskript/ui/compileDialog_ui.py index 6000e975..d302b5fc 100644 --- a/manuskript/ui/compileDialog_ui.py +++ b/manuskript/ui/compileDialog_ui.py @@ -2,11 +2,12 @@ # Form implementation generated from reading ui file 'manuskript/ui/compileDialog_ui.ui' # -# Created by: PyQt5 UI code generator 5.4.1 +# Created: Wed Mar 2 00:30:17 2016 +# by: PyQt5 UI code generator 5.2.1 # # WARNING! All changes made in this file will be lost! -from PyQt5 import QtCore, QtWidgets +from PyQt5 import QtCore, QtGui, QtWidgets class Ui_compileDialog(object): def setupUi(self, compileDialog): diff --git a/manuskript/ui/mainWindow.py b/manuskript/ui/mainWindow.py index fd4b7f94..793c2d8e 100644 --- a/manuskript/ui/mainWindow.py +++ b/manuskript/ui/mainWindow.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'manuskript/ui/mainWindow.ui' # -# Created: Sun Feb 28 13:22:17 2016 +# Created: Wed Mar 2 00:30:17 2016 # by: PyQt5 UI code generator 5.2.1 # # WARNING! All changes made in this file will be lost! @@ -1303,18 +1303,18 @@ class Ui_MainWindow(object): self.actCompile.setShortcut(_translate("MainWindow", "F6")) self.actToolFrequency.setText(_translate("MainWindow", "&Frequency Analyzer")) -from manuskript.ui.views.lineEditView import lineEditView -from manuskript.ui.cheatSheet import cheatSheet -from manuskript.ui.editors.mainEditor import mainEditor -from manuskript.ui.views.outlineView import outlineView -from manuskript.ui.views.treeView import treeView -from manuskript.ui.views.textEditView import textEditView from manuskript.ui.views.storylineView import storylineView +from manuskript.ui.views.textEditView import textEditView +from manuskript.ui.views.lineEditView import lineEditView +from manuskript.ui.views.treeView import treeView +from manuskript.ui.editors.mainEditor import mainEditor +from manuskript.ui.views.basicItemView import basicItemView from manuskript.ui.views.persoTreeView import persoTreeView from manuskript.ui.views.plotTreeView import plotTreeView -from manuskript.ui.search import search -from manuskript.ui.views.basicItemView import basicItemView -from manuskript.ui.welcome import welcome +from manuskript.ui.views.outlineView import outlineView +from manuskript.ui.views.metadataView import metadataView +from manuskript.ui.cheatSheet import cheatSheet from manuskript.ui.views.textEditCompleter import textEditCompleter from manuskript.ui.sldImportance import sldImportance -from manuskript.ui.views.metadataView import metadataView +from manuskript.ui.welcome import welcome +from manuskript.ui.search import search diff --git a/manuskript/ui/revisions_ui.py b/manuskript/ui/revisions_ui.py index c6e26ea2..6052e8ef 100644 --- a/manuskript/ui/revisions_ui.py +++ b/manuskript/ui/revisions_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'manuskript/ui/revisions_ui.ui' # -# Created: Tue Jul 14 19:00:07 2015 +# Created: Wed Mar 2 00:30:17 2016 # by: PyQt5 UI code generator 5.2.1 # # WARNING! All changes made in this file will be lost! diff --git a/manuskript/ui/settings_ui.py b/manuskript/ui/settings_ui.py index 3a0fd0fb..19b1c2e6 100644 --- a/manuskript/ui/settings_ui.py +++ b/manuskript/ui/settings_ui.py @@ -2,7 +2,8 @@ # Form implementation generated from reading ui file 'manuskript/ui/settings_ui.ui' # -# Created by: PyQt5 UI code generator 5.4.2 +# Created: Wed Mar 2 00:30:17 2016 +# by: PyQt5 UI code generator 5.2.1 # # WARNING! All changes made in this file will be lost! @@ -1320,6 +1321,7 @@ class Ui_Settings(object): self.layoutWidget = QtWidgets.QWidget(self.splitter) self.layoutWidget.setObjectName("layoutWidget") self.verticalLayout_14 = QtWidgets.QVBoxLayout(self.layoutWidget) + self.verticalLayout_14.setContentsMargins(0, 0, 0, 0) self.verticalLayout_14.setObjectName("verticalLayout_14") self.cmbThemeEdit = QtWidgets.QComboBox(self.layoutWidget) self.cmbThemeEdit.setObjectName("cmbThemeEdit") diff --git a/manuskript/ui/views/basicItemView.py b/manuskript/ui/views/basicItemView.py index 77a47b2a..2da5fe96 100644 --- a/manuskript/ui/views/basicItemView.py +++ b/manuskript/ui/views/basicItemView.py @@ -43,14 +43,14 @@ class basicItemView(QWidget, Ui_basicItemView): elif len(indexes) == 1: self.setEnabled(True) idx = indexes[0] - self.txtSummarySentance.setCurrentModelIndex(idx) + self.txtSummarySentence.setCurrentModelIndex(idx) self.txtSummaryFull.setCurrentModelIndex(idx) self.cmbPOV.setCurrentModelIndex(idx) self.txtGoal.setCurrentModelIndex(idx) else: self.setEnabled(True) - self.txtSummarySentance.setCurrentModelIndexes(indexes) + self.txtSummarySentence.setCurrentModelIndexes(indexes) self.txtSummaryFull.setCurrentModelIndexes(indexes) self.cmbPOV.setCurrentModelIndexes(indexes) self.txtGoal.setCurrentModelIndexes(indexes) diff --git a/manuskript/ui/views/basicItemView_ui.py b/manuskript/ui/views/basicItemView_ui.py index 94da0b0b..6be70e81 100644 --- a/manuskript/ui/views/basicItemView_ui.py +++ b/manuskript/ui/views/basicItemView_ui.py @@ -2,7 +2,8 @@ # Form implementation generated from reading ui file 'manuskript/ui/views/basicItemView_ui.ui' # -# Created by: PyQt5 UI code generator 5.4.2 +# Created: Wed Mar 2 00:33:34 2016 +# by: PyQt5 UI code generator 5.2.1 # # WARNING! All changes made in this file will be lost! @@ -67,5 +68,5 @@ class Ui_basicItemView(object): self.label_9.setText(_translate("basicItemView", "Few sentences summary:")) from manuskript.ui.views.cmbOutlinePersoChoser import cmbOutlinePersoChoser -from manuskript.ui.views.lineEditView import lineEditView from manuskript.ui.views.textEditView import textEditView +from manuskript.ui.views.lineEditView import lineEditView diff --git a/manuskript/ui/views/basicItemView_ui.ui b/manuskript/ui/views/basicItemView_ui.ui index e632ec22..64552858 100644 --- a/manuskript/ui/views/basicItemView_ui.ui +++ b/manuskript/ui/views/basicItemView_ui.ui @@ -84,7 +84,7 @@ - + diff --git a/manuskript/ui/views/metadataView.py b/manuskript/ui/views/metadataView.py index 52affbac..4791f5b1 100644 --- a/manuskript/ui/views/metadataView.py +++ b/manuskript/ui/views/metadataView.py @@ -49,7 +49,7 @@ class metadataView(QWidget, Ui_metadataView): elif len(indexes) == 1: self.setEnabled(True) idx = indexes[0] - self.txtSummarySentance.setCurrentModelIndex(idx) + self.txtSummarySentence.setCurrentModelIndex(idx) self.txtSummaryFull.setCurrentModelIndex(idx) self.txtNotes.setCurrentModelIndex(idx) self.revisions.setEnabled(True) @@ -57,7 +57,7 @@ class metadataView(QWidget, Ui_metadataView): else: self.setEnabled(True) - self.txtSummarySentance.setCurrentModelIndexes(indexes) + self.txtSummarySentence.setCurrentModelIndexes(indexes) self.txtSummaryFull.setCurrentModelIndexes(indexes) self.txtNotes.setCurrentModelIndexes(indexes) self.revisions.setEnabled(False) diff --git a/manuskript/ui/views/metadataView_ui.py b/manuskript/ui/views/metadataView_ui.py index a5c759d7..a2034baf 100644 --- a/manuskript/ui/views/metadataView_ui.py +++ b/manuskript/ui/views/metadataView_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'manuskript/ui/views/metadataView_ui.ui' # -# Created: Mon Feb 8 09:48:05 2016 +# Created: Wed Mar 2 00:30:18 2016 # by: PyQt5 UI code generator 5.2.1 # # WARNING! All changes made in this file will be lost! @@ -101,9 +101,9 @@ class Ui_metadataView(object): self.txtNotes.setPlaceholderText(_translate("metadataView", "Notes / References")) self.grpRevisions.setTitle(_translate("metadataView", "Revisions")) -from manuskript.ui.revisions import revisions +from manuskript.ui.views.lineEditView import lineEditView from manuskript.ui.views.propertiesView import propertiesView from manuskript.ui.views.textEditCompleter import textEditCompleter from manuskript.ui.views.textEditView import textEditView -from manuskript.ui.views.lineEditView import lineEditView from manuskript.ui.collapsibleGroupBox2 import collapsibleGroupBox2 +from manuskript.ui.revisions import revisions diff --git a/manuskript/ui/views/propertiesView_ui.py b/manuskript/ui/views/propertiesView_ui.py index 43c7215d..0d9a56e2 100644 --- a/manuskript/ui/views/propertiesView_ui.py +++ b/manuskript/ui/views/propertiesView_ui.py @@ -2,7 +2,8 @@ # Form implementation generated from reading ui file 'manuskript/ui/views/propertiesView_ui.ui' # -# Created by: PyQt5 UI code generator 5.4.2 +# Created: Wed Mar 2 00:30:18 2016 +# by: PyQt5 UI code generator 5.2.1 # # WARNING! All changes made in this file will be lost! @@ -107,8 +108,8 @@ class Ui_propertiesView(object): self.page_2 = QtWidgets.QWidget() self.page_2.setObjectName("page_2") self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.page_2) - self.verticalLayout_3.setContentsMargins(0, 0, 0, 0) self.verticalLayout_3.setSpacing(0) + self.verticalLayout_3.setContentsMargins(0, 0, 0, 0) self.verticalLayout_3.setObjectName("verticalLayout_3") self.formLayout_2 = QtWidgets.QFormLayout() self.formLayout_2.setObjectName("formLayout_2") @@ -194,9 +195,9 @@ class Ui_propertiesView(object): self.label_36.setText(_translate("propertiesView", "Goal")) self.txtGoalMulti.setPlaceholderText(_translate("propertiesView", "Word count")) +from manuskript.ui.views.cmbOutlineStatusChoser import cmbOutlineStatusChoser +from manuskript.ui.views.lineEditView import lineEditView from manuskript.ui.views.chkOutlineCompile import chkOutlineCompile from manuskript.ui.views.cmbOutlineLabelChoser import cmbOutlineLabelChoser -from manuskript.ui.views.cmbOutlinePersoChoser import cmbOutlinePersoChoser -from manuskript.ui.views.cmbOutlineStatusChoser import cmbOutlineStatusChoser from manuskript.ui.views.cmbOutlineTypeChoser import cmbOutlineTypeChoser -from manuskript.ui.views.lineEditView import lineEditView +from manuskript.ui.views.cmbOutlinePersoChoser import cmbOutlinePersoChoser From 1c6668cc3d43507488f9f8a9dbf1df0973eb81ad Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 2 Mar 2016 09:57:43 +0100 Subject: [PATCH 018/103] Fixes a bug in Qt < 5.3 --- manuskript/ui/views/corkDelegate.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/manuskript/ui/views/corkDelegate.py b/manuskript/ui/views/corkDelegate.py index 5f28d2eb..732dfbaa 100644 --- a/manuskript/ui/views/corkDelegate.py +++ b/manuskript/ui/views/corkDelegate.py @@ -69,7 +69,11 @@ class corkDelegate(QStyledItemDelegate): edt = QPlainTextEdit(parent) edt.setFocusPolicy(Qt.StrongFocus) edt.setFrameShape(QFrame.NoFrame) - edt.setPlaceholderText(self.tr("Full summary")) + try: + # QPlainTextEdit.setPlaceholderText was introduced in Qt 5.3 + edt.setPlaceholderText(self.tr("Full summary")) + except AttributeError: + pass return edt def updateEditorGeometry(self, editor, option, index): From 420f562c1b7cce170123c87e61ca8732055e9172 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 3 Mar 2016 16:38:38 +0100 Subject: [PATCH 019/103] New CharacterModel (changes name and uses QAbstractItemModel instead of QStandardItemModel) --- manuskript/enums.py | 4 +- manuskript/functions.py | 6 +- manuskript/mainWindow.py | 104 +++---- manuskript/models/characterModel.py | 281 ++++++++++++++++++ manuskript/models/outlineModel.py | 2 +- manuskript/models/persosModel.py | 183 ------------ manuskript/models/plotModel.py | 10 +- manuskript/models/references.py | 15 +- manuskript/ui/cheatSheet.py | 19 +- manuskript/ui/editors/editorWidget.py | 2 +- manuskript/ui/mainWindow.py | 40 +-- manuskript/ui/mainWindow.ui | 8 +- manuskript/ui/views/basicItemView.py | 4 +- ...{persoTreeView.py => characterTreeView.py} | 88 ++++-- manuskript/ui/views/cmbOutlinePersoChoser.py | 14 +- manuskript/ui/views/metadataView.py | 4 +- manuskript/ui/views/outlineBasics.py | 8 +- manuskript/ui/views/outlineDelegates.py | 28 +- manuskript/ui/views/outlineView.py | 10 +- manuskript/ui/views/propertiesView.py | 4 +- manuskript/ui/views/storylineView.py | 14 +- manuskript/ui/welcome.py | 4 +- 22 files changed, 492 insertions(+), 360 deletions(-) create mode 100644 manuskript/models/characterModel.py delete mode 100644 manuskript/models/persosModel.py rename manuskript/ui/views/{persoTreeView.py => characterTreeView.py} (59%) diff --git a/manuskript/enums.py b/manuskript/enums.py index 8fa68a58..591812e0 100644 --- a/manuskript/enums.py +++ b/manuskript/enums.py @@ -9,7 +9,7 @@ from enum import Enum #def enum(**enums): #return type(str('Enum'), (), enums) -class Perso(Enum): +class Character(Enum): name = 0 ID = 1 importance = 2 @@ -21,8 +21,6 @@ class Perso(Enum): summaryPara = 8 summaryFull = 9 notes = 10 - infoName = 11 - infoData = 12 class Plot(Enum): name = 0 diff --git a/manuskript/functions.py b/manuskript/functions.py index 2e442fdb..69799f79 100644 --- a/manuskript/functions.py +++ b/manuskript/functions.py @@ -121,9 +121,9 @@ def outlineItemColors(item): # POV colors["POV"] = QColor(Qt.transparent) POV = item.data(Outline.POV.value) - for i in range(mw.mdlPersos.rowCount()): - if mw.mdlPersos.ID(i) == POV: - colors["POV"] = iconColor(mw.mdlPersos.icon(i)) + for i in range(mw.mdlCharacter.rowCount()): + if mw.mdlCharacter.ID(i) == POV: + colors["POV"] = iconColor(mw.mdlCharacter.icon(i)) # Label lbl = item.data(Outline.label.value) diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index 9fadcf25..4d5959bc 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -9,13 +9,13 @@ from PyQt5.QtWidgets import QMainWindow, QHeaderView, qApp, QMenu, QActionGroup, QLabel from manuskript import settings -from manuskript.enums import Perso, Subplot, Plot, World +from manuskript.enums import Character, Subplot, Plot, World from manuskript.functions import AUC, wordCount, appPath from manuskript.loadSave import loadStandardItemModelXML, loadFilesFromZip from manuskript.loadSave import saveFilesToZip from manuskript.loadSave import saveStandardItemModelXML +from manuskript.models.characterModel import characterModel from manuskript.models.outlineModel import outlineModel -from manuskript.models.persosModel import persosModel from manuskript.models.plotModel import plotModel from manuskript.models.worldModel import worldModel from manuskript.settingsWindow import settingsWindow @@ -144,15 +144,18 @@ class MainWindow(QMainWindow, Ui_MainWindow): # PERSOS ############################################################################### - def changeCurrentPerso(self, trash=None): + def changeCurrentCharacter(self, trash=None): + """ - index = self.lstPersos.currentPersoIndex() - - if not index.isValid(): + @return: + """ + c = self.lstCharacters.currentCharacter() + if not c: self.tabPlot.setEnabled(False) return self.tabPersos.setEnabled(True) + index = c.index() for w in [ self.txtPersoName, @@ -169,27 +172,24 @@ class MainWindow(QMainWindow, Ui_MainWindow): w.setCurrentModelIndex(index) # Button color - self.mdlPersos.updatePersoColor(index) + self.updateCharacterColor(c.ID()) - # Perso Infos + # Character Infos self.tblPersoInfos.setRootIndex(index) - if self.mdlPersos.rowCount(index): + if self.mdlCharacter.rowCount(index): self.updatePersoInfoView() def updatePersoInfoView(self): - # Hide columns - for i in range(self.mdlPersos.columnCount()): - self.tblPersoInfos.hideColumn(i) - self.tblPersoInfos.showColumn(Perso.infoName.value) - self.tblPersoInfos.showColumn(Perso.infoData.value) - - self.tblPersoInfos.horizontalHeader().setSectionResizeMode( - Perso.infoName.value, QHeaderView.ResizeToContents) - self.tblPersoInfos.horizontalHeader().setSectionResizeMode( - Perso.infoData.value, QHeaderView.Stretch) + self.tblPersoInfos.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents) + self.tblPersoInfos.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch) self.tblPersoInfos.verticalHeader().hide() + def updateCharacterColor(self, ID): + c = self.mdlCharacter.getCharacterByID(ID) + color = c.color().name() + self.btnPersoColor.setStyleSheet("background:{};".format(color)) + ############################################################################### # PLOTS ############################################################################### @@ -340,7 +340,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.saveTimerNoChanges.setSingleShot(True) self.mdlFlatData.dataChanged.connect(self.startTimerNoChanges) self.mdlOutline.dataChanged.connect(self.startTimerNoChanges) - self.mdlPersos.dataChanged.connect(self.startTimerNoChanges) + self.mdlCharacter.dataChanged.connect(self.startTimerNoChanges) self.mdlPlots.dataChanged.connect(self.startTimerNoChanges) self.mdlWorld.dataChanged.connect(self.startTimerNoChanges) # self.mdlPersosInfos.dataChanged.connect(self.startTimerNoChanges) @@ -467,8 +467,8 @@ class MainWindow(QMainWindow, Ui_MainWindow): files.append((saveStandardItemModelXML(self.mdlFlatData), "flatModel.xml")) - files.append((saveStandardItemModelXML(self.mdlPersos), - "perso.xml")) + # files.append((saveStandardItemModelXML(self.mdlCharacter), + # "perso.xml")) files.append((saveStandardItemModelXML(self.mdlWorld), "world.xml")) files.append((saveStandardItemModelXML(self.mdlLabels), @@ -491,7 +491,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): def loadEmptyDatas(self): self.mdlFlatData = QStandardItemModel(self) - self.mdlPersos = persosModel(self) + self.mdlCharacter = characterModel(self) # self.mdlPersosProxy = persosProxyModel(self) # self.mdlPersosInfos = QStandardItemModel(self) self.mdlLabels = QStandardItemModel(self) @@ -513,7 +513,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): errors.append("flatModel.xml") if "perso.xml" in files: - loadStandardItemModelXML(self.mdlPersos, + loadStandardItemModelXML(self.mdlCharacter, files["perso.xml"], fromString=True) else: errors.append("perso.xml") @@ -570,7 +570,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): def makeUIConnections(self): "Connections that have to be made once only, event when new project is loaded." - self.lstPersos.currentItemChanged.connect(self.changeCurrentPerso, AUC) + self.lstCharacters.currentItemChanged.connect(self.changeCurrentCharacter, AUC) self.txtPlotFilter.textChanged.connect(self.lstPlots.setFilter, AUC) self.lstPlots.currentItemChanged.connect(self.changeCurrentPlot, AUC) @@ -622,29 +622,29 @@ class MainWindow(QMainWindow, Ui_MainWindow): widget.setCurrentModelIndex(self.mdlFlatData.index(0, col)) # Persos - self.lstPersos.setPersosModel(self.mdlPersos) - self.tblPersoInfos.setModel(self.mdlPersos) + self.lstCharacters.setCharactersModel(self.mdlCharacter) + self.tblPersoInfos.setModel(self.mdlCharacter) - self.btnAddPerso.clicked.connect(self.mdlPersos.addPerso, AUC) - self.btnRmPerso.clicked.connect(self.mdlPersos.removePerso, AUC) - self.btnPersoColor.clicked.connect(self.mdlPersos.chosePersoColor, AUC) + self.btnAddPerso.clicked.connect(self.mdlCharacter.addCharacter, AUC) + self.btnRmPerso.clicked.connect(self.lstCharacters.removeCharacter, AUC) + self.btnPersoColor.clicked.connect(self.lstCharacters.choseCharacterColor, AUC) - self.btnPersoAddInfo.clicked.connect(self.mdlPersos.addPersoInfo, AUC) - self.btnPersoRmInfo.clicked.connect(self.mdlPersos.removePersoInfo, AUC) + self.btnPersoAddInfo.clicked.connect(self.lstCharacters.addCharacterInfo, AUC) + self.btnPersoRmInfo.clicked.connect(self.lstCharacters.removeCharacterInfo, AUC) for w, c in [ - (self.txtPersoName, Perso.name.value), - (self.sldPersoImportance, Perso.importance.value), - (self.txtPersoMotivation, Perso.motivation.value), - (self.txtPersoGoal, Perso.goal.value), - (self.txtPersoConflict, Perso.conflict.value), - (self.txtPersoEpiphany, Perso.epiphany.value), - (self.txtPersoSummarySentence, Perso.summarySentence.value), - (self.txtPersoSummaryPara, Perso.summaryPara.value), - (self.txtPersoSummaryFull, Perso.summaryFull.value), - (self.txtPersoNotes, Perso.notes.value) + (self.txtPersoName, Character.name.value), + (self.sldPersoImportance, Character.importance.value), + (self.txtPersoMotivation, Character.motivation.value), + (self.txtPersoGoal, Character.goal.value), + (self.txtPersoConflict, Character.conflict.value), + (self.txtPersoEpiphany, Character.epiphany.value), + (self.txtPersoSummarySentence, Character.summarySentence.value), + (self.txtPersoSummaryPara, Character.summaryPara.value), + (self.txtPersoSummaryFull, Character.summaryFull.value), + (self.txtPersoNotes, Character.notes.value) ]: - w.setModel(self.mdlPersos) + w.setModel(self.mdlCharacter) w.setColumn(c) self.tabPersos.setEnabled(False) @@ -672,10 +672,10 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.tabPlot.setEnabled(False) self.mdlPlots.updatePlotPersoButton() - self.mdlPersos.dataChanged.connect(self.mdlPlots.updatePlotPersoButton) + self.mdlCharacter.dataChanged.connect(self.mdlPlots.updatePlotPersoButton) self.lstOutlinePlots.setPlotModel(self.mdlPlots) self.lstOutlinePlots.setShowSubPlot(True) - self.plotPersoDelegate = outlinePersoDelegate(self.mdlPersos, self) + self.plotPersoDelegate = outlinePersoDelegate(self.mdlCharacter, self) self.lstPlotPerso.setItemDelegate(self.plotPersoDelegate) self.plotDelegate = plotDelegate(self) self.lstSubPlots.setItemDelegateForColumn(Subplot.meta.value, self.plotDelegate) @@ -702,18 +702,18 @@ class MainWindow(QMainWindow, Ui_MainWindow): # Outline self.treeRedacOutline.setModel(self.mdlOutline) - self.treeOutlineOutline.setModelPersos(self.mdlPersos) + self.treeOutlineOutline.setModelCharacters(self.mdlCharacter) self.treeOutlineOutline.setModelLabels(self.mdlLabels) self.treeOutlineOutline.setModelStatus(self.mdlStatus) - self.redacMetadata.setModels(self.mdlOutline, self.mdlPersos, + self.redacMetadata.setModels(self.mdlOutline, self.mdlCharacter, self.mdlLabels, self.mdlStatus) - self.outlineItemEditor.setModels(self.mdlOutline, self.mdlPersos, + self.outlineItemEditor.setModels(self.mdlOutline, self.mdlCharacter, self.mdlLabels, self.mdlStatus) self.treeOutlineOutline.setModel(self.mdlOutline) # self.redacEditor.setModel(self.mdlOutline) - self.storylineView.setModels(self.mdlOutline, self.mdlPersos, self.mdlPlots) + self.storylineView.setModels(self.mdlOutline, self.mdlCharacter, self.mdlPlots) self.treeOutlineOutline.selectionModel().selectionChanged.connect(lambda: self.outlineItemEditor.selectionChanged( @@ -735,10 +735,10 @@ class MainWindow(QMainWindow, Ui_MainWindow): # Debug self.mdlFlatData.setVerticalHeaderLabels(["Infos générales", "Summary"]) self.tblDebugFlatData.setModel(self.mdlFlatData) - self.tblDebugPersos.setModel(self.mdlPersos) - self.tblDebugPersosInfos.setModel(self.mdlPersos) + self.tblDebugPersos.setModel(self.mdlCharacter) + self.tblDebugPersosInfos.setModel(self.mdlCharacter) self.tblDebugPersos.selectionModel().currentChanged.connect( - lambda: self.tblDebugPersosInfos.setRootIndex(self.mdlPersos.index( + lambda: self.tblDebugPersosInfos.setRootIndex(self.mdlCharacter.index( self.tblDebugPersos.selectionModel().currentIndex().row(), Perso.name.value)), AUC) diff --git a/manuskript/models/characterModel.py b/manuskript/models/characterModel.py new file mode 100644 index 00000000..72f525bf --- /dev/null +++ b/manuskript/models/characterModel.py @@ -0,0 +1,281 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- +from PyQt5.QtCore import QModelIndex, Qt, QAbstractItemModel, QVariant +from PyQt5.QtGui import QIcon, QPixmap, QColor + +from manuskript.functions import randomColor, iconColor, mainWindow +from manuskript.enums import Character as C + + +class characterModel(QAbstractItemModel): + + def __init__(self, parent): + QAbstractItemModel.__init__(self, parent) + + self.characters = [] + +############################################################################### +# QAbstractItemModel subclassed +############################################################################### + + def rowCount(self, parent=QModelIndex()): + if parent.isValid(): + c = parent.internalPointer() + return len(c.infos) + else: + return len(self.characters) + + def columnCount(self, parent=QModelIndex()): + if parent.isValid(): + # Returns characters infos + return 2 + else: + return 1 + + def data(self, index, role=Qt.DisplayRole): + c = index.internalPointer() + if type(c) == Character: + if role == Qt.DisplayRole: + if index.column() in c._data: + return c._data[index.column()] + else: + return QVariant() + + elif type(c) == CharacterInfo: + if role == Qt.DisplayRole or role == Qt.EditRole: + if index.column() == 0: + return c.description + elif index.column() == 1: + return c.value + + def setData(self, index, value, role=Qt.EditRole): + c = index.internalPointer() + if type(c) == Character: + if role == Qt.EditRole: + # We update only if data is different + if index.column() not in c._data or c._data[index.column()] != value: + c._data[index.column()] = value + self.dataChanged.emit(index, index) + return True + + elif type(c) == CharacterInfo: + if role == Qt.EditRole: + if index.column() == 0: + c.description = value + elif index.column() == 1: + c.value = value + self.dataChanged.emit(index, index) + return True + + return False + + def index(self, row, column, parent=QModelIndex()): + if not parent.isValid(): + return self.createIndex(row, column, self.characters[row]) + + else: + c = parent.internalPointer() + if row < len(c.infos): + return self.createIndex(row, column, c.infos[row]) + else: + return QModelIndex() + + def indexFromItem(self, item, column=0): + if not item: + return QModelIndex() + + row = self.characters.index(item) + col = column + return self.createIndex(row, col, item) + + def parent(self, index): + if not index.isValid(): + return QModelIndex() + + child = index.internalPointer() + + if type(child) == Character: + return QModelIndex() + + elif type(child) == CharacterInfo: + return child.character.index() + + def flags(self, index): + if index.parent().isValid(): + return QAbstractItemModel.flags(self, index) | Qt.ItemIsEditable + else: + return QAbstractItemModel.flags(self, index) + +############################################################################### +# CHARACTER QUERRIES +############################################################################### + + def character(self, row): + return self.characters[row] + + def name(self, row): + return self.character(row).name() + + def icon(self, row): + return self.character(row).icon + + def ID(self, row): + return self.character(row).ID() + + def importance(self, row): + return self.character(row).importance() + +############################################################################### +# MODEL QUERRIES +############################################################################### + + def getCharactersByImportance(self): + """ + Lists characters by importance. + + @return: array of array of ´character´, by importance. + """ + r = [[], [], []] + for c in self.characters: + r[2-int(c.importance())].append(c) + return r + + def getCharacterByID(self, ID): + for c in self.characters: + if c.ID() == ID: + return c + return None + +############################################################################### +# ADDING / REMOVING +############################################################################### + + def addCharacter(self): + """ + Creates a new character + @return: nothing + """ + c = Character(model=self, name=self.tr("New character")) + self.beginInsertRows(QModelIndex(), len(self.characters), len(self.characters)) + self.characters.append(c) + self.endInsertRows() + + def removeCharacter(self, ID): + """ + Removes character whose ID is ID... + @param ID: the ID of the character to remove + @return: nothing + """ + c = self.getCharacterByID(ID) + self.beginRemoveRows(QModelIndex(), self.characters.index(c), self.characters.index(c)) + self.characters.remove(c) + +############################################################################### +# CHARACTER INFOS +############################################################################### + + def headerData(self, section, orientation, role=Qt.DisplayRole): + if role == Qt.DisplayRole and orientation == Qt.Horizontal: + if section == 0: + return self.tr("Name") + elif section == 1: + return self.tr("Value") + else: + return C(section).name + + def addCharacterInfo(self, ID): + c = self.getCharacterByID(ID) + self.beginInsertRows(c.index(), len(c.infos), len(c.infos)) + c.infos.append(CharacterInfo(c, description="Description", value="Value")) + self.endInsertRows() + + mainWindow().updatePersoInfoView() + + def removeCharacterInfo(self, ID): + c = self.getCharacterByID(ID) + + rm = [] + for idx in mainWindow().tblPersoInfos.selectedIndexes(): + if not idx.row() in rm: + rm.append(idx.row()) + + rm.sort() + rm.reverse() + for r in rm: + self.beginRemoveRows(c.index(), r, r) + c.infos.pop(r) + self.endRemoveRows() + +############################################################################### +# CHARACTER +############################################################################### + +class Character(): + def __init__(self, model, name): + self._model = model + + self._data = {} + self._data[C.name.value] = name + self.assignUniqueID() + self.assignRandomColor() + self._data[C.importance.value] = "0" + + self.infos = [] + + def name(self): + return self._data[C.name.value] + + def importance(self): + return self._data[C.importance.value] + + def ID(self): + return self._data[C.ID.value] + + def index(self, column=0): + return self._model.indexFromItem(self, column) + + def assignRandomColor(self): + """ + Assigns a random color the the character. + """ + color = randomColor(QColor(Qt.white)) + self.setColor(color) + + def setColor(self, color): + """ + Sets the character's color + @param color: QColor. + """ + px = QPixmap(32, 32) + px.fill(color) + self.icon = QIcon(px) + try: + self._model.dataChanged.emit(self.index(), self.index()) + except: + # If it is the initialisation, won't be able to emit + pass + + def color(self): + """ + Returns character's color in QColor + @return: QColor + """ + return iconColor(self.icon) + + def assignUniqueID(self, parent=QModelIndex()): + """Assigns an unused character ID.""" + vals = [] + for c in self._model.characters: + vals.append(c.ID()) + + k = 0 + while k in vals: + k += 1 + + self._data[C.ID.value] = k + +class CharacterInfo(): + def __init__(self, character, description="", value=""): + self.description = description + self.value = value + self.character = character \ No newline at end of file diff --git a/manuskript/models/outlineModel.py b/manuskript/models/outlineModel.py index a3450a8b..6fc547df 100644 --- a/manuskript/models/outlineModel.py +++ b/manuskript/models/outlineModel.py @@ -855,7 +855,7 @@ class outlineItem(): for c in columns: if c == Outline.POV.value: - searchIn = mainWindow.mdlPersos.getPersoNameByID(self.POV()) + searchIn = mainWindow.mdlCharacter.getPersoNameByID(self.POV()) elif c == Outline.status.value: searchIn = mainWindow.mdlStatus.item(toInt(self.status()), 0).text() diff --git a/manuskript/models/persosModel.py b/manuskript/models/persosModel.py deleted file mode 100644 index bd261911..00000000 --- a/manuskript/models/persosModel.py +++ /dev/null @@ -1,183 +0,0 @@ -#!/usr/bin/env python -# --!-- coding: utf8 --!-- -from PyQt5.QtCore import QModelIndex, Qt -from PyQt5.QtGui import QStandardItemModel, QStandardItem, QColor, QPixmap, QIcon -from PyQt5.QtWidgets import QColorDialog - -from manuskript.enums import Perso -from manuskript.enums import Plot -from manuskript.functions import iconColor -from manuskript.functions import mainWindow -from manuskript.functions import randomColor -from manuskript.functions import toInt - - -class persosModel(QStandardItemModel): - - def __init__(self, parent): - QStandardItemModel.__init__(self, 0, 3, parent) - self.setHorizontalHeaderLabels([i.name for i in Perso]) - self.mw = mainWindow() - # self._proxy = plotsProxyModel() - # self._proxy.setSourceModel(self) - -############################################################################### -# PERSOS QUERRIES -############################################################################### - - def name(self, row): - return self.item(row, Perso.name.value).text() - - def icon(self, row): - return self.item(row, Perso.name.value).icon() - - def ID(self, row): - return self.item(row, Perso.ID.value).text() - - def importance(self, row): - return self.item(row, Perso.importance.value).text() - -############################################################################### -# MODEL QUERRIES -############################################################################### - - def getPersosByImportance(self): - persos = [[], [], []] - for i in range(self.rowCount()): - importance = self.item(i, Perso.importance.value).text() - ID = self.item(i, Perso.ID.value).text() - persos[2-toInt(importance)].append(ID) - return persos - - def getPersoNameByID(self, ID): - index = self.getIndexFromID(ID) - if index.isValid(): - return self.name(index.row()) - return "" - - def getIndexFromID(self, ID): - for i in range(self.rowCount()): - _ID = self.item(i, Perso.ID.value).text() - if _ID == ID or toInt(_ID) == ID: - return self.index(i, 0) - return QModelIndex() - - def getPersoColorByID(self, ID): - idx = self.getIndexFromID(ID) - return self.getPersoColorName(idx) - - def getPersoColorName(self, index): - icon = self.item(index.row()).icon() - return iconColor(icon).name() if icon else "" - - def currentListIndex(self): - i = self.mw.lstPersos.currentIndex() - if i .isValid(): - return i - else: - return None - - def currentPersoIndex(self): - return self.mw.lstPersos.currentPersoIndex() - -############################################################################### -# ADDING / REMOVING -############################################################################### - - def addPerso(self): - """Creates a perso by adding a row in mdlPersos - and a column in mdlPersosInfos with same ID""" - p = QStandardItem(self.tr("New character")) - self.setPersoColor(p, randomColor(QColor(Qt.white))) - - pid = self.getUniqueID() - self.appendRow([p, QStandardItem(pid), QStandardItem("0")]) - - def getUniqueID(self, parent=QModelIndex()): - """Returns an unused perso ID (row 1).""" - vals = [] - for i in range(self.rowCount(parent)): - index = self.index(i, Perso.ID.value, parent) - if index.isValid() and index.data(): - vals.append(int(index.data())) - - k = 0 - while k in vals: - k += 1 - return str(k) - - def removePerso(self): - index = self.currentPersoIndex() - self.takeRow(index.row()) - - def setPersoColor(self, item, color): - px = QPixmap(32, 32) - px.fill(color) - item.setIcon(QIcon(px)) - - def chosePersoColor(self): - idx = self.currentPersoIndex() - item = self.item(idx.row(), Perso.name.value) - if item: - color = iconColor(item.icon()) - else: - color = Qt.white - self.colorDialog = QColorDialog(color, self.mw) - color = self.colorDialog.getColor(color) - if color.isValid(): - self.setPersoColor(item, color) - self.updatePersoColor(idx) - -############################################################################### -# UI -############################################################################### - - def updatePersoColor(self, idx): - # idx = self.currentPersoIndex() - color = self.getPersoColorName(idx) - self.mw.btnPersoColor.setStyleSheet("background:{};".format(color)) - -############################################################################### -# PERSO INFOS -############################################################################### - - def headerData(self, section, orientation, role=Qt.DisplayRole): - if role == Qt.DisplayRole and orientation == Qt.Horizontal: - if section == Perso.infoName.value: - return self.tr("Name") - elif section == Perso.infoData.value: - return self.tr("Value") - else: - return Perso(section).name - else: - return QStandardItemModel.headerData(self, section, orientation, role) - - def addPersoInfo(self): - perso = self.itemFromIndex(self.currentPersoIndex()) - row = perso.rowCount() - perso.setChild(row, Perso.infoName.value, QStandardItem("")) - perso.setChild(row, Perso.infoData.value, QStandardItem("")) - - self.mw.updatePersoInfoView() - - def removePersoInfo(self): - perso = self.itemFromIndex(self.currentPersoIndex()) - - rm = [] - for idx in self.mw.tblPersoInfos.selectedIndexes(): - if not idx.row() in rm: - rm.append(idx.row()) - - rm.sort() - rm.reverse() - for r in rm: - perso.takeRow(r) - - def listPersoInfos(self, index): - infos = [] - for i in range(self.rowCount(index)): - name = self.data(index.child(i, Perso.infoName.value)) - val = self.data(index.child(i, Perso.infoData.value)) - infos.append((name, val)) - - return infos diff --git a/manuskript/models/plotModel.py b/manuskript/models/plotModel.py index 456b8504..b7f4949f 100644 --- a/manuskript/models/plotModel.py +++ b/manuskript/models/plotModel.py @@ -212,13 +212,13 @@ class plotModel(QStandardItemModel): menu.addMenu(m) mpr = QSignalMapper(menu) - for i in range(self.mw.mdlPersos.rowCount()): - a = QAction(self.mw.mdlPersos.name(i), menu) - a.setIcon(self.mw.mdlPersos.icon(i)) + for i in range(self.mw.mdlCharacter.rowCount()): + a = QAction(self.mw.mdlCharacter.name(i), menu) + a.setIcon(self.mw.mdlCharacter.icon(i)) a.triggered.connect(mpr.map) - mpr.setMapping(a, int(self.mw.mdlPersos.ID(i))) + mpr.setMapping(a, int(self.mw.mdlCharacter.ID(i))) - imp = toInt(self.mw.mdlPersos.importance(i)) + imp = toInt(self.mw.mdlCharacter.importance(i)) menus[2 - imp].addAction(a) diff --git a/manuskript/models/references.py b/manuskript/models/references.py index 6ee37ba7..6aeeb374 100644 --- a/manuskript/models/references.py +++ b/manuskript/models/references.py @@ -11,7 +11,7 @@ import re from PyQt5.QtWidgets import qApp from manuskript.enums import Outline -from manuskript.enums import Perso +from manuskript.enums import Character from manuskript.enums import Plot from manuskript.enums import Subplot from manuskript.functions import mainWindow @@ -104,7 +104,7 @@ def infos(ref): if item.POV(): POV = "{text}".format( ref=persoReference(item.POV()), - text=mainWindow().mdlPersos.getPersoNameByID(item.POV())) + text=mainWindow().mdlCharacter.getCharacterByID(item.POV()).name()) # The status of the scene status = item.status() @@ -175,7 +175,7 @@ def infos(ref): # A character elif _type == PersoLetter: - m = mainWindow().mdlPersos + m = mainWindow().mdlCharacter index = m.getIndexFromID(_ref) name = m.name(index.row()) @@ -272,7 +272,7 @@ def infos(ref): Plot.result.value)) # Characters - pM = mainWindow().mdlPersos + pM = mainWindow().mdlCharacter item = m.item(index.row(), Plot.persos.value) characters = "" if item: @@ -412,7 +412,7 @@ def shortInfos(ref): infos["type"] = PersoLetter - m = mainWindow().mdlPersos + m = mainWindow().mdlCharacter item = m.item(int(_ref), Perso.name.value) if item: infos["title"] = item.text() @@ -516,7 +516,7 @@ def refToLink(ref): text = item.title() elif _type == PersoLetter: - m = mainWindow().mdlPersos + m = mainWindow().mdlCharacter text = m.item(int(_ref), Perso.name.value).text() elif _type == PlotLetter: @@ -620,11 +620,12 @@ def open(ref): if _type == PersoLetter: mw = mainWindow() + # FIXME item = mw.lstPersos.getItemByID(_ref) if item: mw.tabMain.setCurrentIndex(mw.TabPersos) - mw.lstPersos.setCurrentItem(item) + mw.lstCharacters.setCurrentItem(item) return True print("Ref not found") diff --git a/manuskript/ui/cheatSheet.py b/manuskript/ui/cheatSheet.py index fc445ab7..1ab6c2b0 100644 --- a/manuskript/ui/cheatSheet.py +++ b/manuskript/ui/cheatSheet.py @@ -4,7 +4,7 @@ from PyQt5.QtCore import pyqtSignal, Qt, QTimer, QRect from PyQt5.QtGui import QBrush, QCursor, QPalette, QFontMetrics from PyQt5.QtWidgets import QWidget, QListWidgetItem, QToolTip, QStyledItemDelegate, QStyle -from manuskript.enums import Perso +from manuskript.enums import Character from manuskript.enums import Plot from manuskript.functions import lightBlue from manuskript.functions import mainWindow @@ -36,7 +36,7 @@ class cheatSheet(QWidget, Ui_cheatSheet): self.line.hide() self.outlineModel = None - self.persoModel = None + self.characterModel = None self.plotModel = None self.worldModel = None @@ -53,12 +53,12 @@ class cheatSheet(QWidget, Ui_cheatSheet): def setModels(self): mw = mainWindow() self.outlineModel = mw.mdlOutline - self.persoModel = mw.mdlPersos + self.characterModel = mw.mdlCharacter self.plotModel = mw.mdlPlots self.worldModel = mw.mdlWorld self.outlineModel.dataChanged.connect(self.populateTimer.start) - self.persoModel.dataChanged.connect(self.populateTimer.start) + self.characterModel.dataChanged.connect(self.populateTimer.start) self.plotModel.dataChanged.connect(self.populateTimer.start) self.worldModel.dataChanged.connect(self.populateTimer.start) @@ -75,15 +75,12 @@ class cheatSheet(QWidget, Ui_cheatSheet): self.list.hide() def populate(self): - if self.persoModel: + if self.characterModel: d = [] - for r in range(self.persoModel.rowCount()): - name = self.persoModel.item(r, Perso.name.value).text() - ID = self.persoModel.item(r, Perso.ID.value).text() - imp = self.persoModel.item(r, Perso.importance.value).text() - imp = [self.tr("Minor"), self.tr("Secondary"), self.tr("Main")][int(imp)] - d.append((name, ID, imp)) + for c in self.characterModel.characters: + imp = [self.tr("Minor"), self.tr("Secondary"), self.tr("Main")][int(c.importance())] + d.append((c.name(), c.ID(), imp)) self.data[(self.tr("Characters"), Ref.PersoLetter)] = d diff --git a/manuskript/ui/editors/editorWidget.py b/manuskript/ui/editors/editorWidget.py index 0451377f..20487b38 100644 --- a/manuskript/ui/editors/editorWidget.py +++ b/manuskript/ui/editors/editorWidget.py @@ -173,7 +173,7 @@ class editorWidget(QWidget, Ui_editorWidget_ui): elif item and item.isFolder() and self.folderView == "outline": self.stack.setCurrentIndex(3) - self.outlineView.setModelPersos(mainWindow().mdlPersos) + self.outlineView.setModelCharacters(mainWindow().mdlCharacter) self.outlineView.setModelLabels(mainWindow().mdlLabels) self.outlineView.setModelStatus(mainWindow().mdlStatus) self.outlineView.setModel(self.mw.mdlOutline) diff --git a/manuskript/ui/mainWindow.py b/manuskript/ui/mainWindow.py index 793c2d8e..9d8762b7 100644 --- a/manuskript/ui/mainWindow.py +++ b/manuskript/ui/mainWindow.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'manuskript/ui/mainWindow.ui' # -# Created: Wed Mar 2 00:30:17 2016 +# Created: Thu Mar 3 13:40:20 2016 # by: PyQt5 UI code generator 5.2.1 # # WARNING! All changes made in this file will be lost! @@ -337,12 +337,12 @@ class Ui_MainWindow(object): self.groupBox.setObjectName("groupBox") self.verticalLayout_8 = QtWidgets.QVBoxLayout(self.groupBox) self.verticalLayout_8.setObjectName("verticalLayout_8") - self.lstPersos = persoTreeView(self.groupBox) - self.lstPersos.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.lstPersos.setDragEnabled(True) - self.lstPersos.setObjectName("lstPersos") - self.lstPersos.headerItem().setText(0, "1") - self.verticalLayout_8.addWidget(self.lstPersos) + self.lstCharacters = characterTreeView(self.groupBox) + self.lstCharacters.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.lstCharacters.setDragEnabled(True) + self.lstCharacters.setObjectName("lstCharacters") + self.lstCharacters.headerItem().setText(0, "1") + self.verticalLayout_8.addWidget(self.lstCharacters) self.horizontalLayout_14 = QtWidgets.QHBoxLayout() self.horizontalLayout_14.setObjectName("horizontalLayout_14") self.btnAddPerso = QtWidgets.QPushButton(self.groupBox) @@ -1166,7 +1166,7 @@ class Ui_MainWindow(object): self.retranslateUi(MainWindow) self.stack.setCurrentIndex(1) - self.tabMain.setCurrentIndex(6) + self.tabMain.setCurrentIndex(2) self.tabSummary.setCurrentIndex(0) self.tabPersos.setCurrentIndex(0) self.tabPlot.setCurrentIndex(0) @@ -1303,18 +1303,18 @@ class Ui_MainWindow(object): self.actCompile.setShortcut(_translate("MainWindow", "F6")) self.actToolFrequency.setText(_translate("MainWindow", "&Frequency Analyzer")) -from manuskript.ui.views.storylineView import storylineView -from manuskript.ui.views.textEditView import textEditView -from manuskript.ui.views.lineEditView import lineEditView -from manuskript.ui.views.treeView import treeView -from manuskript.ui.editors.mainEditor import mainEditor -from manuskript.ui.views.basicItemView import basicItemView -from manuskript.ui.views.persoTreeView import persoTreeView -from manuskript.ui.views.plotTreeView import plotTreeView -from manuskript.ui.views.outlineView import outlineView -from manuskript.ui.views.metadataView import metadataView +from manuskript.ui.search import search from manuskript.ui.cheatSheet import cheatSheet from manuskript.ui.views.textEditCompleter import textEditCompleter -from manuskript.ui.sldImportance import sldImportance +from manuskript.ui.views.lineEditView import lineEditView from manuskript.ui.welcome import welcome -from manuskript.ui.search import search +from manuskript.ui.views.characterTreeView import characterTreeView +from manuskript.ui.sldImportance import sldImportance +from manuskript.ui.views.plotTreeView import plotTreeView +from manuskript.ui.views.basicItemView import basicItemView +from manuskript.ui.views.outlineView import outlineView +from manuskript.ui.views.metadataView import metadataView +from manuskript.ui.views.treeView import treeView +from manuskript.ui.editors.mainEditor import mainEditor +from manuskript.ui.views.storylineView import storylineView +from manuskript.ui.views.textEditView import textEditView diff --git a/manuskript/ui/mainWindow.ui b/manuskript/ui/mainWindow.ui index 1a5390d1..7e34ea70 100644 --- a/manuskript/ui/mainWindow.ui +++ b/manuskript/ui/mainWindow.ui @@ -124,7 +124,7 @@ QTabWidget::Rounded - 6 + 2 true @@ -714,7 +714,7 @@ - + Qt::ScrollBarAlwaysOff @@ -2396,9 +2396,9 @@ QListView::item:hover { 1 - persoTreeView + characterTreeView QTreeWidget -
manuskript.ui.views.persoTreeView.h
+
manuskript.ui.views.characterTreeView.h
cheatSheet diff --git a/manuskript/ui/views/basicItemView.py b/manuskript/ui/views/basicItemView.py index 2da5fe96..42dc1cb3 100644 --- a/manuskript/ui/views/basicItemView.py +++ b/manuskript/ui/views/basicItemView.py @@ -14,8 +14,8 @@ class basicItemView(QWidget, Ui_basicItemView): self.txtSummaryFull.setColumn(Outline.summaryFull.value) self.txtGoal.setColumn(Outline.setGoal.value) - def setModels(self, mdlOutline, mdlPersos, mdlLabels, mdlStatus): - self.cmbPOV.setModels(mdlPersos, mdlOutline) + def setModels(self, mdlOutline, mdlCharacter, mdlLabels, mdlStatus): + self.cmbPOV.setModels(mdlCharacter, mdlOutline) self.txtSummarySentence.setModel(mdlOutline) self.txtSummaryFull.setModel(mdlOutline) self.txtGoal.setModel(mdlOutline) diff --git a/manuskript/ui/views/persoTreeView.py b/manuskript/ui/views/characterTreeView.py similarity index 59% rename from manuskript/ui/views/persoTreeView.py rename to manuskript/ui/views/characterTreeView.py index fd60d192..71e354a1 100644 --- a/manuskript/ui/views/persoTreeView.py +++ b/manuskript/ui/views/characterTreeView.py @@ -2,12 +2,16 @@ # --!-- coding: utf8 --!-- from PyQt5.QtCore import QSize, QModelIndex, Qt from PyQt5.QtGui import QPixmap, QColor, QIcon, QBrush -from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem +from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QColorDialog -from manuskript.enums import Perso +from manuskript.enums import Character +from manuskript.functions import iconColor, mainWindow -class persoTreeView(QTreeWidget): +class characterTreeView(QTreeWidget): + """ + A QTreeWidget that displays characters from a characterModel in respect of their importance. + """ def __init__(self, parent=None): QTreeWidget.__init__(self, parent) self._model = None @@ -24,7 +28,7 @@ class persoTreeView(QTreeWidget): self._rootItem = QTreeWidgetItem() self.insertTopLevelItem(0, self._rootItem) - def setPersosModel(self, model): + def setCharactersModel(self, model): self._model = model self._model.dataChanged.connect(self.updateMaybe) self._model.rowsInserted.connect(self.updateMaybe2) @@ -39,11 +43,11 @@ class persoTreeView(QTreeWidget): if topLeft.parent() != QModelIndex(): return - if topLeft.column() <= Perso.name.value <= bottomRight.column(): + if topLeft.column() <= Character.name.value <= bottomRight.column(): # Update name self.updateNames() - elif topLeft.column() <= Perso.importance.value <= bottomRight.column(): + elif topLeft.column() <= Character.importance.value <= bottomRight.column(): # Importance changed self.updateItems() @@ -56,16 +60,17 @@ class persoTreeView(QTreeWidget): for i in range(self.topLevelItemCount()): item = self.topLevelItem(i) - for c in range(item.childCount()): - sub = item.child(c) + for child in range(item.childCount()): + sub = item.child(child) ID = sub.data(0, Qt.UserRole) - if ID: + if ID is not None: # Update name - name = self._model.getPersoNameByID(ID) + c = self._model.getCharacterByID(ID) + name = c.name() sub.setText(0, name) # Update icon px = QPixmap(32, 32) - color = QColor(self._model.getPersoColorByID(ID)) + color = c.color() px.fill(color) sub.setIcon(0, QIcon(px)) @@ -78,10 +83,12 @@ class persoTreeView(QTreeWidget): self._updating = True self.clear() - persos = self._model.getPersosByImportance() + characters = self._model.getCharactersByImportance() h = [self.tr("Main"), self.tr("Secondary"), self.tr("Minor")] + for i in range(3): + # Create category item cat = QTreeWidgetItem(self, [h[i]]) cat.setBackground(0, QBrush(QColor(Qt.blue).lighter(190))) cat.setForeground(0, QBrush(Qt.darkBlue)) @@ -92,36 +99,67 @@ class persoTreeView(QTreeWidget): self.addTopLevelItem(cat) # cat.setChildIndicatorPolicy(cat.DontShowIndicator) - for ID in persos[i]: - name = self._model.getPersoNameByID(ID) + for c in characters[i]: + name = c.name() + # Check if name passes filter if not self._filter.lower() in name.lower(): continue + item = QTreeWidgetItem(cat, [name]) - item.setData(0, Qt.UserRole, ID) + item.setData(0, Qt.UserRole, c.ID()) px = QPixmap(32, 32) - color = QColor(self._model.getPersoColorByID(ID)) + color = QColor(c.color()) px.fill(color) item.setIcon(0, QIcon(px)) - if ID == self._lastID: + if c.ID() == self._lastID: self.setCurrentItem(item) self.expandAll() self._updating = False - def getItemByID(self, ID): - for t in range(self.topLevelItemCount()): - for i in range(self.topLevelItem(t).childCount()): - item = self.topLevelItem(t).child(i) - if item.data(0, Qt.UserRole) == ID: - return item + def removeCharacter(self): + """ + Removes selected character. + """ + ID = self.currentCharacterID() + if ID: + self._model.removeCharacter(ID) - def currentPersoIndex(self): + def choseCharacterColor(self): + ID = self.currentCharacterID() + c = self._model.getCharacterByID(ID) + if c: + color = iconColor(c.icon) + else: + color = Qt.white + self.colorDialog = QColorDialog(color, mainWindow()) + color = self.colorDialog.getColor(color) + if color.isValid(): + c.setColor(color) + mainWindow().updateCharacterColor(ID) + + def addCharacterInfo(self): + self._model.addCharacterInfo(self.currentCharacterID()) + + def removeCharacterInfo(self): + self._model.removeCharacterInfo(self.currentCharacterID(), + ) + + def currentCharacterID(self): ID = None if self.currentItem(): ID = self.currentItem().data(0, Qt.UserRole) - return self._model.getIndexFromID(ID) + return ID + + def currentCharacter(self): + """ + Returns the selected character + @return: Character + """ + ID = self.currentCharacterID() + return self._model.getCharacterByID(ID) def mouseDoubleClickEvent(self, event): item = self.currentItem() diff --git a/manuskript/ui/views/cmbOutlinePersoChoser.py b/manuskript/ui/views/cmbOutlinePersoChoser.py index 6747b510..dfb9449d 100644 --- a/manuskript/ui/views/cmbOutlinePersoChoser.py +++ b/manuskript/ui/views/cmbOutlinePersoChoser.py @@ -18,9 +18,9 @@ class cmbOutlinePersoChoser(QComboBox): self._updating = False self._various = False - def setModels(self, mdlPersos, mdlOutline): - self.mdlPersos = mdlPersos - self.mdlPersos.dataChanged.connect(self.updateItems) + def setModels(self, mdlCharacter, mdlOutline): + self.mdlCharacters = mdlCharacter + self.mdlCharacters.dataChanged.connect(self.updateItems) self.mdlOutline = mdlOutline self.mdlOutline.dataChanged.connect(self.update) self.updateItems() @@ -37,14 +37,14 @@ class cmbOutlinePersoChoser(QComboBox): self.setItemData(self.count() - 1, QBrush(QColor(Qt.blue).lighter(190)), Qt.BackgroundRole) item = self.model().item(self.count() - 1) item.setFlags(Qt.ItemIsEnabled) - for i in range(self.mdlPersos.rowCount()): - imp = toInt(self.mdlPersos.importance(i)) + for i in range(self.mdlCharacters.rowCount()): + imp = toInt(self.mdlCharacters.importance(i)) if not 2 - imp == importance: continue - self.addItem(self.mdlPersos.icon(i), self.mdlPersos.name(i), self.mdlPersos.ID(i)) - self.setItemData(self.count() - 1, self.mdlPersos.name(i), Qt.ToolTipRole) + self.addItem(self.mdlCharacters.icon(i), self.mdlCharacters.name(i), self.mdlCharacters.ID(i)) + self.setItemData(self.count() - 1, self.mdlCharacters.name(i), Qt.ToolTipRole) self._various = False diff --git a/manuskript/ui/views/metadataView.py b/manuskript/ui/views/metadataView.py index 4791f5b1..0f06d6eb 100644 --- a/manuskript/ui/views/metadataView.py +++ b/manuskript/ui/views/metadataView.py @@ -16,8 +16,8 @@ class metadataView(QWidget, Ui_metadataView): self.txtNotes.setColumn(Outline.notes.value) self.revisions.setEnabled(False) - def setModels(self, mdlOutline, mdlPersos, mdlLabels, mdlStatus): - self.properties.setModels(mdlOutline, mdlPersos, mdlLabels, mdlStatus) + def setModels(self, mdlOutline, mdlCharacter, mdlLabels, mdlStatus): + self.properties.setModels(mdlOutline, mdlCharacter, mdlLabels, mdlStatus) self.txtSummarySentence.setModel(mdlOutline) self.txtSummaryFull.setModel(mdlOutline) self.txtNotes.setModel(mdlOutline) diff --git a/manuskript/ui/views/outlineBasics.py b/manuskript/ui/views/outlineBasics.py index 4d548f60..1a40dac3 100644 --- a/manuskript/ui/views/outlineBasics.py +++ b/manuskript/ui/views/outlineBasics.py @@ -83,12 +83,12 @@ class outlineBasics(QAbstractItemView): self.menuPOV.addMenu(m) mpr = QSignalMapper(self.menuPOV) - for i in range(mw.mdlPersos.rowCount()): - a = QAction(mw.mdlPersos.icon(i), mw.mdlPersos.name(i), self.menuPOV) + for i in range(mw.mdlCharacter.rowCount()): + a = QAction(mw.mdlCharacter.icon(i), mw.mdlCharacter.name(i), self.menuPOV) a.triggered.connect(mpr.map) - mpr.setMapping(a, int(mw.mdlPersos.ID(i))) + mpr.setMapping(a, int(mw.mdlCharacter.ID(i))) - imp = toInt(mw.mdlPersos.importance(i)) + imp = toInt(mw.mdlCharacter.importance(i)) menus[2 - imp].addAction(a) diff --git a/manuskript/ui/views/outlineDelegates.py b/manuskript/ui/views/outlineDelegates.py index 1dcd0c57..1172f766 100644 --- a/manuskript/ui/views/outlineDelegates.py +++ b/manuskript/ui/views/outlineDelegates.py @@ -6,7 +6,7 @@ from PyQt5.QtWidgets import QStyledItemDelegate, QStyleOptionViewItem, QStyle, Q from PyQt5.QtWidgets import qApp from manuskript import settings -from manuskript.enums import Perso, Outline +from manuskript.enums import Character, Outline from manuskript.functions import outlineItemColors, mixColors, colorifyPixmap, toInt, toFloat, drawProgress @@ -93,17 +93,17 @@ class outlineTitleDelegate(QStyledItemDelegate): class outlinePersoDelegate(QStyledItemDelegate): - def __init__(self, mdlPersos, parent=None): + def __init__(self, mdlCharacter, parent=None): QStyledItemDelegate.__init__(self, parent) - self.mdlPersos = mdlPersos + self.mdlCharacter = mdlCharacter def sizeHint(self, option, index): # s = QStyledItemDelegate.sizeHint(self, option, index) item = QModelIndex() - for i in range(self.mdlPersos.rowCount()): - if self.mdlPersos.ID(i) == index.data(): - item = self.mdlPersos.index(i, Perso.name.value) + for i in range(self.mdlCharacter.rowCount()): + if self.mdlCharacter.ID(i) == index.data(): + item = self.mdlCharacter.index(i, Character.name.value) opt = QStyleOptionViewItem(option) self.initStyleOption(opt, item) @@ -136,13 +136,13 @@ class outlinePersoDelegate(QStyledItemDelegate): editor.setItemData(editor.count() - 1, QBrush(QColor(Qt.blue).lighter(190)), Qt.BackgroundRole) item = editor.model().item(editor.count() - 1) item.setFlags(Qt.ItemIsEnabled) - for i in range(self.mdlPersos.rowCount()): - imp = toInt(self.mdlPersos.importance(i)) + for i in range(self.mdlCharacter.rowCount()): + imp = toInt(self.mdlCharacter.importance(i)) if not 2 - imp == importance: continue # try: - editor.addItem(self.mdlPersos.icon(i), self.mdlPersos.name(i), self.mdlPersos.ID(i)) - editor.setItemData(editor.count() - 1, self.mdlPersos.name(i), Qt.ToolTipRole) + editor.addItem(self.mdlCharacter.icon(i), self.mdlCharacter.name(i), self.mdlCharacter.ID(i)) + editor.setItemData(editor.count() - 1, self.mdlCharacter.name(i), Qt.ToolTipRole) # except: # pass @@ -159,9 +159,9 @@ class outlinePersoDelegate(QStyledItemDelegate): ##option.rect.setWidth(option.rect.width() + 18) item = QModelIndex() - for i in range(self.mdlPersos.rowCount()): - if self.mdlPersos.ID(i) == index.data(): - item = self.mdlPersos.index(i, Perso.name.value) + for i in range(self.mdlCharacter.rowCount()): + if self.mdlCharacter.ID(i) == index.data(): + item = self.mdlCharacter.index(i, Character.name.value) opt = QStyleOptionViewItem(option) self.initStyleOption(opt, item) @@ -169,7 +169,7 @@ class outlinePersoDelegate(QStyledItemDelegate): qApp.style().drawControl(QStyle.CE_ItemViewItem, opt, painter) # if index.isValid() and index.internalPointer().data(Outline.POV.value) not in ["", None]: - if index.isValid() and self.mdlPersos.data(index) not in ["", None]: + if index.isValid() and self.mdlCharacter.data(index) not in ["", None]: opt = QStyleOptionComboBox() opt.rect = option.rect r = qApp.style().subControlRect(QStyle.CC_ComboBox, opt, QStyle.SC_ComboBoxArrow) diff --git a/manuskript/ui/views/outlineView.py b/manuskript/ui/views/outlineView.py index 848a8246..670894f2 100644 --- a/manuskript/ui/views/outlineView.py +++ b/manuskript/ui/views/outlineView.py @@ -11,20 +11,20 @@ from manuskript.ui.views.outlineDelegates import outlineTitleDelegate, outlinePe class outlineView(QTreeView, dndView, outlineBasics): - def __init__(self, parent=None, modelPersos=None, modelLabels=None, modelStatus=None): + def __init__(self, parent=None, modelCharacters=None, modelLabels=None, modelStatus=None): QTreeView.__init__(self, parent) dndView.__init__(self) outlineBasics.__init__(self, parent) - self.modelPersos = modelPersos + self.modelCharacters = modelCharacters self.modelLabels = modelLabels self.modelStatus = modelStatus self.header().setStretchLastSection(False) - def setModelPersos(self, model): + def setModelCharacters(self, model): # This is used by outlinePersoDelegate to select character - self.modelPersos = model + self.modelCharacters = model def setModelLabels(self, model): # This is used by outlineLabelDelegate to display labels @@ -41,7 +41,7 @@ class outlineView(QTreeView, dndView, outlineBasics): self.outlineTitleDelegate = outlineTitleDelegate(self) # self.outlineTitleDelegate.setView(self) self.setItemDelegateForColumn(Outline.title.value, self.outlineTitleDelegate) - self.outlinePersoDelegate = outlinePersoDelegate(self.modelPersos) + self.outlinePersoDelegate = outlinePersoDelegate(self.modelCharacters) self.setItemDelegateForColumn(Outline.POV.value, self.outlinePersoDelegate) self.outlineCompileDelegate = outlineCompileDelegate() self.setItemDelegateForColumn(Outline.compile.value, self.outlineCompileDelegate) diff --git a/manuskript/ui/views/propertiesView.py b/manuskript/ui/views/propertiesView.py index 8cdedda3..ce18cf0c 100644 --- a/manuskript/ui/views/propertiesView.py +++ b/manuskript/ui/views/propertiesView.py @@ -12,8 +12,8 @@ class propertiesView(QWidget, Ui_propertiesView): self.setupUi(self) self.txtGoal.setColumn(Outline.setGoal.value) - def setModels(self, mdlOutline, mdlPersos, mdlLabels, mdlStatus): - self.cmbPOV.setModels(mdlPersos, mdlOutline) + def setModels(self, mdlOutline, mdlCharacter, mdlLabels, mdlStatus): + self.cmbPOV.setModels(mdlCharacter, mdlOutline) self.cmbLabel.setModels(mdlLabels, mdlOutline) self.cmbStatus.setModels(mdlStatus, mdlOutline) self.cmbType.setModel(mdlOutline) diff --git a/manuskript/ui/views/storylineView.py b/manuskript/ui/views/storylineView.py index a5f81362..f5f40840 100644 --- a/manuskript/ui/views/storylineView.py +++ b/manuskript/ui/views/storylineView.py @@ -46,7 +46,7 @@ class storylineView(QWidget, Ui_storylineView): self.btnSettings.setMenu(m) - def setModels(self, mdlOutline, mdlPersos, mdlPlots): + def setModels(self, mdlOutline, mdlCharacter, mdlPlots): self._mdlPlots = mdlPlots # self._mdlPlots.dataChanged.connect(self.refresh) # self._mdlPlots.rowsInserted.connect(self.refresh) @@ -54,8 +54,8 @@ class storylineView(QWidget, Ui_storylineView): self._mdlOutline = mdlOutline self._mdlOutline.dataChanged.connect(self.reloadTimer.start) - self._mdlPersos = mdlPersos - self._mdlPersos.dataChanged.connect(self.reloadTimer.start) + self._mdlCharacter = mdlCharacter + self._mdlCharacter.dataChanged.connect(self.reloadTimer.start) def plotReferences(self): "Returns a list of plot references" @@ -73,10 +73,10 @@ class storylineView(QWidget, Ui_storylineView): def persosReferences(self): "Returns a list of character references" - if not self._mdlPersos: + if not self._mdlCharacter: pass - IDs = self._mdlPersos.getPersosByImportance() + IDs = self._mdlCharacter.getPersosByImportance() r = [] for importance in IDs: for ID in importance: @@ -86,7 +86,7 @@ class storylineView(QWidget, Ui_storylineView): return r def refresh(self): - if not self._mdlPlots or not self._mdlOutline or not self._mdlPersos: + if not self._mdlPlots or not self._mdlOutline or not self._mdlCharacter: pass LINE_HEIGHT = 18 @@ -222,7 +222,7 @@ class storylineView(QWidget, Ui_storylineView): for ref in trackedItems: if references.type(ref) == references.PersoLetter: - color = QColor(self._mdlPersos.getPersoColorByID(references.ID(ref))) + color = QColor(self._mdlCharacter.getPersoColorByID(references.ID(ref))) else: color = randomColor() diff --git a/manuskript/ui/welcome.py b/manuskript/ui/welcome.py index 137ceb22..e1248dd1 100644 --- a/manuskript/ui/welcome.py +++ b/manuskript/ui/welcome.py @@ -12,9 +12,9 @@ from PyQt5.QtWidgets import QWidget, QAction, QFileDialog, QSpinBox, QLineEdit, from manuskript import settings from manuskript.enums import Outline from manuskript.functions import mainWindow, iconFromColor, appPath +from manuskript.models.characterModel import characterModel from manuskript.models.outlineModel import outlineItem from manuskript.models.outlineModel import outlineModel -from manuskript.models.persosModel import persosModel from manuskript.models.plotModel import plotModel from manuskript.models.worldModel import worldModel from manuskript.ui.welcome_ui import Ui_welcome @@ -345,7 +345,7 @@ class welcome(QWidget, Ui_welcome): # Persos # self.mw.mdlPersos = QStandardItemModel(0, 0, self.mw) - self.mw.mdlPersos = persosModel(self.mw) + self.mw.mdlCharacter = characterModel(self.mw) # self.mdlPersosProxy = None # persosProxyModel() # None # self.mw.mdlPersosProxy = persosProxyModel(self.mw) From a17745a89a20b1da93a46f8113e6089636e6e714 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 3 Mar 2016 17:18:30 +0100 Subject: [PATCH 020/103] Corrects references, cheatsheet, storyline, ... --- manuskript/models/characterModel.py | 9 +++- manuskript/models/references.py | 62 ++++++++++++----------- manuskript/ui/cheatSheet.py | 4 +- manuskript/ui/editors/basicHighlighter.py | 2 +- manuskript/ui/views/characterTreeView.py | 7 +++ manuskript/ui/views/storylineView.py | 18 +++---- 6 files changed, 61 insertions(+), 41 deletions(-) diff --git a/manuskript/models/characterModel.py b/manuskript/models/characterModel.py index 72f525bf..85745344 100644 --- a/manuskript/models/characterModel.py +++ b/manuskript/models/characterModel.py @@ -39,7 +39,7 @@ class characterModel(QAbstractItemModel): if index.column() in c._data: return c._data[index.column()] else: - return QVariant() + return "" elif type(c) == CharacterInfo: if role == Qt.DisplayRole or role == Qt.EditRole: @@ -141,6 +141,7 @@ class characterModel(QAbstractItemModel): return r def getCharacterByID(self, ID): + ID = int(ID) for c in self.characters: if c.ID() == ID: return c @@ -274,6 +275,12 @@ class Character(): self._data[C.ID.value] = k + def listInfos(self): + r = [] + for i in self.infos: + r.append((i.description, i.value)) + return r + class CharacterInfo(): def __init__(self, character, description="", value=""): self.description = description diff --git a/manuskript/models/references.py b/manuskript/models/references.py index 6aeeb374..16663199 100644 --- a/manuskript/models/references.py +++ b/manuskript/models/references.py @@ -22,7 +22,7 @@ RegExNonCapturing = r"{\w:\d+:?.*?}" # The basic format of the references EmptyRef = "{{{}:{}:{}}}" EmptyRefSearchable = "{{{}:{}:" -PersoLetter = "C" +CharacterLetter = "C" TextLetter = "T" PlotLetter = "P" WorldLetter = "W" @@ -37,13 +37,13 @@ def plotReference(ID, searchable=False): return EmptyRefSearchable.format(PlotLetter, ID, "") -def persoReference(ID, searchable=False): +def characterReference(ID, searchable=False): """Takes the ID of a character and returns a reference for that character. @searchable: returns a stripped version that allows simple text search.""" if not searchable: - return EmptyRef.format(PersoLetter, ID, "") + return EmptyRef.format(CharacterLetter, ID, "") else: - return EmptyRefSearchable.format(PersoLetter, ID, "") + return EmptyRefSearchable.format(CharacterLetter, ID, "") def textReference(ID, searchable=False): @@ -103,7 +103,7 @@ def infos(ref): POV = "" if item.POV(): POV = "{text}".format( - ref=persoReference(item.POV()), + ref=characterReference(item.POV()), text=mainWindow().mdlCharacter.getCharacterByID(item.POV()).name()) # The status of the scene @@ -174,10 +174,12 @@ def infos(ref): return text # A character - elif _type == PersoLetter: + elif _type == CharacterLetter: m = mainWindow().mdlCharacter - index = m.getIndexFromID(_ref) - name = m.name(index.row()) + c = m.getCharacterByID(int(_ref)) + index = c.index() + + name = c.name() # Titles basicTitle = qApp.translate("references", "Basic infos") @@ -191,14 +193,16 @@ def infos(ref): # basic infos basic = [] for i in [ - (Perso.motivation, qApp.translate("references", "Motivation"), False), - (Perso.goal, qApp.translate("references", "Goal"), False), - (Perso.conflict, qApp.translate("references", "Conflict"), False), - (Perso.epiphany, qApp.translate("references", "Epiphany"), False), - (Perso.summarySentence, qApp.translate("references", "Short summary"), True), - (Perso.summaryPara, qApp.translate("references", "Longer summary"), True), + (Character.motivation, qApp.translate("references", "Motivation"), False), + (Character.goal, qApp.translate("references", "Goal"), False), + (Character.conflict, qApp.translate("references", "Conflict"), False), + (Character.epiphany, qApp.translate("references", "Epiphany"), False), + (Character.summarySentence, qApp.translate("references", "Short summary"), True), + (Character.summaryPara, qApp.translate("references", "Longer summary"), True), ]: + val = m.data(index.sibling(index.row(), i[0].value)) + if val: basic.append("{title}:{n}{val}".format( title=i[1], @@ -208,7 +212,7 @@ def infos(ref): # detailed infos detailed = [] - for _name, _val in m.listPersoInfos(index): + for _name, _val in c.listInfos(): detailed.append("{}: {}".format( _name, _val)) @@ -279,7 +283,7 @@ def infos(ref): for r in range(item.rowCount()): ID = item.child(r, 0).text() characters += "
  • {text}".format( - link=persoReference(ID), + link=characterReference(ID), text=pM.getPersoNameByID(ID)) # Resolution steps @@ -408,15 +412,16 @@ def shortInfos(ref): infos["path"] = item.path() return infos - elif _type == PersoLetter: + elif _type == CharacterLetter: - infos["type"] = PersoLetter + infos["type"] = CharacterLetter m = mainWindow().mdlCharacter - item = m.item(int(_ref), Perso.name.value) - if item: - infos["title"] = item.text() - infos["name"] = item.text() + c = m.getCharacterByID(int(_ref)) + + if c: + infos["title"] = c.name() + infos["name"] = c.name() return infos elif _type == PlotLetter: @@ -482,7 +487,7 @@ def tooltip(ref): tt += "
    {}".format(infos["path"]) return tt - elif infos["type"] == PersoLetter: + elif infos["type"] == CharacterLetter: return qApp.translate("references", "Character: {}").format(infos["title"]) elif infos["type"] == PlotLetter: @@ -515,9 +520,9 @@ def refToLink(ref): item = idx.internalPointer() text = item.title() - elif _type == PersoLetter: + elif _type == CharacterLetter: m = mainWindow().mdlCharacter - text = m.item(int(_ref), Perso.name.value).text() + text = m.getCharacterByID(int(_ref)).name() elif _type == PlotLetter: m = mainWindow().mdlPlots @@ -618,17 +623,16 @@ def open(ref): _type = match.group(1) _ref = match.group(2) - if _type == PersoLetter: + if _type == CharacterLetter: mw = mainWindow() - # FIXME - item = mw.lstPersos.getItemByID(_ref) + item = mw.lstCharacters.getItemByID(int(_ref)) if item: mw.tabMain.setCurrentIndex(mw.TabPersos) mw.lstCharacters.setCurrentItem(item) return True - print("Ref not found") + print("Error: Ref {} not found".format(ref)) return False elif _type == TextLetter: diff --git a/manuskript/ui/cheatSheet.py b/manuskript/ui/cheatSheet.py index 1ab6c2b0..2e3a4401 100644 --- a/manuskript/ui/cheatSheet.py +++ b/manuskript/ui/cheatSheet.py @@ -59,6 +59,8 @@ class cheatSheet(QWidget, Ui_cheatSheet): self.outlineModel.dataChanged.connect(self.populateTimer.start) self.characterModel.dataChanged.connect(self.populateTimer.start) + self.characterModel.rowsInserted.connect(self.populateTimer.start) + self.characterModel.rowsRemoved.connect(self.populateTimer.start) self.plotModel.dataChanged.connect(self.populateTimer.start) self.worldModel.dataChanged.connect(self.populateTimer.start) @@ -82,7 +84,7 @@ class cheatSheet(QWidget, Ui_cheatSheet): imp = [self.tr("Minor"), self.tr("Secondary"), self.tr("Main")][int(c.importance())] d.append((c.name(), c.ID(), imp)) - self.data[(self.tr("Characters"), Ref.PersoLetter)] = d + self.data[(self.tr("Characters"), Ref.CharacterLetter)] = d if self.outlineModel: d = [] diff --git a/manuskript/ui/editors/basicHighlighter.py b/manuskript/ui/editors/basicHighlighter.py index b5055060..f0d5050b 100644 --- a/manuskript/ui/editors/basicHighlighter.py +++ b/manuskript/ui/editors/basicHighlighter.py @@ -61,7 +61,7 @@ class basicHighlighter(QSyntaxHighlighter): fmt.setFontWeight(QFont.DemiBold) if txt.group(1) == Ref.TextLetter: fmt.setBackground(QBrush(QColor(Qt.blue).lighter(190))) - elif txt.group(1) == Ref.PersoLetter: + elif txt.group(1) == Ref.CharacterLetter: fmt.setBackground(QBrush(QColor(Qt.yellow).lighter(170))) elif txt.group(1) == Ref.PlotLetter: fmt.setBackground(QBrush(QColor(Qt.red).lighter(170))) diff --git a/manuskript/ui/views/characterTreeView.py b/manuskript/ui/views/characterTreeView.py index 71e354a1..2bfbe55d 100644 --- a/manuskript/ui/views/characterTreeView.py +++ b/manuskript/ui/views/characterTreeView.py @@ -161,6 +161,13 @@ class characterTreeView(QTreeWidget): ID = self.currentCharacterID() return self._model.getCharacterByID(ID) + def getItemByID(self, ID): + for t in range(self.topLevelItemCount()): + for i in range(self.topLevelItem(t).childCount()): + item = self.topLevelItem(t).child(i) + if item.data(0, Qt.UserRole) == ID: + return item + def mouseDoubleClickEvent(self, event): item = self.currentItem() # Catching double clicks to forbid collapsing of toplevel items diff --git a/manuskript/ui/views/storylineView.py b/manuskript/ui/views/storylineView.py index f5f40840..e10fc81e 100644 --- a/manuskript/ui/views/storylineView.py +++ b/manuskript/ui/views/storylineView.py @@ -71,16 +71,16 @@ class storylineView(QWidget, Ui_storylineView): return r - def persosReferences(self): + def charactersReferences(self): "Returns a list of character references" if not self._mdlCharacter: pass - IDs = self._mdlCharacter.getPersosByImportance() + chars = self._mdlCharacter.getCharactersByImportance() r = [] - for importance in IDs: - for ID in importance: - ref = references.persoReference(ID) + for importance in chars: + for c in importance: + ref = references.characterReference(c.ID()) r.append(ref) return r @@ -118,7 +118,7 @@ class storylineView(QWidget, Ui_storylineView): trackedItems += self.plotReferences() if self.actCharacters.isChecked(): - trackedItems += self.persosReferences() + trackedItems += self.charactersReferences() ROWS_HEIGHT = len(trackedItems) * (LINE_HEIGHT + SPACING ) @@ -185,7 +185,7 @@ class storylineView(QWidget, Ui_storylineView): # Tests if POV scenePOV = False # Will hold true of character is POV of the current text, not containing folder - if references.type(ref) == references.PersoLetter: + if references.type(ref) == references.CharacterLetter: ID = references.ID(ref) c = child while c: @@ -221,8 +221,8 @@ class storylineView(QWidget, Ui_storylineView): itemsRect.setPos(0, MAX_LEVEL * LEVEL_HEIGHT + SPACING) for ref in trackedItems: - if references.type(ref) == references.PersoLetter: - color = QColor(self._mdlCharacter.getPersoColorByID(references.ID(ref))) + if references.type(ref) == references.CharacterLetter: + color = self._mdlCharacter.getCharacterByID(references.ID(ref)).color() else: color = randomColor() From 7e05e2227540cae57e6aa828c429b317a70d1479 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 3 Mar 2016 17:41:19 +0100 Subject: [PATCH 021/103] Corrects a few things in combo and other places --- manuskript/models/characterModel.py | 13 +++++++------ manuskript/models/outlineModel.py | 2 +- manuskript/models/references.py | 2 +- manuskript/ui/views/basicItemView_ui.py | 8 ++++---- manuskript/ui/views/basicItemView_ui.ui | 6 +++--- ...PersoChoser.py => cmbOutlineCharacterChoser.py} | 5 ++++- manuskript/ui/views/propertiesView_ui.py | 14 +++++++------- manuskript/ui/views/propertiesView_ui.ui | 8 ++++---- 8 files changed, 31 insertions(+), 27 deletions(-) rename manuskript/ui/views/{cmbOutlinePersoChoser.py => cmbOutlineCharacterChoser.py} (96%) diff --git a/manuskript/models/characterModel.py b/manuskript/models/characterModel.py index 85745344..a9881f94 100644 --- a/manuskript/models/characterModel.py +++ b/manuskript/models/characterModel.py @@ -141,10 +141,11 @@ class characterModel(QAbstractItemModel): return r def getCharacterByID(self, ID): - ID = int(ID) - for c in self.characters: - if c.ID() == ID: - return c + if ID is not None: + ID = str(ID) + for c in self.characters: + if c.ID() == ID: + return c return None ############################################################################### @@ -267,13 +268,13 @@ class Character(): """Assigns an unused character ID.""" vals = [] for c in self._model.characters: - vals.append(c.ID()) + vals.append(int(c.ID())) k = 0 while k in vals: k += 1 - self._data[C.ID.value] = k + self._data[C.ID.value] = str(k) def listInfos(self): r = [] diff --git a/manuskript/models/outlineModel.py b/manuskript/models/outlineModel.py index 6fc547df..9f2c1fe0 100644 --- a/manuskript/models/outlineModel.py +++ b/manuskript/models/outlineModel.py @@ -855,7 +855,7 @@ class outlineItem(): for c in columns: if c == Outline.POV.value: - searchIn = mainWindow.mdlCharacter.getPersoNameByID(self.POV()) + searchIn = mainWindow.mdlCharacter.getCharacterByID(self.POV()).name() elif c == Outline.status.value: searchIn = mainWindow.mdlStatus.item(toInt(self.status()), 0).text() diff --git a/manuskript/models/references.py b/manuskript/models/references.py index 16663199..64261d7e 100644 --- a/manuskript/models/references.py +++ b/manuskript/models/references.py @@ -417,7 +417,7 @@ def shortInfos(ref): infos["type"] = CharacterLetter m = mainWindow().mdlCharacter - c = m.getCharacterByID(int(_ref)) + c = m.getCharacterByID(_ref) if c: infos["title"] = c.name() diff --git a/manuskript/ui/views/basicItemView_ui.py b/manuskript/ui/views/basicItemView_ui.py index 6be70e81..f0594fad 100644 --- a/manuskript/ui/views/basicItemView_ui.py +++ b/manuskript/ui/views/basicItemView_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'manuskript/ui/views/basicItemView_ui.ui' # -# Created: Wed Mar 2 00:33:34 2016 +# Created: Thu Mar 3 17:26:11 2016 # by: PyQt5 UI code generator 5.2.1 # # WARNING! All changes made in this file will be lost! @@ -24,7 +24,7 @@ class Ui_basicItemView(object): self.lblPlanPOV.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.lblPlanPOV.setObjectName("lblPlanPOV") self.horizontalLayout_11.addWidget(self.lblPlanPOV) - self.cmbPOV = cmbOutlinePersoChoser(basicItemView) + self.cmbPOV = cmbOutlineCharacterChoser(basicItemView) self.cmbPOV.setFrame(False) self.cmbPOV.setObjectName("cmbPOV") self.horizontalLayout_11.addWidget(self.cmbPOV) @@ -67,6 +67,6 @@ class Ui_basicItemView(object): self.txtSummarySentence.setPlaceholderText(_translate("basicItemView", "One line summary")) self.label_9.setText(_translate("basicItemView", "Few sentences summary:")) -from manuskript.ui.views.cmbOutlinePersoChoser import cmbOutlinePersoChoser -from manuskript.ui.views.textEditView import textEditView +from manuskript.ui.views.cmbOutlineCharacterChoser import cmbOutlineCharacterChoser from manuskript.ui.views.lineEditView import lineEditView +from manuskript.ui.views.textEditView import textEditView diff --git a/manuskript/ui/views/basicItemView_ui.ui b/manuskript/ui/views/basicItemView_ui.ui index 64552858..6466cc73 100644 --- a/manuskript/ui/views/basicItemView_ui.ui +++ b/manuskript/ui/views/basicItemView_ui.ui @@ -43,7 +43,7 @@ - + false @@ -112,9 +112,9 @@
    manuskript.ui.views.textEditView.h
    - cmbOutlinePersoChoser + cmbOutlineCharacterChoser QComboBox -
    manuskript.ui.views.cmbOutlinePersoChoser.h
    +
    manuskript.ui.views.cmbOutlineCharacterChoser.h
    lineEditView diff --git a/manuskript/ui/views/cmbOutlinePersoChoser.py b/manuskript/ui/views/cmbOutlineCharacterChoser.py similarity index 96% rename from manuskript/ui/views/cmbOutlinePersoChoser.py rename to manuskript/ui/views/cmbOutlineCharacterChoser.py index dfb9449d..4185aca9 100644 --- a/manuskript/ui/views/cmbOutlinePersoChoser.py +++ b/manuskript/ui/views/cmbOutlineCharacterChoser.py @@ -8,7 +8,7 @@ from manuskript.enums import Outline from manuskript.functions import toInt -class cmbOutlinePersoChoser(QComboBox): +class cmbOutlineCharacterChoser(QComboBox): def __init__(self, parent=None): QComboBox.__init__(self, parent) self.activated[int].connect(self.submit) @@ -21,6 +21,9 @@ class cmbOutlinePersoChoser(QComboBox): def setModels(self, mdlCharacter, mdlOutline): self.mdlCharacters = mdlCharacter self.mdlCharacters.dataChanged.connect(self.updateItems) + self.mdlCharacters.rowsInserted.connect(self.updateItems) + self.mdlCharacters.rowsRemoved.connect(self.updateItems) + self.mdlOutline = mdlOutline self.mdlOutline.dataChanged.connect(self.update) self.updateItems() diff --git a/manuskript/ui/views/propertiesView_ui.py b/manuskript/ui/views/propertiesView_ui.py index 0d9a56e2..217b3ab5 100644 --- a/manuskript/ui/views/propertiesView_ui.py +++ b/manuskript/ui/views/propertiesView_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'manuskript/ui/views/propertiesView_ui.ui' # -# Created: Wed Mar 2 00:30:18 2016 +# Created: Thu Mar 3 17:26:11 2016 # by: PyQt5 UI code generator 5.2.1 # # WARNING! All changes made in this file will be lost! @@ -37,7 +37,7 @@ class Ui_propertiesView(object): self.lblPOV = QtWidgets.QLabel(self.page) self.lblPOV.setObjectName("lblPOV") self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.lblPOV) - self.cmbPOV = cmbOutlinePersoChoser(self.page) + self.cmbPOV = cmbOutlineCharacterChoser(self.page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -116,7 +116,7 @@ class Ui_propertiesView(object): self.lblPOV_2 = QtWidgets.QLabel(self.page_2) self.lblPOV_2.setObjectName("lblPOV_2") self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.lblPOV_2) - self.cmbPOVMulti = cmbOutlinePersoChoser(self.page_2) + self.cmbPOVMulti = cmbOutlineCharacterChoser(self.page_2) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -195,9 +195,9 @@ class Ui_propertiesView(object): self.label_36.setText(_translate("propertiesView", "Goal")) self.txtGoalMulti.setPlaceholderText(_translate("propertiesView", "Word count")) -from manuskript.ui.views.cmbOutlineStatusChoser import cmbOutlineStatusChoser from manuskript.ui.views.lineEditView import lineEditView -from manuskript.ui.views.chkOutlineCompile import chkOutlineCompile -from manuskript.ui.views.cmbOutlineLabelChoser import cmbOutlineLabelChoser +from manuskript.ui.views.cmbOutlineCharacterChoser import cmbOutlineCharacterChoser from manuskript.ui.views.cmbOutlineTypeChoser import cmbOutlineTypeChoser -from manuskript.ui.views.cmbOutlinePersoChoser import cmbOutlinePersoChoser +from manuskript.ui.views.chkOutlineCompile import chkOutlineCompile +from manuskript.ui.views.cmbOutlineStatusChoser import cmbOutlineStatusChoser +from manuskript.ui.views.cmbOutlineLabelChoser import cmbOutlineLabelChoser diff --git a/manuskript/ui/views/propertiesView_ui.ui b/manuskript/ui/views/propertiesView_ui.ui index 0c342b84..d76b439d 100644 --- a/manuskript/ui/views/propertiesView_ui.ui +++ b/manuskript/ui/views/propertiesView_ui.ui @@ -53,7 +53,7 @@
    - + 0 @@ -190,7 +190,7 @@ - + 0 @@ -300,9 +300,9 @@
    manuskript.ui.views.lineEditView.h
    - cmbOutlinePersoChoser + cmbOutlineCharacterChoser QComboBox -
    manuskript.ui.views.cmbOutlinePersoChoser.h
    +
    manuskript.ui.views.cmbOutlineCharacterChoser.h
    cmbOutlineStatusChoser From f57e8d2ab1d6c9990227224b27964409892b6381 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 3 Mar 2016 18:48:45 +0100 Subject: [PATCH 022/103] Bug --- manuskript/mainWindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index 4d5959bc..5c6511cc 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -740,7 +740,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.tblDebugPersos.selectionModel().currentChanged.connect( lambda: self.tblDebugPersosInfos.setRootIndex(self.mdlCharacter.index( self.tblDebugPersos.selectionModel().currentIndex().row(), - Perso.name.value)), AUC) + Character.name.value)), AUC) self.tblDebugPlots.setModel(self.mdlPlots) self.tblDebugPlotsPersos.setModel(self.mdlPlots) From cec3d2563c51a00add85e3f35922c5339ca4523f Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 3 Mar 2016 18:48:53 +0100 Subject: [PATCH 023/103] Clean up --- manuskript/loadSave.py | 89 +++++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 41 deletions(-) diff --git a/manuskript/loadSave.py b/manuskript/loadSave.py index af5dfc6d..38c45bb9 100644 --- a/manuskript/loadSave.py +++ b/manuskript/loadSave.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -#--!-- coding: utf8 --!-- +# --!-- coding: utf8 --!-- import zipfile @@ -11,22 +11,25 @@ from lxml import etree as ET from manuskript.functions import iconColor, iconFromColorString try: - import zlib # Used with zipfile for compression + import zlib # Used with zipfile for compression + compression = zipfile.ZIP_DEFLATED except: compression = zipfile.ZIP_STORED + def saveFilesToZip(files, zipname): """Saves given files to zipname. files is actually a list of (content, filename).""" - + zf = zipfile.ZipFile(zipname, mode="w") - + for content, filename in files: zf.writestr(filename, content, compress_type=compression) - + zf.close() - + + def loadFilesFromZip(zipname): """Returns the content of zipfile as a dict of filename:content.""" print(zipname) @@ -35,14 +38,15 @@ def loadFilesFromZip(zipname): for f in zf.namelist(): files[f] = zf.read(f) return files - + + def saveStandardItemModelXML(mdl, xml=None): """Saves the given QStandardItemModel to XML. If xml (filename) is given, saves to xml. Otherwise returns as string.""" - + root = ET.Element("model") root.attrib["version"] = qApp.applicationVersion() - + # Header header = ET.SubElement(root, "header") vHeader = ET.SubElement(header, "vertical") @@ -50,28 +54,29 @@ def saveStandardItemModelXML(mdl, xml=None): vH = ET.SubElement(vHeader, "label") vH.attrib["row"] = str(x) vH.attrib["text"] = str(mdl.headerData(x, Qt.Vertical)) - + hHeader = ET.SubElement(header, "horizontal") for y in range(mdl.columnCount()): hH = ET.SubElement(hHeader, "label") hH.attrib["row"] = str(y) hH.attrib["text"] = str(mdl.headerData(y, Qt.Horizontal)) - + # Data data = ET.SubElement(root, "data") saveItem(data, mdl) - - #print(qApp.tr("Saving to {}.").format(xml)) + + # print(qApp.tr("Saving to {}.").format(xml)) if xml: ET.ElementTree(root).write(xml, encoding="UTF-8", xml_declaration=True, pretty_print=True) else: return ET.tostring(root, encoding="UTF-8", xml_declaration=True, pretty_print=True) - + + def saveItem(root, mdl, parent=QModelIndex()): for x in range(mdl.rowCount(parent)): row = ET.SubElement(root, "row") row.attrib["row"] = str(x) - + for y in range(mdl.columnCount(parent)): col = ET.SubElement(row, "col") col.attrib["col"] = str(y) @@ -82,13 +87,14 @@ def saveItem(root, mdl, parent=QModelIndex()): col.text = mdl.data(mdl.index(x, y, parent)) if mdl.hasChildren(mdl.index(x, y, parent)): saveItem(col, mdl, mdl.index(x, y, parent)) - + + def loadStandardItemModelXML(mdl, xml, fromString=False): """Load data to a QStandardItemModel mdl from xml. By default xml is a filename. If fromString=True, xml is a string containg the data.""" - - #print(qApp.tr("Loading {}... ").format(xml), end="") - + + # print(qApp.tr("Loading {}... ").format(xml), end="") + if not fromString: try: tree = ET.parse(xml) @@ -97,35 +103,36 @@ def loadStandardItemModelXML(mdl, xml, fromString=False): return else: root = ET.fromstring(xml) - - #root = tree.getroot() - - #Header + + # root = tree.getroot() + + # Header hLabels = [] vLabels = [] for l in root.find("header").find("horizontal").findall("label"): hLabels.append(l.attrib["text"]) for l in root.find("header").find("vertical").findall("label"): vLabels.append(l.attrib["text"]) - - #print(root.find("header").find("vertical").text) - - #mdl.setVerticalHeaderLabels(vLabels) - #mdl.setHorizontalHeaderLabels(hLabels) - + + # print(root.find("header").find("vertical").text) + + # mdl.setVerticalHeaderLabels(vLabels) + # mdl.setHorizontalHeaderLabels(hLabels) + # Populates with empty items for i in enumerate(vLabels): row = [] for r in enumerate(hLabels): row.append(QStandardItem()) mdl.appendRow(row) - - #Data + + # Data data = root.find("data") loadItem(data, mdl) - + return True - + + def loadItem(root, mdl, parent=QModelIndex()): for row in root: r = int(row.attrib["row"]) @@ -135,15 +142,15 @@ def loadItem(root, mdl, parent=QModelIndex()): if not item: item = QStandardItem() mdl.itemFromIndex(parent).setChild(r, c, item) - - if col.text: - #mdl.setData(mdl.index(r, c, parent), col.text) + + if col.text: + # mdl.setData(mdl.index(r, c, parent), col.text) item.setText(col.text) - + if "color" in col.attrib: - #mdl.itemFromIndex(mdl.index(r, c, parent)).setIcon(iconFromColorString(col.attrib["color"])) + # mdl.itemFromIndex(mdl.index(r, c, parent)).setIcon(iconFromColorString(col.attrib["color"])) item.setIcon(iconFromColorString(col.attrib["color"])) - + if len(col) != 0: - #loadItem(col, mdl, mdl.index(r, c, parent)) - loadItem(col, mdl, mdl.indexFromItem(item)) \ No newline at end of file + # loadItem(col, mdl, mdl.index(r, c, parent)) + loadItem(col, mdl, mdl.indexFromItem(item)) From aece6ca87f15a40d889acd373cd7c97e5ef4a27b Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 3 Mar 2016 18:55:57 +0100 Subject: [PATCH 024/103] Moves sldImportance to views folder, where it belongs --- manuskript/ui/mainWindow.py | 22 +++++++++---------- manuskript/ui/mainWindow.ui | 2 +- manuskript/ui/{ => views}/sldImportance.py | 2 +- manuskript/ui/{ => views}/sldImportance_ui.py | 7 +++--- manuskript/ui/{ => views}/sldImportance_ui.ui | 0 5 files changed, 17 insertions(+), 16 deletions(-) rename manuskript/ui/{ => views}/sldImportance.py (97%) rename manuskript/ui/{ => views}/sldImportance_ui.py (89%) rename manuskript/ui/{ => views}/sldImportance_ui.ui (100%) diff --git a/manuskript/ui/mainWindow.py b/manuskript/ui/mainWindow.py index 9d8762b7..ed9ab078 100644 --- a/manuskript/ui/mainWindow.py +++ b/manuskript/ui/mainWindow.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'manuskript/ui/mainWindow.ui' # -# Created: Thu Mar 3 13:40:20 2016 +# Created: Thu Mar 3 18:52:22 2016 # by: PyQt5 UI code generator 5.2.1 # # WARNING! All changes made in this file will be lost! @@ -1303,18 +1303,18 @@ class Ui_MainWindow(object): self.actCompile.setShortcut(_translate("MainWindow", "F6")) self.actToolFrequency.setText(_translate("MainWindow", "&Frequency Analyzer")) -from manuskript.ui.search import search +from manuskript.ui.views.outlineView import outlineView +from manuskript.ui.views.textEditView import textEditView +from manuskript.ui.views.basicItemView import basicItemView +from manuskript.ui.views.plotTreeView import plotTreeView from manuskript.ui.cheatSheet import cheatSheet -from manuskript.ui.views.textEditCompleter import textEditCompleter +from manuskript.ui.views.sldImportance import sldImportance +from manuskript.ui.views.metadataView import metadataView +from manuskript.ui.views.characterTreeView import characterTreeView +from manuskript.ui.editors.mainEditor import mainEditor +from manuskript.ui.search import search from manuskript.ui.views.lineEditView import lineEditView from manuskript.ui.welcome import welcome -from manuskript.ui.views.characterTreeView import characterTreeView -from manuskript.ui.sldImportance import sldImportance -from manuskript.ui.views.plotTreeView import plotTreeView -from manuskript.ui.views.basicItemView import basicItemView -from manuskript.ui.views.outlineView import outlineView -from manuskript.ui.views.metadataView import metadataView from manuskript.ui.views.treeView import treeView -from manuskript.ui.editors.mainEditor import mainEditor +from manuskript.ui.views.textEditCompleter import textEditCompleter from manuskript.ui.views.storylineView import storylineView -from manuskript.ui.views.textEditView import textEditView diff --git a/manuskript/ui/mainWindow.ui b/manuskript/ui/mainWindow.ui index 7e34ea70..9f9e7c6e 100644 --- a/manuskript/ui/mainWindow.ui +++ b/manuskript/ui/mainWindow.ui @@ -2358,7 +2358,7 @@ QListView::item:hover { sldImportance QWidget -
    manuskript.ui.sldImportance.h
    +
    manuskript.ui.views.sldImportance.h
    1
    diff --git a/manuskript/ui/sldImportance.py b/manuskript/ui/views/sldImportance.py similarity index 97% rename from manuskript/ui/sldImportance.py rename to manuskript/ui/views/sldImportance.py index ad239a8d..bd303d1e 100644 --- a/manuskript/ui/sldImportance.py +++ b/manuskript/ui/views/sldImportance.py @@ -2,9 +2,9 @@ # --!-- coding: utf8 --!-- from PyQt5.QtCore import pyqtSignal, pyqtProperty from PyQt5.QtWidgets import QWidget +from manuskript.ui.views.sldImportance_ui import Ui_sldImportance from manuskript.functions import toInt -from manuskript.ui.sldImportance_ui import Ui_sldImportance class sldImportance(QWidget, Ui_sldImportance): diff --git a/manuskript/ui/sldImportance_ui.py b/manuskript/ui/views/sldImportance_ui.py similarity index 89% rename from manuskript/ui/sldImportance_ui.py rename to manuskript/ui/views/sldImportance_ui.py index f7e3c0b2..639bbdc7 100644 --- a/manuskript/ui/sldImportance_ui.py +++ b/manuskript/ui/views/sldImportance_ui.py @@ -1,12 +1,13 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'manuskript/ui/sldImportance_ui.ui' +# Form implementation generated from reading ui file 'manuskript/ui/views/sldImportance_ui.ui' # -# Created by: PyQt5 UI code generator 5.4.1 +# Created: Thu Mar 3 18:52:22 2016 +# by: PyQt5 UI code generator 5.2.1 # # WARNING! All changes made in this file will be lost! -from PyQt5 import QtCore, QtWidgets +from PyQt5 import QtCore, QtGui, QtWidgets class Ui_sldImportance(object): def setupUi(self, sldImportance): diff --git a/manuskript/ui/sldImportance_ui.ui b/manuskript/ui/views/sldImportance_ui.ui similarity index 100% rename from manuskript/ui/sldImportance_ui.ui rename to manuskript/ui/views/sldImportance_ui.ui From 1f85ee617196ff45a31bf8d2c60c866cbc3d82c8 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Fri, 4 Mar 2016 21:57:38 +0100 Subject: [PATCH 025/103] Working on a more flexible loading/saving system --- manuskript/functions.py | 4 + manuskript/loadSave.py | 159 ++----------------- manuskript/load_save/__init__.py | 0 manuskript/load_save/version_0.py | 249 ++++++++++++++++++++++++++++++ manuskript/load_save/version_1.py | 81 ++++++++++ manuskript/mainWindow.py | 76 +-------- 6 files changed, 352 insertions(+), 217 deletions(-) create mode 100644 manuskript/load_save/__init__.py create mode 100644 manuskript/load_save/version_0.py create mode 100644 manuskript/load_save/version_1.py diff --git a/manuskript/functions.py b/manuskript/functions.py index 69799f79..fa8cffd8 100644 --- a/manuskript/functions.py +++ b/manuskript/functions.py @@ -179,6 +179,10 @@ def allPaths(suffix=None): return paths def lightBlue(): + """ + A light blue used in several places in manuskript. + @return: QColor + """ return QColor(Qt.blue).lighter(190) def totalObjects(): diff --git a/manuskript/loadSave.py b/manuskript/loadSave.py index 38c45bb9..1476b7df 100644 --- a/manuskript/loadSave.py +++ b/manuskript/loadSave.py @@ -1,156 +1,27 @@ #!/usr/bin/env python # --!-- coding: utf8 --!-- -import zipfile +# The loadSave file calls the propper functions to load and save file +# trying to detect the proper file format if it comes from an older version -from PyQt5.QtCore import QModelIndex, Qt -from PyQt5.QtGui import QColor, QStandardItem -from PyQt5.QtWidgets import qApp -from lxml import etree as ET +import manuskript.load_save.version_0 as v0 +import manuskript.load_save.version_1 as v1 -from manuskript.functions import iconColor, iconFromColorString +def saveProject(version=None): -try: - import zlib # Used with zipfile for compression - - compression = zipfile.ZIP_DEFLATED -except: - compression = zipfile.ZIP_STORED - - -def saveFilesToZip(files, zipname): - """Saves given files to zipname. - files is actually a list of (content, filename).""" - - zf = zipfile.ZipFile(zipname, mode="w") - - for content, filename in files: - zf.writestr(filename, content, compress_type=compression) - - zf.close() - - -def loadFilesFromZip(zipname): - """Returns the content of zipfile as a dict of filename:content.""" - print(zipname) - zf = zipfile.ZipFile(zipname) - files = {} - for f in zf.namelist(): - files[f] = zf.read(f) - return files - - -def saveStandardItemModelXML(mdl, xml=None): - """Saves the given QStandardItemModel to XML. - If xml (filename) is given, saves to xml. Otherwise returns as string.""" - - root = ET.Element("model") - root.attrib["version"] = qApp.applicationVersion() - - # Header - header = ET.SubElement(root, "header") - vHeader = ET.SubElement(header, "vertical") - for x in range(mdl.rowCount()): - vH = ET.SubElement(vHeader, "label") - vH.attrib["row"] = str(x) - vH.attrib["text"] = str(mdl.headerData(x, Qt.Vertical)) - - hHeader = ET.SubElement(header, "horizontal") - for y in range(mdl.columnCount()): - hH = ET.SubElement(hHeader, "label") - hH.attrib["row"] = str(y) - hH.attrib["text"] = str(mdl.headerData(y, Qt.Horizontal)) - - # Data - data = ET.SubElement(root, "data") - saveItem(data, mdl) - - # print(qApp.tr("Saving to {}.").format(xml)) - if xml: - ET.ElementTree(root).write(xml, encoding="UTF-8", xml_declaration=True, pretty_print=True) + if version == 0: + v0.saveProject() else: - return ET.tostring(root, encoding="UTF-8", xml_declaration=True, pretty_print=True) + v1.saveProject() -def saveItem(root, mdl, parent=QModelIndex()): - for x in range(mdl.rowCount(parent)): - row = ET.SubElement(root, "row") - row.attrib["row"] = str(x) +def loadProject(project): - for y in range(mdl.columnCount(parent)): - col = ET.SubElement(row, "col") - col.attrib["col"] = str(y) - if mdl.data(mdl.index(x, y, parent), Qt.DecorationRole) != None: - color = iconColor(mdl.data(mdl.index(x, y, parent), Qt.DecorationRole)).name(QColor.HexArgb) - col.attrib["color"] = color if color != "#ff000000" else "#00000000" - if mdl.data(mdl.index(x, y, parent)) != "": - col.text = mdl.data(mdl.index(x, y, parent)) - if mdl.hasChildren(mdl.index(x, y, parent)): - saveItem(col, mdl, mdl.index(x, y, parent)) + # Detect version + # FIXME + version = 0 - -def loadStandardItemModelXML(mdl, xml, fromString=False): - """Load data to a QStandardItemModel mdl from xml. - By default xml is a filename. If fromString=True, xml is a string containg the data.""" - - # print(qApp.tr("Loading {}... ").format(xml), end="") - - if not fromString: - try: - tree = ET.parse(xml) - except: - print("Failed.") - return + if version == 0: + v0.loadProject(project) else: - root = ET.fromstring(xml) - - # root = tree.getroot() - - # Header - hLabels = [] - vLabels = [] - for l in root.find("header").find("horizontal").findall("label"): - hLabels.append(l.attrib["text"]) - for l in root.find("header").find("vertical").findall("label"): - vLabels.append(l.attrib["text"]) - - # print(root.find("header").find("vertical").text) - - # mdl.setVerticalHeaderLabels(vLabels) - # mdl.setHorizontalHeaderLabels(hLabels) - - # Populates with empty items - for i in enumerate(vLabels): - row = [] - for r in enumerate(hLabels): - row.append(QStandardItem()) - mdl.appendRow(row) - - # Data - data = root.find("data") - loadItem(data, mdl) - - return True - - -def loadItem(root, mdl, parent=QModelIndex()): - for row in root: - r = int(row.attrib["row"]) - for col in row: - c = int(col.attrib["col"]) - item = mdl.itemFromIndex(mdl.index(r, c, parent)) - if not item: - item = QStandardItem() - mdl.itemFromIndex(parent).setChild(r, c, item) - - if col.text: - # mdl.setData(mdl.index(r, c, parent), col.text) - item.setText(col.text) - - if "color" in col.attrib: - # mdl.itemFromIndex(mdl.index(r, c, parent)).setIcon(iconFromColorString(col.attrib["color"])) - item.setIcon(iconFromColorString(col.attrib["color"])) - - if len(col) != 0: - # loadItem(col, mdl, mdl.index(r, c, parent)) - loadItem(col, mdl, mdl.indexFromItem(item)) + v1.loadProject(project) diff --git a/manuskript/load_save/__init__.py b/manuskript/load_save/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manuskript/load_save/version_0.py b/manuskript/load_save/version_0.py new file mode 100644 index 00000000..4023539e --- /dev/null +++ b/manuskript/load_save/version_0.py @@ -0,0 +1,249 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +# Version 0 of file saving format. +# Was used at the begining and up util version XXX when +# it was superseded by Version 1, which is more open and flexible + +import zipfile + +from PyQt5.QtCore import QModelIndex, Qt +from PyQt5.QtGui import QColor, QStandardItem +from PyQt5.QtWidgets import qApp +from lxml import etree as ET + +from manuskript import settings +from manuskript.functions import iconColor, iconFromColorString, mainWindow + +try: + import zlib # Used with zipfile for compression + + compression = zipfile.ZIP_DEFLATED +except: + compression = zipfile.ZIP_STORED + +########################################################################################### +# SAVE +########################################################################################### + +def saveProject(): + """ + Saves the whole project. Call this function to save the project in Version 0 format. + """ + + files = [] + mw = mainWindow() + + files.append((saveStandardItemModelXML(mw.mdlFlatData), + "flatModel.xml")) + files.append((saveStandardItemModelXML(mw.mdlCharacter), + "perso.xml")) + files.append((saveStandardItemModelXML(mw.mdlWorld), + "world.xml")) + files.append((saveStandardItemModelXML(mw.mdlLabels), + "labels.xml")) + files.append((saveStandardItemModelXML(mw.mdlStatus), + "status.xml")) + files.append((saveStandardItemModelXML(mw.mdlPlots), + "plots.xml")) + files.append((mw.mdlOutline.saveToXML(), + "outline.xml")) + files.append((settings.save(), + "settings.pickle")) + + saveFilesToZip(files, mw.currentProject) + +def saveFilesToZip(files, zipname): + """Saves given files to zipname. + files is actually a list of (content, filename).""" + + zf = zipfile.ZipFile(zipname, mode="w") + + for content, filename in files: + zf.writestr(filename, content, compress_type=compression) + + zf.close() + +def saveStandardItemModelXML(mdl, xml=None): + """Saves the given QStandardItemModel to XML. + If xml (filename) is given, saves to xml. Otherwise returns as string.""" + + root = ET.Element("model") + root.attrib["version"] = qApp.applicationVersion() + + # Header + header = ET.SubElement(root, "header") + vHeader = ET.SubElement(header, "vertical") + for x in range(mdl.rowCount()): + vH = ET.SubElement(vHeader, "label") + vH.attrib["row"] = str(x) + vH.attrib["text"] = str(mdl.headerData(x, Qt.Vertical)) + + hHeader = ET.SubElement(header, "horizontal") + for y in range(mdl.columnCount()): + hH = ET.SubElement(hHeader, "label") + hH.attrib["row"] = str(y) + hH.attrib["text"] = str(mdl.headerData(y, Qt.Horizontal)) + + # Data + data = ET.SubElement(root, "data") + saveItem(data, mdl) + + # print(qApp.tr("Saving to {}.").format(xml)) + if xml: + ET.ElementTree(root).write(xml, encoding="UTF-8", xml_declaration=True, pretty_print=True) + else: + return ET.tostring(root, encoding="UTF-8", xml_declaration=True, pretty_print=True) + + +def saveItem(root, mdl, parent=QModelIndex()): + for x in range(mdl.rowCount(parent)): + row = ET.SubElement(root, "row") + row.attrib["row"] = str(x) + + for y in range(mdl.columnCount(parent)): + col = ET.SubElement(row, "col") + col.attrib["col"] = str(y) + if mdl.data(mdl.index(x, y, parent), Qt.DecorationRole) != None: + color = iconColor(mdl.data(mdl.index(x, y, parent), Qt.DecorationRole)).name(QColor.HexArgb) + col.attrib["color"] = color if color != "#ff000000" else "#00000000" + if mdl.data(mdl.index(x, y, parent)) != "": + col.text = mdl.data(mdl.index(x, y, parent)) + if mdl.hasChildren(mdl.index(x, y, parent)): + saveItem(col, mdl, mdl.index(x, y, parent)) + +########################################################################################### +# LOAD +########################################################################################### + +def loadProject(project): + + files = loadFilesFromZip(project) + mw = mainWindow() + + errors = [] + + if "flatModel.xml" in files: + loadStandardItemModelXML(mw.mdlFlatData, + files["flatModel.xml"], fromString=True) + else: + errors.append("flatModel.xml") + + if "perso.xml" in files: + loadStandardItemModelXML(mw.mdlCharacter, + files["perso.xml"], fromString=True) + else: + errors.append("perso.xml") + + if "world.xml" in files: + loadStandardItemModelXML(mw.mdlWorld, + files["world.xml"], fromString=True) + else: + errors.append("world.xml") + + if "labels.xml" in files: + loadStandardItemModelXML(mw.mdlLabels, + files["labels.xml"], fromString=True) + else: + errors.append("perso.xml") + + if "status.xml" in files: + loadStandardItemModelXML(mw.mdlStatus, + files["status.xml"], fromString=True) + else: + errors.append("perso.xml") + + if "plots.xml" in files: + loadStandardItemModelXML(mw.mdlPlots, + files["plots.xml"], fromString=True) + else: + errors.append("perso.xml") + + if "outline.xml" in files: + mw.mdlOutline.loadFromXML(files["outline.xml"], fromString=True) + else: + errors.append("perso.xml") + + if "settings.pickle" in files: + settings.load(files["settings.pickle"], fromString=True) + else: + errors.append("perso.xml") + + return errors + + +def loadFilesFromZip(zipname): + """Returns the content of zipfile as a dict of filename:content.""" + print(zipname) + zf = zipfile.ZipFile(zipname) + files = {} + for f in zf.namelist(): + files[f] = zf.read(f) + return files + + +def loadStandardItemModelXML(mdl, xml, fromString=False): + """Load data to a QStandardItemModel mdl from xml. + By default xml is a filename. If fromString=True, xml is a string containg the data.""" + + # print(qApp.tr("Loading {}... ").format(xml), end="") + + if not fromString: + try: + tree = ET.parse(xml) + except: + print("Failed.") + return + else: + root = ET.fromstring(xml) + + # root = tree.getroot() + + # Header + hLabels = [] + vLabels = [] + for l in root.find("header").find("horizontal").findall("label"): + hLabels.append(l.attrib["text"]) + for l in root.find("header").find("vertical").findall("label"): + vLabels.append(l.attrib["text"]) + + # print(root.find("header").find("vertical").text) + + # mdl.setVerticalHeaderLabels(vLabels) + # mdl.setHorizontalHeaderLabels(hLabels) + + # Populates with empty items + for i in enumerate(vLabels): + row = [] + for r in enumerate(hLabels): + row.append(QStandardItem()) + mdl.appendRow(row) + + # Data + data = root.find("data") + loadItem(data, mdl) + + return True + + +def loadItem(root, mdl, parent=QModelIndex()): + for row in root: + r = int(row.attrib["row"]) + for col in row: + c = int(col.attrib["col"]) + item = mdl.itemFromIndex(mdl.index(r, c, parent)) + if not item: + item = QStandardItem() + mdl.itemFromIndex(parent).setChild(r, c, item) + + if col.text: + # mdl.setData(mdl.index(r, c, parent), col.text) + item.setText(col.text) + + if "color" in col.attrib: + # mdl.itemFromIndex(mdl.index(r, c, parent)).setIcon(iconFromColorString(col.attrib["color"])) + item.setIcon(iconFromColorString(col.attrib["color"])) + + if len(col) != 0: + # loadItem(col, mdl, mdl.index(r, c, parent)) + loadItem(col, mdl, mdl.indexFromItem(item)) \ No newline at end of file diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py new file mode 100644 index 00000000..2760ea82 --- /dev/null +++ b/manuskript/load_save/version_1.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +# Version 1 of file saving format. +# Aims at providing a plain-text way of saving a project +# (except for some elements), allowing collaborative work +# versioning and third-partty editing. +import os +import zipfile + +from manuskript import settings +from manuskript.functions import mainWindow + +try: + import zlib # Used with zipfile for compression + + compression = zipfile.ZIP_DEFLATED +except: + compression = zipfile.ZIP_STORED + + +def saveProject(zip=None): + """ + Saves the project. If zip is False, the project is saved as a multitude of plain-text files for the most parts + and some XML or zip? for settings and stuff. + If zip is True, everything is saved as a single zipped file. Easier to carry around, but does not allow + collaborative work, versionning, or third-party editing. + @param zip: if True, saves as a single file. If False, saves as plain-text. If None, tries to determine based on + settings. + @return: Nothing + """ + if zip is None: + zip = False + # Fixme + + + files = [] + mw = mainWindow() + + # files.append((saveStandardItemModelXML(mw.mdlFlatData), + # "flatModel.xml")) + # # files.append((saveStandardItemModelXML(self.mdlCharacter), + # # "perso.xml")) + # files.append((saveStandardItemModelXML(mw.mdlWorld), + # "world.xml")) + # files.append((saveStandardItemModelXML(mw.mdlLabels), + # "labels.xml")) + # files.append((saveStandardItemModelXML(mw.mdlStatus), + # "status.xml")) + # files.append((saveStandardItemModelXML(mw.mdlPlots), + # "plots.xml")) + # files.append((mw.mdlOutline.saveToXML(), + # "outline.xml")) + # files.append((settings.save(), + # "settings.pickle")) + + files.append(("blabla", "test/machin.txt")) + files.append(("youpi", "encore/truc.txt")) + + project = mw.currentProject + + project = os.path.join( + os.path.dirname(project), + "_" + os.path.basename(project) + ) + + zf = zipfile.ZipFile(project, mode="w") + + for content, filename in files: + zf.writestr(filename, content, compress_type=compression) + + zf.close() + + +def loadProject(project): + """ + Loads a project. + @param project: the filename of the project to open. + @return: an array of errors, empty if None. + """ + pass \ No newline at end of file diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index 5c6511cc..b7fea969 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -11,9 +11,7 @@ from PyQt5.QtWidgets import QMainWindow, QHeaderView, qApp, QMenu, QActionGroup, from manuskript import settings from manuskript.enums import Character, Subplot, Plot, World from manuskript.functions import AUC, wordCount, appPath -from manuskript.loadSave import loadStandardItemModelXML, loadFilesFromZip -from manuskript.loadSave import saveFilesToZip -from manuskript.loadSave import saveStandardItemModelXML +from manuskript.loadSave import saveProject, loadProject from manuskript.models.characterModel import characterModel from manuskript.models.outlineModel import outlineModel from manuskript.models.plotModel import plotModel @@ -462,27 +460,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.currentProject = projectName QSettings().setValue("lastProject", projectName) - # Saving - files = [] - - files.append((saveStandardItemModelXML(self.mdlFlatData), - "flatModel.xml")) - # files.append((saveStandardItemModelXML(self.mdlCharacter), - # "perso.xml")) - files.append((saveStandardItemModelXML(self.mdlWorld), - "world.xml")) - files.append((saveStandardItemModelXML(self.mdlLabels), - "labels.xml")) - files.append((saveStandardItemModelXML(self.mdlStatus), - "status.xml")) - files.append((saveStandardItemModelXML(self.mdlPlots), - "plots.xml")) - files.append((self.mdlOutline.saveToXML(), - "outline.xml")) - files.append((settings.save(), - "settings.pickle")) - - saveFilesToZip(files, self.currentProject) + saveProject(version=0) # Giving some feedback print(self.tr("Project {} saved.").format(self.currentProject)) @@ -501,56 +479,8 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.mdlWorld = worldModel(self) def loadDatas(self, project): - # Loading - files = loadFilesFromZip(project) - errors = [] - - if "flatModel.xml" in files: - loadStandardItemModelXML(self.mdlFlatData, - files["flatModel.xml"], fromString=True) - else: - errors.append("flatModel.xml") - - if "perso.xml" in files: - loadStandardItemModelXML(self.mdlCharacter, - files["perso.xml"], fromString=True) - else: - errors.append("perso.xml") - - if "world.xml" in files: - loadStandardItemModelXML(self.mdlWorld, - files["world.xml"], fromString=True) - else: - errors.append("world.xml") - - if "labels.xml" in files: - loadStandardItemModelXML(self.mdlLabels, - files["labels.xml"], fromString=True) - else: - errors.append("perso.xml") - - if "status.xml" in files: - loadStandardItemModelXML(self.mdlStatus, - files["status.xml"], fromString=True) - else: - errors.append("perso.xml") - - if "plots.xml" in files: - loadStandardItemModelXML(self.mdlPlots, - files["plots.xml"], fromString=True) - else: - errors.append("perso.xml") - - if "outline.xml" in files: - self.mdlOutline.loadFromXML(files["outline.xml"], fromString=True) - else: - errors.append("perso.xml") - - if "settings.pickle" in files: - settings.load(files["settings.pickle"], fromString=True) - else: - errors.append("perso.xml") + errors = loadProject(project) # Giving some feedback if not errors: From 240700ca17bc9db7a445cbe85d7474d21db9e4dc Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Sat, 5 Mar 2016 00:34:50 +0100 Subject: [PATCH 026/103] Updates .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2e6ffc7e..abbe884a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ ExportTest icons/Numix .idea dist -build \ No newline at end of file +build +test-projects \ No newline at end of file From af089e8a6a94a40c3cee4e55f8e3fc404248c623 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Sat, 5 Mar 2016 00:35:14 +0100 Subject: [PATCH 027/103] Plain text file format in progress --- manuskript/load_save/version_1.py | 172 +++++++++++++++++++++++++----- manuskript/mainWindow.py | 2 +- manuskript/settings.py | 22 +++- 3 files changed, 162 insertions(+), 34 deletions(-) diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index 2760ea82..ca6c986c 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -8,8 +8,11 @@ import os import zipfile +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QColor + from manuskript import settings -from manuskript.functions import mainWindow +from manuskript.functions import mainWindow, iconColor try: import zlib # Used with zipfile for compression @@ -19,6 +22,8 @@ except: compression = zipfile.ZIP_STORED +cache = {} + def saveProject(zip=None): """ Saves the project. If zip is False, the project is saved as a multitude of plain-text files for the most parts @@ -30,46 +35,153 @@ def saveProject(zip=None): @return: Nothing """ if zip is None: - zip = False + zip = True # Fixme files = [] mw = mainWindow() - # files.append((saveStandardItemModelXML(mw.mdlFlatData), - # "flatModel.xml")) - # # files.append((saveStandardItemModelXML(self.mdlCharacter), - # # "perso.xml")) - # files.append((saveStandardItemModelXML(mw.mdlWorld), - # "world.xml")) - # files.append((saveStandardItemModelXML(mw.mdlLabels), - # "labels.xml")) - # files.append((saveStandardItemModelXML(mw.mdlStatus), - # "status.xml")) - # files.append((saveStandardItemModelXML(mw.mdlPlots), - # "plots.xml")) - # files.append((mw.mdlOutline.saveToXML(), - # "outline.xml")) - # files.append((settings.save(), - # "settings.pickle")) + # General infos (book and author) + # Saved in plain text, in infos.txt - files.append(("blabla", "test/machin.txt")) - files.append(("youpi", "encore/truc.txt")) + path = "infos.txt" + content = "" + for name, col in [ + ("Title", 0), + ("Subtitle", 1), + ("Serie", 2), + ("Volume", 3), + ("Genre", 4), + ("License", 5), + ("Author", 6), + ("Email", 7), + ]: + val = mw.mdlFlatData.item(0, col).text().strip() + if val: + content += "{name}:{spaces}{value}\n".format( + name=name, + spaces=" " * (15 - len(name)), + value=val + ) + files.append((path, content)) + + # Summary + # In plain text, in summary.txt + + path = "summary.txt" + content = "" + for name, col in [ + ("Situation", 0), + ("Sentence", 1), + ("Paragraph", 2), + ("Page", 3), + ("Full", 4), + ]: + val = mw.mdlFlatData.item(1, col).text().strip() + val = "\n".join([" " * 13 + l for l in val.split("\n")])[13:] + if val: + content += "{name}:{spaces}{value}\n".format( + name=name, + spaces=" " * (12 - len(name)), + value=val + ) + files.append((path, content)) + + # Label & Status + # In plain text + + for mdl, path in [ + (mw.mdlStatus, "status.txt"), + (mw.mdlLabels, "labels.txt") + ]: + + content = "" + + # We skip the first row, which is empty and transparent + for i in range(1, mdl.rowCount()): + color = "" + if mdl.data(mdl.index(i, 0), Qt.DecorationRole) is not None: + color = iconColor(mdl.data(mdl.index(i, 0), Qt.DecorationRole)).name(QColor.HexRgb) + color = color if color != "#ff000000" else "#00000000" + + text = mdl.data(mdl.index(i, 0)) + + if text: + content += "{name}{color}\n".format( + name=text, + color= "" if color == "" else ":" + " " * (20 - len(text)) + color + ) + + files.append((path, content)) + + # Characters (self.mdlCharacter) + # In a character folder + + # TODO + + # Texts + # In an outline folder + + # TODO + + # World (mw.mdlWorld) + # Either in an XML file, or in lots of plain texts? + # More probably text, since there might be writing done in third-party. + + # TODO + + # Plots (mw.mdlPlots) + # Either in XML or lots of plain texts? + # More probably XML since there is not really a lot if writing to do (third-party) + + # TODO + + # Settings + # Saved in readable text (json) for easier versionning. But they mustn't be shared, it seems. + # Maybe include them only if zipped? + # Well, for now, we keep them here... + files.append(("settings.txt", settings.save(protocol=0))) project = mw.currentProject - project = os.path.join( - os.path.dirname(project), - "_" + os.path.basename(project) - ) + # Save to zip + if zip: + project = os.path.join( + os.path.dirname(project), + "_" + os.path.basename(project) + ) - zf = zipfile.ZipFile(project, mode="w") + zf = zipfile.ZipFile(project, mode="w") - for content, filename in files: - zf.writestr(filename, content, compress_type=compression) + for filename, content in files: + zf.writestr(filename, content, compress_type=compression) - zf.close() + zf.close() + + # Save to plain text + else: + dir = os.path.dirname(project) + folder = os.path.splitext(os.path.basename(project))[0] + print("Saving to folder", folder) + + for path, content in files: + filename = os.path.join(dir, folder, path) + os.makedirs(os.path.dirname(filename), exist_ok=True) + print("* Saving file", filename) + + # TODO: the first time it saves, it will overwrite everything, since it's not yet in cache. + # Or we have to cache while loading. + + if not path in cache or cache[path] != content: + print(" Not in cache or changed: we write") + mode = "w"+ ("b" if type(content) == bytes else "") + with open(filename, mode) as f: + f.write(content) + cache[path] = content + + else: + print(" In cache, and identical. Do nothing.") def loadProject(project): @@ -78,4 +190,8 @@ def loadProject(project): @param project: the filename of the project to open. @return: an array of errors, empty if None. """ + + # Don't forget to cache everything that is loaded + # In order to save only what has changed. + pass \ No newline at end of file diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index b7fea969..fb1ca49a 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -460,7 +460,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.currentProject = projectName QSettings().setValue("lastProject", projectName) - saveProject(version=0) + saveProject() # version=0 # Giving some feedback print(self.tr("Project {} saved.").format(self.currentProject)) diff --git a/manuskript/settings.py b/manuskript/settings.py index 4f342f3a..d03f6dec 100644 --- a/manuskript/settings.py +++ b/manuskript/settings.py @@ -1,12 +1,16 @@ # -*- coding: utf-8 -*- import collections +import json import pickle from PyQt5.QtWidgets import qApp from manuskript.enums import Outline +# TODO: move some/all of those settings to application settings and not project settings +# in order to allow a shared project between several writers + viewSettings = { "Tree": { "Icon": "Nothing", @@ -28,7 +32,8 @@ viewSettings = { "Background": "Nothing", }, } - + +# Application spellcheck = False dict = None corkSizeFactor = 100 @@ -81,7 +86,7 @@ frequencyAnalyzer = { "phraseMax": 5 } -def save(filename=None): +def save(filename=None, protocol=None): global spellcheck, dict, corkSliderFactor, viewSettings, corkSizeFactor, folderView, lastTab, openIndexes, \ autoSave, autoSaveDelay, saveOnQuit, autoSaveNoChanges, autoSaveNoChangesDelay, outlineViewColumns, \ @@ -107,7 +112,7 @@ def save(filename=None): "textEditor":textEditor, "revisions":revisions, "frequencyAnalyzer": frequencyAnalyzer - } + } #pp=pprint.PrettyPrinter(indent=4, compact=False) #print("Saving:") @@ -117,8 +122,15 @@ def save(filename=None): f = open(filename, "wb") pickle.dump(allSettings, f) else: - return pickle.dumps(allSettings) - + if protocol == 0: + # This looks stupid + # But a simple json.dumps with sort_keys will throw a TypeError + # because of unorderable types. + return json.dumps(json.loads(json.dumps(allSettings)), indent=4, sort_keys=True) + else: + return pickle.dumps(allSettings) + + def load(string, fromString=False): """Load settings from 'string'. 'string' is the filename of the pickle dump. If fromString=True, string is the data of the pickle dumps.""" From 3fd446dd1eb5dc3ce345d38ab1dae65e40b68b95 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Sat, 5 Mar 2016 12:55:56 +0100 Subject: [PATCH 028/103] Working on backward compatibility --- manuskript/load_save/version_0.py | 24 ++++++++++++-- manuskript/load_save/version_1.py | 51 +++++++++++++++++++++++++++-- manuskript/models/characterModel.py | 2 +- 3 files changed, 71 insertions(+), 6 deletions(-) diff --git a/manuskript/load_save/version_0.py b/manuskript/load_save/version_0.py index 4023539e..1874b050 100644 --- a/manuskript/load_save/version_0.py +++ b/manuskript/load_save/version_0.py @@ -14,6 +14,7 @@ from lxml import etree as ET from manuskript import settings from manuskript.functions import iconColor, iconFromColorString, mainWindow +from manuskript.models.characterModel import Character try: import zlib # Used with zipfile for compression @@ -130,8 +131,7 @@ def loadProject(project): errors.append("flatModel.xml") if "perso.xml" in files: - loadStandardItemModelXML(mw.mdlCharacter, - files["perso.xml"], fromString=True) + loadStandardItemModelXMLForCharacters(mw.mdlCharacter, files["perso.xml"]) else: errors.append("perso.xml") @@ -246,4 +246,22 @@ def loadItem(root, mdl, parent=QModelIndex()): if len(col) != 0: # loadItem(col, mdl, mdl.index(r, c, parent)) - loadItem(col, mdl, mdl.indexFromItem(item)) \ No newline at end of file + loadItem(col, mdl, mdl.indexFromItem(item)) + + +def loadStandardItemModelXMLForCharacters(mdl, xml): + mdl = mainWindow().mdlCharacter + root = ET.fromstring(xml) + data = root.find("data") + for row in data: + char = Character(mdl) + for col in row: + c = int(col.attrib["col"]) + # Value + if col.text: + char._data[c] = col.text + # Color + if "color" in col.attrib: + char.setColor(QColor(col.attrib["color"])) + # TODO: infos + mdl.characters.append(char) \ No newline at end of file diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index ca6c986c..ede22ddb 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -12,6 +12,7 @@ from PyQt5.QtCore import Qt from PyQt5.QtGui import QColor from manuskript import settings +from manuskript.enums import Character from manuskript.functions import mainWindow, iconColor try: @@ -24,6 +25,17 @@ except: cache = {} + +def formatMetaData(name, value, tabLength=10): + + # TODO: escape ":" in name + return "{name}:{spaces}{value}\n".format( + name=name, + spaces=" " * (tabLength - len(name)), + value=value + ) + + def saveProject(zip=None): """ Saves the project. If zip is False, the project is saved as a multitude of plain-text files for the most parts @@ -35,7 +47,7 @@ def saveProject(zip=None): @return: Nothing """ if zip is None: - zip = True + zip = False # Fixme @@ -118,7 +130,42 @@ def saveProject(zip=None): # Characters (self.mdlCharacter) # In a character folder - # TODO + path = os.path.join("characters", "{name}.txt") # Not sure wheter os.path allows this + _map = [ + (Character.name, "Name"), + (Character.ID, "ID"), + (Character.importance, "Importance"), + (Character.motivation, "Motivation"), + (Character.goal, "Goal"), + (Character.conflict, "Conflict"), + (Character.summarySentence, "Phrase Summary"), + (Character.summaryPara, "Paragraph Summary"), + (Character.summaryFull, "Full Summary"), + (Character.notes, "Notes"), + ] + mdl = mw.mdlCharacter + for c in mdl.characters: + content = "" + LENGTH = 20 + for m, name in _map: + val = mdl.data(c.index(m.value)).strip() + if val: + # Multiline formatting + if len(val.split("\n")) > 1: + val = "\n".join([" " * (LENGTH + 1) + l for l in val.split("\n")])[LENGTH + 1:] + + content += formatMetaData(name, val, LENGTH) + + for info in c.infos: + content += formatMetaData(info.description, info.value, LENGTH) + + name = "{ID}-{slugName}".format( + ID=c.ID(), + slugName="FIXME" + ) + files.append(( + path.format(name=name), + content)) # Texts # In an outline folder diff --git a/manuskript/models/characterModel.py b/manuskript/models/characterModel.py index a9881f94..0a922bd7 100644 --- a/manuskript/models/characterModel.py +++ b/manuskript/models/characterModel.py @@ -213,7 +213,7 @@ class characterModel(QAbstractItemModel): ############################################################################### class Character(): - def __init__(self, model, name): + def __init__(self, model, name="No name"): self._model = model self._data = {} From b45a32cfde41cee860cba96dcc8015a0d2fee7e5 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Sat, 5 Mar 2016 17:46:02 +0100 Subject: [PATCH 029/103] Saves World Model. Still work in progress, but a step closer. --- manuskript/load_save/version_0.py | 26 ++++++- manuskript/load_save/version_1.py | 114 ++++++++++++++++++++++------ manuskript/models/characterModel.py | 1 + 3 files changed, 117 insertions(+), 24 deletions(-) diff --git a/manuskript/load_save/version_0.py b/manuskript/load_save/version_0.py index 1874b050..8bc15fcf 100644 --- a/manuskript/load_save/version_0.py +++ b/manuskript/load_save/version_0.py @@ -14,7 +14,7 @@ from lxml import etree as ET from manuskript import settings from manuskript.functions import iconColor, iconFromColorString, mainWindow -from manuskript.models.characterModel import Character +from manuskript.models.characterModel import Character, CharacterInfo try: import zlib # Used with zipfile for compression @@ -250,18 +250,40 @@ def loadItem(root, mdl, parent=QModelIndex()): def loadStandardItemModelXMLForCharacters(mdl, xml): + """ + Loads a standardItemModel saved to XML by version 0, but for the new characterModel. + @param mdl: characterModel + @param xml: the content of the xml + @return: nothing + """ mdl = mainWindow().mdlCharacter root = ET.fromstring(xml) data = root.find("data") + for row in data: char = Character(mdl) + for col in row: c = int(col.attrib["col"]) + # Value if col.text: char._data[c] = col.text + # Color if "color" in col.attrib: char.setColor(QColor(col.attrib["color"])) - # TODO: infos + + # Infos + if len(col) != 0: + for rrow in col: + info = CharacterInfo(char) + for ccol in rrow: + cc = int(ccol.attrib["col"]) + if cc == 11 and ccol.text: + info.description = ccol.text + if cc == 12 and ccol.text: + info.value = ccol.text + char.infos.append(info) + mdl.characters.append(char) \ No newline at end of file diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index ede22ddb..095d5ae3 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -6,14 +6,17 @@ # (except for some elements), allowing collaborative work # versioning and third-partty editing. import os +import string import zipfile -from PyQt5.QtCore import Qt +from PyQt5.QtCore import Qt, QModelIndex from PyQt5.QtGui import QColor from manuskript import settings -from manuskript.enums import Character +from manuskript.enums import Character, World from manuskript.functions import mainWindow, iconColor +from lxml import etree as ET + try: import zlib # Used with zipfile for compression @@ -28,7 +31,17 @@ cache = {} def formatMetaData(name, value, tabLength=10): - # TODO: escape ":" in name + # Multiline formatting + if len(value.split("\n")) > 1: + value = "\n".join([" " * (tabLength + 1) + l for l in value.split("\n")])[tabLength + 1:] + + # Avoid empty description (don't know how much MMD loves that) + if name == "": + name = "None" + + # Escapes ":" in name + name = name.replace(":", "_.._") + return "{name}:{spaces}{value}\n".format( name=name, spaces=" " * (tabLength - len(name)), @@ -36,6 +49,23 @@ def formatMetaData(name, value, tabLength=10): ) +def slugify(name): + """ + A basic slug function, that escapes all spaces to "_" and all non letters/digits to "-". + @param name: name to slugify (str) + @return: str + """ + valid = string.ascii_letters + string.digits + newName = "" + for c in name: + if c in valid: + newName += c + elif c in string.whitespace: + newName += "_" + else: + newName += "-" + return newName + def saveProject(zip=None): """ Saves the project. If zip is False, the project is saved as a multitude of plain-text files for the most parts @@ -50,8 +80,11 @@ def saveProject(zip=None): zip = False # Fixme - + # List of files to be written files = [] + # List of files to be removed + removes = [] + mw = mainWindow() # General infos (book and author) @@ -91,13 +124,9 @@ def saveProject(zip=None): ("Full", 4), ]: val = mw.mdlFlatData.item(1, col).text().strip() - val = "\n".join([" " * 13 + l for l in val.split("\n")])[13:] if val: - content += "{name}:{spaces}{value}\n".format( - name=name, - spaces=" " * (12 - len(name)), - value=val - ) + content += formatMetaData(name, val, 12) + files.append((path, content)) # Label & Status @@ -146,25 +175,23 @@ def saveProject(zip=None): mdl = mw.mdlCharacter for c in mdl.characters: content = "" - LENGTH = 20 for m, name in _map: val = mdl.data(c.index(m.value)).strip() if val: - # Multiline formatting - if len(val.split("\n")) > 1: - val = "\n".join([" " * (LENGTH + 1) + l for l in val.split("\n")])[LENGTH + 1:] - - content += formatMetaData(name, val, LENGTH) + content += formatMetaData(name, val, 20) for info in c.infos: - content += formatMetaData(info.description, info.value, LENGTH) + content += formatMetaData(info.description, info.value, 20) - name = "{ID}-{slugName}".format( + cpath = path.format(name="{ID}-{slugName}".format( ID=c.ID(), - slugName="FIXME" - ) + slugName=slugify(c.name()) + )) + if c.lastPath and cpath != c.lastPath: + removes.append(c.lastPath) + c.lastPath = cpath files.append(( - path.format(name=name), + cpath, content)) # Texts @@ -176,7 +203,15 @@ def saveProject(zip=None): # Either in an XML file, or in lots of plain texts? # More probably text, since there might be writing done in third-party. - # TODO + path = "world.opml" + mdl = mw.mdlWorld + + root = ET.Element("opml") + root.attrib["version"] = "1.0" + body = ET.SubElement(root, "body") + addWorldItem(body, mdl) + content = ET.tostring(root, encoding="UTF-8", xml_declaration=True, pretty_print=True) + files.append((path, content)) # Plots (mw.mdlPlots) # Either in XML or lots of plain texts? @@ -212,6 +247,13 @@ def saveProject(zip=None): folder = os.path.splitext(os.path.basename(project))[0] print("Saving to folder", folder) + for path in removes: + if path not in [p for p,c in files]: + filename = os.path.join(dir, folder, path) + print("* Removing", filename) + os.remove(filename) + cache.pop(path) + for path, content in files: filename = os.path.join(dir, folder, path) os.makedirs(os.path.dirname(filename), exist_ok=True) @@ -230,6 +272,34 @@ def saveProject(zip=None): else: print(" In cache, and identical. Do nothing.") +def addWorldItem(root, mdl, parent=QModelIndex()): + """ + Lists elements in a world model and create an OPML xml file. + @param root: an Etree element + @param mdl: a worldModel + @param parent: the parent index in the world model + @return: root, to which sub element have been added + """ + # List every row (every world item) + for x in range(mdl.rowCount(parent)): + + # For each row, create an outline item. + outline = ET.SubElement(root, "outline") + for y in range(mdl.columnCount(parent)): + + val = mdl.data(mdl.index(x, y, parent)) + + if not val: + continue + + for w in World: + if y == w.value: + outline.attrib[w.name] = val + + if mdl.hasChildren(mdl.index(x, y, parent)): + addWorldItem(outline, mdl, mdl.index(x, y, parent)) + + return root def loadProject(project): """ diff --git a/manuskript/models/characterModel.py b/manuskript/models/characterModel.py index 0a922bd7..84133d96 100644 --- a/manuskript/models/characterModel.py +++ b/manuskript/models/characterModel.py @@ -215,6 +215,7 @@ class characterModel(QAbstractItemModel): class Character(): def __init__(self, model, name="No name"): self._model = model + self.lastPath = "" self._data = {} self._data[C.name.value] = name From fca0e9679f15b26c539b027fb4ddfc128a99520a Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Sat, 5 Mar 2016 17:53:25 +0100 Subject: [PATCH 030/103] Corrects a potential segfault --- manuskript/models/characterModel.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/manuskript/models/characterModel.py b/manuskript/models/characterModel.py index 84133d96..fce95f4b 100644 --- a/manuskript/models/characterModel.py +++ b/manuskript/models/characterModel.py @@ -33,6 +33,9 @@ class characterModel(QAbstractItemModel): return 1 def data(self, index, role=Qt.DisplayRole): + if not index.isValid(): + return None + c = index.internalPointer() if type(c) == Character: if role == Qt.DisplayRole: From 65579dbd6e725bdd82a595f964db233b2987d644 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Sat, 5 Mar 2016 18:20:39 +0100 Subject: [PATCH 031/103] Updates makefile for gdb --- makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/makefile b/makefile index f003a312..46e7e6e3 100644 --- a/makefile +++ b/makefile @@ -10,7 +10,7 @@ run: $(UIs) bin/manuskript debug: $(UIs) - gdb --args python3 manuskript/main.py + gdb --args python3 bin/manuskript lineprof: kernprof -l -v manuskript/main.py From e824f7c9256aab2e18dce0c9538e53c8dd392003 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Sat, 5 Mar 2016 18:28:29 +0100 Subject: [PATCH 032/103] =?UTF-8?q?Refactoring=20outlinePersoDelegate=20?= =?UTF-8?q?=E2=86=92=20outlineCharacterDelegate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- i18n/manuskript_fr.ts | 2 +- manuskript/mainWindow.py | 4 ++-- manuskript/ui/views/outlineDelegates.py | 2 +- manuskript/ui/views/outlineView.py | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/i18n/manuskript_fr.ts b/i18n/manuskript_fr.ts index d4e27417..6a308d39 100644 --- a/i18n/manuskript_fr.ts +++ b/i18n/manuskript_fr.ts @@ -1669,7 +1669,7 @@ des lignes: - outlinePersoDelegate + outlineCharacterDelegate None diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index fb1ca49a..143794cb 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -22,7 +22,7 @@ from manuskript.ui.compileDialog import compileDialog from manuskript.ui.helpLabel import helpLabel from manuskript.ui.mainWindow import Ui_MainWindow from manuskript.ui.tools.frequencyAnalyzer import frequencyAnalyzer -from manuskript.ui.views.outlineDelegates import outlinePersoDelegate +from manuskript.ui.views.outlineDelegates import outlineCharacterDelegate from manuskript.ui.views.plotDelegate import plotDelegate # Spellcheck support @@ -605,7 +605,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.mdlCharacter.dataChanged.connect(self.mdlPlots.updatePlotPersoButton) self.lstOutlinePlots.setPlotModel(self.mdlPlots) self.lstOutlinePlots.setShowSubPlot(True) - self.plotPersoDelegate = outlinePersoDelegate(self.mdlCharacter, self) + self.plotPersoDelegate = outlineCharacterDelegate(self.mdlCharacter, self) self.lstPlotPerso.setItemDelegate(self.plotPersoDelegate) self.plotDelegate = plotDelegate(self) self.lstSubPlots.setItemDelegateForColumn(Subplot.meta.value, self.plotDelegate) diff --git a/manuskript/ui/views/outlineDelegates.py b/manuskript/ui/views/outlineDelegates.py index 1172f766..383d2cf1 100644 --- a/manuskript/ui/views/outlineDelegates.py +++ b/manuskript/ui/views/outlineDelegates.py @@ -92,7 +92,7 @@ class outlineTitleDelegate(QStyledItemDelegate): # QStyledItemDelegate.paint(self, painter, option, index) -class outlinePersoDelegate(QStyledItemDelegate): +class outlineCharacterDelegate(QStyledItemDelegate): def __init__(self, mdlCharacter, parent=None): QStyledItemDelegate.__init__(self, parent) self.mdlCharacter = mdlCharacter diff --git a/manuskript/ui/views/outlineView.py b/manuskript/ui/views/outlineView.py index 670894f2..6bb55a27 100644 --- a/manuskript/ui/views/outlineView.py +++ b/manuskript/ui/views/outlineView.py @@ -6,7 +6,7 @@ from manuskript import settings from manuskript.enums import Outline from manuskript.ui.views.dndView import dndView from manuskript.ui.views.outlineBasics import outlineBasics -from manuskript.ui.views.outlineDelegates import outlineTitleDelegate, outlinePersoDelegate, outlineCompileDelegate, \ +from manuskript.ui.views.outlineDelegates import outlineTitleDelegate, outlineCharacterDelegate, outlineCompileDelegate, \ outlineStatusDelegate, outlineGoalPercentageDelegate, outlineLabelDelegate @@ -23,7 +23,7 @@ class outlineView(QTreeView, dndView, outlineBasics): self.header().setStretchLastSection(False) def setModelCharacters(self, model): - # This is used by outlinePersoDelegate to select character + # This is used by outlineCharacterDelegate to select character self.modelCharacters = model def setModelLabels(self, model): @@ -41,7 +41,7 @@ class outlineView(QTreeView, dndView, outlineBasics): self.outlineTitleDelegate = outlineTitleDelegate(self) # self.outlineTitleDelegate.setView(self) self.setItemDelegateForColumn(Outline.title.value, self.outlineTitleDelegate) - self.outlinePersoDelegate = outlinePersoDelegate(self.modelCharacters) + self.outlinePersoDelegate = outlineCharacterDelegate(self.modelCharacters) self.setItemDelegateForColumn(Outline.POV.value, self.outlinePersoDelegate) self.outlineCompileDelegate = outlineCompileDelegate() self.setItemDelegateForColumn(Outline.compile.value, self.outlineCompileDelegate) From e6913849de235a66c9bb1b68fd0cc53ef84d5f2a Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Sun, 6 Mar 2016 08:47:51 +0100 Subject: [PATCH 033/103] Eradicating 'persos' --- manuskript/ui/views/outlineView.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manuskript/ui/views/outlineView.py b/manuskript/ui/views/outlineView.py index 6bb55a27..50e44ecc 100644 --- a/manuskript/ui/views/outlineView.py +++ b/manuskript/ui/views/outlineView.py @@ -41,8 +41,8 @@ class outlineView(QTreeView, dndView, outlineBasics): self.outlineTitleDelegate = outlineTitleDelegate(self) # self.outlineTitleDelegate.setView(self) self.setItemDelegateForColumn(Outline.title.value, self.outlineTitleDelegate) - self.outlinePersoDelegate = outlineCharacterDelegate(self.modelCharacters) - self.setItemDelegateForColumn(Outline.POV.value, self.outlinePersoDelegate) + self.outlineCharacterDelegate = outlineCharacterDelegate(self.modelCharacters) + self.setItemDelegateForColumn(Outline.POV.value, self.outlineCharacterDelegate) self.outlineCompileDelegate = outlineCompileDelegate() self.setItemDelegateForColumn(Outline.compile.value, self.outlineCompileDelegate) self.outlineStatusDelegate = outlineStatusDelegate(self.modelStatus) From 50dc7f37395cd84469af1e3a11b71147f8f4d9c4 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Sun, 6 Mar 2016 09:21:10 +0100 Subject: [PATCH 034/103] Tracking segfault, hopefuly done now --- manuskript/mainWindow.py | 4 ++-- manuskript/models/characterModel.py | 5 +---- manuskript/ui/views/outlineDelegates.py | 21 ++++++++++++--------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index 143794cb..630d6856 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -605,8 +605,8 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.mdlCharacter.dataChanged.connect(self.mdlPlots.updatePlotPersoButton) self.lstOutlinePlots.setPlotModel(self.mdlPlots) self.lstOutlinePlots.setShowSubPlot(True) - self.plotPersoDelegate = outlineCharacterDelegate(self.mdlCharacter, self) - self.lstPlotPerso.setItemDelegate(self.plotPersoDelegate) + self.plotCharacterDelegate = outlineCharacterDelegate(self.mdlCharacter, self) + self.lstPlotPerso.setItemDelegate(self.plotCharacterDelegate) self.plotDelegate = plotDelegate(self) self.lstSubPlots.setItemDelegateForColumn(Subplot.meta.value, self.plotDelegate) diff --git a/manuskript/models/characterModel.py b/manuskript/models/characterModel.py index fce95f4b..e1a4e684 100644 --- a/manuskript/models/characterModel.py +++ b/manuskript/models/characterModel.py @@ -30,12 +30,9 @@ class characterModel(QAbstractItemModel): # Returns characters infos return 2 else: - return 1 + return len(C) def data(self, index, role=Qt.DisplayRole): - if not index.isValid(): - return None - c = index.internalPointer() if type(c) == Character: if role == Qt.DisplayRole: diff --git a/manuskript/ui/views/outlineDelegates.py b/manuskript/ui/views/outlineDelegates.py index 383d2cf1..d091070f 100644 --- a/manuskript/ui/views/outlineDelegates.py +++ b/manuskript/ui/views/outlineDelegates.py @@ -101,9 +101,9 @@ class outlineCharacterDelegate(QStyledItemDelegate): # s = QStyledItemDelegate.sizeHint(self, option, index) item = QModelIndex() - for i in range(self.mdlCharacter.rowCount()): - if self.mdlCharacter.ID(i) == index.data(): - item = self.mdlCharacter.index(i, Character.name.value) + character = self.mdlCharacter.getCharacterByID(index.data()) + if character: + item = character.index(Character.name.value) opt = QStyleOptionViewItem(option) self.initStyleOption(opt, item) @@ -158,18 +158,21 @@ class outlineCharacterDelegate(QStyledItemDelegate): # QStyledItemDelegate.paint(self, painter, option, index) ##option.rect.setWidth(option.rect.width() + 18) - item = QModelIndex() - for i in range(self.mdlCharacter.rowCount()): - if self.mdlCharacter.ID(i) == index.data(): - item = self.mdlCharacter.index(i, Character.name.value) + itemIndex = QModelIndex() + character = self.mdlCharacter.getCharacterByID(index.data()) + if character: + itemIndex = character.index(Character.name.value) + else: + # Character ID not found in character model. + return opt = QStyleOptionViewItem(option) - self.initStyleOption(opt, item) + self.initStyleOption(opt, itemIndex) qApp.style().drawControl(QStyle.CE_ItemViewItem, opt, painter) # if index.isValid() and index.internalPointer().data(Outline.POV.value) not in ["", None]: - if index.isValid() and self.mdlCharacter.data(index) not in ["", None]: + if itemIndex.isValid() and self.mdlCharacter.data(itemIndex) not in ["", None]: opt = QStyleOptionComboBox() opt.rect = option.rect r = qApp.style().subControlRect(QStyle.CC_ComboBox, opt, QStyle.SC_ComboBoxArrow) From 599a60ecff74372929a3c297a7ebaf8606f30687 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Sun, 6 Mar 2016 09:26:59 +0100 Subject: [PATCH 035/103] Changes some more 'persos' to 'characters' --- manuskript/enums.py | 3 +- manuskript/load_save/version_1.py | 58 +++++++++++++++++++++++++++++-- manuskript/mainWindow.py | 4 +-- manuskript/models/plotModel.py | 6 ++-- manuskript/models/references.py | 2 +- 5 files changed, 63 insertions(+), 10 deletions(-) diff --git a/manuskript/enums.py b/manuskript/enums.py index 591812e0..1368dfdc 100644 --- a/manuskript/enums.py +++ b/manuskript/enums.py @@ -26,7 +26,7 @@ class Plot(Enum): name = 0 ID = 1 importance = 2 - persos = 3 + characters = 3 description = 4 result = 5 subplots = 6 @@ -64,4 +64,3 @@ class Outline(Enum): # (sum of all sub-items' goals) textFormat = 15 revisions = 16 - diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index 095d5ae3..19b7e627 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -13,7 +13,7 @@ from PyQt5.QtCore import Qt, QModelIndex from PyQt5.QtGui import QColor from manuskript import settings -from manuskript.enums import Character, World +from manuskript.enums import Character, World, Plot from manuskript.functions import mainWindow, iconColor from lxml import etree as ET @@ -217,7 +217,15 @@ def saveProject(zip=None): # Either in XML or lots of plain texts? # More probably XML since there is not really a lot if writing to do (third-party) - # TODO + path = "plots.opml" + mdl = mw.mdlPlots + + root = ET.Element("opml") + root.attrib["version"] = "1.0" + body = ET.SubElement(root, "body") + addPlotItem(body, mdl) + content = ET.tostring(root, encoding="UTF-8", xml_declaration=True, pretty_print=True) + files.append((path, content)) # Settings # Saved in readable text (json) for easier versionning. But they mustn't be shared, it seems. @@ -301,6 +309,52 @@ def addWorldItem(root, mdl, parent=QModelIndex()): return root + +def addPlotItem(root, mdl, parent=QModelIndex()): + """ + Lists elements in a plot model and create an OPML xml file. + @param root: an Etree element + @param mdl: a plotModel + @param parent: the parent index in the plot model + @return: root, to which sub element have been added + """ + + # List every row (every plot item) + for x in range(mdl.rowCount(parent)): + + # For each row, create an outline item. + outline = ET.SubElement(root, "outline") + for y in range(mdl.columnCount(parent)): + + index = mdl.index(x, y, parent) + val = mdl.data(index) + + if not val: + continue + + for w in Plot: + if y == w.value: + outline.attrib[w.name] = val + + if y == Plot.characters.value: + if mdl.hasChildren(index): + characters = [] + for cX in range(mdl.rowCount(index)): + for cY in range(mdl.columnCount(index)): + cIndex = mdl.index(cX, cY, index) + characters.append(mdl.data(cIndex)) + outline.attrib[Plot.characters.name] = ",".join(characters) + else: + outline.attrib.pop(Plot.characters.name) + + elif y == Plot.subplots.value and mdl.hasChildren(index): + outline.attrib.pop(Plot.subplots.name) + + + # addWorldItem(outline, mdl, index) + + return root + def loadProject(project): """ Loads a project. diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index 630d6856..b01edd22 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -205,7 +205,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.txtPlotResult.setCurrentModelIndex(index) self.sldPlotImportance.setCurrentModelIndex(index) self.lstPlotPerso.setRootIndex(index.sibling(index.row(), - Plot.persos.value)) + Plot.characters.value)) subplotindex = index.sibling(index.row(), Plot.subplots.value) self.lstSubPlots.setRootIndex(subplotindex) if self.mdlPlots.rowCount(subplotindex): @@ -678,7 +678,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.tblDebugPlots.selectionModel().currentChanged.connect( lambda: self.tblDebugPlotsPersos.setRootIndex(self.mdlPlots.index( self.tblDebugPlots.selectionModel().currentIndex().row(), - Plot.persos.value)), AUC) + Plot.characters.value)), AUC) self.tblDebugPlots.selectionModel().currentChanged.connect( lambda: self.tblDebugSubPlots.setRootIndex(self.mdlPlots.index( self.tblDebugPlots.selectionModel().currentIndex().row(), diff --git a/manuskript/models/plotModel.py b/manuskript/models/plotModel.py index b7f4949f..6b272af8 100644 --- a/manuskript/models/plotModel.py +++ b/manuskript/models/plotModel.py @@ -182,10 +182,10 @@ class plotModel(QStandardItemModel): def addPlotPerso(self, v): index = self.mw.lstPlots.currentPlotIndex() if index.isValid(): - if not self.item(index.row(), Plot.persos.value): - self.setItem(index.row(), Plot.persos.value, QStandardItem()) + if not self.item(index.row(), Plot.characters.value): + self.setItem(index.row(), Plot.characters.value, QStandardItem()) - item = self.item(index.row(), Plot.persos.value) + item = self.item(index.row(), Plot.characters.value) # We check that the PersoID is not in the list yet for i in range(item.rowCount()): diff --git a/manuskript/models/references.py b/manuskript/models/references.py index 64261d7e..8c6b86a1 100644 --- a/manuskript/models/references.py +++ b/manuskript/models/references.py @@ -277,7 +277,7 @@ def infos(ref): # Characters pM = mainWindow().mdlCharacter - item = m.item(index.row(), Plot.persos.value) + item = m.item(index.row(), Plot.characters.value) characters = "" if item: for r in range(item.rowCount()): From 8949d7b8e31c3cab679b2ce224aa26fc85e4f5f0 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Sun, 6 Mar 2016 16:10:25 +0100 Subject: [PATCH 036/103] Changes subplots to steps --- manuskript/enums.py | 4 ++-- manuskript/load_save/version_1.py | 4 ++-- manuskript/mainWindow.py | 20 ++++++++++---------- manuskript/models/plotModel.py | 22 +++++++++++----------- manuskript/models/references.py | 10 +++++----- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/manuskript/enums.py b/manuskript/enums.py index 1368dfdc..661915f0 100644 --- a/manuskript/enums.py +++ b/manuskript/enums.py @@ -29,10 +29,10 @@ class Plot(Enum): characters = 3 description = 4 result = 5 - subplots = 6 + steps = 6 summary = 7 -class Subplot(Enum): +class PlotStep(Enum): name = 0 ID = 1 meta = 2 diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index 19b7e627..3dbddc21 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -347,8 +347,8 @@ def addPlotItem(root, mdl, parent=QModelIndex()): else: outline.attrib.pop(Plot.characters.name) - elif y == Plot.subplots.value and mdl.hasChildren(index): - outline.attrib.pop(Plot.subplots.name) + elif y == Plot.steps.value and mdl.hasChildren(index): + outline.attrib.pop(Plot.steps.name) # addWorldItem(outline, mdl, index) diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index b01edd22..401b10cc 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -9,7 +9,7 @@ from PyQt5.QtWidgets import QMainWindow, QHeaderView, qApp, QMenu, QActionGroup, QLabel from manuskript import settings -from manuskript.enums import Character, Subplot, Plot, World +from manuskript.enums import Character, PlotStep, Plot, World from manuskript.functions import AUC, wordCount, appPath from manuskript.loadSave import saveProject, loadProject from manuskript.models.characterModel import characterModel @@ -206,7 +206,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.sldPlotImportance.setCurrentModelIndex(index) self.lstPlotPerso.setRootIndex(index.sibling(index.row(), Plot.characters.value)) - subplotindex = index.sibling(index.row(), Plot.subplots.value) + subplotindex = index.sibling(index.row(), Plot.steps.value) self.lstSubPlots.setRootIndex(subplotindex) if self.mdlPlots.rowCount(subplotindex): self.updateSubPlotView() @@ -222,18 +222,18 @@ class MainWindow(QMainWindow, Ui_MainWindow): # Hide columns for i in range(self.mdlPlots.columnCount()): self.lstSubPlots.hideColumn(i) - self.lstSubPlots.showColumn(Subplot.name.value) - self.lstSubPlots.showColumn(Subplot.meta.value) + self.lstSubPlots.showColumn(PlotStep.name.value) + self.lstSubPlots.showColumn(PlotStep.meta.value) self.lstSubPlots.horizontalHeader().setSectionResizeMode( - Subplot.name.value, QHeaderView.Stretch) + PlotStep.name.value, QHeaderView.Stretch) self.lstSubPlots.horizontalHeader().setSectionResizeMode( - Subplot.meta.value, QHeaderView.ResizeToContents) + PlotStep.meta.value, QHeaderView.ResizeToContents) self.lstSubPlots.verticalHeader().hide() def changeCurrentSubPlot(self, index): # Got segfaults when using textEditView model system, so ad hoc stuff. - index = index.sibling(index.row(), Subplot.summary.value) + index = index.sibling(index.row(), PlotStep.summary.value) item = self.mdlPlots.itemFromIndex(index) if not item: self.txtSubPlotSummary.setEnabled(False) @@ -251,7 +251,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): index = self.lstSubPlots.currentIndex() if not index.isValid(): return - index = index.sibling(index.row(), Subplot.summary.value) + index = index.sibling(index.row(), PlotStep.summary.value) item = self.mdlPlots.itemFromIndex(index) self._updatingSubPlot = True @@ -608,7 +608,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.plotCharacterDelegate = outlineCharacterDelegate(self.mdlCharacter, self) self.lstPlotPerso.setItemDelegate(self.plotCharacterDelegate) self.plotDelegate = plotDelegate(self) - self.lstSubPlots.setItemDelegateForColumn(Subplot.meta.value, self.plotDelegate) + self.lstSubPlots.setItemDelegateForColumn(PlotStep.meta.value, self.plotDelegate) # World self.treeWorld.setModel(self.mdlWorld) @@ -682,7 +682,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.tblDebugPlots.selectionModel().currentChanged.connect( lambda: self.tblDebugSubPlots.setRootIndex(self.mdlPlots.index( self.tblDebugPlots.selectionModel().currentIndex().row(), - Plot.subplots.value)), AUC) + Plot.steps.value)), AUC) self.treeDebugWorld.setModel(self.mdlWorld) self.treeDebugOutline.setModel(self.mdlOutline) self.lstDebugLabels.setModel(self.mdlLabels) diff --git a/manuskript/models/plotModel.py b/manuskript/models/plotModel.py index 6b272af8..5ac706f5 100644 --- a/manuskript/models/plotModel.py +++ b/manuskript/models/plotModel.py @@ -9,7 +9,7 @@ from PyQt5.QtGui import QStandardItemModel from PyQt5.QtWidgets import QAction, QMenu from manuskript.enums import Plot -from manuskript.enums import Subplot +from manuskript.enums import PlotStep from manuskript.functions import toInt, mainWindow @@ -37,7 +37,7 @@ class plotModel(QStandardItemModel): index = self.getIndexFromID(ID) if not index.isValid(): return - index = index.sibling(index.row(), Plot.subplots.value) + index = index.sibling(index.row(), Plot.steps.value) item = self.itemFromIndex(index) lst = [] for i in range(item.rowCount()): @@ -86,8 +86,8 @@ class plotModel(QStandardItemModel): p = QStandardItem(self.tr("New plot")) _id = QStandardItem(self.getUniqueID()) importance = QStandardItem(str(0)) - self.appendRow([p, _id, importance, QStandardItem("Persos"), - QStandardItem(), QStandardItem(), QStandardItem("Subplots")]) + self.appendRow([p, _id, importance, QStandardItem("Characters"), + QStandardItem(), QStandardItem(), QStandardItem("Resolution steps")]) def getUniqueID(self, parent=QModelIndex()): """Returns an unused ID""" @@ -114,9 +114,9 @@ class plotModel(QStandardItemModel): def headerData(self, section, orientation, role=Qt.DisplayRole): if role == Qt.DisplayRole: if orientation == Qt.Horizontal: - if section == Subplot.name.value: + if section == PlotStep.name.value: return self.tr("Name") - elif section == Subplot.meta.value: + elif section == PlotStep.meta.value: return self.tr("Meta") else: return "" @@ -127,8 +127,8 @@ class plotModel(QStandardItemModel): def data(self, index, role=Qt.DisplayRole): if index.parent().isValid() and \ - index.parent().column() == Plot.subplots.value and \ - index.column() == Subplot.meta.value: + index.parent().column() == Plot.steps.value and \ + index.column() == PlotStep.meta.value: if role == Qt.TextAlignmentRole: return Qt.AlignRight | Qt.AlignVCenter elif role == Qt.ForegroundRole: @@ -144,13 +144,13 @@ class plotModel(QStandardItemModel): if not index.isValid(): return - parent = index.sibling(index.row(), Plot.subplots.value) - parentItem = self.item(index.row(), Plot.subplots.value) + parent = index.sibling(index.row(), Plot.steps.value) + parentItem = self.item(index.row(), Plot.steps.value) if not parentItem: return - p = QStandardItem(self.tr("New subplot")) + p = QStandardItem(self.tr("New step")) _id = QStandardItem(self.getUniqueID(parent)) summary = QStandardItem() diff --git a/manuskript/models/references.py b/manuskript/models/references.py index 8c6b86a1..a8a86cb7 100644 --- a/manuskript/models/references.py +++ b/manuskript/models/references.py @@ -13,7 +13,7 @@ from PyQt5.QtWidgets import qApp from manuskript.enums import Outline from manuskript.enums import Character from manuskript.enums import Plot -from manuskript.enums import Subplot +from manuskript.enums import PlotStep from manuskript.functions import mainWindow RegEx = r"{(\w):(\d+):?.*?}" @@ -288,12 +288,12 @@ def infos(ref): # Resolution steps steps = "" - item = m.item(index.row(), Plot.subplots.value) + item = m.item(index.row(), Plot.steps.value) if item: for r in range(item.rowCount()): - title = item.child(r, Subplot.name.value).text() - summary = item.child(r, Subplot.summary.value).text() - meta = item.child(r, Subplot.meta.value).text() + title = item.child(r, PlotStep.name.value).text() + summary = item.child(r, PlotStep.summary.value).text() + meta = item.child(r, PlotStep.meta.value).text() if meta: meta = " ({})".format(meta) steps += "
  • {title}{summary}{meta}
  • ".format( From c31681a724f64038a0d4d1aa1638c30eebe80758 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Sun, 6 Mar 2016 16:27:03 +0100 Subject: [PATCH 037/103] Saves plots, correctly. --- manuskript/load_save/version_1.py | 42 +++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index 3dbddc21..6e582914 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -13,7 +13,7 @@ from PyQt5.QtCore import Qt, QModelIndex from PyQt5.QtGui import QColor from manuskript import settings -from manuskript.enums import Character, World, Plot +from manuskript.enums import Character, World, Plot, PlotStep from manuskript.functions import mainWindow, iconColor from lxml import etree as ET @@ -25,7 +25,6 @@ try: except: compression = zipfile.ZIP_STORED - cache = {} @@ -66,6 +65,7 @@ def slugify(name): newName += "-" return newName + def saveProject(zip=None): """ Saves the project. If zip is False, the project is saved as a multitude of plain-text files for the most parts @@ -87,6 +87,10 @@ def saveProject(zip=None): mw = mainWindow() + if zip: + # File format version + files.append(("VERSION", "1")) + # General infos (book and author) # Saved in plain text, in infos.txt @@ -187,6 +191,8 @@ def saveProject(zip=None): ID=c.ID(), slugName=slugify(c.name()) )) + # Has the character been renamed? + # If so, we remove the old file (if not zipped) if c.lastPath and cpath != c.lastPath: removes.append(c.lastPath) c.lastPath = cpath @@ -217,13 +223,11 @@ def saveProject(zip=None): # Either in XML or lots of plain texts? # More probably XML since there is not really a lot if writing to do (third-party) - path = "plots.opml" + path = "plots.xml" mdl = mw.mdlPlots - root = ET.Element("opml") - root.attrib["version"] = "1.0" - body = ET.SubElement(root, "body") - addPlotItem(body, mdl) + root = ET.Element("root") + addPlotItem(root, mdl) content = ET.tostring(root, encoding="UTF-8", xml_declaration=True, pretty_print=True) files.append((path, content)) @@ -280,6 +284,7 @@ def saveProject(zip=None): else: print(" In cache, and identical. Do nothing.") + def addWorldItem(root, mdl, parent=QModelIndex()): """ Lists elements in a world model and create an OPML xml file. @@ -312,7 +317,7 @@ def addWorldItem(root, mdl, parent=QModelIndex()): def addPlotItem(root, mdl, parent=QModelIndex()): """ - Lists elements in a plot model and create an OPML xml file. + Lists elements in a plot model and create an xml file. @param root: an Etree element @param mdl: a plotModel @param parent: the parent index in the plot model @@ -323,7 +328,7 @@ def addPlotItem(root, mdl, parent=QModelIndex()): for x in range(mdl.rowCount(parent)): # For each row, create an outline item. - outline = ET.SubElement(root, "outline") + outline = ET.SubElement(root, "plot") for y in range(mdl.columnCount(parent)): index = mdl.index(x, y, parent) @@ -336,6 +341,7 @@ def addPlotItem(root, mdl, parent=QModelIndex()): if y == w.value: outline.attrib[w.name] = val + # List characters as attrib if y == Plot.characters.value: if mdl.hasChildren(index): characters = [] @@ -347,14 +353,24 @@ def addPlotItem(root, mdl, parent=QModelIndex()): else: outline.attrib.pop(Plot.characters.name) - elif y == Plot.steps.value and mdl.hasChildren(index): + # List resolution steps as sub items + elif y == Plot.steps.value: + if mdl.hasChildren(index): + for cX in range(mdl.rowCount(index)): + step = ET.SubElement(outline, "step") + for cY in range(mdl.columnCount(index)): + cIndex = mdl.index(cX, cY, index) + val = mdl.data(cIndex) + + for w in PlotStep: + if cY == w.value: + step.attrib[w.name] = val + outline.attrib.pop(Plot.steps.name) - - # addWorldItem(outline, mdl, index) - return root + def loadProject(project): """ Loads a project. From 05715a26e048294f2122e7f0ebb8ecc8fcf1c09f Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 8 Mar 2016 09:21:44 +0100 Subject: [PATCH 038/103] Writes outline --- manuskript/loadSave.py | 26 +++++++++- manuskript/load_save/version_0.py | 18 +++---- manuskript/load_save/version_1.py | 84 +++++++++++++++++++++++++++++-- manuskript/models/outlineModel.py | 9 +++- 4 files changed, 122 insertions(+), 15 deletions(-) diff --git a/manuskript/loadSave.py b/manuskript/loadSave.py index 1476b7df..0433d264 100644 --- a/manuskript/loadSave.py +++ b/manuskript/loadSave.py @@ -3,10 +3,12 @@ # The loadSave file calls the propper functions to load and save file # trying to detect the proper file format if it comes from an older version +import zipfile import manuskript.load_save.version_0 as v0 import manuskript.load_save.version_1 as v1 + def saveProject(version=None): if version == 0: @@ -18,9 +20,31 @@ def saveProject(version=None): def loadProject(project): # Detect version - # FIXME + # Is it a zip? + isZip = False version = 0 + try: + zf = zipfile.ZipFile(project) + isZip = True + except zipfile.BadZipFile: + isZip = False + + # Does it have a VERSION in zip root? + if isZip and "VERSION" in zf.namelist(): + version = int(zf.read("VERSION")) + + # Zip but no VERSION: oldest file format + elif isZip: + version = 0 + + # Not a zip + else: + # FIXME + pass + + print("Detected file format version:", version) + if version == 0: v0.loadProject(project) else: diff --git a/manuskript/load_save/version_0.py b/manuskript/load_save/version_0.py index 8bc15fcf..76cd886d 100644 --- a/manuskript/load_save/version_0.py +++ b/manuskript/load_save/version_0.py @@ -37,8 +37,9 @@ def saveProject(): files.append((saveStandardItemModelXML(mw.mdlFlatData), "flatModel.xml")) - files.append((saveStandardItemModelXML(mw.mdlCharacter), - "perso.xml")) + print("ERROR: file format 0 does not save characters !") + # files.append((saveStandardItemModelXML(mw.mdlCharacter), + # "perso.xml")) files.append((saveStandardItemModelXML(mw.mdlWorld), "world.xml")) files.append((saveStandardItemModelXML(mw.mdlLabels), @@ -145,36 +146,35 @@ def loadProject(project): loadStandardItemModelXML(mw.mdlLabels, files["labels.xml"], fromString=True) else: - errors.append("perso.xml") + errors.append("labels.xml") if "status.xml" in files: loadStandardItemModelXML(mw.mdlStatus, files["status.xml"], fromString=True) else: - errors.append("perso.xml") + errors.append("status.xml") if "plots.xml" in files: loadStandardItemModelXML(mw.mdlPlots, files["plots.xml"], fromString=True) else: - errors.append("perso.xml") + errors.append("plots.xml") if "outline.xml" in files: mw.mdlOutline.loadFromXML(files["outline.xml"], fromString=True) else: - errors.append("perso.xml") + errors.append("outline.xml") if "settings.pickle" in files: settings.load(files["settings.pickle"], fromString=True) else: - errors.append("perso.xml") + errors.append("settings.pickle") return errors def loadFilesFromZip(zipname): """Returns the content of zipfile as a dict of filename:content.""" - print(zipname) zf = zipfile.ZipFile(zipname) files = {} for f in zf.namelist(): @@ -286,4 +286,4 @@ def loadStandardItemModelXMLForCharacters(mdl, xml): info.value = ccol.text char.infos.append(info) - mdl.characters.append(char) \ No newline at end of file + mdl.characters.append(char) diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index 6e582914..8b113d1a 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -13,7 +13,7 @@ from PyQt5.QtCore import Qt, QModelIndex from PyQt5.QtGui import QColor from manuskript import settings -from manuskript.enums import Character, World, Plot, PlotStep +from manuskript.enums import Character, World, Plot, PlotStep, Outline from manuskript.functions import mainWindow, iconColor from lxml import etree as ET @@ -163,7 +163,7 @@ def saveProject(zip=None): # Characters (self.mdlCharacter) # In a character folder - path = os.path.join("characters", "{name}.txt") # Not sure wheter os.path allows this + path = os.path.join("characters", "{name}.txt") _map = [ (Character.name, "Name"), (Character.ID, "ID"), @@ -203,7 +203,9 @@ def saveProject(zip=None): # Texts # In an outline folder - # TODO + mdl = mw.mdlOutline + for filename, content in exportOutlineItem(mdl.rootItem): + files.append((filename, content)) # World (mw.mdlWorld) # Either in an XML file, or in lots of plain texts? @@ -371,6 +373,82 @@ def addPlotItem(root, mdl, parent=QModelIndex()): return root +def exportOutlineItem(root): + """ + Takes an outline item, and returns an array of (`filename`, `content`) sets, representing the whole tree + of items converted to mmd. + + @param root: OutlineItem + @return: (str, str) + """ + r = [] + path = "outline" + + k=0 + for child in root.children(): + spath = os.path.join(path, *outlineItemPath(child)) + k += 1 + + if child.type() == "folder": + fpath = os.path.join(spath, "folder.txt") + content = outlineToMMD(child) + r.append((fpath, content)) + + elif child.type() in ["txt", "t2t"]: + content = outlineToMMD(child) + r.append((spath, content)) + + elif child.type() in ["html"]: + # Convert first + pass + + else: + print("Unknown type") + + r += exportOutlineItem(child) + + return r + +def outlineItemPath(item): + # Root item + if not item.parent(): + return [] + else: + name = "{ID}-{name}{ext}".format( + ID=item.row(), + name=slugify(item.title()), + ext="" if item.type() == "folder" else ".{}".format(item.type()) + ) + return outlineItemPath(item.parent()) + [name] + +def outlineToMMD(item): + content = "" + + # We don't want to write some datas (computed) + exclude = [Outline.wordCount, Outline.goal, Outline.goalPercentage, Outline.revisions, Outline.text] + # We want to force some data even if they're empty + force = [Outline.compile] + + for attrib in Outline: + if attrib in exclude: continue + val = item.data(attrib.value) + if val or attrib in force: + content += formatMetaData(attrib.name, str(val), 15) + + content += "\n\n" + content += item.data(Outline.text.value) + + # Saving revisions + # TODO + # rev = item.revisions() + # for r in rev: + # revItem = ET.Element("revision") + # revItem.set("timestamp", str(r[0])) + # revItem.set("text", r[1]) + # item.append(revItem) + + return content + def loadProject(project): """ Loads a project. diff --git a/manuskript/models/outlineModel.py b/manuskript/models/outlineModel.py index 9f2c1fe0..dffbff76 100644 --- a/manuskript/models/outlineModel.py +++ b/manuskript/models/outlineModel.py @@ -854,8 +854,13 @@ class outlineItem(): text = text.lower() if not caseSensitive else text for c in columns: - if c == Outline.POV.value: - searchIn = mainWindow.mdlCharacter.getCharacterByID(self.POV()).name() + if c == Outline.POV.value and self.POV(): + c = mainWindow.mdlCharacter.getCharacterByID(self.POV()) + if c: + searchIn = c.name() + else: + searchIn = "" + print("Character POV not found:", self.POV()) elif c == Outline.status.value: searchIn = mainWindow.mdlStatus.item(toInt(self.status()), 0).text() From 518d4c7201474eed620ae95e1c737abb637d9e22 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 9 Mar 2016 13:19:03 +0100 Subject: [PATCH 039/103] Stores open IDs and not indexes in settings --- manuskript/mainWindow.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index 401b10cc..cd1156fa 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -308,7 +308,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): # Load settings for i in settings.openIndexes: - idx = self.mdlOutline.indexFromPath(i) + idx = self.mdlOutline.getIndexByID(i) self.mainEditor.setCurrentModelIndex(idx, newTab=True) self.generateViewMenu() self.mainEditor.sldCorkSizeFactor.setValue(settings.corkSizeFactor) @@ -432,10 +432,10 @@ class MainWindow(QMainWindow, Ui_MainWindow): settings.lastTab = self.tabMain.currentIndex() if self.currentProject: - # Remembering the current items + # Remembering the current items (stores outlineItem's ID) sel = [] for i in range(self.mainEditor.tab.count()): - sel.append(self.mdlOutline.pathToIndex(self.mainEditor.tab.widget(i).currentIndex)) + sel.append(self.mdlOutline.ID(self.mainEditor.tab.widget(i).currentIndex)) settings.openIndexes = sel # Save data from models @@ -461,6 +461,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): QSettings().setValue("lastProject", projectName) saveProject() # version=0 + self.saveTimerNoChanges.stop() # Giving some feedback print(self.tr("Project {} saved.").format(self.currentProject)) From 97903b2781c245a8f43d8699d08362b20d1590e0 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 9 Mar 2016 13:20:52 +0100 Subject: [PATCH 040/103] Never thought it would be so boring to code that part --- manuskript/load_save/version_1.py | 67 +++++++++++++++++++++++-------- manuskript/models/outlineModel.py | 21 ++++------ 2 files changed, 58 insertions(+), 30 deletions(-) diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index 8b113d1a..5d084036 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -5,7 +5,7 @@ # Aims at providing a plain-text way of saving a project # (except for some elements), allowing collaborative work # versioning and third-partty editing. -import os +import os, shutil import string import zipfile @@ -80,6 +80,8 @@ def saveProject(zip=None): zip = False # Fixme + print("\n\n", "Saving to:", "zip" if zip else "folder") + # List of files to be written files = [] # List of files to be removed @@ -204,8 +206,9 @@ def saveProject(zip=None): # In an outline folder mdl = mw.mdlOutline - for filename, content in exportOutlineItem(mdl.rootItem): - files.append((filename, content)) + f, r = exportOutlineItem(mdl.rootItem) + files += f + removes += r # World (mw.mdlWorld) # Either in an XML file, or in lots of plain texts? @@ -259,24 +262,36 @@ def saveProject(zip=None): else: dir = os.path.dirname(project) folder = os.path.splitext(os.path.basename(project))[0] - print("Saving to folder", folder) + print("\nSaving to folder", folder) for path in removes: if path not in [p for p,c in files]: filename = os.path.join(dir, folder, path) print("* Removing", filename) - os.remove(filename) - cache.pop(path) + if os.path.isdir(filename): + shutil.rmtree(filename) + # FIXME: when deleting a folder, there are still files in "removes". + + # FIXME: if user copied custom files in the directory, they will be lost. + # need to find a way to rename instead of remove. + + # FIXME: items removed have to be removed (not just the renamed) + + else: # elif os.path.exists(filename) + os.remove(filename) + + cache.pop(path, 0) for path, content in files: filename = os.path.join(dir, folder, path) os.makedirs(os.path.dirname(filename), exist_ok=True) - print("* Saving file", filename) + # print("* Saving file", filename) # TODO: the first time it saves, it will overwrite everything, since it's not yet in cache. # Or we have to cache while loading. if not path in cache or cache[path] != content: + print("* Saving file", filename) print(" Not in cache or changed: we write") mode = "w"+ ("b" if type(content) == bytes else "") with open(filename, mode) as f: @@ -284,7 +299,8 @@ def saveProject(zip=None): cache[path] = content else: - print(" In cache, and identical. Do nothing.") + pass + # print(" In cache, and identical. Do nothing.") def addWorldItem(root, mdl, parent=QModelIndex()): @@ -375,13 +391,17 @@ def addPlotItem(root, mdl, parent=QModelIndex()): def exportOutlineItem(root): """ - Takes an outline item, and returns an array of (`filename`, `content`) sets, representing the whole tree - of items converted to mmd. + Takes an outline item, and returns two lists: + 1. of (`filename`, `content`), representing the whole tree of files to be written, in multimarkdown. + 2. of `filename`, representing files to be removed. @param root: OutlineItem - @return: (str, str) + @return: [(str, str)], [str] """ - r = [] + + files = [] + removes = [] + path = "outline" k=0 @@ -389,25 +409,38 @@ def exportOutlineItem(root): spath = os.path.join(path, *outlineItemPath(child)) k += 1 + # Has the item been renamed? + # If so, we mark the old file for removal + if "Herod_dies" in spath: + print(child.title(), spath, "<==", child.lastPath) + if child.lastPath and spath != child.lastPath: + removes.append(child.lastPath) + print(child.title(), "has been renamed (", child.lastPath, " → ", spath, ")") + print(" → We remove:", child.lastPath) + + child.lastPath = spath + if child.type() == "folder": fpath = os.path.join(spath, "folder.txt") content = outlineToMMD(child) - r.append((fpath, content)) + files.append((fpath, content)) elif child.type() in ["txt", "t2t"]: content = outlineToMMD(child) - r.append((spath, content)) + files.append((spath, content)) elif child.type() in ["html"]: - # Convert first + # FIXME: Convert first pass else: print("Unknown type") - r += exportOutlineItem(child) + f, r = exportOutlineItem(child) + files += f + removes += r - return r + return files, removes def outlineItemPath(item): # Root item diff --git a/manuskript/models/outlineModel.py b/manuskript/models/outlineModel.py index dffbff76..3bedf3ab 100644 --- a/manuskript/models/outlineModel.py +++ b/manuskript/models/outlineModel.py @@ -400,19 +400,6 @@ class outlineModel(QAbstractItemModel): self.rootItem = outlineItem(model=self, xml=ET.tostring(root), ID="0") self.rootItem.checkIDs() - def pathToIndex(self, index, path=""): - # FIXME: Use item's ID instead of rows - if not index.isValid(): - return "" - if index.parent().isValid(): - path = self.pathToIndex(index.parent()) - if path: - path = "{},{}".format(path, str(index.row())) - else: - path = str(index.row()) - - return path - def indexFromPath(self, path): path = path.split(",") item = self.rootItem @@ -431,6 +418,8 @@ class outlineItem(): self._model = model self.defaultTextType = None self.IDs = [] # used by root item to store unique IDs + self.lastPath = "" # used by loadSave version_1 to remember which files the items comes from, + # in case it is renamed / removed if title: self._data[Outline.title] = title @@ -758,6 +747,9 @@ class outlineItem(): revItem.set("text", r[1]) item.append(revItem) + # Saving lastPath + item.set("lastPath", self.lastPath) + for i in self.childItems: item.append(ET.XML(i.toXML())) @@ -773,6 +765,9 @@ class outlineItem(): # else: self.setData(Outline.__members__[k].value, str(root.attrib[k])) + if "lastPath" in root.attrib: + self.lastPath = root.attrib["lastPath"] + for child in root: if child.tag == "outlineItem": item = outlineItem(self._model, xml=ET.tostring(child), parent=self) From da5bfb8951ec61d4d7eb278aebe923f3f1e774f5 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 9 Mar 2016 14:10:22 +0100 Subject: [PATCH 041/103] Removes deleted character's files when saving --- manuskript/load_save/version_1.py | 126 ++++++++++++++++++++-------- manuskript/models/characterModel.py | 6 ++ 2 files changed, 97 insertions(+), 35 deletions(-) diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index 5d084036..9d4d24fd 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -66,6 +66,10 @@ def slugify(name): return newName +def log(*args): + print(" ".join(str(a) for a in args)) + + def saveProject(zip=None): """ Saves the project. If zip is False, the project is saved as a multitude of plain-text files for the most parts @@ -80,12 +84,14 @@ def saveProject(zip=None): zip = False # Fixme - print("\n\n", "Saving to:", "zip" if zip else "folder") + log("\n\nSaving to:", "zip" if zip else "folder") # List of files to be written files = [] # List of files to be removed removes = [] + # List of files to be moved + moves = [] mw = mainWindow() @@ -117,6 +123,7 @@ def saveProject(zip=None): ) files.append((path, content)) + #################################################################################################################### # Summary # In plain text, in summary.txt @@ -135,6 +142,7 @@ def saveProject(zip=None): files.append((path, content)) + #################################################################################################################### # Label & Status # In plain text @@ -162,7 +170,8 @@ def saveProject(zip=None): files.append((path, content)) - # Characters (self.mdlCharacter) + #################################################################################################################### + # Characters # In a character folder path = os.path.join("characters", "{name}.txt") @@ -179,7 +188,11 @@ def saveProject(zip=None): (Character.notes, "Notes"), ] mdl = mw.mdlCharacter + + # Review characters for c in mdl.characters: + + # Generates file's content content = "" for m, name in _map: val = mdl.data(c.index(m.value)).strip() @@ -189,28 +202,46 @@ def saveProject(zip=None): for info in c.infos: content += formatMetaData(info.description, info.value, 20) + # generate file's path cpath = path.format(name="{ID}-{slugName}".format( ID=c.ID(), slugName=slugify(c.name()) )) - # Has the character been renamed? - # If so, we remove the old file (if not zipped) - if c.lastPath and cpath != c.lastPath: - removes.append(c.lastPath) - c.lastPath = cpath - files.append(( - cpath, - content)) + # Has the character been renamed? + if c.lastPath and cpath != c.lastPath: + moves.append((c.lastPath, cpath)) + + # Update character's path + c.lastPath = cpath + + files.append((cpath, content)) + + # List removed characters + for c in mdl.removed: + # generate file's path + cpath = path.format(name="{ID}-{slugName}".format( + ID=c.ID(), + slugName=slugify(c.name()) + )) + + # Mark for removal + removes.append(cpath) + + mdl.removed.clear() + + #################################################################################################################### # Texts # In an outline folder mdl = mw.mdlOutline - f, r = exportOutlineItem(mdl.rootItem) + f, m, r = exportOutlineItem(mdl.rootItem) files += f + moves += m removes += r - # World (mw.mdlWorld) + #################################################################################################################### + # World # Either in an XML file, or in lots of plain texts? # More probably text, since there might be writing done in third-party. @@ -224,6 +255,7 @@ def saveProject(zip=None): content = ET.tostring(root, encoding="UTF-8", xml_declaration=True, pretty_print=True) files.append((path, content)) + #################################################################################################################### # Plots (mw.mdlPlots) # Either in XML or lots of plain texts? # More probably XML since there is not really a lot if writing to do (third-party) @@ -236,15 +268,19 @@ def saveProject(zip=None): content = ET.tostring(root, encoding="UTF-8", xml_declaration=True, pretty_print=True) files.append((path, content)) + #################################################################################################################### # Settings # Saved in readable text (json) for easier versionning. But they mustn't be shared, it seems. # Maybe include them only if zipped? # Well, for now, we keep them here... + files.append(("settings.txt", settings.save(protocol=0))) project = mw.currentProject + #################################################################################################################### # Save to zip + if zip: project = os.path.join( os.path.dirname(project), @@ -258,41 +294,58 @@ def saveProject(zip=None): zf.close() + #################################################################################################################### # Save to plain text + else: + + # Project path dir = os.path.dirname(project) + + # Folder containing file: name of the project file (without .msk extension) folder = os.path.splitext(os.path.basename(project))[0] - print("\nSaving to folder", folder) + + # Debug + log("\nSaving to folder", folder) + + # Moving files that have been renamed + for old, new in moves: + + # Get full path + oldPath = os.path.join(dir, folder, old) + newPath = os.path.join(dir, folder, new) + + # Move the old file to the new place + os.replace(oldPath, newPath) + log("* Renaming {} to {}".format(old, new)) + + # Update cache + if old in cache: + cache[new] = cache.pop(old) for path in removes: if path not in [p for p,c in files]: filename = os.path.join(dir, folder, path) - print("* Removing", filename) + log("* Removing", path) + if os.path.isdir(filename): shutil.rmtree(filename) - # FIXME: when deleting a folder, there are still files in "removes". - # FIXME: if user copied custom files in the directory, they will be lost. - # need to find a way to rename instead of remove. - - # FIXME: items removed have to be removed (not just the renamed) - else: # elif os.path.exists(filename) os.remove(filename) + # Clear cache cache.pop(path, 0) for path, content in files: filename = os.path.join(dir, folder, path) os.makedirs(os.path.dirname(filename), exist_ok=True) - # print("* Saving file", filename) # TODO: the first time it saves, it will overwrite everything, since it's not yet in cache. # Or we have to cache while loading. if not path in cache or cache[path] != content: - print("* Saving file", filename) - print(" Not in cache or changed: we write") + log("* Writing file", path) mode = "w"+ ("b" if type(content) == bytes else "") with open(filename, mode) as f: f.write(content) @@ -300,7 +353,7 @@ def saveProject(zip=None): else: pass - # print(" In cache, and identical. Do nothing.") + # log(" In cache, and identical. Do nothing.") def addWorldItem(root, mdl, parent=QModelIndex()): @@ -393,6 +446,7 @@ def exportOutlineItem(root): """ Takes an outline item, and returns two lists: 1. of (`filename`, `content`), representing the whole tree of files to be written, in multimarkdown. + 3. of (`filename`, `filename`) listing files to be moved 2. of `filename`, representing files to be removed. @param root: OutlineItem @@ -400,6 +454,7 @@ def exportOutlineItem(root): """ files = [] + moves = [] removes = [] path = "outline" @@ -410,16 +465,15 @@ def exportOutlineItem(root): k += 1 # Has the item been renamed? - # If so, we mark the old file for removal - if "Herod_dies" in spath: - print(child.title(), spath, "<==", child.lastPath) if child.lastPath and spath != child.lastPath: - removes.append(child.lastPath) - print(child.title(), "has been renamed (", child.lastPath, " → ", spath, ")") - print(" → We remove:", child.lastPath) + moves.append((child.lastPath, spath)) + log(child.title(), "has been renamed (", child.lastPath, " → ", spath, ")") + log(" → We mark for moving:", child.lastPath) + # Updates item last's path child.lastPath = spath + # Generating content if child.type() == "folder": fpath = os.path.join(spath, "folder.txt") content = outlineToMMD(child) @@ -431,16 +485,18 @@ def exportOutlineItem(root): elif child.type() in ["html"]: # FIXME: Convert first - pass + content = outlineToMMD(child) + files.append((spath, content)) else: - print("Unknown type") + log("Unknown type") - f, r = exportOutlineItem(child) + f, m, r = exportOutlineItem(child) files += f + moves += m removes += r - return files, removes + return files, moves, removes def outlineItemPath(item): # Root item @@ -472,7 +528,7 @@ def outlineToMMD(item): content += item.data(Outline.text.value) # Saving revisions - # TODO + # TODO: saving revisions? # rev = item.revisions() # for r in rev: # revItem = ET.Element("revision") diff --git a/manuskript/models/characterModel.py b/manuskript/models/characterModel.py index e1a4e684..fad34f1d 100644 --- a/manuskript/models/characterModel.py +++ b/manuskript/models/characterModel.py @@ -12,7 +12,11 @@ class characterModel(QAbstractItemModel): def __init__(self, parent): QAbstractItemModel.__init__(self, parent) + # CharacterItems are stored in this list self.characters = [] + # We keep track of removed character, so that when we save in multiple files, we can remove old character's + # files. + self.removed = [] ############################################################################### # QAbstractItemModel subclassed @@ -170,7 +174,9 @@ class characterModel(QAbstractItemModel): """ c = self.getCharacterByID(ID) self.beginRemoveRows(QModelIndex(), self.characters.index(c), self.characters.index(c)) + self.removed.append(c) self.characters.remove(c) + self.endRemoveRows() ############################################################################### # CHARACTER INFOS From dc1b7577700cbd572f5d1945798e25581b524377 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 9 Mar 2016 15:48:59 +0100 Subject: [PATCH 042/103] Saving seems to work kind of smoothly --- manuskript/load_save/version_1.py | 116 ++++++++++++++++++++---------- manuskript/models/outlineModel.py | 24 +++++-- 2 files changed, 94 insertions(+), 46 deletions(-) diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index 9d4d24fd..253bcdd0 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -217,16 +217,16 @@ def saveProject(zip=None): files.append((cpath, content)) - # List removed characters - for c in mdl.removed: - # generate file's path - cpath = path.format(name="{ID}-{slugName}".format( - ID=c.ID(), - slugName=slugify(c.name()) - )) - - # Mark for removal - removes.append(cpath) + # # List removed characters + # for c in mdl.removed: + # # generate file's path + # cpath = path.format(name="{ID}-{slugName}".format( + # ID=c.ID(), + # slugName=slugify(c.name()) + # )) + # + # # Mark for removal + # removes.append(cpath) mdl.removed.clear() @@ -235,11 +235,19 @@ def saveProject(zip=None): # In an outline folder mdl = mw.mdlOutline + + # Go through the tree f, m, r = exportOutlineItem(mdl.rootItem) files += f moves += m removes += r + # List removed items + # for item in mdl.removed: + # path = outlineItemPath(item) + # log("* Marking for removal:", path) + + #################################################################################################################### # World # Either in an XML file, or in lots of plain texts? @@ -299,6 +307,8 @@ def saveProject(zip=None): else: + global cache + # Project path dir = os.path.dirname(project) @@ -316,27 +326,23 @@ def saveProject(zip=None): newPath = os.path.join(dir, folder, new) # Move the old file to the new place - os.replace(oldPath, newPath) - log("* Renaming {} to {}".format(old, new)) + try: + os.replace(oldPath, newPath) + log("* Renaming/moving {} to {}".format(old, new)) + except FileNotFoundError: + # Maybe parent folder has been renamed + pass # Update cache - if old in cache: - cache[new] = cache.pop(old) - - for path in removes: - if path not in [p for p,c in files]: - filename = os.path.join(dir, folder, path) - log("* Removing", path) - - if os.path.isdir(filename): - shutil.rmtree(filename) - - else: # elif os.path.exists(filename) - os.remove(filename) - - # Clear cache - cache.pop(path, 0) + cache2 = {} + for f in cache: + f2 = f.replace(old, new) + if f2 != f: + log(" * Updating cache:", f, f2) + cache2[f2] = cache[f] + cache = cache2 + # Writing files for path, content in files: filename = os.path.join(dir, folder, path) os.makedirs(os.path.dirname(filename), exist_ok=True) @@ -355,6 +361,31 @@ def saveProject(zip=None): pass # log(" In cache, and identical. Do nothing.") + # Removing phantoms + for path in [p for p in cache if p not in [p for p,c in files]]: + filename = os.path.join(dir, folder, path) + log("* Removing", path) + + if os.path.isdir(filename): + shutil.rmtree(filename) + + else: # elif os.path.exists(filename) + os.remove(filename) + + # Clear cache + cache.pop(path, 0) + + # Removing empty directories + for root, dirs, files in os.walk(os.path.join(dir, folder, "outline")): + for dir in dirs: + newDir = os.path.join(root, dir) + try: + os.removedirs(newDir) + log("* Removing empty directory:", newDir) + except: + # Directory not empty, we don't remove. + pass + def addWorldItem(root, mdl, parent=QModelIndex()): """ @@ -457,21 +488,22 @@ def exportOutlineItem(root): moves = [] removes = [] - path = "outline" - k=0 for child in root.children(): - spath = os.path.join(path, *outlineItemPath(child)) + itemPath = outlineItemPath(child) + spath = os.path.join(*itemPath) + k += 1 # Has the item been renamed? - if child.lastPath and spath != child.lastPath: - moves.append((child.lastPath, spath)) - log(child.title(), "has been renamed (", child.lastPath, " → ", spath, ")") - log(" → We mark for moving:", child.lastPath) + lp = child._lastPath + if lp and spath != lp: + moves.append((lp, spath)) + log(child.title(), "has been renamed (", lp, " → ", spath, ")") + log(" → We mark for moving:", lp) # Updates item last's path - child.lastPath = spath + child._lastPath = spath # itemPath[-1] # Generating content if child.type() == "folder": @@ -484,7 +516,7 @@ def exportOutlineItem(root): files.append((spath, content)) elif child.type() in ["html"]: - # FIXME: Convert first + # Save as html. Not the most beautiful, but hey. content = outlineToMMD(child) files.append((spath, content)) @@ -499,14 +531,20 @@ def exportOutlineItem(root): return files, moves, removes def outlineItemPath(item): + """ + Returns the outlineItem file path (like the path where it will be written on the disk). As a list of folder's + name. To be joined by os.path.join. + @param item: outlineItem + @return: list of folder's names + """ # Root item if not item.parent(): - return [] + return ["outline"] else: name = "{ID}-{name}{ext}".format( ID=item.row(), name=slugify(item.title()), - ext="" if item.type() == "folder" else ".{}".format(item.type()) + ext="" if item.type() == "folder" else ".md" # ".{}".format(item.type()) # To have .txt, .t2t, .html, ... ) return outlineItemPath(item.parent()) + [name] diff --git a/manuskript/models/outlineModel.py b/manuskript/models/outlineModel.py index 3bedf3ab..55d676ca 100644 --- a/manuskript/models/outlineModel.py +++ b/manuskript/models/outlineModel.py @@ -18,7 +18,7 @@ from manuskript.enums import Outline from manuskript.functions import mainWindow, toInt, wordCount locale.setlocale(locale.LC_ALL, '') -import time +import time, os class outlineModel(QAbstractItemModel): @@ -27,6 +27,9 @@ class outlineModel(QAbstractItemModel): self.rootItem = outlineItem(self, title="root", ID="0") + # Stores removed item, in order to remove them on disk when saving, depending on the file format. + self.removed = [] + def index(self, row, column, parent): if not self.hasIndex(row, column, parent): @@ -363,7 +366,8 @@ class outlineModel(QAbstractItemModel): self.beginRemoveRows(parent, row, row + count - 1) for i in range(count): - parentItem.removeChild(row) + item = parentItem.removeChild(row) + self.removed.append(item) self.endRemoveRows() return True @@ -418,7 +422,7 @@ class outlineItem(): self._model = model self.defaultTextType = None self.IDs = [] # used by root item to store unique IDs - self.lastPath = "" # used by loadSave version_1 to remember which files the items comes from, + self._lastPath = "" # used by loadSave version_1 to remember which files the items comes from, # in case it is renamed / removed if title: @@ -592,7 +596,7 @@ class outlineItem(): self.parent().updateWordCount(emit) def row(self): - if self.parent: + if self.parent(): return self.parent().childItems.index(self) def appendChild(self, child): @@ -634,9 +638,15 @@ class outlineItem(): c.emitDataChanged(cols, recursive=True) def removeChild(self, row): - self.childItems.pop(row) + """ + Removes child at position `row` and returns it. + @param row: index (int) of the child to remove. + @return: the removed outlineItem + """ + r = self.childItems.pop(row) # Might be causing segfault when updateWordCount emits dataChanged self.updateWordCount(emit=False) + return r def parent(self): return self._parent @@ -748,7 +758,7 @@ class outlineItem(): item.append(revItem) # Saving lastPath - item.set("lastPath", self.lastPath) + item.set("lastPath", self._lastPath) for i in self.childItems: item.append(ET.XML(i.toXML())) @@ -766,7 +776,7 @@ class outlineItem(): self.setData(Outline.__members__[k].value, str(root.attrib[k])) if "lastPath" in root.attrib: - self.lastPath = root.attrib["lastPath"] + self._lastPath = root.attrib["lastPath"] for child in root: if child.tag == "outlineItem": From b29fbebd25401b3845a90f446fa6d9893a1b8faa Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 9 Mar 2016 15:54:01 +0100 Subject: [PATCH 043/103] PEP8 cleaning --- manuskript/load_save/version_1.py | 56 +++++++++++++++++-------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index 253bcdd0..ec6ab5f4 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -5,7 +5,9 @@ # Aims at providing a plain-text way of saving a project # (except for some elements), allowing collaborative work # versioning and third-partty editing. -import os, shutil + +import os +import shutil import string import zipfile @@ -105,15 +107,15 @@ def saveProject(zip=None): path = "infos.txt" content = "" for name, col in [ - ("Title", 0), - ("Subtitle", 1), - ("Serie", 2), - ("Volume", 3), - ("Genre", 4), - ("License", 5), - ("Author", 6), - ("Email", 7), - ]: + ("Title", 0), + ("Subtitle", 1), + ("Serie", 2), + ("Volume", 3), + ("Genre", 4), + ("License", 5), + ("Author", 6), + ("Email", 7), + ]: val = mw.mdlFlatData.item(0, col).text().strip() if val: content += "{name}:{spaces}{value}\n".format( @@ -130,12 +132,12 @@ def saveProject(zip=None): path = "summary.txt" content = "" for name, col in [ - ("Situation", 0), - ("Sentence", 1), - ("Paragraph", 2), - ("Page", 3), - ("Full", 4), - ]: + ("Situation", 0), + ("Sentence", 1), + ("Paragraph", 2), + ("Page", 3), + ("Full", 4), + ]: val = mw.mdlFlatData.item(1, col).text().strip() if val: content += formatMetaData(name, val, 12) @@ -165,7 +167,7 @@ def saveProject(zip=None): if text: content += "{name}{color}\n".format( name=text, - color= "" if color == "" else ":" + " " * (20 - len(text)) + color + color="" if color == "" else ":" + " " * (20 - len(text)) + color ) files.append((path, content)) @@ -247,7 +249,6 @@ def saveProject(zip=None): # path = outlineItemPath(item) # log("* Marking for removal:", path) - #################################################################################################################### # World # Either in an XML file, or in lots of plain texts? @@ -350,9 +351,9 @@ def saveProject(zip=None): # TODO: the first time it saves, it will overwrite everything, since it's not yet in cache. # Or we have to cache while loading. - if not path in cache or cache[path] != content: + if path not in cache or cache[path] != content: log("* Writing file", path) - mode = "w"+ ("b" if type(content) == bytes else "") + mode = "w" + ("b" if type(content) == bytes else "") with open(filename, mode) as f: f.write(content) cache[path] = content @@ -362,7 +363,7 @@ def saveProject(zip=None): # log(" In cache, and identical. Do nothing.") # Removing phantoms - for path in [p for p in cache if p not in [p for p,c in files]]: + for path in [p for p in cache if p not in [p for p, c in files]]: filename = os.path.join(dir, folder, path) log("* Removing", path) @@ -488,10 +489,9 @@ def exportOutlineItem(root): moves = [] removes = [] - k=0 + k = 0 for child in root.children(): - itemPath = outlineItemPath(child) - spath = os.path.join(*itemPath) + spath = os.path.join(*outlineItemPath(child)) k += 1 @@ -503,7 +503,7 @@ def exportOutlineItem(root): log(" → We mark for moving:", lp) # Updates item last's path - child._lastPath = spath # itemPath[-1] + child._lastPath = spath # Generating content if child.type() == "folder": @@ -530,6 +530,7 @@ def exportOutlineItem(root): return files, moves, removes + def outlineItemPath(item): """ Returns the outlineItem file path (like the path where it will be written on the disk). As a list of folder's @@ -548,6 +549,7 @@ def outlineItemPath(item): ) return outlineItemPath(item.parent()) + [name] + def outlineToMMD(item): content = "" @@ -557,7 +559,8 @@ def outlineToMMD(item): force = [Outline.compile] for attrib in Outline: - if attrib in exclude: continue + if attrib in exclude: + continue val = item.data(attrib.value) if val or attrib in force: content += formatMetaData(attrib.name, str(val), 15) @@ -576,6 +579,7 @@ def outlineToMMD(item): return content + def loadProject(project): """ Loads a project. From fc89207ca88eb1077b172659b73c979836b71d16 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 9 Mar 2016 16:02:22 +0100 Subject: [PATCH 044/103] Saving seems to be done --- manuskript/loadSave.py | 16 +++++++++++++--- manuskript/load_save/version_1.py | 18 ------------------ manuskript/models/characterModel.py | 4 ---- 3 files changed, 13 insertions(+), 25 deletions(-) diff --git a/manuskript/loadSave.py b/manuskript/loadSave.py index 0433d264..dead6050 100644 --- a/manuskript/loadSave.py +++ b/manuskript/loadSave.py @@ -3,6 +3,7 @@ # The loadSave file calls the propper functions to load and save file # trying to detect the proper file format if it comes from an older version +import os import zipfile import manuskript.load_save.version_0 as v0 @@ -20,10 +21,10 @@ def saveProject(version=None): def loadProject(project): # Detect version - # Is it a zip? isZip = False version = 0 + # Is it a zip? try: zf = zipfile.ZipFile(project) isZip = True @@ -40,8 +41,17 @@ def loadProject(project): # Not a zip else: - # FIXME - pass + # Project path + dir = os.path.dirname(project) + + # Folder containing file: name of the project file (without .msk extension) + folder = os.path.splitext(os.path.basename(project))[0] + + # Reading VERSION file + path = os.path.join(dir, folder, "VERSION") + if os.path.exists(path): + with open(path, "r") as f: + version = int(f.read()) print("Detected file format version:", version) diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index ec6ab5f4..af946453 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -219,19 +219,6 @@ def saveProject(zip=None): files.append((cpath, content)) - # # List removed characters - # for c in mdl.removed: - # # generate file's path - # cpath = path.format(name="{ID}-{slugName}".format( - # ID=c.ID(), - # slugName=slugify(c.name()) - # )) - # - # # Mark for removal - # removes.append(cpath) - - mdl.removed.clear() - #################################################################################################################### # Texts # In an outline folder @@ -244,11 +231,6 @@ def saveProject(zip=None): moves += m removes += r - # List removed items - # for item in mdl.removed: - # path = outlineItemPath(item) - # log("* Marking for removal:", path) - #################################################################################################################### # World # Either in an XML file, or in lots of plain texts? diff --git a/manuskript/models/characterModel.py b/manuskript/models/characterModel.py index fad34f1d..1ec975f9 100644 --- a/manuskript/models/characterModel.py +++ b/manuskript/models/characterModel.py @@ -14,9 +14,6 @@ class characterModel(QAbstractItemModel): # CharacterItems are stored in this list self.characters = [] - # We keep track of removed character, so that when we save in multiple files, we can remove old character's - # files. - self.removed = [] ############################################################################### # QAbstractItemModel subclassed @@ -174,7 +171,6 @@ class characterModel(QAbstractItemModel): """ c = self.getCharacterByID(ID) self.beginRemoveRows(QModelIndex(), self.characters.index(c), self.characters.index(c)) - self.removed.append(c) self.characters.remove(c) self.endRemoveRows() From ed7e5f69b503605e674575437d0c6e609beeb619 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 9 Mar 2016 17:20:43 +0100 Subject: [PATCH 045/103] Saves revision even in non-zip format --- manuskript/loadSave.py | 17 ++++------------- manuskript/load_save/version_1.py | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/manuskript/loadSave.py b/manuskript/loadSave.py index dead6050..7763d027 100644 --- a/manuskript/loadSave.py +++ b/manuskript/loadSave.py @@ -15,7 +15,7 @@ def saveProject(version=None): if version == 0: v0.saveProject() else: - v1.saveProject() + v1.saveProject(zip=True) def loadProject(project): @@ -41,21 +41,12 @@ def loadProject(project): # Not a zip else: - # Project path - dir = os.path.dirname(project) - - # Folder containing file: name of the project file (without .msk extension) - folder = os.path.splitext(os.path.basename(project))[0] - - # Reading VERSION file - path = os.path.join(dir, folder, "VERSION") - if os.path.exists(path): - with open(path, "r") as f: - version = int(f.read()) + with open(project, "r") as f: + version = int(f.read()) print("Detected file format version:", version) - if version == 0: + if version == 0 or True: v0.loadProject(project) else: v1.loadProject(project) diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index af946453..b0ba4fe5 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -231,6 +231,10 @@ def saveProject(zip=None): moves += m removes += r + # Writes revisions (if asked for) + if settings.revisions["keep"]: + files.append(("revisions.xml", mdl.saveToXML())) + #################################################################################################################### # World # Either in an XML file, or in lots of plain texts? @@ -273,10 +277,10 @@ def saveProject(zip=None): # Save to zip if zip: - project = os.path.join( - os.path.dirname(project), - "_" + os.path.basename(project) - ) + # project = os.path.join( + # os.path.dirname(project), + # "_" + os.path.basename(project) + # ) zf = zipfile.ZipFile(project, mode="w") @@ -369,6 +373,10 @@ def saveProject(zip=None): # Directory not empty, we don't remove. pass + # Write the project file's content + with open(project, "w") as f: + f.write("1") # Format number + def addWorldItem(root, mdl, parent=QModelIndex()): """ From 22f2bd7899b7da28b1d360a0e2e4afe025377283 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 9 Mar 2016 17:38:12 +0100 Subject: [PATCH 046/103] Corrects bug when reopening file --- manuskript/mainWindow.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index cd1156fa..a60f5613 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -557,11 +557,14 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.tblPersoInfos.setModel(self.mdlCharacter) self.btnAddPerso.clicked.connect(self.mdlCharacter.addCharacter, AUC) - self.btnRmPerso.clicked.connect(self.lstCharacters.removeCharacter, AUC) - self.btnPersoColor.clicked.connect(self.lstCharacters.choseCharacterColor, AUC) - - self.btnPersoAddInfo.clicked.connect(self.lstCharacters.addCharacterInfo, AUC) - self.btnPersoRmInfo.clicked.connect(self.lstCharacters.removeCharacterInfo, AUC) + try: + self.btnRmPerso.clicked.connect(self.lstCharacters.removeCharacter, AUC) + self.btnPersoColor.clicked.connect(self.lstCharacters.choseCharacterColor, AUC) + self.btnPersoAddInfo.clicked.connect(self.lstCharacters.addCharacterInfo, AUC) + self.btnPersoRmInfo.clicked.connect(self.lstCharacters.removeCharacterInfo, AUC) + except TypeError: + # Connection has already been made + pass for w, c in [ (self.txtPersoName, Character.name.value), From 10fdb89eefc2bf0ed983aa47dfc6408e5bb45aa4 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 10 Mar 2016 11:01:35 +0100 Subject: [PATCH 047/103] Bug corrected --- manuskript/settings.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/manuskript/settings.py b/manuskript/settings.py index d03f6dec..fc6065f0 100644 --- a/manuskript/settings.py +++ b/manuskript/settings.py @@ -131,7 +131,7 @@ def save(filename=None, protocol=None): return pickle.dumps(allSettings) -def load(string, fromString=False): +def load(string, fromString=False, protocol=None): """Load settings from 'string'. 'string' is the filename of the pickle dump. If fromString=True, string is the data of the pickle dumps.""" global allSettings @@ -145,8 +145,11 @@ def load(string, fromString=False): print("{} doesn't exist, cannot load settings.".format(string)) return else: - allSettings = pickle.loads(string) - + if protocol == 0: + allSettings = json.loads(string) + else: + allSettings = pickle.loads(string) + #pp=pprint.PrettyPrinter(indent=4, compact=False) #print("Loading:") #pp.pprint(allSettings) @@ -223,6 +226,17 @@ def load(string, fromString=False): global revisions revisions = allSettings["revisions"] + # With JSON we had to convert int keys to str, and None to "null", so we roll back. + r = {} + for i in revisions["rules"]: + if i == "null": + r[None] = revisions["rules"]["null"] + continue + elif i == None: + continue + r[int(i)] = revisions["rules"][i] + revisions["rules"] = r + if "frequencyAnalyzer" in allSettings: global frequencyAnalyzer frequencyAnalyzer = allSettings["frequencyAnalyzer"] From b2a51e1a0957df0aa44abcc0a69176ea82474243 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 10 Mar 2016 11:45:40 +0100 Subject: [PATCH 048/103] Reads characters --- manuskript/loadSave.py | 12 +- manuskript/load_save/version_1.py | 365 ++++++++++++++++++++++++++-- manuskript/models/characterModel.py | 3 +- 3 files changed, 350 insertions(+), 30 deletions(-) diff --git a/manuskript/loadSave.py b/manuskript/loadSave.py index 7763d027..28eecb2f 100644 --- a/manuskript/loadSave.py +++ b/manuskript/loadSave.py @@ -12,10 +12,15 @@ import manuskript.load_save.version_1 as v1 def saveProject(version=None): + # While debugging, we don't save the project + return + if version == 0: v0.saveProject() else: - v1.saveProject(zip=True) + v1.saveProject() + + # FIXME: add settings to chose between saving as zip or not. def loadProject(project): @@ -44,9 +49,10 @@ def loadProject(project): with open(project, "r") as f: version = int(f.read()) + print("Loading:", project) print("Detected file format version:", version) - if version == 0 or True: + if version == 0: v0.loadProject(project) else: - v1.loadProject(project) + v1.loadProject(project, zip=isZip) diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index b0ba4fe5..e38af597 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -7,18 +7,22 @@ # versioning and third-partty editing. import os +import re import shutil import string import zipfile +from collections import OrderedDict from PyQt5.QtCore import Qt, QModelIndex -from PyQt5.QtGui import QColor +from PyQt5.QtGui import QColor, QStandardItem from manuskript import settings from manuskript.enums import Character, World, Plot, PlotStep, Outline -from manuskript.functions import mainWindow, iconColor +from manuskript.functions import mainWindow, iconColor, iconFromColorString from lxml import etree as ET +from manuskript.load_save.version_0 import loadFilesFromZip +from manuskript.models.characterModel import CharacterInfo try: import zlib # Used with zipfile for compression @@ -30,6 +34,31 @@ except: cache = {} +characterMap = OrderedDict([ + (Character.name, "Name"), + (Character.ID, "ID"), + (Character.importance, "Importance"), + (Character.motivation, "Motivation"), + (Character.goal, "Goal"), + (Character.conflict, "Conflict"), + (Character.summarySentence, "Phrase Summary"), + (Character.summaryPara, "Paragraph Summary"), + (Character.summaryFull, "Full Summary"), + (Character.notes, "Notes"), +]) +# characterMap = { +# Character.name: "Name", +# Character.ID: "ID", +# Character.importance: "Importance", +# Character.motivation: "Motivation", +# Character.goal: "Goal", +# Character.conflict: "Conflict", +# Character.summarySentence: "Phrase Summary", +# Character.summaryPara: "Paragraph Summary", +# Character.summaryFull: "Full Summary", +# Character.notes: "Notes", +# } + def formatMetaData(name, value, tabLength=10): # Multiline formatting @@ -177,18 +206,6 @@ def saveProject(zip=None): # In a character folder path = os.path.join("characters", "{name}.txt") - _map = [ - (Character.name, "Name"), - (Character.ID, "ID"), - (Character.importance, "Importance"), - (Character.motivation, "Motivation"), - (Character.goal, "Goal"), - (Character.conflict, "Conflict"), - (Character.summarySentence, "Phrase Summary"), - (Character.summaryPara, "Paragraph Summary"), - (Character.summaryFull, "Full Summary"), - (Character.notes, "Notes"), - ] mdl = mw.mdlCharacter # Review characters @@ -196,11 +213,15 @@ def saveProject(zip=None): # Generates file's content content = "" - for m, name in _map: + for m in characterMap: val = mdl.data(c.index(m.value)).strip() if val: - content += formatMetaData(name, val, 20) + content += formatMetaData(characterMap[m], val, 20) + # Character's color: + content += formatMetaData("Color", c.color().name(QColor.HexRgb), 20) + + # Character's infos for info in c.infos: content += formatMetaData(info.description, info.value, 20) @@ -334,9 +355,7 @@ def saveProject(zip=None): filename = os.path.join(dir, folder, path) os.makedirs(os.path.dirname(filename), exist_ok=True) - # TODO: the first time it saves, it will overwrite everything, since it's not yet in cache. - # Or we have to cache while loading. - + # Check if content is in cache, and write if necessary if path not in cache or cache[path] != content: log("* Writing file", path) mode = "w" + ("b" if type(content) == bytes else "") @@ -344,10 +363,6 @@ def saveProject(zip=None): f.write(content) cache[path] = content - else: - pass - # log(" In cache, and identical. Do nothing.") - # Removing phantoms for path in [p for p in cache if p not in [p for p, c in files]]: filename = os.path.join(dir, folder, path) @@ -569,15 +584,313 @@ def outlineToMMD(item): return content +######################################################################################################################## +# LOAD +######################################################################################################################## -def loadProject(project): +def loadProject(project, zip=None): """ Loads a project. @param project: the filename of the project to open. + @param zip: whether the project is a zipped or not. @return: an array of errors, empty if None. """ - # Don't forget to cache everything that is loaded + # FIXME: Don't forget to cache everything that is loaded # In order to save only what has changed. - pass \ No newline at end of file + mw = mainWindow() + errors = [] + + #################################################################################################################### + # Read and store everything in a dict + + log("\nLoading {} ({})".format(project, "ZIP" if zip else "not zip")) + if zip: + files = loadFilesFromZip(project) + + # Decode files + for f in files: + if f[-4:] not in [".xml", "opml"]: + files[f] = files[f].decode("utf-8") + + else: + # Project path + dir = os.path.dirname(project) + + # Folder containing file: name of the project file (without .msk extension) + folder = os.path.splitext(os.path.basename(project))[0] + + # The full path towards the folder containing files + path = os.path.join(dir, folder, "") + + files = {} + for dirpath, dirnames, filenames in os.walk(path): + p = dirpath.replace(path, "") + for f in filenames: + mode = "r" + ("b" if f[-4:] in [".xml", "opml"] else "") + with open(os.path.join(dirpath, f), mode) as fo: + files[os.path.join(p, f)] = fo.read() + + #################################################################################################################### + # Settings + + if "settings.txt" in files: + settings.load(files["settings.txt"], fromString=True, protocol=0) + else: + errors.append("settings.txt") + + #################################################################################################################### + # Labels + + mdl = mw.mdlLabels + mdl.appendRow(QStandardItem("")) # Empty = No labels + if "labels.txt" in files: + log("\nReading labels:") + for s in files["labels.txt"].split("\n"): + if not s: + continue + + m = re.search(r"^(.*?):\s*(.*)$", s) + txt = m.group(1) + col = m.group(2) + log("* Add status: {} ({})".format(txt, col)) + icon = iconFromColorString(col) + mdl.appendRow(QStandardItem(icon, txt)) + + else: + errors.append("labels.txt") + + #################################################################################################################### + # Status + + mdl = mw.mdlStatus + mdl.appendRow(QStandardItem("")) # Empty = No status + if "status.txt" in files: + log("\nReading Status:") + for s in files["status.txt"].split("\n"): + if not s: + continue + log("* Add status:", s) + mdl.appendRow(QStandardItem(s)) + else: + errors.append("status.txt") + + #################################################################################################################### + # Infos + + mdl = mw.mdlFlatData + if "infos.txt" in files: + md, body = parseMMDFile(files["infos.txt"], asDict=True) + + row = [] + for name in ["Title", "Subtitle", "Serie", "Volume", "Genre", "License", "Author", "Email"]: + row.append(QStandardItem(md.get(name, ""))) + + mdl.appendRow(row) + + else: + errors.append("infos.txt") + + #################################################################################################################### + # Summary + + mdl = mw.mdlFlatData + if "summary.txt" in files: + md, body = parseMMDFile(files["summary.txt"], asDict=True) + + row = [] + for name in ["Situation", "Sentence", "Paragraph", "Page", "Full"]: + row.append(QStandardItem(md.get(name, ""))) + + mdl.appendRow(row) + + else: + errors.append("summary.txt") + + #################################################################################################################### + # Plots + + mdl = mw.mdlPlots + if "plots.xml" in files: + log("\nReading plots:") + # xml = bytearray(files["plots.xml"], "utf-8") + root = ET.fromstring(files["plots.xml"]) + + for plot in root: + # Create row + row = getStandardItemRowFromXMLEnum(plot, Plot) + + # Log + log("* Add plot: ", row[0].text()) + + # Characters + if row[Plot.characters.value].text(): + IDs = row[Plot.characters.value].text().split(",") + item = QStandardItem() + for ID in IDs: + item.appendRow(QStandardItem(ID.strip())) + row[Plot.characters.value] = item + + # Subplots + for step in plot: + row[Plot.steps.value].appendRow( + getStandardItemRowFromXMLEnum(step, PlotStep) + ) + + # Add row to the model + mdl.appendRow(row) + + else: + errors.append("plots.xml") + + #################################################################################################################### + # World + + mdl = mw.mdlWorld + if "world.opml" in files: + log("\nReading World:") + # xml = bytearray(files["plots.xml"], "utf-8") + root = ET.fromstring(files["world.opml"]) + body = root.find("body") + + for outline in body: + row = getOutlineItem(outline, World) + mdl.appendRow(row) + + else: + errors.append("world.opml") + + #################################################################################################################### + # Characters + + mdl = mw.mdlCharacter + log("\nReading Characters:") + for f in [f for f in files if "characters" in f]: + md, body = parseMMDFile(files[f]) + c = mdl.addCharacter() + + color = False + for desc, val in md: + + # Base infos + if desc in characterMap.values(): + key = [key for key, value in characterMap.items() if value == desc][0] + index = c.index(key.value) + mdl.setData(index, val) + + # Character color + elif desc == "Color" and not color: + c.setColor(QColor(val)) + # We remember the first time we found "Color": it is the icon color. + # If "Color" comes a second time, it is a Character's info. + color = True + + # Character's infos + else: + c.infos.append(CharacterInfo(c, desc, val)) + + log("* Adds {} ({})".format(c.name(), c.ID())) + + + # if "perso.xml" in files: + # loadStandardItemModelXMLForCharacters(mw.mdlCharacter, files["perso.xml"]) + # else: + # errors.append("perso.xml") + # + # + # if "outline.xml" in files: + # mw.mdlOutline.loadFromXML(files["outline.xml"], fromString=True) + # else: + # errors.append("outline.xml") + # + # return errors + +def getOutlineItem(item, enum): + row = getStandardItemRowFromXMLEnum(item, enum) + log("* Add worldItem:", row[0].text()) + for child in item: + sub = getOutlineItem(child, enum) + row[0].appendRow(sub) + + return row + +def getStandardItemRowFromXMLEnum(item, enum): + """ + Reads and etree item and creates a row of QStandardItems by cross-referencing an enum. + Returns a list of QStandardItems that can be added to a QStandardItemModel by appendRow. + @param item: the etree item + @param enum: the enum + @return: list of QStandardItems + """ + row = [] + for i in range(len(enum)): + row.append(QStandardItem("")) + + for name in item.attrib: + if name in enum.__members__: + row[enum[name].value] = QStandardItem(item.attrib[name]) + return row + +def parseMMDFile(text, asDict=False): + """ + Takes the content of a MultiMarkDown file (str) and returns: + 1. A list containing metadatas: (description, value) if asDict is False. + If asDict is True, returns metadatas as an OrderedDict. Be aware that if multiple metadatas have the same description + (which is stupid, but hey), they will be lost except the last one. + 2. The body of the file + @param text: the content of the file + @return: (list, str) or (OrderedDict, str) + """ + md = [] + mdd = OrderedDict() + body = [] + descr = "" + val = "" + inBody = False + for s in text.split("\n"): + if not inBody: + m = re.match(r"^(.*?):\s*(.*)$", s) + if m: + # Commit last metadata + if descr: + if descr == "None": + descr = "" + md.append((descr, val)) + mdd[descr] = val + descr = "" + val = "" + + # Store new values + descr = m.group(1) + val = m.group(2) + + elif s[:4] == " ": + val += "\n" + s.strip() + + elif s == "": + # End of metadatas + inBody = True + + # Commit last metadata + if descr: + if descr == "None": + descr = "" + md.append((descr, val)) + mdd[descr] = val + + else: + body.append[s] + + # We remove the second empty line (since we save with two empty lines) + if body and body[0] == "": + body = body[1:] + + body = "\n".join(body) + + if not asDict: + return md, body + else: + return mdd, body + + diff --git a/manuskript/models/characterModel.py b/manuskript/models/characterModel.py index 1ec975f9..d6f7016c 100644 --- a/manuskript/models/characterModel.py +++ b/manuskript/models/characterModel.py @@ -156,12 +156,13 @@ class characterModel(QAbstractItemModel): def addCharacter(self): """ Creates a new character - @return: nothing + @return: the character """ c = Character(model=self, name=self.tr("New character")) self.beginInsertRows(QModelIndex(), len(self.characters), len(self.characters)) self.characters.append(c) self.endInsertRows() + return c def removeCharacter(self, ID): """ From b26de717a9f1022b5aac9da4b1d82e5750a5bc4b Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 10 Mar 2016 13:10:31 +0100 Subject: [PATCH 049/103] Seems that loading works --- manuskript/load_save/version_1.py | 163 +++++++++++++++++++++++++----- manuskript/models/outlineModel.py | 9 +- 2 files changed, 144 insertions(+), 28 deletions(-) diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index e38af597..6aecb198 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -23,6 +23,7 @@ from lxml import etree as ET from manuskript.load_save.version_0 import loadFilesFromZip from manuskript.models.characterModel import CharacterInfo +from manuskript.models.outlineModel import outlineItem try: import zlib # Used with zipfile for compression @@ -573,15 +574,6 @@ def outlineToMMD(item): content += "\n\n" content += item.data(Outline.text.value) - # Saving revisions - # TODO: saving revisions? - # rev = item.revisions() - # for r in rev: - # revItem = ET.Element("revision") - # revItem.set("timestamp", str(r[0])) - # revItem.set("text", r[1]) - # item.append(revItem) - return content ######################################################################################################################## @@ -596,9 +588,6 @@ def loadProject(project, zip=None): @return: an array of errors, empty if None. """ - # FIXME: Don't forget to cache everything that is loaded - # In order to save only what has changed. - mw = mainWindow() errors = [] @@ -632,6 +621,12 @@ def loadProject(project, zip=None): with open(os.path.join(dirpath, f), mode) as fo: files[os.path.join(p, f)] = fo.read() + # Sort files by keys + files = OrderedDict(sorted(files.items())) + + # Saves to cache + cache = files + #################################################################################################################### # Settings @@ -792,21 +787,138 @@ def loadProject(project, zip=None): log("* Adds {} ({})".format(c.name(), c.ID())) + #################################################################################################################### + # Texts + # We read outline form the outline folder. If revisions are saved, then there's also a revisions.xml which contains + # everything, but the outline folder takes precedence (in cases it's been edited outside of manuksript. + + mdl = mw.mdlOutline + log("\nReading outline:") + paths = [f for f in files if "outline" in f] + outline = OrderedDict() + + # We create a structure of imbricated OrderedDict to store the whole tree. + for f in paths: + split = f.split(os.path.sep)[1:] + # log("* ", split) + + last = "" + parent = outline + for i in split: + if last: + parent = parent[last] + last = i + + if not i in parent: + # If not last item, then it is folder + if i != split[-1]: + parent[i] = OrderedDict() + + # If file, we store it + else: + parent[i] = files[f] + + # We now just have to recursively add items. + addTextItems(mdl, outline) + + # Adds revisions + if "revisions.xml" in files: + root = ET.fromstring(files["revisions.xml"]) + appendRevisions(mdl, root) + + # Check IDS + mdl.rootItem.checkIDs() + + return errors + + +def addTextItems(mdl, odict, parent=None): + """ + Adds a text / outline items from an OrderedDict. + @param mdl: model to add to + @param odict: OrderedDict + @return: nothing + """ + if parent is None: + parent = mdl.rootItem + + for k in odict: + + # In case k is a folder: + if type(odict[k]) == OrderedDict and "folder.txt" in odict[k]: + + # Adds folder + log("{}* Adds {} to {} (folder)".format(" " * parent.level(), k, parent.title())) + item = outlineFromMMD(odict[k]["folder.txt"], parent=parent) + + # Read content + addTextItems(mdl, odict[k], parent=item) + + # In case it is not + elif k != "folder.txt": + log("{}* Adds {} to {} (file)".format(" " * parent.level(), k, parent.title())) + item = outlineFromMMD(odict[k], parent=parent) + + +def outlineFromMMD(text, parent): + """ + Creates outlineItem from multimarkdown file. + @param text: content of the file + @param parent: appends item to parent (outlineItem) + @return: outlineItem + """ + + item = outlineItem(parent=parent) + md, body = parseMMDFile(text, asDict=True) + + # Store metadata + for k in md: + if k in Outline.__members__: + item.setData(Outline.__members__[k].value, str(md[k])) + + # Store body + item.setData(Outline.text.value, str(body)) + + # FIXME: add lastpath + + return item + + +def appendRevisions(mdl, root): + """ + Parse etree item to find outlineItem's with revisions, and adds them to model `mdl`. + @param mdl: outlineModel + @param root: etree + @return: nothing + """ + for child in root: + # Recursively go through items + if child.tag == "outlineItem": + appendRevisions(mdl, child) + + # Revision found. + elif child.tag == "revision": + # Get root's ID + ID = root.attrib["ID"] + if not ID: + log("* Serious problem: no ID!") + return + + # Find outline item in model + item = mdl.getItemByID(ID) + + # Store revision + log("* Appends revision ({}) to {}".format(child.attrib["timestamp"], item.title())) + item.appendRevision(child.attrib["timestamp"], child.attrib["text"]) - # if "perso.xml" in files: - # loadStandardItemModelXMLForCharacters(mw.mdlCharacter, files["perso.xml"]) - # else: - # errors.append("perso.xml") - # - # - # if "outline.xml" in files: - # mw.mdlOutline.loadFromXML(files["outline.xml"], fromString=True) - # else: - # errors.append("outline.xml") - # - # return errors def getOutlineItem(item, enum): + """ + Reads outline items from an opml file. Returns a row of QStandardItem, easy to add to a QStandardItemModel. + @param item: etree item + @param enum: enum to read keys from + @return: [QStandardItem] + """ row = getStandardItemRowFromXMLEnum(item, enum) log("* Add worldItem:", row[0].text()) for child in item: @@ -815,6 +927,7 @@ def getOutlineItem(item, enum): return row + def getStandardItemRowFromXMLEnum(item, enum): """ Reads and etree item and creates a row of QStandardItems by cross-referencing an enum. @@ -880,7 +993,7 @@ def parseMMDFile(text, asDict=False): mdd[descr] = val else: - body.append[s] + body.append(s) # We remove the second empty line (since we save with two empty lines) if body and body[0] == "": diff --git a/manuskript/models/outlineModel.py b/manuskript/models/outlineModel.py index 55d676ca..564c818b 100644 --- a/manuskript/models/outlineModel.py +++ b/manuskript/models/outlineModel.py @@ -77,9 +77,7 @@ class outlineModel(QAbstractItemModel): in columns ``columns`` (being a list of int).""" return self.rootItem.findItemsContaining(text, columns, mainWindow(), caseSensitive) - def getIndexByID(self, ID): - "Returns the index of item whose ID is ``ID``. If none, returns QModelIndex()." - + def getItemByID(self, ID): def search(item): if item.ID() == ID: return item @@ -89,6 +87,11 @@ class outlineModel(QAbstractItemModel): return r item = search(self.rootItem) + return item + + def getIndexByID(self, ID): + "Returns the index of item whose ID is ``ID``. If none, returns QModelIndex()." + item = self.getItemByID(ID) if not item: return QModelIndex() else: From fa386896db4c8ab0b0ea45fe70c858c649ec30d2 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 10 Mar 2016 14:11:28 +0100 Subject: [PATCH 050/103] Saving 2.0 works. --- manuskript/loadSave.py | 2 +- manuskript/load_save/version_1.py | 68 +++++++++++++++++++------------ 2 files changed, 43 insertions(+), 27 deletions(-) diff --git a/manuskript/loadSave.py b/manuskript/loadSave.py index 28eecb2f..0e177e6f 100644 --- a/manuskript/loadSave.py +++ b/manuskript/loadSave.py @@ -13,7 +13,7 @@ import manuskript.load_save.version_1 as v1 def saveProject(version=None): # While debugging, we don't save the project - return + # return if version == 0: v0.saveProject() diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index 6aecb198..546fa008 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -47,18 +47,9 @@ characterMap = OrderedDict([ (Character.summaryFull, "Full Summary"), (Character.notes, "Notes"), ]) -# characterMap = { -# Character.name: "Name", -# Character.ID: "ID", -# Character.importance: "Importance", -# Character.motivation: "Motivation", -# Character.goal: "Goal", -# Character.conflict: "Conflict", -# Character.summarySentence: "Phrase Summary", -# Character.summaryPara: "Paragraph Summary", -# Character.summaryFull: "Full Summary", -# Character.notes: "Notes", -# } + +# If true, logs infos while saving and loading. +LOG = False def formatMetaData(name, value, tabLength=10): @@ -99,7 +90,8 @@ def slugify(name): def log(*args): - print(" ".join(str(a) for a in args)) + if LOG: + print(" ".join(str(a) for a in args)) def saveProject(zip=None): @@ -327,6 +319,10 @@ def saveProject(zip=None): # Debug log("\nSaving to folder", folder) + # If cache is empty (meaning we haven't loaded from disk), we wipe folder, just to be sure. + if not cache: + shutil.rmtree(os.path.join(dir, folder)) + # Moving files that have been renamed for old, new in moves: @@ -358,7 +354,7 @@ def saveProject(zip=None): # Check if content is in cache, and write if necessary if path not in cache or cache[path] != content: - log("* Writing file", path) + log("* Writing file {} ({})".format(path, "not in cache" if path not in cache else "different")) mode = "w" + ("b" if type(content) == bytes else "") with open(filename, mode) as f: f.write(content) @@ -372,6 +368,10 @@ def saveProject(zip=None): if os.path.isdir(filename): shutil.rmtree(filename) + elif path == "VERSION": + # If loading from zip, but saving to path, file VERSION is not needed. + continue + else: # elif os.path.exists(filename) os.remove(filename) @@ -442,12 +442,12 @@ def addPlotItem(root, mdl, parent=QModelIndex()): index = mdl.index(x, y, parent) val = mdl.data(index) - - if not val: - continue + # + # if not val: + # continue for w in Plot: - if y == w.value: + if y == w.value and val: outline.attrib[w.name] = val # List characters as attrib @@ -459,7 +459,8 @@ def addPlotItem(root, mdl, parent=QModelIndex()): cIndex = mdl.index(cX, cY, index) characters.append(mdl.data(cIndex)) outline.attrib[Plot.characters.name] = ",".join(characters) - else: + + elif Plot.characters.name in outline.attrib: outline.attrib.pop(Plot.characters.name) # List resolution steps as sub items @@ -475,7 +476,8 @@ def addPlotItem(root, mdl, parent=QModelIndex()): if cY == w.value: step.attrib[w.name] = val - outline.attrib.pop(Plot.steps.name) + elif Plot.steps.name in outline.attrib: + outline.attrib.pop(Plot.steps.name) return root @@ -621,12 +623,13 @@ def loadProject(project, zip=None): with open(os.path.join(dirpath, f), mode) as fo: files[os.path.join(p, f)] = fo.read() + # Saves to cache (only if we loaded from disk and not zip) + global cache + cache = files + # Sort files by keys files = OrderedDict(sorted(files.items())) - # Saves to cache - cache = files - #################################################################################################################### # Settings @@ -764,6 +767,7 @@ def loadProject(project, zip=None): for f in [f for f in files if "characters" in f]: md, body = parseMMDFile(files[f]) c = mdl.addCharacter() + c.lastPath = f color = False for desc, val in md: @@ -804,13 +808,15 @@ def loadProject(project, zip=None): last = "" parent = outline + parentLastPath = "outline" for i in split: if last: parent = parent[last] + parentLastPath = os.path.join(parentLastPath, last) last = i if not i in parent: - # If not last item, then it is folder + # If not last item, then it is a folder if i != split[-1]: parent[i] = OrderedDict() @@ -818,6 +824,11 @@ def loadProject(project, zip=None): else: parent[i] = files[f] + # We store f to add it later as lastPath + parent[i + ":lastPath"] = os.path.join(parentLastPath, i) + + + # We now just have to recursively add items. addTextItems(mdl, outline) @@ -850,14 +861,19 @@ def addTextItems(mdl, odict, parent=None): # Adds folder log("{}* Adds {} to {} (folder)".format(" " * parent.level(), k, parent.title())) item = outlineFromMMD(odict[k]["folder.txt"], parent=parent) + item._lastPath = odict[k + ":lastPath"] # Read content addTextItems(mdl, odict[k], parent=item) - # In case it is not - elif k != "folder.txt": + # k is not a folder + elif type(odict[k]) == str and k != "folder.txt" and not ":lastPath" in k: log("{}* Adds {} to {} (file)".format(" " * parent.level(), k, parent.title())) item = outlineFromMMD(odict[k], parent=parent) + item._lastPath = odict[k + ":lastPath"] + + elif not ":lastPath" in k and k != "folder.txt": + print("* Strange things in file {}".format(k)) def outlineFromMMD(text, parent): From 482641c7f278e5a3a36c076f8f00893c2b6bfeaa Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 10 Mar 2016 14:15:03 +0100 Subject: [PATCH 051/103] Adds a few FIXME as roadmap --- manuskript/loadSave.py | 2 +- manuskript/load_save/version_1.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/manuskript/loadSave.py b/manuskript/loadSave.py index 0e177e6f..69f2b229 100644 --- a/manuskript/loadSave.py +++ b/manuskript/loadSave.py @@ -50,7 +50,7 @@ def loadProject(project): version = int(f.read()) print("Loading:", project) - print("Detected file format version:", version) + print("Detected file format version: {}. Zip: {}.".format(version, isZip)) if version == 0: v0.loadProject(project) diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index 546fa008..f0d23de1 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -106,7 +106,7 @@ def saveProject(zip=None): """ if zip is None: zip = False - # Fixme + # FIXME: use value from settings log("\n\nSaving to:", "zip" if zip else "folder") @@ -626,6 +626,8 @@ def loadProject(project, zip=None): # Saves to cache (only if we loaded from disk and not zip) global cache cache = files + + # FIXME: watch directory for changes # Sort files by keys files = OrderedDict(sorted(files.items())) From a3f1d1132411907e5130282147b9cbe1fe96cf39 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 10 Mar 2016 14:38:46 +0100 Subject: [PATCH 052/103] Bug corrected --- manuskript/load_save/version_1.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index f0d23de1..e996927a 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -49,7 +49,7 @@ characterMap = OrderedDict([ ]) # If true, logs infos while saving and loading. -LOG = False +LOG = True def formatMetaData(name, value, tabLength=10): @@ -321,7 +321,8 @@ def saveProject(zip=None): # If cache is empty (meaning we haven't loaded from disk), we wipe folder, just to be sure. if not cache: - shutil.rmtree(os.path.join(dir, folder)) + if os.path.exists(os.path.join(dir, folder)): + shutil.rmtree(os.path.join(dir, folder)) # Moving files that have been renamed for old, new in moves: @@ -897,8 +898,6 @@ def outlineFromMMD(text, parent): # Store body item.setData(Outline.text.value, str(body)) - # FIXME: add lastpath - return item @@ -920,10 +919,13 @@ def appendRevisions(mdl, root): ID = root.attrib["ID"] if not ID: log("* Serious problem: no ID!") - return + continue # Find outline item in model item = mdl.getItemByID(ID) + if not item: + log("* Error: no item whose ID is", ID) + continue # Store revision log("* Appends revision ({}) to {}".format(child.attrib["timestamp"], item.title())) @@ -981,7 +983,7 @@ def parseMMDFile(text, asDict=False): inBody = False for s in text.split("\n"): if not inBody: - m = re.match(r"^(.*?):\s*(.*)$", s) + m = re.match(r"^([^\s].*?):\s*(.*)$", s) if m: # Commit last metadata if descr: From e35369dc58a3537984c5a45e3c130e294aba15a6 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 10 Mar 2016 14:47:05 +0100 Subject: [PATCH 053/103] Sample project 'book of acts' to new file version --- sample-projects/book-of-acts.msk | Bin 30732 -> 1 bytes .../book-of-acts/characters/0-Peter.txt | 4 + .../book-of-acts/characters/1-Paul.txt | 4 + .../book-of-acts/characters/2-Philip.txt | 4 + .../book-of-acts/characters/3-Stephen.txt | 4 + .../book-of-acts/characters/4-Herod.txt | 4 + .../book-of-acts/characters/5-Barnabas.txt | 4 + sample-projects/book-of-acts/infos.txt | 3 + sample-projects/book-of-acts/labels.txt | 5 + .../0-Jerusalem/0-Chapter_1/0-Introduction.md | 7 ++ .../1-Jesus_taken_up_into_heaven.md | 11 ++ .../2-Matthias_chosen_to_replace_Judas.md | 15 +++ .../0-Jerusalem/0-Chapter_1/folder.txt | 7 ++ .../1-Chapter_2/0-The_promised_Spirit.md | 7 ++ .../1-Chapter_2/1-Peter_adresses_the_crowd.md | 39 +++++++ .../2-The_life_of_the_first_believers.md | 7 ++ .../0-Jerusalem/1-Chapter_2/folder.txt | 7 ++ .../2-Chapter_3/0-Peter_heals_a_beggar.md | 8 ++ .../2-Chapter_3/1-Peter_preaches.md | 11 ++ .../0-Jerusalem/2-Chapter_3/folder.txt | 7 ++ ...eter_and_John_in_front_of_the_Sanhedrin.md | 13 +++ .../3-Chapter_4/1-The_believers_pray.md | 15 +++ .../3-Chapter_4/2-The_believers_share.md | 8 ++ .../0-Jerusalem/3-Chapter_4/folder.txt | 7 ++ .../4-Chapter_5/0-Ananias_and_Sapphira.md | 11 ++ .../1-Many_healings_done_by_the_aposles.md | 7 ++ .../4-Chapter_5/2-The_persecutions.md | 14 +++ .../0-Jerusalem/4-Chapter_5/folder.txt | 7 ++ .../5-Chapter_6/0-The_choosing_of_seven.md | 9 ++ .../5-Chapter_6/1-Stephen_is_taken.md | 8 ++ .../0-Jerusalem/5-Chapter_6/folder.txt | 7 ++ .../6-Chapter_7/0-Stephen_preaches.md | 27 +++++ .../6-Chapter_7/1-Stephen_is_killed.md | 9 ++ .../0-Jerusalem/6-Chapter_7/folder.txt | 7 ++ .../outline/0-Jerusalem/folder.txt | 7 ++ .../0-Chapter_8/0-The_church_scattered.md | 8 ++ .../0-Chapter_8/1-Philip_in_Samaria.md | 9 ++ .../0-Chapter_8/2-Simon_the_Sorcerer.md | 11 ++ .../0-Chapter_8/3-Philip_and_the_Ethiopian.md | 22 ++++ .../0-Chapter_8/folder.txt | 7 ++ .../1-Chapter_9/0-Saul-s_conversion.md | 17 +++ .../1-Saul_in_Damascus_and_Jerusalem.md | 11 ++ .../2-Peter_visits_the_church_in_Judea.md | 9 ++ .../1-Chapter_9/folder.txt | 7 ++ .../0-Cornelius_calls_for_Peter.md | 12 ++ .../2-Chapter_10/1-Peter-s_vision.md | 12 ++ .../2-Chapter_10/2-The_conversion_of_Peter.md | 12 ++ .../2-Chapter_10/folder.txt | 8 ++ .../0-Peter_explains_his_actions.md | 11 ++ .../3-Chapter_11/1-The_Church_in_Antioch.md | 11 ++ .../3-Chapter_11/folder.txt | 7 ++ .../0-Peter_escapes_from_prison.md | 13 +++ .../4-Chapter_12/1-Herod_dies.md | 12 ++ .../4-Chapter_12/folder.txt | 7 ++ .../outline/1-Judea_and_Samaria/folder.txt | 7 ++ .../0-Saul_and_Barnabas_are_sent.md | 12 ++ .../0-Chapter_13/1-Cyprus.md | 10 ++ .../0-Chapter_13/2-Psidian_Antioch.md | 20 ++++ .../0-Chapter_13/folder.txt | 7 ++ .../1-Chapter_14/0-Iconium.md | 7 ++ .../1-Chapter_14/1-Lystra_and_Derbe.md | 10 ++ .../1-Chapter_14/2-Back_to_Antioch.md | 8 ++ .../1-Chapter_14/folder.txt | 7 ++ .../0-The_Council_at_Jerusalem.md | 19 +++ ...The_Council_writes_to_gentile_believers.md | 10 ++ .../2-Paul_and_Barnabas_fight_and_split.md | 9 ++ .../2-Chapter_15/folder.txt | 7 ++ .../3-Chapter_16/folder.txt | 7 ++ .../4-Chapter_17/folder.txt | 7 ++ .../5-Chapter_18/folder.txt | 7 ++ .../6-Chapter_19/folder.txt | 7 ++ .../7-Chapter_20/folder.txt | 7 ++ .../0-Asia_Minor_and_Greece/folder.txt | 7 ++ .../1-Rome/0-Chapter_21/folder.txt | 7 ++ .../1-Rome/1-Chapter_22/folder.txt | 7 ++ .../1-Rome/2-Chapter_23/folder.txt | 7 ++ .../1-Rome/3-Chapter_24/folder.txt | 7 ++ .../1-Rome/4-Chapter_25/folder.txt | 7 ++ .../1-Rome/5-Chapter_26/folder.txt | 7 ++ .../1-Rome/6-Chapter_27/folder.txt | 7 ++ .../1-Rome/7-Chapter_28/folder.txt | 7 ++ .../1-Rome/folder.txt | 7 ++ .../folder.txt | 7 ++ sample-projects/book-of-acts/plots.xml | 6 + sample-projects/book-of-acts/revisions.xml | 109 ++++++++++++++++++ sample-projects/book-of-acts/settings.txt | 79 +++++++++++++ sample-projects/book-of-acts/status.txt | 4 + sample-projects/book-of-acts/summary.txt | 0 sample-projects/book-of-acts/world.opml | 20 ++++ 89 files changed, 983 insertions(+) create mode 100644 sample-projects/book-of-acts/characters/0-Peter.txt create mode 100644 sample-projects/book-of-acts/characters/1-Paul.txt create mode 100644 sample-projects/book-of-acts/characters/2-Philip.txt create mode 100644 sample-projects/book-of-acts/characters/3-Stephen.txt create mode 100644 sample-projects/book-of-acts/characters/4-Herod.txt create mode 100644 sample-projects/book-of-acts/characters/5-Barnabas.txt create mode 100644 sample-projects/book-of-acts/infos.txt create mode 100644 sample-projects/book-of-acts/labels.txt create mode 100644 sample-projects/book-of-acts/outline/0-Jerusalem/0-Chapter_1/0-Introduction.md create mode 100644 sample-projects/book-of-acts/outline/0-Jerusalem/0-Chapter_1/1-Jesus_taken_up_into_heaven.md create mode 100644 sample-projects/book-of-acts/outline/0-Jerusalem/0-Chapter_1/2-Matthias_chosen_to_replace_Judas.md create mode 100644 sample-projects/book-of-acts/outline/0-Jerusalem/0-Chapter_1/folder.txt create mode 100644 sample-projects/book-of-acts/outline/0-Jerusalem/1-Chapter_2/0-The_promised_Spirit.md create mode 100644 sample-projects/book-of-acts/outline/0-Jerusalem/1-Chapter_2/1-Peter_adresses_the_crowd.md create mode 100644 sample-projects/book-of-acts/outline/0-Jerusalem/1-Chapter_2/2-The_life_of_the_first_believers.md create mode 100644 sample-projects/book-of-acts/outline/0-Jerusalem/1-Chapter_2/folder.txt create mode 100644 sample-projects/book-of-acts/outline/0-Jerusalem/2-Chapter_3/0-Peter_heals_a_beggar.md create mode 100644 sample-projects/book-of-acts/outline/0-Jerusalem/2-Chapter_3/1-Peter_preaches.md create mode 100644 sample-projects/book-of-acts/outline/0-Jerusalem/2-Chapter_3/folder.txt create mode 100644 sample-projects/book-of-acts/outline/0-Jerusalem/3-Chapter_4/0-Peter_and_John_in_front_of_the_Sanhedrin.md create mode 100644 sample-projects/book-of-acts/outline/0-Jerusalem/3-Chapter_4/1-The_believers_pray.md create mode 100644 sample-projects/book-of-acts/outline/0-Jerusalem/3-Chapter_4/2-The_believers_share.md create mode 100644 sample-projects/book-of-acts/outline/0-Jerusalem/3-Chapter_4/folder.txt create mode 100644 sample-projects/book-of-acts/outline/0-Jerusalem/4-Chapter_5/0-Ananias_and_Sapphira.md create mode 100644 sample-projects/book-of-acts/outline/0-Jerusalem/4-Chapter_5/1-Many_healings_done_by_the_aposles.md create mode 100644 sample-projects/book-of-acts/outline/0-Jerusalem/4-Chapter_5/2-The_persecutions.md create mode 100644 sample-projects/book-of-acts/outline/0-Jerusalem/4-Chapter_5/folder.txt create mode 100644 sample-projects/book-of-acts/outline/0-Jerusalem/5-Chapter_6/0-The_choosing_of_seven.md create mode 100644 sample-projects/book-of-acts/outline/0-Jerusalem/5-Chapter_6/1-Stephen_is_taken.md create mode 100644 sample-projects/book-of-acts/outline/0-Jerusalem/5-Chapter_6/folder.txt create mode 100644 sample-projects/book-of-acts/outline/0-Jerusalem/6-Chapter_7/0-Stephen_preaches.md create mode 100644 sample-projects/book-of-acts/outline/0-Jerusalem/6-Chapter_7/1-Stephen_is_killed.md create mode 100644 sample-projects/book-of-acts/outline/0-Jerusalem/6-Chapter_7/folder.txt create mode 100644 sample-projects/book-of-acts/outline/0-Jerusalem/folder.txt create mode 100644 sample-projects/book-of-acts/outline/1-Judea_and_Samaria/0-Chapter_8/0-The_church_scattered.md create mode 100644 sample-projects/book-of-acts/outline/1-Judea_and_Samaria/0-Chapter_8/1-Philip_in_Samaria.md create mode 100644 sample-projects/book-of-acts/outline/1-Judea_and_Samaria/0-Chapter_8/2-Simon_the_Sorcerer.md create mode 100644 sample-projects/book-of-acts/outline/1-Judea_and_Samaria/0-Chapter_8/3-Philip_and_the_Ethiopian.md create mode 100644 sample-projects/book-of-acts/outline/1-Judea_and_Samaria/0-Chapter_8/folder.txt create mode 100644 sample-projects/book-of-acts/outline/1-Judea_and_Samaria/1-Chapter_9/0-Saul-s_conversion.md create mode 100644 sample-projects/book-of-acts/outline/1-Judea_and_Samaria/1-Chapter_9/1-Saul_in_Damascus_and_Jerusalem.md create mode 100644 sample-projects/book-of-acts/outline/1-Judea_and_Samaria/1-Chapter_9/2-Peter_visits_the_church_in_Judea.md create mode 100644 sample-projects/book-of-acts/outline/1-Judea_and_Samaria/1-Chapter_9/folder.txt create mode 100644 sample-projects/book-of-acts/outline/1-Judea_and_Samaria/2-Chapter_10/0-Cornelius_calls_for_Peter.md create mode 100644 sample-projects/book-of-acts/outline/1-Judea_and_Samaria/2-Chapter_10/1-Peter-s_vision.md create mode 100644 sample-projects/book-of-acts/outline/1-Judea_and_Samaria/2-Chapter_10/2-The_conversion_of_Peter.md create mode 100644 sample-projects/book-of-acts/outline/1-Judea_and_Samaria/2-Chapter_10/folder.txt create mode 100644 sample-projects/book-of-acts/outline/1-Judea_and_Samaria/3-Chapter_11/0-Peter_explains_his_actions.md create mode 100644 sample-projects/book-of-acts/outline/1-Judea_and_Samaria/3-Chapter_11/1-The_Church_in_Antioch.md create mode 100644 sample-projects/book-of-acts/outline/1-Judea_and_Samaria/3-Chapter_11/folder.txt create mode 100644 sample-projects/book-of-acts/outline/1-Judea_and_Samaria/4-Chapter_12/0-Peter_escapes_from_prison.md create mode 100644 sample-projects/book-of-acts/outline/1-Judea_and_Samaria/4-Chapter_12/1-Herod_dies.md create mode 100644 sample-projects/book-of-acts/outline/1-Judea_and_Samaria/4-Chapter_12/folder.txt create mode 100644 sample-projects/book-of-acts/outline/1-Judea_and_Samaria/folder.txt create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/0-Chapter_13/0-Saul_and_Barnabas_are_sent.md create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/0-Chapter_13/1-Cyprus.md create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/0-Chapter_13/2-Psidian_Antioch.md create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/0-Chapter_13/folder.txt create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/1-Chapter_14/0-Iconium.md create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/1-Chapter_14/1-Lystra_and_Derbe.md create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/1-Chapter_14/2-Back_to_Antioch.md create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/1-Chapter_14/folder.txt create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/2-Chapter_15/0-The_Council_at_Jerusalem.md create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/2-Chapter_15/1-The_Council_writes_to_gentile_believers.md create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/2-Chapter_15/2-Paul_and_Barnabas_fight_and_split.md create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/2-Chapter_15/folder.txt create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/3-Chapter_16/folder.txt create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/4-Chapter_17/folder.txt create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/5-Chapter_18/folder.txt create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/6-Chapter_19/folder.txt create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/7-Chapter_20/folder.txt create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/folder.txt create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/1-Rome/0-Chapter_21/folder.txt create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/1-Rome/1-Chapter_22/folder.txt create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/1-Rome/2-Chapter_23/folder.txt create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/1-Rome/3-Chapter_24/folder.txt create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/1-Rome/4-Chapter_25/folder.txt create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/1-Rome/5-Chapter_26/folder.txt create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/1-Rome/6-Chapter_27/folder.txt create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/1-Rome/7-Chapter_28/folder.txt create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/1-Rome/folder.txt create mode 100644 sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/folder.txt create mode 100644 sample-projects/book-of-acts/plots.xml create mode 100644 sample-projects/book-of-acts/revisions.xml create mode 100644 sample-projects/book-of-acts/settings.txt create mode 100644 sample-projects/book-of-acts/status.txt create mode 100644 sample-projects/book-of-acts/summary.txt create mode 100644 sample-projects/book-of-acts/world.opml diff --git a/sample-projects/book-of-acts.msk b/sample-projects/book-of-acts.msk index 2f939bee1c5172dcede70535acf19aad02d5ca01..56a6051ca2b02b04ef92d5150c9ef600403cb1de 100644 GIT binary patch literal 1 IcmXp6001%oG5`Po literal 30732 zcmaHyLzHMim!;FTZQC|q+O}=m=1beQZQHhOJNs2t59;qh&-S{bSl>Qz_KG-h<)wf@ zPyhe`AOJQybpCV&-)#v20stHU0|3DO`)X=!;4EWjY+_C4Ze!h?xGOurfHHFH5t+Cn zO>b=7w^x8YUltL8PG;Cer?rmNx#BE3&wjw^&N!JHI7X=LQD5}!yL)cYn>at;m2%fF zL);`8RJIN2U#X2Qlx%Uw)YFRvR#+u!rE}LE=e*(2DcpbtP|ZQzra`+sf>|||zGaNbB@2VLESxU0F&Y*WZ= zW0*CX>_QFsv*HF4H5{YB$G94TkrDkIc=vI+b zP`)Q7P14KbkRNOu*U0W02>0U9j_@Ofl>e82{VP|E*f20+G%j!7bbv+sw?X3)T>d{` z_ssW6jQ)ci3Jm}N`46_eiKCO0z6^CF{@N7g#lg?I z5VlAnV&3QLLsaMD0?COUZRo^j7i7jsR%&tGY&ekiuNBo>6ZVTeI{^$Qt7 z_svX$tY$_xG%>KE}1pYs6yV*Hf8~>Nv-O01EK>`RPw?0q2|JuQ>o(uW}8`BR`Diek>$eLyY+d0 zL^4qEl}>HmotYg>@54`sLV9Liw)>v1-HkPzL1{qOyD9`wl{E(5=#(L6c9uCQ*)F1O zRO+fr*y?$u$+PkHJm=00N|oeJ=ab{tTPZ4jKLB*#xp@bk%N9p{fIrYH-EYZcG=_aA zm+osqzK0|BQam@dsLddgG}B9Z{i~!#2upJ$am|NfBhsxpZVTn3GG@w3F&yBD$*N=n z2=n%|<@Np1Xv~-f<@ejm<^*_stMjU)Kv1)zB4L)akNoXN$Q?J7#F-1}p1_L+_g8E9 zCB?om*eQ&ZA^9rcS!*rdU+Zic3UBFnOJ|YxkcnmJY@V-^tKsY`QxmEK;IVnpX@=1P z{VYn+&-e&RZF2;3Z)^9MpIyDuJg?~)@V){5(+$5GKS9WUZW#P)bWs2Hp0$CYiM7*z z*%6Z{WjjcKAn?iuN(5bNc~?6Dlqgy(x$QhZ3rLZ(l7fSbxFp8wGwS*$2`Yqukhv?J z9L%_$(;EB%)^#_tUg9yj6hzEs!}rT_;k9+N)1~8Y#99X0j7O50=t#LV1R`!3+EvpK z0J3)Na$Rs6V;Ou^;|aJGg@e64>@+3x{njJV6!h#Q13ho;K-WVT+~dZh*DPq~mALcB zORuSOD;vd{3Ft`aCQ`NIMOx^wt^<-4GeTA)TJH#(^KjhZ)o8_U>{rL36W&?894$q^ z{cmJd_&A52@$878wCadSV(dhi>^06>T5Y5o*KUgUGJ}|$+U~-Ia|ilbP(ueaB&b%7 zO5JvL3S73x zYZ`>PJ}k2nG^I8|5Z0vV3p%ap`ax>AtFK7#EPDB;SBSWhT|DohXi3BFi0+uDCVMJb zY5}Vby5M{;IBS<(b5ujMs#(boq(M_?QHG;PvKai(ZM?Z zE1YrA|J$^!?VSG`!*J5H?EnMJ=q*o3GIXheW{)d`=!QWenT-&(mwytLW_gSP64=J# ztTzlryzK%U4$<(>rtC+f5~pS=sCPj<=eYs1b#tb_r=&&A>EOxe(fv&iXC!t*5lWmx z!FHsk4I(>$aVV8&<9b6sAYE>Sp;<$rJme{+rb8@1m=!w;Q5#v|_eynT{%?$hPahM2O;e<-`Y!@HiTAJG_7n z_AfP@pG3ZTWKn@xwJUpnVl=W6A!`#c3xx;O<2@MshY%i@_CHHh$*zdJ&H?~n@-HC3zeokJb8)t|ur>MbMD5;; zC0TbM{ovEvKLUg+7NaPA&}4UjT9#?3)ZD%Tt2|+G-{B$RVeVoc;61xhSnm%2@Ezij z2LSye_W|z%)qAw2>AJqjOH>&-jYcvhp)Yn78GSd|v&N>?V!giG`{vt7x1*w?*E&7x z=j!=zW+u8!vthEm_4Q@;`*>aZb2C1x`xW>5@_KEnzAE$VS;jUj4}W=a%74A#`et$e zfzJPXUYLtE=u3^S(|T#L{N1G9lxY)loAv$V_tjd!@p4IDVY`}oX<3-c(VL}PxNE~^ z((0$gD!fi}1aGINa__N87p14><}+{CS)u(Rx{b14-1GkNyMHK+xA#Wu-9ptWyo_4< z0fFxuXgBE@??73zS%Yz8v15H%gD2)j{bBjmMZ5ZM5$A>CaaSj!cE25%9j@(KFia00 zAf-PbgRhB|aLcbjU)_Be+N(PJD32}Ow7TeSI^4?4-aEEx&z`HZM{W;wI!d%rj@>Dz zG~k%|zN_eu7bVt5VS^PI8;0v>CXGXWuqy59(~*sat1!Fj{wKOtUnNW^hwUTj5r0u#WH8n0oHLNRZQj(W+jp)L7chL1x@6V3WTi--EQ#lhs zTfZLhf6D291o<>uG82hPl*yVacb3=KnF=#2@F%8KPtFb=9_HWQ*O!fQn-89Te|lx{ zdHvG5e!s>$Dfp`2PJ3$E9$i8ZyULF?bR5*+y$`2hofA{5j&9U3pU8sM~C?RR1VmfGn0Kf6E~B#6>A&IWg{mG-jctb zqFk59u4t*-U$w84zP;Yzr%XBbPTOF8z11ck($orTrB5p@z;P8|;&;j{Ze@SI!u(DG zy9Nd>sXTNw{?&8u?xk@{CY6|JNp9+QwJZ_%;bco9CiQWuJ4Sm2O`Wf~OUn_Xt;-Oj zQ|)I>)e8MrO%8Sk7CPWEiviOaG zD`yB3Or>Uz?<&f%t@=1J>}TSEgo1YleAacSAvJYlR4P6!9^Hh)nvF@&B)weZuIVA& zt0k3&C~uBZPcptR^B45yYt(iFRXf9Ol4HWsF^y~WmJRz@w5>5cs=9nPJ&FeEAQjoU z#XW7#jB9!ib{x^2p{j)OYujPG1VUwI}_H(;>K6j;h{Dwc3$vF9qa*_LRL5pLx z_O_E?r);X5EXwXUJtI{dydkWNUWD}Hq5=0Kwa52-=#}*~RMxq?(fFo{Fg0CC{OUgp z^u{KWM0xvJ_XEhtWto8J<9=+5>&I2Ilf%`wCCgC`BN z%PNDhWI8<1!!dOF24rSIA+d~9s6TW++r z1n?Acl#6aPQKntFm!>C=lx(^q1bE)$p)J%y7q)e%9o`!z(XS5)v(F?6X742hn1RU& zztq6Qz+Vj-uFKn1Z(=Lb-NPO$wvz8(N>xX$wCt(;zAA8z>)fAz;nXMFecE?wCZL#~ zOLm&Lj(+V_CN}0ws5{GWzWvqWSG(eKYizjZyjx>%Xre;a$g&C#Dz1$=MyXY&9NR}J z0~dvKy>;j%IrJSXU8oz+M+F?n?nO8H}a5!m9cz5nPDsjQ|dLlHo zf2(nWwggMn)R0#lBIs4CQ!PeJ zU>^lWe2ypGo`qz2Fg&_Jog=RWEn*DY9F<12os2prx(iEeIGwf3Esak-s8<#E{+SilWlYOo20`nEI zA5Qtd?vJaz$wp_SX@c$a(w6xvup{xuc&3Ma>E!N`8^v)RGB&?t$=u7Z^opL;jyHG5 z3avjd(c84or8u4Mb1EHm;kU%pIWiSZT{WJsHMP;UVcrZE9sB8SsW5TdZF$E?f6iz9 zK2~#oUgMm~>1*lmf%3uiKKfIA?~3ug&;MWPs+ZxH!icTYXzg6M|F&@W_0gfLO#buT zs8n@T>oRG*v=BnPIC0pe#9g66px++P z>zb_%9fVUlwazJvd7tLyoZ04mJQm_{Q+Yjs9<;WP2QJp~?LN1W+Z+G+SyGT(Befo2 zhVE*!Hfdt2`lO$OdI5A0cu~?XTE6wS-+s||;GO+5z%2$n%uIkB!>PDm*uiyNaHwc; zILFp>TMNyn$`V_yyA2O%Rb7h%#EcOfyN$(B`EcO|({`0qZIK_{Ozk6uqi-lVK8*d< z$Eaols%AD1J>~fNu+Wd!H0NxfntDk2r;J<_3jJS>m}xY>nrt|$bO4uzHAmNKt3BG9 z6?SD>r&avE%l)ph&*Je8{`B+{E*^1?I&=ljzs1nB`SGMQc!ek66%>V0oD)iZ#6>6Y zF_HSbza8{b@v0YtS3c<{? z*l6a7fj?}Dmz4;9deqP9VK4i?Xd}UmP-sL7NS{W{hpU(H%CP)q4 zp`v?I6nxTO)Y7IFz*htIKHIUx3833Bd+`DEb1J%&^LJXOIKOjgRI}mCZAT5(%Wm7} zz!K&cqJBn9f84%`j}}EIIIcSzLMJ99sj-qrF2x76<}}Z&$77=DR`LBUaR-_48}~1KS}hFrNiv@=~nHb98=3k z%_zmlxX)!aYOvK zFYI4Z?dvr!R$r&|b}yTPr9=b|^q22FNp8p9J}s$kDc6x(vvaN~M{lsL3>Gu$s2|qZ z>i5q%v!0%s-ed-GT@b%d4T+-UzwRf%jvQtfN$Pzkn3p*=omzY=r`HeUb?aDKxY8#y z)tHcG#z{SXzgfOfz=rD&rX0q`C%ShJr^Bl!jCLC#hTjek_akHDN1IK58-;14p3zCB zYUt?kq)&pzy&2Ns$sT`S$&~gJpiAOlbq#kA;Ryv7hbnU-7~An!at4IEtVo%j8jt{x z)+{`BxTPF6qP@arw419$VI?#MkTEtpn9PlHLe9K~c&EZ6rxp9`2j_Kx8rqD%s-$>S zzZ*7LTZcet>f1LZl?mXt)QTj`+qN^(M`%slYTCAHzd1PGLDb3!a&izA&~Q`_!r!2Gx>M4c!xmQj_dt=-{b7`58^EI!7vvz}K+BX?T3mVRcFaKK})Ktfs~)kvb_TpD=jN+9kv@@=wf4b@d&G-Qd1C z^Cfnwe&zETQf&5J9m>L*kv4YIaBUT-J=lSoLnKY?z07_piOk-+WoN1Zw)1SX?bL*G zcl!fGA3$$N?B}X!OdUxr#)bmZdi*X0n;BtrP&ZL~$bWfUMhfzL%Ku%d`wa6h&bAvT zY|=v@hu#BVUy%oGj;MR-{OCVz)B!QUhseRYtpMAnUe#w>oHW`}HpMsiGV54ap~&~l zCNtCkUV=82`Y%b(5_VtBSVw45r#hOt5vKxvBPgSBEoPlm09qZuHw@Y&~w|g3dycAXWCM zTFMRwa`yeZIQ7)o2;J=K4;s{TUUuV9y=v7Pw^LsXFAeBcdXujHJD6VSs&$UW=Y=zf zW=+8}ht;I7N$w%`b)*96c0B9%hW=AkeqnHo4bD$ff4&^MQ25xxaTw~#d@5*0K>`1i(emdYQrv>h zr#9L5=$w@SNivCAL$6|qZv8bz7b(JD(aO30=bhE-;?h#*S zGR6zK6UXn%dO5}DH}x{# z5lOR!cQEh%@6sCx9bU_5S?C~Py%WmZJkSeIIcxp^;gm+Mf8l)!)O<=d&y}cYv?0la zuLA3261d8aZ!L^`!h-RqIFA4=+QBLr?Ii?~i-1))jHXkTD61KH^Q=##U1P&}as-%P9zmTCNlL zteH`e@NBCd;%~gm7TEdoR|L6w>Coz~vX&wlDkKC~EofR!%HFZAXMq@QG9GV!E(%8?R0x?%I;O)-J+ZznYxb;JqxEG=AhAb*}CQ5h8? zr8aqQu(k0Z6Gvkk2pJ!zvE@@;MSC9|9_sD@*+VK+)ZE|zn*PrNm$cLKcg1yRp0!m` z88yC{)(QN2O^TYPvkkQFV+@Rj_|PFapBZ}TOu`)wLu~%DZ`NjaVdEeD!IB%1>)wz? zspI%m7^8vk35noKNuID{vJyq>m5`AB*972)kkKYee{wwfyLl?leQJ3e9{LRpmRM;j zD0dh~f>D7RH{CBNTWSZaK6Pz-cMK;Eh68bJ`2z{{RF6NY4HD>*-hJ-V!^2e!@?SCs ziIw>-MQ|*pJWLd?HpJ*{OAcp<>abJfgO!P>I$m#~6C_%z#QwI$q7shVU+N-a^$PCg zB7(>zX9tp3*LWxP5#Cub?@4>!AVbDD+BPj#alYR1oCD#DYLKv`76N+v9X7B!5N2Yd zP3;<^uB_P3_ZW%Fhj!rPi!Sdq&4!@tjgf>7>*?)FqGpoVniqZO&9BobO@fZz4q{xx zqZJ7}eB3?&5%>ZJ;PG#OB) z_J2{F-vF_E{X_Bd_QB2n$sc}5te-O~)y70aFlB1d8>-I>TMHjQI&oC&ouM;vR3a~b z6FmKNaP_ti$KiKy&hO!qMq0H0CM0q=t?md4J&H#mDU! zTBVwWhmWT1sTiI zY0m25;z+Pty3LAC)0jbfU`xcGKUNg9nAkXfcRfPvOJX^K%dBnSsHLy)S*|4RN98x1n8d9h=T&UeW#E#wrua8rA#9FPg8xJiEXS;^jt^vNq_}#^^ z^EPNr{C?Ehs3oA~Wt7ykmji3E#pla=S;dHA&W+y5+ zSeLz1?~xah|F(a**U5RZ>bAxaG}xAnEUn2seG*sGX~D;TA3rDNB|d`P$PBGG)5fo$ zXTyL_uJl;FWD?*31@2!Txx!rsYpFJzMai9{h-F`s5R;vf2=tVvqJ!xn(jra?I;^VO z-K?gU@K}L=N9p)k5>UMn%JQj_;ilDn37xjB`1>7DJ!G!nR*mVk&{01!8c9<_Ht%Y~ z{Vv#M!wvONPFARq#U8I&6xf3^4+6Y;Bkpf;U9e69_es}DO~ZvO?z_pheqSudD7ufk zJjpVUe5%!!l-KnL-7fy65feEqYpUovQ&fUY>@}I*Nrg5`+cPu-uquB`2t-gdjZTnw zu=K6{Dbs4OAw8?NPpfCn-|O&?Vkh& zz<0%87&16EbI_|eh1C&dctMeYad^?sH?V^|4Kx>as&dHUqKB?D%V~tI9O9SvsqV7s z*hK-Jb9p>gg+bw#kU;78!78tZeg%?54jwkXmdr-@yx_ArAg( zUc?)mEhwYN2&A-6{dXO5$Z&&Qz_*|vYbV{%_SW9@;@T?Up-j1e8?ijm_Qm@wUV#fJ z^&m@w4LfpE;kp@R)Pjo?rD+K5iIXz`h=l03R+B`%+TjnFCP`(2unb~9_zZ@#qZ-x$Hqn;Nyk7^UyVjYMlJYRNB`iQ zJX7gzBdC`Bxr(H71kKJVL;XETsTfWIWCAy9c3`geV$~E3S^p+w^8pr&Y4|whF9dRU z5VdR4+DV!2no5V(zZk=uj{HqymTNCqbK0+3ISYOMMj*8;P%d}N2D+dLqoXQu({hdG zqjdQQCr}2cz|@--MBrHI;O3f5S7%K3S|2a`3~b%43#YuhV~DG;J!3q^k~ zGacKvXe%mSnP~qFT4}JhZRTA@7ZoFI7#MTCB2`J6;yGLnEBdb_*<~c7W!+PDMBSs( zMiMtwHCvYtqf1N)L(wHi$4}Uph$8p3D=q6zFmEBO8XDxXv4<%h8Q3EyQOKR^s2ZNr z4_INDH*z-jrg(Q9tIeweLp)(Rc3RXOYwptl1v~|$2qK$LVaL#Qcxz#lI`>;@8pKpQ zT<{JSQe}+Zt|YaYG7YbPg7h@qzce&bH&L4mmUI2uYkKJSeKKLCL!(Fl|4agg; z3hQiX_G|X{_B&3XPmvC=nPXfYJ9VZn`10<(V#3b!X7}_P1>?O9eKQUEW9e6X@}A^S z?p(t6d{mOaIryaO;QTOjHz|WlI0-$J@0-eMx3`n(ok~3<_(>AxiW`#Mp)Ma=&N`Qt zH)(TZz|v(d-aX6s9)(yOgo4}(=P@c@_c9nna2-NmCZ+FuT6P=AXGGfhfy_9jroHE& zYnRujFV4@TtWv6=-(3oY38hmoo=};Q9Z6uJNLb+H$_buhu6iS(71{Rc4gd*(N0C*qcmucU=Odl&*={{f#>~MDvzc~7444QWI+n7BiY>UjyZ{0Gb50q_TiRz zO&2_~7}G<^|Ppj45A(Wcr|LF7#Iq!C4*=`zkRxM zDx^J)#S7f>j&$dujQy3@1;`-;g{kAuRsuw+F1q~KhU=o3jr)xJyN7^plGsqOqQ#`> z4IgcI#vMYIjTO(dfrJ+uCy~pNo8ivrD;$xJx-qd&v533eWX(e^*M*wf_}YBJw|chc zIddAD@d!d-Ah1-EENp63A4=@~RK3lXqm|dvP3I*ebpL!WyoY52ZU^_fK`|`%ZB+;&S~!v5PKPyzqx14@0UM zaXhFRb!HI`2FlcBSrvKSGY#v2IzYq4XOB74EPjKrLax6AWLI_#`gt!Ad&$rTAR)?z zP!>5dVxf=pw>bdK3mUWC*+3R%z%gZP6VJ3Z3@PG_(zkpi1K#q%Fby#;Sp z-~KDV3A{`8YkLU+_}v}8;Xu`1aJ+tULM-vI7%hRDy4;X!EdjbgzbOO4J0v6K5W~q5 zQK{r&N)r2!2S9>x`KU$Ix604bTq6miq>)0lz=pU{05iEdR|3}kxk$deQ{SXBgCG!m z8U`8wmMP9LPajPLi=})`tpVxP(hLgdKjAxIek2@o2B)kPY8TZedE3e90h(^3x&^^1 z;PxvDq!xi<@*V}~GBNcnaP@U*HqH7|L+k*Q zu2c|C48R@(r-dNe4{cjPQrwkq#Q1f+Q|1$guE`qH9j;qgK)^m4)Y+EQ_BvL zWAE)qF-e-X^M~>CvO+-M&{$K2GMgaDPZ)$uE?b7HPD#mI^1Dgt4-e?mlMPjH8R+oA5PZ4VapY99HYH7 zB9o`8KE=#T(qq>fap=c$i$d2QOkU`~a#wVDzfYKbR=+K_(VI%`eAO2wtcY2a3~IoG z8&NK*TrYAg5Bd~71yM4jcJSuP8jRqF;`Ufil+Z1+^dNHgAi+<+{4T<~^#(-g#6nOo zhx0PBJJ{RRd8YLkJSnTl4#1?N>Z0tO`{=i#rp$~_! zPK^+b683L`(zc1|ng_9OYB9Zw-Xmdxgps+7f<1Pa$`^Wjq&1@}rv$#Tq5K72I#CwL zfq~N(S_n>Fkm}K|`S-R<)va3BKM-|d?!8M!VbVks_N_aFN0)$k(Nsz5MXhwb??+9` zBl{7I_XL)QRWk*+aVR2@s!7uP&$-2JKfA+%q^l#=JtbF)6aMWNF+l=7a{<|!DuD_j zs&o`afBE7YJO&1{&r=8B_VA%RG`|k-1?`THa5u+>PX~)>Sv!LHkx99oYt+)f_jJWX zM`FJu-scJhfGOGbt>^AmDmzUx<~&2iv}$Xl+G24K>|H2S#y;|b-^dtyjfGLgq}l51 zxxA~2ENv!Qje%wOY`vepzjjL;rYqgDgb+!rF{z^Ylg%9NhgkWf^6V^*ngj zGzWj1CP?QGC9Bbu*yleT;Dai_cUVrjHJB2}mBBND!m4LLnKf5=6L`gX>t8imF+jK% z|7MA4)qk>$ix;$+WeLGB6ilF@oy~gVNNX;@&j0?Q&(?`vc5kFyDEu{F;OAY(c%G8u zdjwb{BNiogW>;$77mHzW%3_I1$!lzu0E1Bv!tr!zCf=A1Rs)x+10M?Y8K+yv(Rw3I z+=#-E)m@@CXlSHwRIdr#*m?WlXwbXI?KQS z90%^NA7!LS-kkvJcx@7B5whMbn@qCn-@OKKYO<)#;vHYq>%CL)9R;q}^$)hf#Hn;z z>P~84O#zXdF>1>-HAsXKr+Rq)%f4*FNRK5u-B~8LophlqPb*;2I<6^`C`cSO+WnAz z%}J)Q>CxOO@{Hht_W_}~;T3F}sgP14e^cFzmKwFCZo=8!4JJfoje(orhmF42W4qdvqP-{VrS46ADf3t{W)ojwN?WqGF zv@%<_6kFywtfs*>oLA8n`8`rSl%FK6+i8>#HPY zB~9bL_;PWoIS|z4WHy-`7SDJ@>)<=4yvRYf@yIt2+b%^oF~91RfG;5W#fd*#yi1%0 zmA=WI)tM_oivNc7-W0|kInOE5rwunv!_T^JUwN_S3)KgcM#d4~SBF)nQ`fElBz3xe z9HVIPnh)_-zrt-1$+g*m)jqxjbL_wfbBW84IL0 z*1dgkkRhHZo{!fa|Ur+gh^*kWqgHghheo3Vmr1#n$o&=VqxzfVu*(}nP z({!{L3^b8(BQQTzrGm$kFf`04tm!GCPLl&>4)pJuo7C$Oo^bB!?09nVi_8|Wn04>f z)r{$-#_t-Ys=11_Qqe0-2CJQdSiKm`OCp^xw^@dZR!RcO78*^a1v-Z=3~%U7G;K}0 zP|MB&2h)o|J!LCTri$+|rG;YZ-_2&nWVp_U)#n21y}@o#A;cI|fR-fF@}t`8j-qYJ zX+(8!u%8LT=dQMi3b$kd+*lu~v|e_NZuqB)n~@=ND|bdsvI7rSF0Rq{Neh(?+luVM zYh9mb-h>#6KfAdo^t9_2Tj@y%26bhe)9v28&=Z_J_<D*hWQv;U&-=E*S1LBe@Lh770|tNet4DzWX>;czzTLX5&m(hbC+B!X0!v?RWgsr z4DsTe|8^+^4{$MgrWu;vC2O%Hm6Cj=tz%c28?=a6loMte3B$rB;8+;+>7n!T=z*04 zb2;o_Tv;IH2_-Ek*bD<{)IskmSD+N0Njqhsu;O##LcTXMI$@%RoI#3Htv9}8wDQM-BG^#0=~Etsw?{%o|^=T^@Lof3?3f!W(w)$5-~g7 zuUGfLho0aQ|B=UB_p&-H41+PMSFS=$CP8#pgJMbjK-O_PLql(8q5eie`>qtNEYnH3Tw+-yR#t}B)umV; zkrlnq!X*~0T9X>Mr~F6@hI*cpu*XQ~Qgo=aUvA{f6gC?<7b2CqG};f52tjfaIFS=r zzdm1b<_?-DcSsG-e!OY5BxK$L3x^jzm%%n$4?&7Jn4h0Qyc*ij%*sb1MQPa=HYQs6 zb)_xq%Akl?;;_ynltS&`f%SqgTjE<@Q=@UBX18E&F3hEsC?|$oBQ|HrY{E--VOzgR zL%~Yz;DBPlG**X&s^t|NC4@xaYfc?+h7)+7EQ#_^Ia+II3DgGl`}o!xic%gNT~L3U z+cBH)!_YX*Ae;Bz4%)KnYSNUqMsH2+T97r;FuTB4s%e)Ui(2YnEP8VM-0aRdMU8d2 z6sA5L^K?}(XjlRjuD?E^)RvXJ{i~7;f@zD8NZ@K_JwPuKD>17J`E|BO07a{TcNqcs zGt@8K%6^WNn=GTjHWOdmVbe7raqj}gBHjY#C`UeZ=4C5>I%%*(VOtt(9yS#G#eca{ zg2~9-vA)bXsVT2jNQPZKRYg>g;BGY@XETD-NIx--Q6s87#yV7slnwu4MD7b~H6?MtBQ5y&hWN~Ed$+^yc9!9K1zNhAd5C&A*YU z#uy^N-XK~fE=)x-hos;rSe9XNhXJ9xpgk{=zQ2J^MLiANr!6hr0Dn3hu?S7WAyp_b zg$DzZM_W4y6q7p zBs97tZsIaEVFGm6S)mhkTJfE9hlc{xUt(01n9V|K}a<*D7K1t|ZGM?vA}O&xTK%tBy` zFbZlv6^g>9)`@L9(7tM!pG;=~eb1$BLkuJNsjP2HnKltAspIck$%wC`+mhY`Nh;d` z#_2b`ULP)(5>+gb2}+bfD0=KjS91hq3%4yq%{;fr^Llpg^8oRVc9bJcb2%KA+_cUw zQ2D9eTA}(ogp+S3l_r|;13zD40}^S|xiN%Z+su&CChF?d=4EmGi^$Y$Wh*kBd8#JD zS^Bq1ch%%b_M3`JoS*Y*!=%6L={qqsJ5FjdW$PHJt?8(Rz`HYdOW$;q{SvfyuTV$6 zyArzzuM+%+r;P`E3+9QqQ##~H<~sowTsmEfkUAfYDv$>dX87_MhC+ZHL4u}R3V+q< zX&d0|?9x8nvq0mTBY(reI=y~TOCEx+3m8DI{}EwCj-zMd}|T0PW%vr5u> zyj<0WfWiu{6=6cyPMtgUhh}!&L-)2(mXhe|$)QKBOQ(TyX=08ADK0UV$>KyYpfSH> zZzS+vy*lEV+@ZpB3%2l3;!Dlyuv?61Qe2H)LywTp4 z4_Wd`1lTM>@awKX9)$6&CJ#wqioeyBJ&Qb=S&w2xh0Hbds#?Q9w`NK{_Y|wlNUhEL z#@?XNb(i@=ry+7_un?ve)@_%CdlwtbSL@zJ$JZZ5?g`te7XazlS*#-tWSI*_C?yTS zEUlUmJblkBh3UxSM@3ZVWz)JK$&I=#f}itrh>V*%kld4Rb;KAm4k;Z@1dVCwT2SF0 zmzY8{@wUk489%PIjz8UhA#OT61**GlmEWqA7^&!gzeTITDvHTRg^-HF#(u zo6xVi$Hj2+!7JTWy?Ie{R;l)*oK|Y)LRw(@b(4NnI&IOZ?WjSVqaKbsc8zVq zBi1JTi|Msj*$uC7uXenmpsP+=f}~$0cACgl*!o}xL}n{_|NSR3`R+?g*9m>Dr(Gw{ zy$KDm3YVLjD?)hiH^6@RIk`2hwYrMjv_Lj31uf+FZ)Ub@;#xxI0!ZZ8nZiu#>D>HE zDml5SF{mNBq3E<>^GXfa!b_!2`rlqS^Fhe?r_^Sk`)KssIfD8a9g}1Dq>TEJE0>qdo!}pcST1McnnE%e|+LA(u)nt^aVyX*_lb=qBK>uauS~4x zsunzo;wO~t5Ts?;K4#()Jy_!b4MHf$a;RK?6lL^rW;~U*)6enBl>*Np=m|*fa7P4T z3t?pFl3)8tx_U~ByJDTsxpN*my4p&^#5JMtZ;tzWcUee{<{bY#X)?<;3=PM1bu|gB zScTp0SC`cVl*Eq~A*wCzDqi~2+CM}5nYU3AgpM*uKi1<21|LLM%MKB4 zTeyZpDX?`ETMuQocV~bcipT42FdS>pn+)}qOZdDCBGyWVC4c{S!6dW3tP0}mdoRd3 z*~;^LwD#$;BafVvJ1B*RnE7Ro^%J2H6I2542YYA_E^(iCw1Ha_iv;JFRgBy2+J09R#3GZ?;;aJLYyr;?dELbAK5pt6$`sGW8vadGZ4cvscU zm(_)7aBFkHoK$9FHcVgPb|nf47p*!ua$-m|WC^ZQk{yrd)_O3ClTqWXhNYjrJiS29 zLK(KgqP_enU*qZ#w2QOih%^I;x?53A$Ha6P?CQcmHHD~AZCo&?dDX=8?q9(7?nC2= zYg0sA@L|UPfJIFgqmyRRjuYf>A^I>zu5{j?UvR>$TkV5ucLVPiT*uT6$9kd%Ni(dUvunFEw+d4)*tVUOj zpXzmxE7oIi^cWDcFJB2oZ7z7dN?dNH)IYU zl(v~X;H3*qfrg}$<#{75(N9)!$T}qsM6R$+x=36*;{+x17Kz=IVYO`XUmH=5A`_xv zvr$XW-Kqyk@KRG~|Hg<0<1vi&0a;U`>%A&j@tin~F28ucBbad`qKbCmOwWGYYE}ML ziP)s(ih{Gk>@-WA4lrP-+mGSZrBeR;Db0$H)qB=@#Cw$R2uHiKl7Vj08z=xbz|DMMj?no$X@V0m1po9-{4Z)+MH%veMIZ8NVhDsiS`=VqBNkD3y4R9bQy ziBgsppU1H6)wK@d@UP7lw(Y%kd`&wq^CCs$lgD4@m5#SI_3K4LLm*4MVpZ?|QXvBec`>=??Do0mN!qZt8LdE~7>m7qLeWHiW zI1|pqPA1mGw(aDJJ+bY{#I|iGPi&h{Y}>Z6^X}HZ|J~o#_NVR-S5?CT0>-(hzpZQ25Ns2pN^0E8BD-dZFIF>`d&~@J`1{f7e3-q!NI+VrJIcO^M5Y- z`bgqzwK{t%uqk9z@R&tx2^MGQ9p)vxdc3^dk~u0$K0NGZ7Fzu7uazy4!u)WO>5{1Y z`^-iGIxO((aB8%2g>S=Al_#vZ*zPVZe>9WaC0YI4sFKcN{LOo3`)hK{D2*dr+coP& z#vybc;V0}(kftg;g z>8xX9o>|}Imot^3(Aw)mB=erP!ctuR$yWVg6^A1;(}B;B-3);4Mf;XgJ~A%%)pI0V zVrt4A%5pRRYJ$^&NSH(auuR%PoF7lRr<|1IfigoIRjlU68%B+SI+zw7rA*1Wi2}^4 zDdD)A<;zc}4J8u2CUchKqPJvX`JeASS3s2l;2v6atWPCTm8*GeA8!Ea&p1o7BMyE> zXz@5j$K1_~E_Kw6QKt_7@;|K2s)Ym;TVlHT;xr!nq7Kr)Lk^nRd-`RBQla{- zg^FKj@l%zJ-uzK_d>98Ezp_9RQUpa522HD?3ppplCmWyk6}jsvAtHREAm)y>0&CxV zL>v^W)&5w+U5r2}kbAeoanK?kY&U!JF0iWB-`6a=OgsiZyenp}O zwjV6wsj;v|XFp$IW4?a`%solE?J~2_?)Z|;83mW?>=Gi;cMrF*2! zY9{jzo>$R6$8|oHYy=8D^htoGY{&lA-cdL_u%XyxnFm=vV$XTw~Twb-2gF8;O3TE=--Y@X?Ua!tC~ z0e)Gxa?KN8F(sh`C%`X{-cXaL?ReDo`M9M00k+5OS~A`7+ct$TcMtopdX+XpzI1=nmS$;T+!D<_ zei$4Bm85D@n-c$$*5|NZ0(%o+vxKhvW&`-I?~T3{*!i~Xv*%>?PovT`BifN zov9Pe9i__eLHT^0s9Ycs4AyrA0aJ+{k%)&d69&y!0!`ZuYZ1$cjv|wE9#{ zy7RM4f-w`IBTI3@;VlQ1r?IZVgZxx&k$k|6wiPJNwD=0WsjT8XN`*CPDc_jJw#Ung zwp(d}r_r(_fWu=Zr42Euqd;cY$qUG1S5>e_HrG;`BUx+SjjK7ZNV+2^wayAtpd$XDVUE zxi|v7EsmYms4ZbSxlYP+)*4UYM1Z|iP_v+>PXMk1~I#FPG9UI}g! z{JF*4E*s@qDeVM)vp;uO6(jOYUA*Ro?32Ya^oR2$u&AOw3)0gpnr?i8FNsfN!w4z+ z1Z(Ld6=b(c6*nT^RW@r3=E*HFNf;%*d15*0HH-brMXt1N-anlrVm^{Ge|yhjevgt@&`uuc0|D02 zG_8sO%lg1RCSo;e{8FVGdFLeGfE@_`s-4fmLwy^n?F(O1 zDUqeo0SsE)KJP;aI+fSn(kPMwXw%dO(T72qplq)H3@NT9Di1kUo~ME0n)EV$YTNG| zkqCvxUNEiVtf15_`iwG7tzYjYP@SHhX>v*vq2uwpi^*%9&D+Vu7h7t$axzQ5g=*q$ zKX{k(X#s}PU?UeNmsy{tS)>pXVX_>)RJ2TnEin$dMyF)IIz2*3MMvU81!*cHXB|o9 zj}#X$4CCDUj*T?Hh#r;V!-uj+Om^WI{^IT^yb!qbO9oGv5wRODdVcZ&!G4@uB=_h7n?IW#CqsK}t?L_g)k%-YJV#G^yG?of{5wq! zni-dSXm;3hyk0Tu4>zzW1tsArDL}ZCuGo?b4J9Zb_LbS`q(@WUzcxBd>Mc-8tIpGH zEz>5!(o_kylrc4mvS?RgL62}%OXG^33YX| z^R+wsS>8<6@01h$QvcV~=S3gg&71C6b9yTykF0;uq6brlj}Ngbx;a}w01~r8 zT~)(_*U@qx#heyTjO}SfG}IbRBpLk_J8g;K31o6|&=c;CUHS(YlO&pCk`v!Mc3hUM zi?px4Rxqn}kpywIj=J{ROJeMD?a&pz^?P0dkF{U#1t+IMODD0<{G`Oyhmc^BHX^Hx z=Hlq=xzQWELo@Gs1~BiarL@cQoL>89dI8-j(0IyKn-G%7<#;on-BQ(4-Gc z3Rf@;rQV1?9L|~HriIJMSe@2Tua?D0H}9-%d(zkCs;kI~sb&UuF5ta~eb0P+XjmLs zK4nrpeDN>K8LPwaqGrxOdMSrC>uRIO#C~K7hNSXp1!K~I4b>KKNi4f>akTWQ=g=4+ zSR>ij3oFS+$nP_gLX0X5u06O(?Evl;VUUC^BR0Pe6H1V&S} zv>EHeKRNzc2J^hjTIQ;%Tm0%rq)iI;4!-pPl+DT}@E)Y&1ZJn7Cy^*j>mQiYPdcka z64Z{u<%8raMG|h5&-Rf*`IE$fwqxxC@9 zkGwzj-2gmqx35DY8X9L1E`JMjq)Hr5@Q-J5kRa(l0j=ai1+x;WcIW zxEnGb+k*LhHNJL-n35?v4i_r2^@!(o;UYqPk{w2}=~d$x^CG*o@DGm2bG55(FVz(F znGmG=`gqHk;|U*XTQy*_|C`Z*azsF6-}b<@YEl*ne<>vqbWx_hPA&9lKZVco{`ZV` z0i%SkY0i7IPC9O$B?STIQ{y+!VR_saNtDW5c-PI1`pfVutw3q7BW7cKV(#AkF5?~A zV<#LZL-S^}kipHOw%YV2)yfBTlm?*dgj_SnbqU9FRq0r7d+WkkM@2_V+#2(kbCoI^ z#5=pV9>M~-cM*SOQhCOmb~J>6EQB@9YQbYfV5FlFW2ZLm1#aN79~HsJ&5NF@81~|)ZbNI_RFHWbAo5&(mAXLR^0C!D;Jr4Lm$@1ih3AEtcM11_H6TW=5Yv$Df)Fv$82#Gl6o zFo$f`)IVIQ0=uwPX%r+uGUneB77xcsm!XazJF-2n6za{wDvquD#%UC^r`B*uq&jpr zH4>Lp_i6v|;AGH|kMN>8w_((i8fAA^&&@agK!b5c6#?g*vvf=RrcG0EI^=42T|rVD zJl|u>PemLZ;nd*SFEX~~WjaL5Ko)HZkN{ARMHQJPa)cfMprtm&tbokvyJ=6x*R&=o z>0GkU5?EuD8FgItfv<&?LPhu@ElRbC>5XYa&6=SM%OxQ^!obr$+_mKLW3yGW5$4W> zdMVn$)%9+gB%X4UVy!*POxzp82Js90>#OoHosKB9!PF%oh*DMp`~%l30YQBa8RtQq zZubOUcRpqPzxT0ko{k-wQU|4|lyE+XO0KCcMDir1T2BRn+rnNgqOcG35x+gFcLWcp zQ7RJjg*YWMAztm3YAiC=L*^1RNH5IfOy^-n!@ks&*3QqBz45(O+kGxatyw{=r(!wW z;cH{F@j34873aS89mDD4W*Dk=k}F#_J|t^K^-ClhQCdGx;aw_uU3VQ?yC#;a>bHAi zR+G_sTJy3M*-^7YxmIPTEdv-u0Xf>W-gw^QFc_Q%1h@#NwI-idD}i|78;-H0R_&5y zn!VYheXH^+-^l}is$&Bvb2^8*Mb=_qF<-v^VB7Yd*ZgY092+CnH}kH_63@VOL22}= z>|l({c!w%aD~-p$^%oqwOpminR*QHhp-q&wA_dM>Z!iDgptV~~+kp1_1~buEvmn~S z{=%w}{_)m5diAS8__$>XDV)fI`?;WAS{rt}@i%~XLMS6IJ11K9hKL2SO`%rCFdYOk zUvgs&ovEV>(>`qCTYwxDoz~END4sR;v$BZ%jjd|+Wsl8*{JS`FbMI!H)z<-lSD~l` zC~~Ufh^-^ZE5S-5)aKvZOdEFvq`C)BAmp!2mp;(cC!oLGk+`igFId7rcJi?s2YJfU zzvbLBxsx$xC60Mh6*ajrK~}dbqp?)_!VV+dX@_U)5>V?CiyDK8)HoOH z&hrz=t}|bp3d2gf+^6NGURG9}zS~@u1NW}Tu&g{FUILOPEAZCcM_R0n#(|-MVK8PU z+86;AS#QW%{uos{XBK8ihY3K^>ry=X;7W@oIUFL*xnf{rHn)1RRpk8FUEB}Jt-rT< zRU=q2KyWA5mfP1`{0-#{Bg^bj?GnYn0Unw0*n-;ol?J0L9denWiDrPXyws7Yn+u0B z_pJ19jksQSd<>j2#zCz;jgl3Ei2{rtEL_#MZTuGlr7|Xwy{%?ts;3_6RgrJ=UIC(6 zDph0?Z9aM7C&hvlZSC?w%@SNO4SG}Cqf-F2k&%%g!Br z_rR3Kdql&~lX@}x77RQ<*`I9UpE)Ae5x+IDR--(y%W!OG*Ffgwc#X-rwFRNRrcbuG zX{2ZgTsN$N6U9%sf^-@Au?miUq(p_e$^B`wCCgVS@)!j0fx zKY8-^?vDk2esE1F?0a9N6qsKm7#|2{*{xGiC6|Sh7*cPQj6p6m|0;zK&KeUn`%}Hg zM=!yHYG1&9AsIL2K`2HXc*FUv*q!VQHx%lZ6I7*U-_Jn{$9yjm@}j6r*^nsMw0ZPy zT}vtB;>=5COWa%W3yt|G>T!4mwD@wE8{RjtI&M4{#tvA`1zg-gYf7pLQO?ERLEvzW z;oA+kkhg4gWX?<6%^k?Z+=WZX%>b94j%#pPC zUjvIX-t8%*_nKQR5j@bcIijN_)8aK>z0t-3g=_!uK{4f5OXzh@teE@68rwSas_oCY zd}A-hSM6I09=rX&>ICH6a>n=g)joVq^HStp%2U5vO?>%F6da|uy_x4KEH7rQak#K> zr^=}Z%?-(P%$j5BW>N){3fw`fAx3cXns;5q?g8EK-sq(7G zB>xyM*;KZ-KBVlipqd6J^=pm-d))stkUoxa@rf$?TRX)-{4qou48F#Bn*B|SM4bXM z;ytYgv1-MchvkFY{-LwK1g;N(G_0qqbY%nGj|vS2g0)qVr_~FRN>=W&0*LKn*+;lP z0}q+i6D*&N8H#67GzYO|qLwM=k%ibg6xBh%nXt1?Vtyqfg8Os9pQzqnic<7?XXkLQ zgwJ{|!Jl$mTU;DCqgW4vDG&MhsQU-D(w3o?v_{#bh>K-O03)kX=!81tX~hjS5!8?M z8(Wf`O3+5#q3hr9Id0>yJ7Oa_SU}HDYov2CxMssW!*5O~1dq;%KZD#7N8SnB`6S?E z7OPWZN;aq?Yn5OR%R4WeYEk)}fns#LD<9WtCnaW>x?hr=vvh6Ad&b(D=}Ji*7xp}} zLL|^j`a7~>>*QuDgZ1jFOY~b!xw?O}?HY zgz(t8s@-$(sJWlW5mU67`(DOA_Q61B$!~GoiAYmb*^pxFO>vtg*Z3`QxU2o*##*Zi zRZ-xIDHnr~e5YY6U+5DhG-~5_w;tC8=kfW}M7FP@W6xIaCs5#S3Z6Gi(@ZL>3gmHC zg3WhU{u6DOPo4Cz1-08^ebD;Dx7x!ef%OGQ_Vr!Gl+B8!8pR6QfNz3MBeKJT{pk zRZY5^6)#hoWFbY(t3JUjSNG`OMvZ3FObdB}C{;OOH3*-I@6mHy-oq+io+-eTj1wHZ z5_LmW@tdx?MD;S^1S=4Ks&KW9^Kkgc*S~v&x>sph$NBz6Lp~AWmNbgCpl7Ejc!ukY z`x~s% zTWSdT*knt!k|k2yDSsLO37p1t0;};QYj_nl&ZwddmR*T|t`_meXW`n1y(3l?AxXo6 zj74Y*iY{t`jd`uVCBCb1tE=~&+uGG%2L)q%5JJEY67A%DWNPtxaglNYTT5TAzPa zV|he`^45wQ!xM<^3oI;01)UTk6pIN7)M(k6^Y>ii_@55Cc`_pwtno|QrfHP%)GBvn zgEko$btXt~YRULe=7qoOhGqKdy_u?LrWU)O#5|I)MpP5ntq`+!Iz+XYous(%?ItX+ z!}MB_3(@=~9eErQgXpn>+imKqjAYRCFFGPTXY7{iOiOvJJf#bcx&klRWG)%%>C?v; zJp`=t&6;&aoY*9s8yWht-o;M{rk){uU`uWje~bwff9Lm2%}mcM-%Powk`I5uPJppG zr$5eCfAF>dhrs01g-}r)SFt(Z+|H^P9_mwS!t_)%N!B*{b?Ac(T4|KDS42HavqYlM z4-t)vFTB6jQ3;7{ev@ZewmT)qTw3EyjCPK^c_O@X6VL@&G_%;@@Pr3L-zM0ogiDoN z<$-11zFFImyD0S^)CJmXK#Lf{CE92P2@9|HcIV3dl`Gn}qSFpD=D{kXBXp9;M5a6} zvwFG>49i+biTiBKjtHw#Ob<4p*=H*qfQYH1Yk-#= z8b`s(|`xKo{)}pJq;f-ytV!6DkmnDp4QfB5;F!iGOSKkkr) z@y}))J4+6ME51hG+-?aGaBB%llHrL+7h!}=B977M=UJvy{*%u=J1NWL>WyA7acAD= zv$;iV_RaiXKCm67@-tgZwqYxIVTBKL%5XmQSlgNyu`72*AVC^f+u|_q;vA|m4r1B_ zoJlW=dMLk-9h>SNM4MKUdB_-p(WF5oS$8#HF7>f!;}FcYtIhs4Alxn(pmb~dv=>U2Ohca!Muyw&bx zt>4y;R7M<2lOOh$RYtAio@_F*%U4z10xF@~^9Cs?@OXdkVCRe5vRh0p+}gi*RYG<0 z9DT}9_M;UrygPPoVcSh@RX*ccXLLQZh$Pvr=ro-g_gd7SbT^`#R^a_%0bxYj`$p@> zX8cq_O>5$03J*XCCqiv}33)Q_@kFVLi!hIp0k?A^ z2omPgF;cwDD?Hv!M1``8Fz11PASI_U|k zk5o*xfg{Q;-Lh7hXsuR2UkKiZCOF*L5hhypMsO|}2T{r5$MsH9S)Il6XMaj^kxP1S+wIln4jqMjj*lYK2d6%>_!6-|?zfq}dB{YoL!tp^lOv7`b@eG6E*~wnEpX{H&B!ei1_yRr zrLdz_H_clR%@1R}ze-&d1pafib0K&@(8*XkfoB4jWAXTjYRC|MT=okB7~JP{`r#@S zWv&Nxb(-h21rv> z(76rLXP&cHO=Mo!l@;q9)2BxGLlviiuB%fn_^H28?rK%vNAARFhWpZyu5I?1e_sSI z=&cN)uATYnV~p}1n#gu+PSe}o#H8F+=_KWqOJt+nfJ8`>d4gBJHcCPd9B_n0L7ha} zC%!D@DQ+}3j2H5yGAcG--{961nAItC%f&OXRWdP9fVeNc>1~>osRV#}CTtNmD9tiU zqupP4v+SEy>nm=}U48{8NFHHIp2tI4KQ=zM=B*B^waz?=FQZN8rRts^2^Gz?EQalm zszk2FOm%i4;4=_vC=38m!61inwX3J_i#k@&lWv ztyH;2>z$H9VoIhQ_JiBvbjZs`sR3>gNVDw4#stUWSBH{r)&zrVYkt+^9w+70R0MGe z(^0+7lOD3kbh%BSWD!t4;yWS8>@*yea!`8zu42 z)e6e6m#aCr7~OOiU_qgvxCb6z`a*)7DN=^;;^C0A_5#K{_jJ{oO(k?rW6;fJUi)WS zx`);&o>fWS-_}#OTxQGzV{^@cq|r|`#~;gR1(HQ44^kL-Y7A5FaQ!zDGfI}--w z(iAmZh>=rWi$B1#7RZ44gnpYMv@z~mnlu{ymrUHOkAkP@sUjl2!*4(A>V#k7J7840 zd-&l(Z1?Sn&!=I9qKXe&?_0)QV`YOLG;XVOtGa!&_`C5jUJN7NKR778fl= zRfUG@ActS(#F7a^gvTCEQA9<*A!|CDpYmyl!W%Rq1&DT2R^RoFa}2dKZMIMfiQp`4 z^IYntIC$7n71`32JCSEm4KcxT{9zH%{&wt6*XWaU*W`YjeyQ-DvgM?T=G}aB=^pNs z!>0&)d#p$3qAU&LyF|~Wgvs%<2IMBc3mNOyWOcPb7_uTivNKxzoyGS?$fO*E2;sGY z+v@(P_W@;iO_V%UrTGFjNdBh=>s|Mcy$S6wQM?~8NnLk{cQ2QR@OP^u5Sxo zVtsz`ocL>aj@9%9+&!3Iif&ni@+*2vI#BtTeX*4tR9edk2|0RP-_ED?Jx)Uwr zH0^plPt3oa~A#`=QcT&NA((!%osD;y9ohBQi@hA)s@%cc|p1~ zOOYR`9J1dq72-ez``Fl1NZ6>3c9N0N30CXP#4be$>-}<}c9t#1yu+qiU}9b>TN-ox z4^p0xYRspFM^8NpkG+xwsWwarxA{QKwl7ymi}Dpzxdcd~M;lip`qQnH!^d-IWII{`dY@Pl3&l<^~&=7f6Q> zO2woHt7s?}FMF!9i6#QzIS<+%&GaD;01Y}rQzB_fW+A7@#e{S9jJC&I zUE!F~)Y2WKVm9nW?8vj_-j64DZQq8JyJ#f4SaHWHaVt~B!K7z6B+zD&(phw=&9FcW z|D(HVFiM!D;^5{fVS6YZe5io*oul;-tKCL!@mS!j^tLSQ!!1I$`RFLY6!*maz&I|} zvnrF?h6Ty1Jmmqv*tFGl3(=Ix55MkDSszev@#Gr5mAJ7g!+Y#q&>a^dZ=cKw-!%8; z2>u<0ZI?B{P&K|CKdj)oLPaG<=~W}k;33T%93ubJe-OqtO(4pht;l5=XA8CQIa4FP zNr8c`B)`pU5pFdxAQ-X1M`(u#)6gxcoe@A`P=`Pb-KA$N65}hQ7MIeR?CeNj9Ou=k zVxeM|aysi_R!7n1fDxyq#AuMrD<5E!Ra)AW5ynOPw#T*Hx=0YIf~6>44TMYzFt%q; z4%)9b2v|`t0oL|iQE@uHwmjRgMQS(#v!%w0)%I--*1vrd;S)Y)*+@mE0DG3FCBBW` zkrv)sfE;*c^;x$<)-$W01Nu&gI12@dOpGh4jlIhl`*#$6NaEE0Vm5dFe&K3WBB)in zsiDDjv4Z=(YPT#N)?IlPZ-;^RB+TsJBlk=h8#iH2Kv}lEkK4iR_98e$f^J-2pNTs2 zd`@ht3xXB{S01JkDolgc-i+qX9 zz=~Hg+cpxa=%u&m(&D&7A`y`vcU}A{N?}Ru_QOM)S{Om#6guyu=cJc!%xNe2>XN%# z2X+>^s1{#gMFa&>hXV=_nYz<(!?FDFc!3=?UY)e8U>}h-!Y<*Gz_t_3#Dlr7P7NiV zTirldUlJHp4;saThSG)B1f_oK2iHA6=s|`AQg`JrVzOApn*JeQ)!%7Eu?jRw-%~MK z)mO{n9-CoIVoS@Ys@Q6UL9+M_u2U)A=z$wFDBM_%!g1uWd*rmY%95RmK6`YTA(JcN zAp7jJ0&eI5v~YJWOem(PB(;1!+uf0=hY}EMIOPjRp+iqxEWo^W%^7~GA@ZrwSdg%i zMz^z1@3~0D45(N1ODJYL@p$HbgW8<>2ZqdtOwwn4fli!T^miXds#s$LFLXLX^YNeBVN2RbU zJ7e%+U{bj8{6@m$|4dgql5dm5KL>wMXC&Wx3jWCrD>lMFaWa(>hxUEaLSfrW8AZAU zh&6t@+RwlUT9MObZs6|gAJI_PvPdRgv}p=;s$by#fp0cxqiRF<7c?t<32%9E%UA8A zFPBIDy*{WRuN*-TDkrH*nzGLnmO4P=S?|S~0eAb)m|LW~>2h#c@f<5>L#3ccLD4d% z4V@azFore!r|{u=W4kzEC-hI)*63QR!bnzmB==U7!0oVkLsklT|E7GT=#Ejj5Be9D z3OFHlDKf2}J)tZ1-@4Fdvtq~1kN=%PI$VX0pwu6E+#XO zgP9m7I2bn-^{r%=p}UzU^)mBM-T$yFPq!j1n2Y0dJo2Y32IoyFA#F`2w(GeIQetWd zE%=j?mK=w$Lzsxxj6&^z-H8zK&OgXymS>Xg`$KK0p4U;ygN6f)y<7O*n!2 zgw&YuN?ylAMHa_6XX&lk8gzYTp>q7HOzn`Wc=QtQX&_u;9A{)VlT-T9zk9-A5ln-d zRW?HuxzfRg7(hGng*6?Q#$?>Ara4X?6S^#4x~Figa@!O9F&C|2J*UMBM=VmsXmKJK z|LGu)NI1K7M(2_#?x>W38PDT%04j`;R(7><(Ie1y6U0 zWJtjS6HZv-tt#-U^pl6=B?ldp)_96TO}f=?o7M$-O;(hW_X|($j!1dFO$mxnwWVS> z@OwX}t!)hC$Pg>f*&pWz~N52n@O+~Kh*KdLaViG0}txx5S z{;?8MZEGX-ul`(=7fM>UAcKqiIr0bf zcEssWZwIl=(~N2hhp8P-0aa~}D>tY=DTz&e%*&g~h*MwqT*(~l)x38=+hXaI&0|5r z;Htjj__nZajtwW5d&TO}HH;#df-s7fu#}&sPBH2YH!W#?FpW-3i`PSu)aJM~|K6s8 zCf`Q!+uTx4dmIfW0st)dSs(a$~! zN==_64pOGU`Q?hvu59y_!1s)YH~byp(TfZ)5vj+ZjNIAlgFoRZr#OsI>G!%I2ZRp} z2a*E}PzHQPVBurAhu5OU;^ofT@6MD7JX$V0Yvl( zc#Ke3u_a1KF4)|0ufQT{N@w1I{dTB343$4&m5n zaq}d=M<74*++D2spw~ERb?5b7K>F_feXN)`@R-id+QA0Gai90K4)=Hz#|yib?ow7? z7b!31Ygv0Voiw^V0Uu8aVOH)+49!dSbjA#PW>Md{U7RD!WB{6i*9~AtbsFugHj-V1+XsrCUqtc<3k} z8=1Q)+=Y_ltf*Tv?Xt9UIir7sN{Ll*h(EGk)kcRKo(Ji|9MWIc|3%BBtXk z(4F39O01vfFAVhi*M!5H)6g?poVav24(|`Veky>MwM%h_u~qmt~^rhuGkW7k~9Q?X^X9pelra8wkWHZ z-o_Fq4&8K_@sT}+UaIWgJe3w%%r8OwFsN6>-NqD70=b0xL%X*O&i;H&W=>j{h(-tY z%&_E9Q*jW*4y$iH_+-!=vZl&t0NI@K&ifCn4)g3x0%~Vn$6Gq_r4hY~19XCAh%i-2Z=0A5-U1ZN1;77+_4*_c}< zfER5VA8YV?-+(Abb*Tf*88w=&@=gDYh9v@8$&aV&0*;xiF9ZVGXC@{6ys271r|Bau zvZl4_JDD{X5=N$(358f#4)P7l6ce>mafAQ5<L)V`4o8s;Pb0zO_G z^9)+2^NaTHNtS5BPC)`5?LB|jA10-|J%@v8FATFxLVfV(_o~?czDL?Dp&SKU}A3)2n#)sFMG6KmoqK zU9p!XSK+~WvR^`QgtQzK?aNn8>Zf?bIrLnx*5n~;fN#5rtUEX$-$**|=j|u&L8dIS zazz>XZj~&|7quPQq!v8%`{1sy8Epre`hC|W{rUM`879De9Szy^x=V^>R*m+#f9X7I zzZn@8YhDx!`FXD9o2=<%?j)k(^X}?X)$jrO@x-f61=7T_7r>q7k~Bf~+s8o?gGM5c zXgY^Q!pX=41LYPibAjBip>knq0+`+Tr_0dq^NUnpoXGWOJd=bb1F5}eXq|I&6U7J7 z-!4Cs{W`N_sSG?cy;YrSFIxsK?WXNlffu%X%d4A%0XK4|RDzHsgy(QWu+>sW{lxq@ zUy|W~T2y&vKZX9$3c|%<(l!CLI0UWSfWPB%5boyylTRA7ux@D#5%rm1jaajtK&FLm za77t#2z1E*e`D;w-}C<*zW*F~|MAEE7yYmQ^#20Ez + + + + + diff --git a/sample-projects/book-of-acts/revisions.xml b/sample-projects/book-of-acts/revisions.xml new file mode 100644 index 00000000..4ee7b177 --- /dev/null +++ b/sample-projects/book-of-acts/revisions.xml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sample-projects/book-of-acts/settings.txt b/sample-projects/book-of-acts/settings.txt new file mode 100644 index 00000000..d02fd20d --- /dev/null +++ b/sample-projects/book-of-acts/settings.txt @@ -0,0 +1,79 @@ +{ + "autoSave": false, + "autoSaveDelay": 5, + "autoSaveNoChanges": true, + "autoSaveNoChangesDelay": 5, + "corkBackground": { + "color": "#926239", + "image": "/home/olivier/Dropbox/Documents/Travail/Geekeries/Python/PyCharmProjects/manuskript/resources/backgrounds/spacedreams.jpg" + }, + "corkSizeFactor": 84, + "defaultTextType": "txt", + "dict": "en_US", + "folderView": "text", + "frequencyAnalyzer": { + "phraseMax": 5, + "phraseMin": 2, + "wordExclude": "a, and, or", + "wordMin": 1 + }, + "fullScreenTheme": "gentleblues", + "lastTab": 4, + "openIndexes": [ + null + ], + "outlineViewColumns": [ + 0, + 5, + 8, + 9, + 11, + 12, + 13, + 7 + ], + "revisions": { + "keep": true, + "rules": { + "2592000": 86400, + "3600": 600, + "600": 60, + "86400": 3600 + }, + "smartremove": true + }, + "saveOnQuit": true, + "spellcheck": false, + "textEditor": { + "background": "#fff", + "font": "Oxygen-Sans,10,-1,5,50,0,0,0,0,0", + "fontColor": "#000", + "indent": true, + "lineSpacing": 100, + "misspelled": "#F00", + "spacingAbove": 5, + "spacingBelow": 5, + "tabWidth": 20 + }, + "viewSettings": { + "Cork": { + "Background": "Nothing", + "Border": "Nothing", + "Corner": "Label", + "Icon": "Nothing", + "Text": "Nothing" + }, + "Outline": { + "Background": "Nothing", + "Icon": "Nothing", + "Text": "Compile" + }, + "Tree": { + "Background": "Nothing", + "Icon": "Nothing", + "InfoFolder": "Summary", + "InfoText": "Nothing", + "Text": "Compile" + } + } +} \ No newline at end of file diff --git a/sample-projects/book-of-acts/status.txt b/sample-projects/book-of-acts/status.txt new file mode 100644 index 00000000..7feb740d --- /dev/null +++ b/sample-projects/book-of-acts/status.txt @@ -0,0 +1,4 @@ +TODO +First draft +Second draft +Final diff --git a/sample-projects/book-of-acts/summary.txt b/sample-projects/book-of-acts/summary.txt new file mode 100644 index 00000000..e69de29b diff --git a/sample-projects/book-of-acts/world.opml b/sample-projects/book-of-acts/world.opml new file mode 100644 index 00000000..b5fb6b50 --- /dev/null +++ b/sample-projects/book-of-acts/world.opml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + From e02c5fe6fec7a7821cf267a99c83fc19349a0098 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 10 Mar 2016 15:35:41 +0100 Subject: [PATCH 054/103] One more bug corrected --- manuskript/load_save/version_1.py | 2 +- manuskript/settings.py | 9 ++++++--- sample-projects/book-of-acts/settings.txt | 7 ++++--- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index e996927a..19f84e2b 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -49,7 +49,7 @@ characterMap = OrderedDict([ ]) # If true, logs infos while saving and loading. -LOG = True +LOG = False def formatMetaData(name, value, tabLength=10): diff --git a/manuskript/settings.py b/manuskript/settings.py index fc6065f0..cffff200 100644 --- a/manuskript/settings.py +++ b/manuskript/settings.py @@ -231,10 +231,13 @@ def load(string, fromString=False, protocol=None): for i in revisions["rules"]: if i == "null": r[None] = revisions["rules"]["null"] - continue + elif i == None: - continue - r[int(i)] = revisions["rules"][i] + r[None] = revisions["rules"][None] + + else: + r[int(i)] = revisions["rules"][i] + revisions["rules"] = r if "frequencyAnalyzer" in allSettings: diff --git a/sample-projects/book-of-acts/settings.txt b/sample-projects/book-of-acts/settings.txt index d02fd20d..1eca0915 100644 --- a/sample-projects/book-of-acts/settings.txt +++ b/sample-projects/book-of-acts/settings.txt @@ -10,7 +10,7 @@ "corkSizeFactor": 84, "defaultTextType": "txt", "dict": "en_US", - "folderView": "text", + "folderView": "cork", "frequencyAnalyzer": { "phraseMax": 5, "phraseMin": 2, @@ -18,7 +18,7 @@ "wordMin": 1 }, "fullScreenTheme": "gentleblues", - "lastTab": 4, + "lastTab": 6, "openIndexes": [ null ], @@ -38,7 +38,8 @@ "2592000": 86400, "3600": 600, "600": 60, - "86400": 3600 + "86400": 3600, + "null": 604800 }, "smartremove": true }, From 6f510130760299a96818b7875d0e39fd27c767af Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 10 Mar 2016 16:36:28 +0100 Subject: [PATCH 055/103] Updates codeclimate config file --- .codeclimate.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.codeclimate.yml b/.codeclimate.yml index 6549c314..8f316b85 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -23,3 +23,4 @@ ratings: - "**.rb" exclude_paths: - libs/**/* +- **/*_ui.py From 0cd7c08f1afba1c1b83dd784ea126c78018e806e Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Fri, 11 Mar 2016 14:56:16 +0100 Subject: [PATCH 056/103] Bug corrected --- manuskript/load_save/version_1.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index 19f84e2b..09473c7a 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -138,7 +138,12 @@ def saveProject(zip=None): ("Author", 6), ("Email", 7), ]: - val = mw.mdlFlatData.item(0, col).text().strip() + item = mw.mdlFlatData.item(0, col) + if item: + val = item.text().strip() + else: + val = "" + if val: content += "{name}:{spaces}{value}\n".format( name=name, @@ -160,7 +165,12 @@ def saveProject(zip=None): ("Page", 3), ("Full", 4), ]: - val = mw.mdlFlatData.item(1, col).text().strip() + item = mw.mdlFlatData.item(1, col) + if item: + val = item.text().strip() + else: + val = "" + if val: content += formatMetaData(name, val, 12) From 6e43756669e062b423dd6e372982966f387d2c9b Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Fri, 11 Mar 2016 15:05:40 +0100 Subject: [PATCH 057/103] Bug correction --- manuskript/ui/welcome.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manuskript/ui/welcome.py b/manuskript/ui/welcome.py index e1248dd1..ebc50a51 100644 --- a/manuskript/ui/welcome.py +++ b/manuskript/ui/welcome.py @@ -151,7 +151,7 @@ class welcome(QWidget, Ui_welcome): self.tr("Manuskript project (*.msk)"))[0] if filename: - if filename[:-4] != ".msk": + if filename[-4:] != ".msk": filename += ".msk" self.appendToRecentFiles(filename) self.loadDefaultDatas() From a27e5db6f51989bbd16029fcdd59bce44296052b Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Fri, 11 Mar 2016 15:45:51 +0100 Subject: [PATCH 058/103] Needs to do the Markdown syntax highlighter now... --- manuskript/loadSave.py | 4 ++++ manuskript/load_save/version_1.py | 2 +- manuskript/mainWindow.py | 8 ++++---- manuskript/models/outlineModel.py | 14 ++++++++++---- manuskript/settings.py | 2 +- manuskript/settingsWindow.py | 7 ++----- manuskript/ui/editors/MMDHighlighter.py | 16 ++++++++++++++++ manuskript/ui/editors/textFormat.py | 3 ++- manuskript/ui/views/cmbOutlineTypeChoser.py | 12 +++++------- manuskript/ui/views/textEditView.py | 11 ++++++++++- manuskript/ui/welcome.py | 13 ++++++++----- 11 files changed, 63 insertions(+), 29 deletions(-) create mode 100644 manuskript/ui/editors/MMDHighlighter.py diff --git a/manuskript/loadSave.py b/manuskript/loadSave.py index 69f2b229..a7b336ba 100644 --- a/manuskript/loadSave.py +++ b/manuskript/loadSave.py @@ -23,6 +23,10 @@ def saveProject(version=None): # FIXME: add settings to chose between saving as zip or not. +def clearSaveCache(): + v1.cache = {} + + def loadProject(project): # Detect version diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index 09473c7a..b1281c74 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -530,7 +530,7 @@ def exportOutlineItem(root): content = outlineToMMD(child) files.append((fpath, content)) - elif child.type() in ["txt", "t2t"]: + elif child.type() in ["txt", "t2t", "md"]: content = outlineToMMD(child) files.append((spath, content)) diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index a60f5613..ef817217 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -11,7 +11,7 @@ from PyQt5.QtWidgets import QMainWindow, QHeaderView, qApp, QMenu, QActionGroup, from manuskript import settings from manuskript.enums import Character, PlotStep, Plot, World from manuskript.functions import AUC, wordCount, appPath -from manuskript.loadSave import saveProject, loadProject +from manuskript import loadSave from manuskript.models.characterModel import characterModel from manuskript.models.outlineModel import outlineModel from manuskript.models.plotModel import plotModel @@ -375,8 +375,8 @@ class MainWindow(QMainWindow, Ui_MainWindow): # Clear datas self.loadEmptyDatas() - self.saveTimer.stop() + loadSave.clearSaveCache() # UI for i in [self.actSave, self.actSaveAs, self.actCloseProject, @@ -460,7 +460,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.currentProject = projectName QSettings().setValue("lastProject", projectName) - saveProject() # version=0 + loadSave.saveProject() # version=0 self.saveTimerNoChanges.stop() # Giving some feedback @@ -481,7 +481,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): def loadDatas(self, project): - errors = loadProject(project) + errors = loadSave.loadProject(project) # Giving some feedback if not errors: diff --git a/manuskript/models/outlineModel.py b/manuskript/models/outlineModel.py index 564c818b..b71c72f3 100644 --- a/manuskript/models/outlineModel.py +++ b/manuskript/models/outlineModel.py @@ -481,9 +481,9 @@ class outlineItem(): elif role == Qt.DecorationRole and column == Outline.title.value: if self.isFolder(): return QIcon.fromTheme("folder") - elif self.isText(): + elif self.isText: return QIcon.fromTheme("text-x-generic") - elif self.isT2T(): + elif self.isT2T() or self.isMD(): return QIcon.fromTheme("text-x-script") elif self.isHTML(): return QIcon.fromTheme("text-html") @@ -529,12 +529,12 @@ class outlineItem(): # Stuff to do before if column == Outline.type.value: oldType = self._data[Outline.type] - if oldType == "html" and data in ["txt", "t2t"]: + if oldType == "html" and data in ["txt", "t2t", "md"]: # Resource inneficient way to convert HTML to plain text e = QTextEdit() e.setHtml(self._data[Outline.text]) self._data[Outline.text] = e.toPlainText() - elif oldType in ["txt", "t2t"] and data == "html" and Outline.text in self._data: + elif oldType in ["txt", "t2t", "md"] and data == "html" and Outline.text in self._data: self._data[Outline.text] = self._data[Outline.text].replace("\n", "
    ") elif column == Outline.text.value: @@ -663,6 +663,12 @@ class outlineItem(): def isT2T(self): return self._data[Outline.type] == "t2t" + def isMD(self): + return self._data[Outline.type] == "md" + + def isMMD(self): + return self._data[Outline.type] == "md" + def isHTML(self): return self._data[Outline.type] == "html" diff --git a/manuskript/settings.py b/manuskript/settings.py index cffff200..8086eaef 100644 --- a/manuskript/settings.py +++ b/manuskript/settings.py @@ -52,7 +52,7 @@ corkBackground = { "color": "#926239", "image": "" } -defaultTextType = "t2t" +defaultTextType = "md" fullScreenTheme = "spacedreams" textEditor = { diff --git a/manuskript/settingsWindow.py b/manuskript/settingsWindow.py index 2b7a58ab..1a745a2b 100644 --- a/manuskript/settingsWindow.py +++ b/manuskript/settingsWindow.py @@ -18,6 +18,7 @@ from manuskript.ui.editors.themes import getThemeName from manuskript.ui.editors.themes import loadThemeDatas from manuskript.ui.settings_ui import Ui_Settings from manuskript.ui.views.textEditView import textEditView +from manuskript.ui.welcome import welcome try: import enchant @@ -59,11 +60,7 @@ class settingsWindow(QWidget, Ui_Settings): self.chkAutoLoad.setChecked(autoLoad) self.chkAutoLoad.stateChanged.connect(self.saveSettingsChanged) - dtt = [ - ("t2t", self.tr("Txt2Tags"), "text-x-script"), - ("html", self.tr("Rich Text (html)"), "text-html"), - ("txt", self.tr("Plain Text"), "text-x-generic"), - ] + dtt = welcome.defaultTextType() self.cmbDefaultTextType.clear() for t in dtt: self.cmbDefaultTextType.addItem(QIcon.fromTheme(t[2]), t[1], t[0]) diff --git a/manuskript/ui/editors/MMDHighlighter.py b/manuskript/ui/editors/MMDHighlighter.py new file mode 100644 index 00000000..023028da --- /dev/null +++ b/manuskript/ui/editors/MMDHighlighter.py @@ -0,0 +1,16 @@ +#!/usr/bin/python +# -*- coding: utf8 -*- +from manuskript.ui.editors.basicHighlighter import basicHighlighter + + +class MMDHighlighter(basicHighlighter): + + def __init__(self, editor, style="Default"): + basicHighlighter.__init__(self, editor) + + def highlightBlock(self, text): + basicHighlighter.highlightBlockBefore(self, text) + + # FIXME + + basicHighlighter.highlightBlockAfter(self, text) diff --git a/manuskript/ui/editors/textFormat.py b/manuskript/ui/editors/textFormat.py index a2eb0e4d..e09f6807 100644 --- a/manuskript/ui/editors/textFormat.py +++ b/manuskript/ui/editors/textFormat.py @@ -62,7 +62,8 @@ class textFormat(QWidget, Ui_textFormat): elif item.isText(): self.align.setVisible(False) self.format.setVisible(False) - elif item.isT2T(): + + elif item.isT2T() or item.isMD(): self.align.setVisible(False) def setFormat(self): diff --git a/manuskript/ui/views/cmbOutlineTypeChoser.py b/manuskript/ui/views/cmbOutlineTypeChoser.py index 829cf7ee..41842f18 100644 --- a/manuskript/ui/views/cmbOutlineTypeChoser.py +++ b/manuskript/ui/views/cmbOutlineTypeChoser.py @@ -5,6 +5,7 @@ from PyQt5.QtGui import QIcon, QBrush from PyQt5.QtWidgets import QComboBox from manuskript.enums import Outline +from manuskript.ui.welcome import welcome class cmbOutlineTypeChoser(QComboBox): @@ -24,11 +25,8 @@ class cmbOutlineTypeChoser(QComboBox): def updateItems(self): self.clear() - types = [ - ("t2t", self.tr("Txt2Tags"), "text-x-generic"), - ("html", self.tr("Rich Text (html)"), "text-html"), - ("txt", self.tr("Plain Text"), "text-x-generic"), - ] + types = welcome.defaultTextType() + for t in types: self.addItem(QIcon.fromTheme(t[2]), t[1], t[0]) @@ -43,7 +41,7 @@ class cmbOutlineTypeChoser(QComboBox): index = index.sibling(index.row(), self._column) self._index = index # Disabled if item type is not text - self.setEnabled(index.internalPointer().type() in ["t2t", "html", "txt"]) + self.setEnabled(index.internalPointer().type() in ["t2t", "html", "txt", "md"]) self.updateItems() self.updateSelectedItem() @@ -57,7 +55,7 @@ class cmbOutlineTypeChoser(QComboBox): if i.column() != self._column: i = i.sibling(i.row(), self._column) self._indexes.append(i) - if i.internalPointer().type() in ["t2t", "html", "txt"]: + if i.internalPointer().type() in ["t2t", "html", "txt", "md"]: hasText = True self.setEnabled(hasText) diff --git a/manuskript/ui/views/textEditView.py b/manuskript/ui/views/textEditView.py index 498bcf68..f853fcaa 100644 --- a/manuskript/ui/views/textEditView.py +++ b/manuskript/ui/views/textEditView.py @@ -11,6 +11,7 @@ from manuskript.enums import Outline from manuskript.functions import AUC from manuskript.functions import toString from manuskript.models.outlineModel import outlineModel +from manuskript.ui.editors.MMDHighlighter import MMDHighlighter from manuskript.ui.editors.basicHighlighter import basicHighlighter from manuskript.ui.editors.t2tFunctions import t2tClearFormat from manuskript.ui.editors.t2tFunctions import t2tFormatSelection @@ -160,6 +161,8 @@ class textEditView(QTextEdit): self._textFormat = "html" elif item.isT2T(): self._textFormat = "t2t" + elif item.isMD(): + self._textFormat = "md" else: self._textFormat = "text" @@ -174,8 +177,10 @@ class textEditView(QTextEdit): item = index.internalPointer() if self._column == Outline.text.value and not item.isT2T(): self.highlighter = basicHighlighter(self) - else: + elif item.isT2T(): self.highlighter = t2tHighlighter(self) + elif item.isMD(): + self.highlighter = MMDHighlighter(self) self.highlighter.setDefaultBlockFormat(self._defaultBlockFormat) @@ -551,3 +556,7 @@ class textEditView(QTextEdit): t2tFormatSelection(self, 2) elif _format == "Clear": t2tClearFormat(self) + + elif self._textFormat == "md": + # FIXME + print("Not implemented yet.") diff --git a/manuskript/ui/welcome.py b/manuskript/ui/welcome.py index ebc50a51..33314385 100644 --- a/manuskript/ui/welcome.py +++ b/manuskript/ui/welcome.py @@ -7,7 +7,8 @@ import os from PyQt5.QtCore import QSettings, QRegExp, Qt, QDir from PyQt5.QtGui import QIcon, QBrush, QColor, QStandardItemModel, QStandardItem -from PyQt5.QtWidgets import QWidget, QAction, QFileDialog, QSpinBox, QLineEdit, QLabel, QPushButton, QTreeWidgetItem +from PyQt5.QtWidgets import QWidget, QAction, QFileDialog, QSpinBox, QLineEdit, QLabel, QPushButton, QTreeWidgetItem, \ + qApp from manuskript import settings from manuskript.enums import Outline @@ -191,11 +192,13 @@ class welcome(QWidget, Ui_welcome): ]) ] - def defaultTextType(self): + @classmethod + def defaultTextType(cls): return [ - ("t2t", self.tr("Txt2Tags"), "text-x-generic"), - ("html", self.tr("Rich Text (html)"), "text-html"), - ("txt", self.tr("Plain Text"), "text-x-generic"), + ("t2t", qApp.translate("Welcome", "Txt2Tags"), "text-x-generic"), + ("html", qApp.translate("Welcome", "Rich Text (html)"), "text-html"), + ("txt", qApp.translate("Welcome", "Plain Text"), "text-x-generic"), + ("md", qApp.translate("Welcome", "Multi-Markdown"), "text-x-generic"), ] def changeTemplate(self, item, column): From b04c3a4445e6169da7885b193d53032fb6e384e4 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Mon, 14 Mar 2016 13:32:41 +0100 Subject: [PATCH 059/103] Adds: spanish translation (thanks to @vmpajares) --- i18n/manuskript.pro | 2 +- i18n/manuskript_es.qm | Bin 0 -> 23231 bytes i18n/manuskript_es.ts | 1669 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1670 insertions(+), 1 deletion(-) create mode 100644 i18n/manuskript_es.qm create mode 100644 i18n/manuskript_es.ts diff --git a/i18n/manuskript.pro b/i18n/manuskript.pro index 5ec79072..e8e8c6e5 100644 --- a/i18n/manuskript.pro +++ b/i18n/manuskript.pro @@ -55,4 +55,4 @@ SOURCES += ../manuskript/ui/views/plotTreeView.py SOURCES += ../manuskript/ui/views/plotDelegate.py TRANSLATIONS += manuskript_fr.ts - +TRANSLATIONS += manuskript_es.ts diff --git a/i18n/manuskript_es.qm b/i18n/manuskript_es.qm new file mode 100644 index 0000000000000000000000000000000000000000..8a53b807a03e0577469453c6c3cc7a967cd274aa GIT binary patch literal 23231 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 HP0jxSi}AV< literal 0 HcmV?d00001 diff --git a/i18n/manuskript_es.ts b/i18n/manuskript_es.ts new file mode 100644 index 00000000..48d629ab --- /dev/null +++ b/i18n/manuskript_es.ts @@ -0,0 +1,1669 @@ + + + + + MainWindow + + + + General + General + + + + Book infos + Informaciones de libros + + + + Title + Título + + + + Subtitle + Subtítulo + + + + Serie + Serie + + + + Volume + Volumen + + + + Genre + Género + + + + License + Licencia + + + + Author + Autor + + + + + + Name + Nombre + + + + Email + Email + + + + + + Summary + Resumen + + + + Situation: + Situación: + + + + + Summary: + Resumen: + + + + One sentance + Una frase + + + + + One paragraph + Un párrafo + + + + + One page + Una página + + + + + Full + Lleno + + + + + One sentance summary + Resumen de una frase + + + + + One paragraph summary + Resumen de un párrafo + + + + Expand each sentence of your one paragraph summary to a paragraph + En tu resumen de un párrafo expande cada frase en un párrafo + + + + One page summary + Resumen de una página + + + + Full summary + Resumen completo + + + + + + + + + + Next + Siguiente + + + + What if...? + Y si...? + + + + Characters + Personajes + + + + Names + Nombres + + + + + + + Filter + Filtro + + + + + Basic infos + Informaciónes básicas + + + + + Importance + Importancia + + + + Motivation + Motivación + + + + Goal + Meta + + + + Conflict + Conflicto + + + + Epiphany + Epifanía + + + + <html><head/><body><p align="right">One sentance<br/>summary</p></body></html> + <html><head/><body><p align="right">Resumen de<br/>una frase</p></body></html> + + + + <html><head/><body><p align="right">One paragraph<br/>summary</p></body></html> + <html><head/><body><p align="right">Resumen de<br/>un párrafo</p></body></html> + + + + Notes + Notas + + + + Detailed infos + Informaciones detalladas + + + + + + Plots + Tramas + + + + Plot + Trama + + + + Character(s) + Personaje(s) + + + + + Description + Descripción + + + + Result + Resultado + + + + Resolution steps + Pasos para la resolución + + + + + World + Mundo + + + + + Populates with empty data + Rellenas con datos vacíos + + + + More + Más + + + + Source of passion + Fuente de la pasión + + + + Source of conflict + Fuente del conflicto + + + + + + Outline + + + + + Redaction + Redacción + + + + Debug + Depurar + + + + FlatData + + + + + Persos + + + + + Labels + Etiquetas + + + + Fi&le + &Archivo + + + + &Recents + &Recientes + + + + &Mode + &Modo + + + + Help + Ayuda + + + + &Tools + &Herramientas + + + + E&dit + &Editar + + + + &View + &Ver + + + + &Cheat sheet + &Chuleta + + + + Sea&rch + &Buscar + + + + &Navigation + &Navegación + + + + &Open + &Abrir + + + + Ctrl+O + Ctrl+O + + + + &Save + &Guardar + + + + Ctrl+S + Ctrl+S + + + + Sa&ve as... + G&uardar Como... + + + + Ctrl+Shift+S + Ctrl+Shift+S + + + + &Quit + &Quitar + + + + Ctrl+Q + Ctrl+Q + + + + &Show help texts + &Ver textos de ayuda + + + + Ctrl+Shift+B + Ctrl+Shift+B + + + + &Spellcheck + &Corrector Ortográfico + + + + F9 + F9 + + + + &Labels... + &Etiquetas... + + + + &Status... + E&stado... + + + + Tree + Árbol + + + + &Normal + &Normal + + + + &Simple + &Simple + + + + &Fractal + &Fractal + + + + Index cards + Fichas + + + + S&ettings + &Preferencias + + + + F8 + F8 + + + + &Close project + &Cerrar proyecto + + + + Co&mpile + C&ompilar + + + + F6 + F6 + + + + &Frequency Analyzer + A&nalizador de frecuencia + + + + Settings + + + Settings + Preferencias + + + + General + General + + + + + Revisions + Revisiones + + + + Views + Vistas + + + + + Labels + Etiquetas + + + + + + Status + Estado + + + + + Fullscreen + Pantalla Completa + + + + General settings + Preferencias generales + + + + Application style + Estilo de la aplicación + + + + You might need to restart manuskript in order to avoid some visual issues. + Podrias necesitar reiniciar manuskript para evitar problemas visuales. + + + + Loading + Cargando + + + + Automatically load last project on startup + Cargar automáticamente el último proyecto al iniciar + + + + Saving + Guardando + + + + Automatically save every + Grabar automáticamente en + + + + minutes. + minutos. + + + + If no changes during + si no hay cambios durante + + + + seconds. + segundos. + + + + Save on quit + Guardar al salir + + + + Default text format + Formato de texto por defecto + + + + The format set by default when you create a new text item. You can change this on a per item basis. + El formato se indica por defecto cuando creas un nuevo elemento de texto. Puedes cambiar esta opción para cada elemento. + + + + Revisions are a way to keep track of modifications. For each text item, it stores any changes you make to the main text, allowing you to see and restoring previous versions. + Las revisiones son una manera de realizar un seguimiento de las modificaciones. Para cada elemento de texto, almacena cualquier cambio que hagas en el texto principal, permientiendote ver y restaurar versiones anteriores. + + + + Keep revisions + Alamacenar revisiones + + + + S&mart remove + Al&macenamiento inteligente + + + + Keep: + Almacenar: + + + + Smart remove allows you to keep only a certain number of revisions. It is strongly recommended to use it, lest you file will becomes full of thousands of insignificant changes. + El almacenamiento inteligente te permite almacenar solo un cierto número de revisiones. Se recomienda encarecidamente que lo uses, para que tu archivo no se llene de miles de cambios insignificantes. + + + + revisions per day for the last month + revisiones por día durante el último mes + + + + revisions per minute for the last 10 minutes + revisiones por minuto durante los últimos 10 minutos + + + + revisions per hour for the last day + revisiones por hora durante el último día + + + + revisions per 10 minutes for the last hour + revisiones cada 10 minutos durante la última hora + + + + revisions per week till the end of time + revisiones por semana hasta el fin de los tiempos + + + + Views settings + Preferencias de visualización + + + + Tree + Árbol + + + + + Colors + Colores + + + + + + Icon color: + Color del icono: + + + + + + + + + + + + + + + + Nothing + Ninguno + + + + + + + + + + + + + + + POV + + + + + + + + + + + + + + + + Label + Etiqueta + + + + + + + + + + + + + + Progress + Progreso + + + + + + + + + + + + + + + Compile + Compilar + + + + + + Text color: + Color del texto: + + + + + + Background color: + Color del fondo: + + + + Folders + Carpetas + + + + Show ite&m count + Ver número de ele&mentos + + + + + Show wordcount + Ver número de palabras + + + + + Show progress + Ver progreso + + + + + Show summary + Ver resumen + + + + Text + Texto + + + + Outline + + + + + Visible columns + Columnas visibles + + + + Goal + Meta + + + + Word count + Número de palabras + + + + Percentage + Porcentaje + + + + Title + Título + + + + Index cards + Fichas + + + + Item colors + Color de los elementos + + + + Border color: + Color del borde: + + + + Corner color: + Color de la esquina: + + + + Background + Fondo + + + + + + + + Color: + Color: + + + + + Ctrl+S + Ctrl+S + + + + + Image: + Imagen: + + + + Text editor + Editor de texto + + + + Font + Fuente + + + + Family: + Familia: + + + + + Size: + Tamaño: + + + + + Misspelled: + Errata: + + + + Background: + Fondo: + + + + Paragraphs + Párrafos + + + + + Line spacing: + Espaciado de linea: + + + + + Single + Sencilla + + + + + 1.5 lines + 1.5 lineas + + + + + Double + Doble + + + + + Proportional + Proporcional + + + + + + % + % + + + + + Tab width: + Anchura del tabulador: + + + + + + + + + + + + + px + px + + + + + Indent 1st line + Indentar la 1ª linea + + + + + Spacing: + Espaciado: + + + + New + Nuevo + + + + Edit + Editar + + + + Delete + Borrar + + + + Theme name: + Nombre del tema + + + + Apply + Aplicar + + + + Cancel + Cancelar + + + + Window Background + Fondo de la ventana + + + + Text Background + Fondo del texto + + + + Text Options + Opciones de texto + + + + Paragraph Options + Opciones de párrafo + + + + Type: + Tipo + + + + No Image + Sin Imagen + + + + Tiled + Tileado + + + + Centered + Centrado + + + + + Stretched + Ajustado + + + + Scaled + Escalado + + + + Zoomed + Agrandado + + + + Opacity: + Opacidad: + + + + Position: + Posición: + + + + Left + Izquierda + + + + Center + Centro + + + + Right + Derecha + + + + Width: + Anchura: + + + + Corner radius: + Radio de la esquina: + + + + Margins: + Margenes: + + + + Padding: + Bad translate? + Relleno: + + + + Font: + Fuente: + + + + basicItemView + + + Form + Formulario + + + + POV: + + + + + Goal: + Meta: + + + + Word count + Número de palabras + + + + One line summary + Resumen de una línea + + + + Few sentences summary: + Resumen de unas pocas frases: + + + + cheatSheet + + + Form + Formulario + + + + Filter (type the name of anything in your project) + Filtro (escribe el nombre de cualquier elemento de tu proyecto) + + + + compileDialog + + + Dialog + Dialogo + + + + Compile as: + Compilar como: + + + + Folder: + Carpeta: + + + + + ... + ... + + + + File name: + Nombre de archivo: + + + + Compile + Compilar + + + + Cancel + Cancelar + + + + editorWidget_ui + + + Form + + + + + locker + + + Form + Formulario + + + + Lock screen: + Bloquear pantalla: + + + + Word target + Objetivo de palabras + + + + Time target + Objetivo de tiempo + + + + words + palabras + + + + minutes + minutos + + + + Lock ! + ¡ Bloquear ! + + + + metadataView + + + Form + Formulario + + + + Properties + Propiedades + + + + Summary + Resumen + + + + One line summary + Resumen de una línea + + + + Full summary + Resumen completo + + + + + Notes / References + Noras / Referencias + + + + Revisions + Revisiones + + + + outlineBasics + + + New Folder + Nueva Carpeta + + + + New Text + Nuevo Texto + + + + Delete + Borrar + + + + Copy + Copiar + + + + Cut + Cortar + + + + Paste + Pegar + + + + Set POV + + + + + None + Ninguno + + + + Set Status + Establecer Estado + + + + Set Label + Establecer Etiqueta + + + + New + Nuevo + + + + outlineModel + + + {} words / {} ({}) + {} palabras / {} ({}) + + + + {} words + {} palabras + + + + propertiesView + + + Form + Formulario + + + + + POV + + + + + + Status + Estado + + + + + Label + Etiqueta + + + + + Compile + Compilar + + + + + Goal + Meta + + + + + Word count + Número de palabras + + + + Text type: + Tipo de texto: + + + + references + + + + Not a reference: {}. + No es una referencia: {}. + + + + + + Unknown reference: {}. + Referencia desconocida: {}. + + + + Path: + Ruta: + + + + Stats: + Características: + + + + POV: + + + + + Status: + Estado: + + + + Label: + Etiqueta: + + + + Short summary: + Resumen corto: + + + + Long summary: + Resumen largo: + + + + Notes: + Notas: + + + + Basic infos + Informaciones básicas + + + + Detailed infos + Informaciones detalladas + + + + POV of: + + + + + + + Go to {}. + Ir a {}. + + + + + Description + Descripción + + + + Result + Resultado + + + + Characters + Personajes + + + + Resolution steps + Pasos para la resolución + + + + Passion + Pasión + + + + Conflict + Conflicto + + + + Folder: <b>{}</b> + Carpeta: <b>{}</b> + + + + Text: <b>{}</b> + Texto: <b>{}</b> + + + + Character: <b>{}</b> + Personaje: <b>{}</b> + + + + Plot: <b>{}</b> + Trama: <b>{}</b> + + + + World: <b>{name}</b>{path} + Mundo: <b>{name}</b>{path} + + + + <b>Unknown reference:</b> {}. + <b>Referencia desconocida:</b> {}. + + + + Referenced in: + Referenciado en: + + + + revisions + + + Form + Formulario + + + + Options + Opciones + + + + Restore + Restaurar + + + + Delete + Borrar + + + + sldImportance + + + Form + Formulario + + + + TextLabel + EtiquetadeTexto + + + + welcome + + + Form + Formulario + + + + 1 + 1 + + + + Templates + Plantillas + + + + Empty + Vacía + + + + Novel + Novela + + + + Novella + Extracted from crosslanguage wikipedia + Novela Corta + + + + Short Story + Extracted from crosslanguage wikipedia + Cuento + + + + Research paper + Bad translation + Árticulo de investigación + + + + Demo projects + Proyectos de ejemplo + + + + Add level + Añadir nivel + + + + Add wordcount + Añadir cuenta de palabras + + + + Default text type: + Tipo de texto por defecto: + + + + Next time, automatically open last project + La proxima vez, abrir el último proyecto automáticamente + + + + Open... + Abrir... + + + + Recent + Reciente + + + + Create + Crear + + + From d0e01b9d45894d8e51261437185c6fad497991ce Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 15 Mar 2016 12:31:01 +0100 Subject: [PATCH 060/103] Updates spec file for PyInstaller --- manuskript.spec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manuskript.spec b/manuskript.spec index a6bb8a1d..7429eee8 100644 --- a/manuskript.spec +++ b/manuskript.spec @@ -4,7 +4,7 @@ block_cipher = None a = Analysis(['bin/manuskript'], - pathex=['/home/olivier/Dropbox/Documents/Travail/Geekeries/Python/PyCharmProjects/manuskript_pyinstaller tests'], + pathex=['.'], binaries=None, datas=[ ("icons", "icons"), @@ -12,7 +12,7 @@ a = Analysis(['bin/manuskript'], ("resources", "resources"), ("sample-projects", "sample-projects"), ], - hiddenimports=[], + hiddenimports=["xml.dom"], hookspath=[], runtime_hooks=[], excludes=[], From a33789c2b437f3082db79c99c7da58a1bd4e2ee2 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 16 Mar 2016 12:51:30 +0100 Subject: [PATCH 061/103] Bug corrected: cannot launch if pyenchant is installed but locale dictionnary is not present --- manuskript/ui/views/textEditView.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/manuskript/ui/views/textEditView.py b/manuskript/ui/views/textEditView.py index 498bcf68..e1979dcc 100644 --- a/manuskript/ui/views/textEditView.py +++ b/manuskript/ui/views/textEditView.py @@ -76,7 +76,11 @@ class textEditView(QTextEdit): # Spellchecking if enchant and self.spellcheck: - self._dict = enchant.Dict(self.currentDict if self.currentDict else enchant.get_default_language()) + try: + self._dict = enchant.Dict(self.currentDict if self.currentDict else enchant.get_default_language()) + except enchant.errors.DictNotFoundError: + self.spellcheck = False + else: self.spellcheck = False From c5feb0fb6ff2b5da98b995d7cd17b6480116aab9 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 17 Mar 2016 11:12:19 +0100 Subject: [PATCH 062/103] Minor bug --- manuskript/ui/views/textEditView.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manuskript/ui/views/textEditView.py b/manuskript/ui/views/textEditView.py index f853fcaa..fcbfe123 100644 --- a/manuskript/ui/views/textEditView.py +++ b/manuskript/ui/views/textEditView.py @@ -175,7 +175,7 @@ class textEditView(QTextEdit): # Setting highlighter if self._highlighting: item = index.internalPointer() - if self._column == Outline.text.value and not item.isT2T(): + if self._column == Outline.text.value and not (item.isT2T() or item.isMD()): self.highlighter = basicHighlighter(self) elif item.isT2T(): self.highlighter = t2tHighlighter(self) From 7e60cc132853e473280ae2cf867a30676e6ce42d Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Fri, 18 Mar 2016 09:38:08 +0100 Subject: [PATCH 063/103] Corrects a minor bug in painting characterDelegate --- manuskript/ui/views/outlineDelegates.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/manuskript/ui/views/outlineDelegates.py b/manuskript/ui/views/outlineDelegates.py index d091070f..2e8c88d5 100644 --- a/manuskript/ui/views/outlineDelegates.py +++ b/manuskript/ui/views/outlineDelegates.py @@ -162,9 +162,6 @@ class outlineCharacterDelegate(QStyledItemDelegate): character = self.mdlCharacter.getCharacterByID(index.data()) if character: itemIndex = character.index(Character.name.value) - else: - # Character ID not found in character model. - return opt = QStyleOptionViewItem(option) self.initStyleOption(opt, itemIndex) From c92daf32d18653c907c87d60b2fd170dc2e2dd07 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Fri, 18 Mar 2016 16:31:24 +0100 Subject: [PATCH 064/103] Basic MMD highlighting --- manuskript/ui/editors/MMDHighlighter.py | 51 ++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/manuskript/ui/editors/MMDHighlighter.py b/manuskript/ui/editors/MMDHighlighter.py index 023028da..2490a58c 100644 --- a/manuskript/ui/editors/MMDHighlighter.py +++ b/manuskript/ui/editors/MMDHighlighter.py @@ -1,16 +1,65 @@ #!/usr/bin/python # -*- coding: utf8 -*- +import re + +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QTextCharFormat, QFont, QTextCursor, QFontMetrics + from manuskript.ui.editors.basicHighlighter import basicHighlighter class MMDHighlighter(basicHighlighter): + MARKDOWN_REGEX = { + 'Bold': '(\*\*)(.+?)(\*\*)', + 'Bold2': '(__)(.+?)(__)', + 'Italic': '(\*)([^\*].+?[^\*])(\*)', + 'Italic2': '(_)([^_].+?[^_])(_)', + 'Title': '^(#+)(\s*)(.*)(#*)', + 'HTML': '<.+?>', + } + def __init__(self, editor, style="Default"): basicHighlighter.__init__(self, editor) + self.editor = editor + def highlightBlock(self, text): basicHighlighter.highlightBlockBefore(self, text) - # FIXME + self.doHighlightBlock(text) basicHighlighter.highlightBlockAfter(self, text) + + def doHighlightBlock(self, text): + + fop = QTextCharFormat() + fop.setForeground(Qt.lightGray) + fb = QTextCharFormat() + fb.setFontWeight(QFont.Bold) + fi = QTextCharFormat() + fi.setFontItalic(True) + + for name, style in [ + ("Italic", fi), + ("Italic2", fi), + ("Bold", fb), + ("Bold2", fb), + ]: + r = re.compile(self.MARKDOWN_REGEX[name]) + + for m in r.finditer(text): + self.setFormat(m.start(1), len(m.group(1)), fop) + self.setFormat(m.start(2), len(m.group(2)), style) + self.setFormat(m.start(3), len(m.group(3)), fop) + + fps = self._defaultCharFormat.font().pointSize() + + r = re.compile(self.MARKDOWN_REGEX["Title"]) + for m in r.finditer(text): + fop.setFontPointSize(fps + 12 - 2 * len(m.group(1))) + fb.setFontPointSize(fps + 12 - 2 * len(m.group(1))) + + self.setFormat(m.start(1), len(m.group(1)), fop) + self.setFormat(m.start(3), len(m.group(3)), fb) + self.setFormat(m.start(4), len(m.group(4)), fop) \ No newline at end of file From f5addd3985d779ae5bdc23fc66c87c1d320cf993 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 24 Mar 2016 10:50:13 +0100 Subject: [PATCH 065/103] Finishes quick-n-dirty MD syntax highlighter --- manuskript/ui/editors/MMDHighlighter.py | 107 +++++++++++++++++++----- 1 file changed, 86 insertions(+), 21 deletions(-) diff --git a/manuskript/ui/editors/MMDHighlighter.py b/manuskript/ui/editors/MMDHighlighter.py index 2490a58c..a6740f3d 100644 --- a/manuskript/ui/editors/MMDHighlighter.py +++ b/manuskript/ui/editors/MMDHighlighter.py @@ -17,6 +17,13 @@ class MMDHighlighter(basicHighlighter): 'Italic2': '(_)([^_].+?[^_])(_)', 'Title': '^(#+)(\s*)(.*)(#*)', 'HTML': '<.+?>', + 'Blockquotes': '^(> )+.*$', + 'OrderedList': '^\d+\.\s+', + 'UnorderedList': '^[\*\+-]\s+', + 'Code': '^\s{4,}.*$', + 'Links-inline': '(\[)(.*?)(\])(\()(.*?)(\))', + 'Links-ref': '(\[)(.*?)(\])\s?(\[)(.*?)(\])', + 'Links-ref2': '^\s{,3}(\[)(.*?)(\]:)\s+([^\s]*)\s*(.*?)*$', } def __init__(self, editor, style="Default"): @@ -24,6 +31,10 @@ class MMDHighlighter(basicHighlighter): self.editor = editor + self.rules = {} + for key in self.MARKDOWN_REGEX: + self.rules[key] = re.compile(self.MARKDOWN_REGEX[key]) + def highlightBlock(self, text): basicHighlighter.highlightBlockBefore(self, text) @@ -32,34 +43,88 @@ class MMDHighlighter(basicHighlighter): basicHighlighter.highlightBlockAfter(self, text) def doHighlightBlock(self, text): + """ + A quick-n-dirty very basic highlighter, that fails in most non-trivial cases. And is ugly. + """ - fop = QTextCharFormat() - fop.setForeground(Qt.lightGray) - fb = QTextCharFormat() - fb.setFontWeight(QFont.Bold) - fi = QTextCharFormat() - fi.setFontItalic(True) + # Creates textCharFormat + cfOperator = QTextCharFormat() + cfOperator.setForeground(Qt.lightGray) + cfBold = QTextCharFormat() + cfBold.setFontWeight(QFont.Bold) + cfItalic = QTextCharFormat() + cfItalic.setFontItalic(True) + # Titles (only atx-style, with #, not underlined) + defaultSize = self._defaultCharFormat.font().pointSize() + r = self.rules["Title"] + for m in r.finditer(text): + cfOperator.setFontPointSize(defaultSize + 12 - 2 * len(m.group(1))) + cfBold.setFontPointSize(defaultSize + 12 - 2 * len(m.group(1))) + + self.setFormat(m.start(1), len(m.group(1)), cfOperator) + self.setFormat(m.start(3), len(m.group(3)), cfBold) + self.setFormat(m.start(4), len(m.group(4)), cfOperator) + + # Code blocks + r = self.rules["Code"] + format = QTextCharFormat() + format.setForeground(Qt.darkGray) + format.setFontFixedPitch(True) + for m in r.finditer(text): + self.setFormat(m.start(), m.end() - m.start(), format) + + # Basic stuff + stuff = [ + ("Blockquotes", Qt.blue), + ("OrderedList", Qt.red), + ("UnorderedList", Qt.darkRed), + ("HTML", Qt.darkGreen), + ] + for name, color in stuff: + r = self.rules[name] + format = QTextCharFormat() + format.setForeground(color) + for m in r.finditer(text): + self.setFormat(m.start(), m.end() - m.start(), format) + + # Bold and Italic for name, style in [ - ("Italic", fi), - ("Italic2", fi), - ("Bold", fb), - ("Bold2", fb), + ("Italic", cfItalic), + ("Italic2", cfItalic), + ("Bold", cfBold), + ("Bold2", cfBold), ]: - r = re.compile(self.MARKDOWN_REGEX[name]) + r = self.rules[name] for m in r.finditer(text): - self.setFormat(m.start(1), len(m.group(1)), fop) + self.setFormat(m.start(1), len(m.group(1)), cfOperator) self.setFormat(m.start(2), len(m.group(2)), style) - self.setFormat(m.start(3), len(m.group(3)), fop) + self.setFormat(m.start(3), len(m.group(3)), cfOperator) - fps = self._defaultCharFormat.font().pointSize() + # Links + cfURL = QTextCharFormat() + cfURL.setForeground(Qt.darkGreen) + cfURL.setFontItalic(True) + cfText = QTextCharFormat() + cfText.setForeground(Qt.darkBlue) + cfIdentifier = QTextCharFormat() + cfIdentifier.setForeground(Qt.darkMagenta) - r = re.compile(self.MARKDOWN_REGEX["Title"]) + for type in ['Links-inline', 'Links-ref']: + r = self.rules[type] + for m in r.finditer(text): + self.setFormat(m.start(1), len(m.group(1)), cfOperator) + self.setFormat(m.start(2), len(m.group(2)), cfText) + self.setFormat(m.start(3), len(m.group(3)), cfOperator) + self.setFormat(m.start(4), len(m.group(4)), cfOperator) + self.setFormat(m.start(5), len(m.group(5)), cfURL if "inline" in type else cfIdentifier) + self.setFormat(m.start(6), len(m.group(6)), cfOperator) + + r = self.rules["Links-ref2"] for m in r.finditer(text): - fop.setFontPointSize(fps + 12 - 2 * len(m.group(1))) - fb.setFontPointSize(fps + 12 - 2 * len(m.group(1))) - - self.setFormat(m.start(1), len(m.group(1)), fop) - self.setFormat(m.start(3), len(m.group(3)), fb) - self.setFormat(m.start(4), len(m.group(4)), fop) \ No newline at end of file + self.setFormat(m.start(1), len(m.group(1)), cfOperator) + self.setFormat(m.start(2), len(m.group(2)), cfIdentifier) + self.setFormat(m.start(3), len(m.group(3)), cfOperator) + self.setFormat(m.start(4), len(m.group(4)), cfURL) + self.setFormat(m.start(5), len(m.group(5)), cfText) From ccaa302616c070a83e32a89fe49a179646bca2ea Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 24 Mar 2016 11:17:48 +0100 Subject: [PATCH 066/103] Little bit of cleaning --- manuskript/mainWindow.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index ef817217..13b4cd97 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -58,7 +58,6 @@ class MainWindow(QMainWindow, Ui_MainWindow): # Welcome self.welcome.updateValues() - # self.welcome.btnCreate.clicked.connect self.stack.setCurrentIndex(0) # Word count @@ -139,7 +138,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.treeOutlineOutline.delete() ############################################################################### - # PERSOS + # CHARACTERS ############################################################################### def changeCurrentCharacter(self, trash=None): @@ -278,11 +277,11 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.txtWorldName.setCurrentModelIndex(index) self.txtWorldDescription.setCurrentModelIndex(index) self.txtWorldPassion.setCurrentModelIndex(index) - self.txtWorldConflict.setCurrentModelIndex(index) - - ############################################################################### - # LOAD AND SAVE - ############################################################################### + # self.txtWorldConflict.setCurrentModelIndex(index) + # + # ############################################################################### + # # LOAD AND SAVE + # ############################################################################### def loadProject(self, project, loadFromFile=True): """Loads the project ``project``. @@ -500,7 +499,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): ############################################################################### def makeUIConnections(self): - "Connections that have to be made once only, event when new project is loaded." + "Connections that have to be made once only, even when a new project is loaded." self.lstCharacters.currentItemChanged.connect(self.changeCurrentCharacter, AUC) self.txtPlotFilter.textChanged.connect(self.lstPlots.setFilter, AUC) @@ -552,7 +551,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): widget.setColumn(col) widget.setCurrentModelIndex(self.mdlFlatData.index(0, col)) - # Persos + # Characters self.lstCharacters.setCharactersModel(self.mdlCharacter) self.tblPersoInfos.setModel(self.mdlCharacter) From 9f0c5fe527abe31103fa97eeead6166a3968cd56 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 24 Mar 2016 11:37:18 +0100 Subject: [PATCH 067/103] Display bug corrected in outline view --- manuskript/models/characterModel.py | 6 ++++++ manuskript/ui/views/outlineDelegates.py | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/manuskript/models/characterModel.py b/manuskript/models/characterModel.py index d6f7016c..d53457d5 100644 --- a/manuskript/models/characterModel.py +++ b/manuskript/models/characterModel.py @@ -42,6 +42,12 @@ class characterModel(QAbstractItemModel): else: return "" + elif role == Qt.DecorationRole: + if index.column() == C.name.value: + return c.icon + else: + return QVariant() + elif type(c) == CharacterInfo: if role == Qt.DisplayRole or role == Qt.EditRole: if index.column() == 0: diff --git a/manuskript/ui/views/outlineDelegates.py b/manuskript/ui/views/outlineDelegates.py index 2e8c88d5..818718dd 100644 --- a/manuskript/ui/views/outlineDelegates.py +++ b/manuskript/ui/views/outlineDelegates.py @@ -278,7 +278,7 @@ class outlineStatusDelegate(QStyledItemDelegate): def paint(self, painter, option, index): QStyledItemDelegate.paint(self, painter, option, index) - if index.isValid() and index.internalPointer().data(Outline.status.value) not in ["", None, "0"]: + if index.isValid() and index.internalPointer().data(Outline.status.value) not in ["", None, "0", 0]: opt = QStyleOptionComboBox() opt.rect = option.rect r = qApp.style().subControlRect(QStyle.CC_ComboBox, opt, QStyle.SC_ComboBoxArrow) @@ -344,7 +344,7 @@ class outlineLabelDelegate(QStyledItemDelegate): qApp.style().drawControl(QStyle.CE_ItemViewItem, opt, painter) # Drop down indicator - if index.isValid() and index.internalPointer().data(Outline.label.value) not in ["", None, "0"]: + if index.isValid() and index.internalPointer().data(Outline.label.value) not in ["", None, "0", 0]: opt = QStyleOptionComboBox() opt.rect = option.rect r = qApp.style().subControlRect(QStyle.CC_ComboBox, opt, QStyle.SC_ComboBoxArrow) From 7bb52c80a5aa57cedc76f19e25daa840c926e67f Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 24 Mar 2016 13:42:47 +0100 Subject: [PATCH 068/103] View mode working --- manuskript/functions.py | 45 +++++++++++++--- manuskript/mainWindow.py | 69 ++++++++++++++++++++++--- manuskript/settings.py | 11 +++- manuskript/settingsWindow.py | 10 ++-- manuskript/ui/collapsibleDockWidgets.py | 11 +++- manuskript/ui/mainWindow.py | 66 +++++++++++------------ manuskript/ui/mainWindow.ui | 50 ++++++++++-------- manuskript/ui/views/outlineView.py | 4 ++ 8 files changed, 189 insertions(+), 77 deletions(-) diff --git a/manuskript/functions.py b/manuskript/functions.py index fa8cffd8..948d9fc3 100644 --- a/manuskript/functions.py +++ b/manuskript/functions.py @@ -4,7 +4,7 @@ import os from random import * -from PyQt5.QtCore import Qt, QRect, QStandardPaths, QObject +from PyQt5.QtCore import Qt, QRect, QStandardPaths, QObject, QRegExp # Used to detect multiple connections from PyQt5.QtGui import QBrush, QIcon, QPainter @@ -18,27 +18,32 @@ from manuskript.enums import Outline AUC = Qt.AutoConnection | Qt.UniqueConnection MW = None + def wordCount(text): return len(text.strip().replace(" ", "\n").split("\n")) if text else 0 + def toInt(text): if text: return int(text) else: return 0 - + + def toFloat(text): if text: return float(text) else: return 0. - + + def toString(text): if text in [None, "None"]: return "" else: return str(text) - + + def drawProgress(painter, rect, progress, radius=0): painter.setPen(Qt.NoPen) painter.setBrush(QColor("#dddddd")) @@ -49,7 +54,8 @@ def drawProgress(painter, rect, progress, radius=0): r2 = QRect(rect) r2.setWidth(r2.width() * min(progress, 1)) painter.drawRoundedRect(r2, radius, radius) - + + def colorFromProgress(progress): progress = toFloat(progress) c1 = QColor(Qt.red) @@ -65,7 +71,8 @@ def colorFromProgress(progress): return c4 else: return c3 - + + def mainWindow(): global MW if not MW: @@ -77,6 +84,7 @@ def mainWindow(): else: return MW + def iconColor(icon): """Returns a QRgb from a QIcon, assuming its all the same color""" px = icon.pixmap(5, 5) @@ -85,14 +93,17 @@ def iconColor(icon): else: return QColor(Qt.transparent) + def iconFromColor(color): px = QPixmap(32, 32) px.fill(color) return QIcon(px) + def iconFromColorString(string): return iconFromColor(QColor(string)) + def randomColor(mix=None): """Generates a random color. If mix (QColor) is given, mixes the random color and mix.""" r = randint(0, 255) @@ -106,6 +117,7 @@ def randomColor(mix=None): return QColor(r, g, b) + def mixColors(col1, col2, f=.5): f2 = 1-f r = col1.red() * f + col2.red() * f2 @@ -113,6 +125,7 @@ def mixColors(col1, col2, f=.5): b = col1.blue() * f + col2.blue() * f2 return QColor(r, g, b) + def outlineItemColors(item): """Takes an OutlineItem and returns a dict of colors.""" colors = {} @@ -144,7 +157,8 @@ def outlineItemColors(item): colors["Compile"] = QColor(Qt.black) return colors - + + def colorifyPixmap(pixmap, color): # FIXME: ugly p = QPainter(pixmap) @@ -152,12 +166,14 @@ def colorifyPixmap(pixmap, color): p.fillRect(pixmap.rect(), color) return pixmap + def appPath(suffix=None): p = os.path.realpath(os.path.join(os.path.split(__file__)[0], "..")) if suffix: p = os.path.join(p, suffix) return p + def writablePath(suffix=None): if hasattr(QStandardPaths, "AppLocalDataLocation"): p = QStandardPaths.writableLocation(QStandardPaths.AppLocalDataLocation) @@ -170,6 +186,7 @@ def writablePath(suffix=None): os.makedirs(p) return p + def allPaths(suffix=None): paths = [] # src directory @@ -178,6 +195,7 @@ def allPaths(suffix=None): paths.append(writablePath(suffix)) return paths + def lightBlue(): """ A light blue used in several places in manuskript. @@ -185,8 +203,19 @@ def lightBlue(): """ return QColor(Qt.blue).lighter(190) + def totalObjects(): return len(mainWindow().findChildren(QObject)) + def printObjects(): - print("Objects:", str(totalObjects())) \ No newline at end of file + print("Objects:", str(totalObjects())) + + +def findWidgetsOfClass(cls): + """ + Returns all widgets, children of MainWindow, whose class is cls. + @param cls: a class + @return: list of QWidgets + """ + return mainWindow().findChildren(cls, QRegExp()) diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index 13b4cd97..f92c73eb 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -9,8 +9,8 @@ from PyQt5.QtWidgets import QMainWindow, QHeaderView, qApp, QMenu, QActionGroup, QLabel from manuskript import settings -from manuskript.enums import Character, PlotStep, Plot, World -from manuskript.functions import AUC, wordCount, appPath +from manuskript.enums import Character, PlotStep, Plot, World, Outline +from manuskript.functions import AUC, wordCount, appPath, findWidgetsOfClass from manuskript import loadSave from manuskript.models.characterModel import characterModel from manuskript.models.outlineModel import outlineModel @@ -93,8 +93,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): # Main Menu for i in [self.actSave, self.actSaveAs, self.actCloseProject, - self.menuEdit, self.menuMode, self.menuView, self.menuTools, - self.menuHelp]: + self.menuEdit, self.menuView, self.menuTools, self.menuHelp]: i.setEnabled(False) self.actOpen.triggered.connect(self.welcome.openFile) @@ -109,6 +108,14 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.actToolFrequency.triggered.connect(self.frequencyAnalyzer) self.generateViewMenu() + self.actModeGroup = QActionGroup(self) + self.actModeSimple.setActionGroup(self.actModeGroup) + self.actModeFiction.setActionGroup(self.actModeGroup) + self.actModeSnowflake.setActionGroup(self.actModeGroup) + self.actModeSimple.triggered.connect(self.setViewModeSimple) + self.actModeFiction.triggered.connect(self.setViewModeFiction) + self.actModeSnowflake.setEnabled(False) + self.makeUIConnections() # self.loadProject(os.path.join(appPath(), "test_project.zip")) @@ -322,6 +329,10 @@ class MainWindow(QMainWindow, Ui_MainWindow): # We force to emit even if it opens on the current tab self.tabMain.currentChanged.emit(settings.lastTab) self.mainEditor.updateCorkBackground() + if settings.viewMode == "simple": + self.setViewModeSimple() + else: + self.setViewModeFiction() # Set autosave self.saveTimer = QTimer() @@ -349,8 +360,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): # UI for i in [self.actSave, self.actSaveAs, self.actCloseProject, - self.menuEdit, self.menuMode, self.menuView, self.menuTools, - self.menuHelp]: + self.menuEdit, self.menuView, self.menuTools, self.menuHelp]: i.setEnabled(True) # FIXME: set Window's name: project name @@ -379,8 +389,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): # UI for i in [self.actSave, self.actSaveAs, self.actCloseProject, - self.menuEdit, self.menuMode, self.menuView, self.menuTools, - self.menuHelp]: + self.menuEdit, self.menuView, self.menuTools, self.menuHelp]: i.setEnabled(False) # Reload recent files @@ -979,6 +988,8 @@ class MainWindow(QMainWindow, Ui_MainWindow): } self.menuView.clear() + self.menuView.addMenu(self.menuMode) + self.menuView.addSeparator() # print("Generating menus with", settings.viewSettings) @@ -1014,6 +1025,48 @@ class MainWindow(QMainWindow, Ui_MainWindow): if item == "Tree": self.treeRedacOutline.viewport().update() + ############################################################################### + # VIEW MODES + ############################################################################### + + def setViewModeSimple(self): + settings.viewMode = "simple" + self.tabMain.setCurrentIndex(self.TabRedac) + self.viewModeFictionVisibilitySwitch(False) + self.actModeSimple.setChecked(True) + + def setViewModeFiction(self): + settings.viewMode = "fiction" + self.viewModeFictionVisibilitySwitch(True) + self.actModeFiction.setChecked(True) + + def viewModeFictionVisibilitySwitch(self, val): + """ + Swtiches the visibility of some UI components useful for fiction only + @param val: sets visibility to val + """ + + # Menu navigation & boutton in toolbar + self.toolbar.setDockVisibility(self.dckNavigation, val) + + # POV in metadatas + from manuskript.ui.views.propertiesView import propertiesView + for w in findWidgetsOfClass(propertiesView): + w.lblPOV.setVisible(val) + w.cmbPOV.setVisible(val) + + # POV in outline view + if Outline.POV.value in settings.outlineViewColumns: + settings.outlineViewColumns.remove(Outline.POV.value) + + from manuskript.ui.views.outlineView import outlineView + for w in findWidgetsOfClass(outlineView): + w.hideColumns() + + # TODO: clean up all other fiction things in non-fiction view mode + # Character in search widget + # POV in settings / views + ############################################################################### # COMPILE ############################################################################### diff --git a/manuskript/settings.py b/manuskript/settings.py index 8086eaef..9bff1208 100644 --- a/manuskript/settings.py +++ b/manuskript/settings.py @@ -85,12 +85,14 @@ frequencyAnalyzer = { "phraseMin": 2, "phraseMax": 5 } + +viewMode = "fiction" def save(filename=None, protocol=None): global spellcheck, dict, corkSliderFactor, viewSettings, corkSizeFactor, folderView, lastTab, openIndexes, \ autoSave, autoSaveDelay, saveOnQuit, autoSaveNoChanges, autoSaveNoChangesDelay, outlineViewColumns, \ - corkBackground, fullScreenTheme, defaultTextType, textEditor, revisions, frequencyAnalyzer + corkBackground, fullScreenTheme, defaultTextType, textEditor, revisions, frequencyAnalyzer, viewMode allSettings = { "viewSettings": viewSettings, @@ -111,7 +113,8 @@ def save(filename=None, protocol=None): "defaultTextType":defaultTextType, "textEditor":textEditor, "revisions":revisions, - "frequencyAnalyzer": frequencyAnalyzer + "frequencyAnalyzer": frequencyAnalyzer, + "viewMode": viewMode, } #pp=pprint.PrettyPrinter(indent=4, compact=False) @@ -244,3 +247,7 @@ def load(string, fromString=False, protocol=None): global frequencyAnalyzer frequencyAnalyzer = allSettings["frequencyAnalyzer"] + if "viewMode" in allSettings: + global viewMode + viewMode = allSettings["viewMode"] + diff --git a/manuskript/settingsWindow.py b/manuskript/settingsWindow.py index 1a745a2b..5e1bf928 100644 --- a/manuskript/settingsWindow.py +++ b/manuskript/settingsWindow.py @@ -11,12 +11,13 @@ from PyQt5.QtWidgets import qApp # Spell checker support from manuskript import settings from manuskript.enums import Outline -from manuskript.functions import allPaths, iconColor, writablePath, appPath +from manuskript.functions import allPaths, iconColor, writablePath, appPath, findWidgetsOfClass from manuskript.functions import mainWindow from manuskript.ui.editors.themes import createThemePreview from manuskript.ui.editors.themes import getThemeName from manuskript.ui.editors.themes import loadThemeDatas from manuskript.ui.settings_ui import Ui_Settings +from manuskript.ui.views.outlineView import outlineView from manuskript.ui.views.textEditView import textEditView from manuskript.ui.welcome import welcome @@ -99,6 +100,8 @@ class settingsWindow(QWidget, Ui_Settings): chk.setChecked(col in settings.outlineViewColumns) chk.stateChanged.connect(self.outlineColumnsChanged) + self.chkOutlinePOV.setVisible(settings.viewMode != "simple") # Hides checkbox if non-fiction view mode + for item, what, value in [ (self.rdoTreeItemCount, "InfoFolder", "Count"), (self.rdoTreeWC, "InfoFolder", "WC"), @@ -264,6 +267,7 @@ class settingsWindow(QWidget, Ui_Settings): item, part = self.viewSettingsDatas()[cmb] element = lst[cmb.currentIndex()] self.mw.setViewSettings(item, part, element) + self.mw.generateViewMenu() def outlineColumnsData(self): return { @@ -287,8 +291,8 @@ class settingsWindow(QWidget, Ui_Settings): settings.outlineViewColumns.remove(col) # Update views - self.mw.redacEditor.outlineView.hideColumns() - self.mw.treePlanOutline.hideColumns() + for w in findWidgetsOfClass(outlineView): + w.hideColumns() def treeViewSettignsChanged(self): for item, what, value in [ diff --git a/manuskript/ui/collapsibleDockWidgets.py b/manuskript/ui/collapsibleDockWidgets.py index 5afebf0d..9562b2bd 100644 --- a/manuskript/ui/collapsibleDockWidgets.py +++ b/manuskript/ui/collapsibleDockWidgets.py @@ -2,7 +2,7 @@ # --!-- coding: utf8 --!-- from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QToolBar, QDockWidget, QAction, QToolButton, QSizePolicy, QStylePainter, \ - QStyleOptionButton, QStyle, QLabel + QStyleOptionButton, QStyle class collapsibleDockWidgets(QToolBar): @@ -31,6 +31,8 @@ class collapsibleDockWidgets(QToolBar): # self.setAllowedAreas(self.TRANSPOSED_AREA[self._area]) self.parent().addToolBar(self.TRANSPOSED_AREA[self._area], self) + self._dockToButtonAction = {} + # Dock widgets for d in self._dockWidgets(): b = verticalButton(self) @@ -48,7 +50,8 @@ class collapsibleDockWidgets(QToolBar): background: lightBlue; } """) - self.addWidget(b) + a = self.addWidget(b) + self._dockToButtonAction[d] = a self.addSeparator() @@ -88,6 +91,10 @@ class collapsibleDockWidgets(QToolBar): else: action.setVisible(True) + def setDockVisibility(self, dock, val): + dock.setVisible(val) + self._dockToButtonAction[dock].setVisible(val) + def saveState(self): # We just need to save states of the custom widgets. state = [] diff --git a/manuskript/ui/mainWindow.py b/manuskript/ui/mainWindow.py index ed9ab078..1b2a6149 100644 --- a/manuskript/ui/mainWindow.py +++ b/manuskript/ui/mainWindow.py @@ -2,8 +2,7 @@ # Form implementation generated from reading ui file 'manuskript/ui/mainWindow.ui' # -# Created: Thu Mar 3 18:52:22 2016 -# by: PyQt5 UI code generator 5.2.1 +# Created by: PyQt5 UI code generator 5.4.2 # # WARNING! All changes made in this file will be lost! @@ -17,8 +16,8 @@ class Ui_MainWindow(object): self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.centralwidget) - self.horizontalLayout_2.setSpacing(0) self.horizontalLayout_2.setContentsMargins(0, 6, 0, 0) + self.horizontalLayout_2.setSpacing(0) self.horizontalLayout_2.setObjectName("horizontalLayout_2") self.stack = QtWidgets.QStackedWidget(self.centralwidget) self.stack.setObjectName("stack") @@ -809,7 +808,6 @@ class Ui_MainWindow(object): self.layoutWidget = QtWidgets.QWidget(self.splitterOutlineH) self.layoutWidget.setObjectName("layoutWidget") self.verticalLayout_14 = QtWidgets.QVBoxLayout(self.layoutWidget) - self.verticalLayout_14.setContentsMargins(0, 0, 0, 0) self.verticalLayout_14.setObjectName("verticalLayout_14") self.splitterOutlineV = QtWidgets.QSplitter(self.layoutWidget) self.splitterOutlineV.setOrientation(QtCore.Qt.Vertical) @@ -996,7 +994,7 @@ class Ui_MainWindow(object): self.horizontalLayout_2.addWidget(self.stack) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) - self.menubar.setGeometry(QtCore.QRect(0, 0, 1112, 20)) + self.menubar.setGeometry(QtCore.QRect(0, 0, 1112, 30)) self.menubar.setObjectName("menubar") self.menuFile = QtWidgets.QMenu(self.menubar) self.menuFile.setObjectName("menuFile") @@ -1004,8 +1002,6 @@ class Ui_MainWindow(object): icon = QtGui.QIcon.fromTheme("folder-recent") self.menuRecents.setIcon(icon) self.menuRecents.setObjectName("menuRecents") - self.menuMode = QtWidgets.QMenu(self.menubar) - self.menuMode.setObjectName("menuMode") self.menuHelp = QtWidgets.QMenu(self.menubar) self.menuHelp.setObjectName("menuHelp") self.menuTools = QtWidgets.QMenu(self.menubar) @@ -1014,6 +1010,8 @@ class Ui_MainWindow(object): self.menuEdit.setObjectName("menuEdit") self.menuView = QtWidgets.QMenu(self.menubar) self.menuView.setObjectName("menuView") + self.menuMode = QtWidgets.QMenu(self.menuView) + self.menuMode.setObjectName("menuMode") MainWindow.setMenuBar(self.menubar) self.statusbar = QtWidgets.QStatusBar(MainWindow) self.statusbar.setObjectName("statusbar") @@ -1113,16 +1111,16 @@ class Ui_MainWindow(object): self.actStatus.setObjectName("actStatus") self.actViewTree = QtWidgets.QAction(MainWindow) self.actViewTree.setObjectName("actViewTree") - self.actModeNorma = QtWidgets.QAction(MainWindow) - self.actModeNorma.setCheckable(True) - self.actModeNorma.setChecked(True) - self.actModeNorma.setObjectName("actModeNorma") self.actModeSimple = QtWidgets.QAction(MainWindow) self.actModeSimple.setCheckable(True) + self.actModeSimple.setChecked(True) self.actModeSimple.setObjectName("actModeSimple") - self.actModeFractal = QtWidgets.QAction(MainWindow) - self.actModeFractal.setCheckable(True) - self.actModeFractal.setObjectName("actModeFractal") + self.actModeFiction = QtWidgets.QAction(MainWindow) + self.actModeFiction.setCheckable(True) + self.actModeFiction.setObjectName("actModeFiction") + self.actModeSnowflake = QtWidgets.QAction(MainWindow) + self.actModeSnowflake.setCheckable(True) + self.actModeSnowflake.setObjectName("actModeSnowflake") self.actViewCork = QtWidgets.QAction(MainWindow) self.actViewCork.setObjectName("actViewCork") self.actViewOutline = QtWidgets.QAction(MainWindow) @@ -1139,6 +1137,8 @@ class Ui_MainWindow(object): self.actCompile.setObjectName("actCompile") self.actToolFrequency = QtWidgets.QAction(MainWindow) self.actToolFrequency.setObjectName("actToolFrequency") + self.actionTest = QtWidgets.QAction(MainWindow) + self.actionTest.setObjectName("actionTest") self.menuFile.addAction(self.actOpen) self.menuFile.addAction(self.menuRecents.menuAction()) self.menuFile.addAction(self.actSave) @@ -1148,25 +1148,26 @@ class Ui_MainWindow(object): self.menuFile.addAction(self.actCompile) self.menuFile.addSeparator() self.menuFile.addAction(self.actQuit) - self.menuMode.addAction(self.actModeNorma) - self.menuMode.addAction(self.actModeSimple) - self.menuMode.addAction(self.actModeFractal) self.menuHelp.addAction(self.actShowHelp) self.menuTools.addAction(self.actSpellcheck) self.menuTools.addAction(self.actToolFrequency) self.menuEdit.addAction(self.actLabels) self.menuEdit.addAction(self.actStatus) self.menuEdit.addAction(self.actSettings) + self.menuMode.addAction(self.actModeSimple) + self.menuMode.addAction(self.actModeFiction) + self.menuMode.addAction(self.actModeSnowflake) + self.menuView.addAction(self.menuMode.menuAction()) + self.menuView.addSeparator() self.menubar.addAction(self.menuFile.menuAction()) self.menubar.addAction(self.menuEdit.menuAction()) - self.menubar.addAction(self.menuMode.menuAction()) self.menubar.addAction(self.menuView.menuAction()) self.menubar.addAction(self.menuTools.menuAction()) self.menubar.addAction(self.menuHelp.menuAction()) self.retranslateUi(MainWindow) self.stack.setCurrentIndex(1) - self.tabMain.setCurrentIndex(2) + self.tabMain.setCurrentIndex(4) self.tabSummary.setCurrentIndex(0) self.tabPersos.setCurrentIndex(0) self.tabPlot.setCurrentIndex(0) @@ -1268,11 +1269,11 @@ class Ui_MainWindow(object): self.tabMain.setTabText(self.tabMain.indexOf(self.lytTabDebug), _translate("MainWindow", "Debug")) self.menuFile.setTitle(_translate("MainWindow", "Fi&le")) self.menuRecents.setTitle(_translate("MainWindow", "&Recents")) - self.menuMode.setTitle(_translate("MainWindow", "&Mode")) self.menuHelp.setTitle(_translate("MainWindow", "Help")) self.menuTools.setTitle(_translate("MainWindow", "&Tools")) self.menuEdit.setTitle(_translate("MainWindow", "E&dit")) self.menuView.setTitle(_translate("MainWindow", "&View")) + self.menuMode.setTitle(_translate("MainWindow", "&Mode")) self.dckCheatSheet.setWindowTitle(_translate("MainWindow", "&Cheat sheet")) self.dckSearch.setWindowTitle(_translate("MainWindow", "Sea&rch")) self.dckNavigation.setWindowTitle(_translate("MainWindow", "&Navigation")) @@ -1291,9 +1292,9 @@ class Ui_MainWindow(object): self.actLabels.setText(_translate("MainWindow", "&Labels...")) self.actStatus.setText(_translate("MainWindow", "&Status...")) self.actViewTree.setText(_translate("MainWindow", "Tree")) - self.actModeNorma.setText(_translate("MainWindow", "&Normal")) self.actModeSimple.setText(_translate("MainWindow", "&Simple")) - self.actModeFractal.setText(_translate("MainWindow", "&Fractal")) + self.actModeFiction.setText(_translate("MainWindow", "&Fiction")) + self.actModeSnowflake.setText(_translate("MainWindow", "&Snowflake")) self.actViewCork.setText(_translate("MainWindow", "Index cards")) self.actViewOutline.setText(_translate("MainWindow", "Outline")) self.actSettings.setText(_translate("MainWindow", "S&ettings")) @@ -1302,19 +1303,20 @@ class Ui_MainWindow(object): self.actCompile.setText(_translate("MainWindow", "Co&mpile")) self.actCompile.setShortcut(_translate("MainWindow", "F6")) self.actToolFrequency.setText(_translate("MainWindow", "&Frequency Analyzer")) + self.actionTest.setText(_translate("MainWindow", "test")) -from manuskript.ui.views.outlineView import outlineView -from manuskript.ui.views.textEditView import textEditView -from manuskript.ui.views.basicItemView import basicItemView -from manuskript.ui.views.plotTreeView import plotTreeView from manuskript.ui.cheatSheet import cheatSheet -from manuskript.ui.views.sldImportance import sldImportance -from manuskript.ui.views.metadataView import metadataView -from manuskript.ui.views.characterTreeView import characterTreeView from manuskript.ui.editors.mainEditor import mainEditor from manuskript.ui.search import search +from manuskript.ui.views.basicItemView import basicItemView +from manuskript.ui.views.characterTreeView import characterTreeView from manuskript.ui.views.lineEditView import lineEditView -from manuskript.ui.welcome import welcome -from manuskript.ui.views.treeView import treeView -from manuskript.ui.views.textEditCompleter import textEditCompleter +from manuskript.ui.views.metadataView import metadataView +from manuskript.ui.views.outlineView import outlineView +from manuskript.ui.views.plotTreeView import plotTreeView +from manuskript.ui.views.sldImportance import sldImportance from manuskript.ui.views.storylineView import storylineView +from manuskript.ui.views.textEditCompleter import textEditCompleter +from manuskript.ui.views.textEditView import textEditView +from manuskript.ui.views.treeView import treeView +from manuskript.ui.welcome import welcome diff --git a/manuskript/ui/mainWindow.ui b/manuskript/ui/mainWindow.ui index 9f9e7c6e..f673905c 100644 --- a/manuskript/ui/mainWindow.ui +++ b/manuskript/ui/mainWindow.ui @@ -124,7 +124,7 @@ QTabWidget::Rounded - 2 + 4 true @@ -1979,7 +1979,7 @@ 0 0 1112 - 20 + 30 @@ -2006,14 +2006,6 @@ - - - &Mode - - - - - Help @@ -2039,10 +2031,19 @@ &View + + + &Mode + + + + + + + - @@ -2266,31 +2267,31 @@ QListView::item:hover { Tree - + true true - - &Normal - - - - - true - &Simple - + true - &Fractal + &Fiction + + + + + true + + + &Snowflake @@ -2338,6 +2339,11 @@ QListView::item:hover { &Frequency Analyzer + + + test + +
    diff --git a/manuskript/ui/views/outlineView.py b/manuskript/ui/views/outlineView.py index 50e44ecc..8c4d4bc1 100644 --- a/manuskript/ui/views/outlineView.py +++ b/manuskript/ui/views/outlineView.py @@ -65,6 +65,10 @@ class outlineView(QTreeView, dndView, outlineBasics): self.header().setSectionResizeMode(Outline.goalPercentage.value, QHeaderView.ResizeToContents) def hideColumns(self): + if not self.model(): + # outlineView is probably not initialized, because editorWidgets shows index cards or text. + return + for c in range(self.model().columnCount()): self.hideColumn(c) for c in settings.outlineViewColumns: From 694b30a6008ab1dc6b7ffd4b1fa745411f7156df Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 24 Mar 2016 14:17:26 +0100 Subject: [PATCH 069/103] Fullscreen theme list uses theme's propper name --- manuskript/functions.py | 17 +++++++++++++++++ manuskript/settingsWindow.py | 1 + manuskript/ui/editors/fullScreenEditor.py | 10 ++++++++-- manuskript/ui/editors/themes.py | 15 +-------------- manuskript/ui/views/corkView.py | 4 +++- 5 files changed, 30 insertions(+), 17 deletions(-) diff --git a/manuskript/functions.py b/manuskript/functions.py index 948d9fc3..28a3ddb9 100644 --- a/manuskript/functions.py +++ b/manuskript/functions.py @@ -2,6 +2,7 @@ #--!-- coding: utf8 --!-- import os +import re from random import * from PyQt5.QtCore import Qt, QRect, QStandardPaths, QObject, QRegExp @@ -219,3 +220,19 @@ def findWidgetsOfClass(cls): @return: list of QWidgets """ return mainWindow().findChildren(cls, QRegExp()) + + +def findBackground(filename): + """ + Returns the full path to a background file of name filename within ressource folders. + """ + return findFirstFile(re.escape(filename), "resources/backgrounds") + + +def findFirstFile(regex, path="resources"): + paths = allPaths(path) + for p in paths: + lst = os.listdir(p) + for l in lst: + if re.match(regex, l): + return os.path.join(p, l) \ No newline at end of file diff --git a/manuskript/settingsWindow.py b/manuskript/settingsWindow.py index 5e1bf928..b6ff6cf3 100644 --- a/manuskript/settingsWindow.py +++ b/manuskript/settingsWindow.py @@ -326,6 +326,7 @@ class settingsWindow(QWidget, Ui_Settings): def setCorkBackground(self, i): img = self.cmbCorkImage.itemData(i) + img = os.path.basename(img) if img: settings.corkBackground["image"] = img else: diff --git a/manuskript/ui/editors/fullScreenEditor.py b/manuskript/ui/editors/fullScreenEditor.py index 2a64368b..a02a70d9 100644 --- a/manuskript/ui/editors/fullScreenEditor.py +++ b/manuskript/ui/editors/fullScreenEditor.py @@ -94,8 +94,13 @@ class fullScreenEditor(QWidget): lst = [i for i in os.listdir(p) if os.path.splitext(i)[1] == ".theme"] for t in lst: themeIni = os.path.join(p, t) - self.lstThemes.addItem(os.path.splitext(t)[0]) - self.lstThemes.setCurrentText(settings.fullScreenTheme) + name = loadThemeDatas(themeIni)["Name"] + # self.lstThemes.addItem(os.path.splitext(t)[0]) + self.lstThemes.addItem(name) + self.lstThemes.setItemData(self.lstThemes.count()-1, os.path.splitext(t)[0]) + + self.lstThemes.setCurrentIndex(self.lstThemes.findData(settings.fullScreenTheme)) + # self.lstThemes.setCurrentText(settings.fullScreenTheme) self.lstThemes.currentTextChanged.connect(self.setTheme) self.lstThemes.setMaximumSize(QSize(300, QFontMetrics(qApp.font()).height())) self.bottomPanel.layout().addWidget(QLabel(self.tr("Theme:"), self)) @@ -125,6 +130,7 @@ class fullScreenEditor(QWidget): self.btnClose.setVisible(not val) def setTheme(self, themeName): + themeName = self.lstThemes.currentData() settings.fullScreenTheme = themeName self._theme = findThemePath(themeName) self._themeDatas = loadThemeDatas(self._theme) diff --git a/manuskript/ui/editors/themes.py b/manuskript/ui/editors/themes.py index d2cb94ab..f618aea5 100644 --- a/manuskript/ui/editors/themes.py +++ b/manuskript/ui/editors/themes.py @@ -9,7 +9,7 @@ from PyQt5.QtCore import QSettings, QRect, QSize, Qt, QPoint, QFile, QIODevice, from PyQt5.QtGui import QPixmap, QPainter, QColor, QBrush, QImage, QTextBlockFormat, QTextCharFormat, QFont, qGray from PyQt5.QtWidgets import qApp, QFrame -from manuskript.functions import allPaths, appPath +from manuskript.functions import allPaths, appPath, findBackground, findFirstFile from manuskript.ui.views.textEditView import textEditView @@ -119,19 +119,6 @@ def findThemePath(themeName): return p -def findBackground(filename): - return findFirstFile(re.escape(filename), "resources/backgrounds") - - -def findFirstFile(regex, path="resources"): - paths = allPaths(path) - for p in paths: - lst = os.listdir(p) - for l in lst: - if re.match(regex, l): - return os.path.join(p, l) - - def generateTheme(themeDatas, screenRect): # Window Background px = QPixmap(screenRect.size()) diff --git a/manuskript/ui/views/corkView.py b/manuskript/ui/views/corkView.py index aeb340f0..305cd26e 100644 --- a/manuskript/ui/views/corkView.py +++ b/manuskript/ui/views/corkView.py @@ -3,6 +3,7 @@ from PyQt5.QtWidgets import QListView from manuskript import settings +from manuskript.functions import findBackground from manuskript.ui.views.corkDelegate import corkDelegate from manuskript.ui.views.dndView import dndView from manuskript.ui.views.outlineBasics import outlineBasics @@ -24,12 +25,13 @@ class corkView(QListView, dndView, outlineBasics): self.updateBackground() def updateBackground(self): + img = findBackground(settings.corkBackground["image"]) self.setStyleSheet("""QListView {{ background:{color}; background-image: url({url}); }}""".format( color=settings.corkBackground["color"], - url=settings.corkBackground["image"] + url=img )) def dragMoveEvent(self, event): From 38d80963fe7c99e4563bc3af8c3624b4b9896aac Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 29 Mar 2016 17:21:21 +0200 Subject: [PATCH 070/103] Fixes random plot colors in story line --- manuskript/ui/views/storylineView.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/manuskript/ui/views/storylineView.py b/manuskript/ui/views/storylineView.py index e10fc81e..3c4b0792 100644 --- a/manuskript/ui/views/storylineView.py +++ b/manuskript/ui/views/storylineView.py @@ -5,8 +5,6 @@ from PyQt5.QtGui import QBrush, QPen, QFontMetrics, QFontMetricsF, QColor from PyQt5.QtWidgets import QWidget, QGraphicsScene, QGraphicsSimpleTextItem, QMenu, QAction, QGraphicsRectItem, \ QGraphicsLineItem, QGraphicsEllipseItem -from manuskript.enums import Outline -from manuskript.functions import randomColor from manuskript.models import references from manuskript.ui.views.storylineView_ui import Ui_storylineView @@ -220,11 +218,18 @@ class storylineView(QWidget, Ui_storylineView): itemsRect = s.addRect(0, 0, 0, 0) itemsRect.setPos(0, MAX_LEVEL * LEVEL_HEIGHT + SPACING) + # Set of colors for plots (as long as they don't have their own colors) + colors = [ + "#D97777", "#AE5F8C", "#D9A377", "#FFC2C2", "#FFDEC2", "#D2A0BC", + "#7B0F0F", "#7B400F", "#620C3D", "#AA3939", "#AA6C39", "#882D61", + "#4C0000", "#4C2200", "#3D0022", + ] + for ref in trackedItems: if references.type(ref) == references.CharacterLetter: color = self._mdlCharacter.getCharacterByID(references.ID(ref)).color() else: - color = randomColor() + color = QColor(colors[i % len(colors)]) # Rect r = QGraphicsRectItem(0, 0, TITLE_WIDTH, LINE_HEIGHT, itemsRect) From 146a3fc6c17db47d33da7a6edd37a447971fb3d6 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 29 Mar 2016 17:31:33 +0200 Subject: [PATCH 071/103] Fixes bug #12 --- manuskript/ui/views/textEditView.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/manuskript/ui/views/textEditView.py b/manuskript/ui/views/textEditView.py index 5c438bb1..f7061031 100644 --- a/manuskript/ui/views/textEditView.py +++ b/manuskript/ui/views/textEditView.py @@ -402,7 +402,13 @@ class textEditView(QTextEdit): def toggleSpellcheck(self, v): self.spellcheck = v if enchant and self.spellcheck and not self._dict: - self._dict = enchant.Dict(self.currentDict if self.currentDict else enchant.get_default_language()) + if self.currentDict: + self._dict = enchant.Dict(self.currentDict) + elif enchant.get_default_language() in enchant.get_default_language(): + self._dict = enchant.Dict(enchant.get_default_language()) + else: + self.spellcheck = False + if self.highlighter: self.highlighter.rehighlight() else: From 824f15e54c768beb6021f533e9f0235ca5694c57 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 29 Mar 2016 18:39:33 +0200 Subject: [PATCH 072/103] Adds: GUI settings to change translation --- manuskript/main.py | 35 ++++++++++++++++----------- manuskript/settingsWindow.py | 28 +++++++++++++++++++-- manuskript/ui/settings_ui.py | 36 ++++++++++++++++++++++----- manuskript/ui/settings_ui.ui | 47 +++++++++++++++++++++++++++++++++--- 4 files changed, 121 insertions(+), 25 deletions(-) diff --git a/manuskript/main.py b/manuskript/main.py index 07262b1e..f765a1af 100644 --- a/manuskript/main.py +++ b/manuskript/main.py @@ -1,13 +1,14 @@ # -*- coding: utf-8 -*- import faulthandler +import os import sys from PyQt5.QtCore import QLocale, QTranslator, QSettings from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QApplication, qApp -from manuskript.functions import appPath +from manuskript.functions import appPath, writablePath _version = "0.2.0" @@ -28,27 +29,33 @@ def run(): app.setStyle("Fusion") - # Translation process - locale = QLocale.system().name() - # locale = "fr_CH" - - appTranslator = QTranslator() - if appTranslator.load(appPath("i18n/manuskript_{}.qm").format(locale)): - app.installTranslator(appTranslator) - print(app.tr("Loaded transation: {}.").format(locale)) - else: - print(app.tr("Failed to load translator for {}...").format(locale)) - # Load style from QSettings settings = QSettings(app.organizationName(), app.applicationName()) if settings.contains("applicationStyle"): style = settings.value("applicationStyle") app.setStyle(style) + # Translation process + locale = QLocale.system().name() + + appTranslator = QTranslator() + # By default: locale + translation = appPath(os.path.join("i18n", "manuskript_{}.qm".format(locale))) + + # Load translation from settings + if settings.contains("applicationTranslation"): + translation = appPath(os.path.join("i18n", settings.value("applicationTranslation"))) + print("Found translation in settings:", translation) + + if appTranslator.load(translation): + app.installTranslator(appTranslator) + print(app.tr("Loaded translation: {}.").format(translation)) + + else: + print(app.tr("Warning: failed to load translator for locale {}...").format(locale)) + QIcon.setThemeSearchPaths(QIcon.themeSearchPaths() + [appPath("icons")]) QIcon.setThemeName("NumixMsk") - print(QIcon.hasThemeIcon("dialog-no")) - print(QIcon.themeSearchPaths()) # qApp.setWindowIcon(QIcon.fromTheme("im-aim")) # Seperating launch to avoid segfault, so it seem. diff --git a/manuskript/settingsWindow.py b/manuskript/settingsWindow.py index b6ff6cf3..76571e8b 100644 --- a/manuskript/settingsWindow.py +++ b/manuskript/settingsWindow.py @@ -1,11 +1,12 @@ #!/usr/bin/env python # --!-- coding: utf8 --!-- import os +from collections import OrderedDict -from PyQt5.QtCore import QSize, QSettings, QRegExp +from PyQt5.QtCore import QSize, QSettings, QRegExp, QTranslator, QObject from PyQt5.QtCore import Qt from PyQt5.QtGui import QIntValidator, QIcon, QFont, QColor, QPixmap, QStandardItem, QPainter -from PyQt5.QtWidgets import QStyleFactory, QWidget, QStyle, QColorDialog, QListWidgetItem +from PyQt5.QtWidgets import QStyleFactory, QWidget, QStyle, QColorDialog, QListWidgetItem, QMessageBox from PyQt5.QtWidgets import qApp # Spell checker support @@ -45,6 +46,21 @@ class settingsWindow(QWidget, Ui_Settings): self.cmbStyle.setCurrentIndex([i.lower() for i in list(QStyleFactory.keys())].index(qApp.style().objectName())) self.cmbStyle.currentIndexChanged[str].connect(self.setStyle) + self.cmbTranslation.clear() + tr = OrderedDict() + tr["English"] = "" + tr["Français"] = "manuskript_fr.qm" + tr["Español"] = "manuskript_es.qm" + + for name in tr: + self.cmbTranslation.addItem(name, tr[name]) + + sttgs = QSettings(qApp.organizationName(), qApp.applicationName()) + if sttgs.contains("applicationTranslation") and sttgs.value("applicationTranslation") in tr.values(): + self.cmbTranslation.setCurrentText([i for i in tr if tr[i] == sttgs.value("applicationTranslation")][0]) + + self.cmbTranslation.currentIndexChanged.connect(self.setTranslation) + self.txtAutoSave.setValidator(QIntValidator(0, 999, self)) self.txtAutoSaveNoChanges.setValidator(QIntValidator(0, 999, self)) self.chkAutoSave.setChecked(settings.autoSave) @@ -209,6 +225,14 @@ class settingsWindow(QWidget, Ui_Settings): sttgs.setValue("applicationStyle", style) qApp.setStyle(style) + def setTranslation(self, index): + path = self.cmbTranslation.currentData() + # Save settings + sttgs = QSettings(qApp.organizationName(), qApp.applicationName()) + sttgs.setValue("applicationTranslation", path) + + # QMessageBox.information(self, "Warning", "You'll have to restart manuskript.") + def saveSettingsChanged(self): if self.txtAutoSave.text() in ["", "0"]: self.txtAutoSave.setText("1") diff --git a/manuskript/ui/settings_ui.py b/manuskript/ui/settings_ui.py index 19b1c2e6..75dfd057 100644 --- a/manuskript/ui/settings_ui.py +++ b/manuskript/ui/settings_ui.py @@ -2,8 +2,7 @@ # Form implementation generated from reading ui file 'manuskript/ui/settings_ui.ui' # -# Created: Wed Mar 2 00:30:17 2016 -# by: PyQt5 UI code generator 5.2.1 +# Created by: PyQt5 UI code generator 5.4.2 # # WARNING! All changes made in this file will be lost! @@ -12,7 +11,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Settings(object): def setupUi(self, Settings): Settings.setObjectName("Settings") - Settings.resize(658, 491) + Settings.resize(658, 561) self.horizontalLayout_8 = QtWidgets.QHBoxLayout(Settings) self.horizontalLayout_8.setObjectName("horizontalLayout_8") self.lstMenu = QtWidgets.QListWidget(Settings) @@ -77,6 +76,30 @@ class Ui_Settings(object): self.cmbStyle.setObjectName("cmbStyle") self.verticalLayout_5.addWidget(self.cmbStyle) self.verticalLayout_7.addWidget(self.groupBox_2) + self.groupBox_14 = QtWidgets.QGroupBox(self.stackedWidgetPage1) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.groupBox_14.setFont(font) + self.groupBox_14.setObjectName("groupBox_14") + self.verticalLayout_20 = QtWidgets.QVBoxLayout(self.groupBox_14) + self.verticalLayout_20.setObjectName("verticalLayout_20") + self.label_52 = QtWidgets.QLabel(self.groupBox_14) + font = QtGui.QFont() + font.setBold(False) + font.setWeight(50) + self.label_52.setFont(font) + self.label_52.setWordWrap(True) + self.label_52.setObjectName("label_52") + self.verticalLayout_20.addWidget(self.label_52) + self.cmbTranslation = QtWidgets.QComboBox(self.groupBox_14) + font = QtGui.QFont() + font.setBold(False) + font.setWeight(50) + self.cmbTranslation.setFont(font) + self.cmbTranslation.setObjectName("cmbTranslation") + self.verticalLayout_20.addWidget(self.cmbTranslation) + self.verticalLayout_7.addWidget(self.groupBox_14) self.groupBox_10 = QtWidgets.QGroupBox(self.stackedWidgetPage1) font = QtGui.QFont() font.setBold(True) @@ -1321,7 +1344,6 @@ class Ui_Settings(object): self.layoutWidget = QtWidgets.QWidget(self.splitter) self.layoutWidget.setObjectName("layoutWidget") self.verticalLayout_14 = QtWidgets.QVBoxLayout(self.layoutWidget) - self.verticalLayout_14.setContentsMargins(0, 0, 0, 0) self.verticalLayout_14.setObjectName("verticalLayout_14") self.cmbThemeEdit = QtWidgets.QComboBox(self.layoutWidget) self.cmbThemeEdit.setObjectName("cmbThemeEdit") @@ -1601,7 +1623,7 @@ class Ui_Settings(object): self.horizontalLayout_8.addWidget(self.stack) self.retranslateUi(Settings) - self.stack.setCurrentIndex(2) + self.stack.setCurrentIndex(0) self.tabViews.setCurrentIndex(0) self.themeStack.setCurrentIndex(1) self.themeEditStack.setCurrentIndex(0) @@ -1630,6 +1652,8 @@ class Ui_Settings(object): self.lblTitleGeneral.setText(_translate("Settings", "General settings")) self.groupBox_2.setTitle(_translate("Settings", "Application style")) self.label_2.setText(_translate("Settings", "You might need to restart manuskript in order to avoid some visual issues.")) + self.groupBox_14.setTitle(_translate("Settings", "Application language")) + self.label_52.setText(_translate("Settings", "You will need to restart manuskript for the translation to take effect.")) self.groupBox_10.setTitle(_translate("Settings", "Loading")) self.chkAutoLoad.setText(_translate("Settings", "Automatically load last project on startup")) self.groupBox.setTitle(_translate("Settings", "Saving")) @@ -1676,7 +1700,7 @@ class Ui_Settings(object): self.rdoTreeWC.setText(_translate("Settings", "Show wordcount")) self.rdoTreeProgress.setText(_translate("Settings", "Show progress")) self.rdoTreeSummary.setText(_translate("Settings", "Show summary")) - self.rdoTreeNothing.setText(_translate("Settings", "Nothing")) + self.rdoTreeNothing.setText(_translate("Settings", "&Nothing")) self.groupBox_9.setTitle(_translate("Settings", "Text")) self.rdoTreeTextWC.setText(_translate("Settings", "Show wordcount")) self.rdoTreeTextProgress.setText(_translate("Settings", "Show progress")) diff --git a/manuskript/ui/settings_ui.ui b/manuskript/ui/settings_ui.ui index 7c843263..fd5bc9ab 100644 --- a/manuskript/ui/settings_ui.ui +++ b/manuskript/ui/settings_ui.ui @@ -7,7 +7,7 @@ 0 0 658 - 491 + 561 @@ -51,7 +51,7 @@ - 2 + 0 @@ -133,6 +133,47 @@ text-align:center; + + + + + 75 + true + + + + Application language + + + + + + + 50 + false + + + + You will need to restart manuskript for the translation to take effect. + + + true + + + + + + + + 50 + false + + + + + + + @@ -1032,7 +1073,7 @@ text-align:center; - Nothing + &Nothing true From 2c0e3074ce7d12b5dc7229f76738b6599fda463d Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 29 Mar 2016 19:13:51 +0200 Subject: [PATCH 073/103] Adds: UI setting to change project format (single file, or directory) --- manuskript/loadSave.py | 2 -- manuskript/load_save/version_1.py | 6 ++++-- manuskript/settings.py | 8 +++++++- manuskript/settingsWindow.py | 4 ++++ manuskript/ui/settings_ui.py | 13 ++++++++++++- manuskript/ui/settings_ui.ui | 24 +++++++++++++++++++++++- manuskript/ui/welcome.py | 2 +- 7 files changed, 51 insertions(+), 8 deletions(-) diff --git a/manuskript/loadSave.py b/manuskript/loadSave.py index a7b336ba..908bc371 100644 --- a/manuskript/loadSave.py +++ b/manuskript/loadSave.py @@ -20,8 +20,6 @@ def saveProject(version=None): else: v1.saveProject() - # FIXME: add settings to chose between saving as zip or not. - def clearSaveCache(): v1.cache = {} diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index b1281c74..07d9bd0d 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -105,8 +105,7 @@ def saveProject(zip=None): @return: Nothing """ if zip is None: - zip = False - # FIXME: use value from settings + zip = settings.saveToZip log("\n\nSaving to:", "zip" if zip else "folder") @@ -651,6 +650,9 @@ def loadProject(project, zip=None): else: errors.append("settings.txt") + # Just to be sure + settings.saveToZip = zip + #################################################################################################################### # Labels diff --git a/manuskript/settings.py b/manuskript/settings.py index 9bff1208..c0cc2edb 100644 --- a/manuskript/settings.py +++ b/manuskript/settings.py @@ -87,12 +87,14 @@ frequencyAnalyzer = { } viewMode = "fiction" +saveToZip = True def save(filename=None, protocol=None): global spellcheck, dict, corkSliderFactor, viewSettings, corkSizeFactor, folderView, lastTab, openIndexes, \ autoSave, autoSaveDelay, saveOnQuit, autoSaveNoChanges, autoSaveNoChangesDelay, outlineViewColumns, \ - corkBackground, fullScreenTheme, defaultTextType, textEditor, revisions, frequencyAnalyzer, viewMode + corkBackground, fullScreenTheme, defaultTextType, textEditor, revisions, frequencyAnalyzer, viewMode, \ + saveToZip allSettings = { "viewSettings": viewSettings, @@ -115,6 +117,7 @@ def save(filename=None, protocol=None): "revisions":revisions, "frequencyAnalyzer": frequencyAnalyzer, "viewMode": viewMode, + "saveToZip": saveToZip, } #pp=pprint.PrettyPrinter(indent=4, compact=False) @@ -251,3 +254,6 @@ def load(string, fromString=False, protocol=None): global viewMode viewMode = allSettings["viewMode"] + if "saveToZip" in allSettings: + global saveToZip + saveToZip = allSettings["saveToZip"] \ No newline at end of file diff --git a/manuskript/settingsWindow.py b/manuskript/settingsWindow.py index 76571e8b..59f41514 100644 --- a/manuskript/settingsWindow.py +++ b/manuskript/settingsWindow.py @@ -68,9 +68,11 @@ class settingsWindow(QWidget, Ui_Settings): self.txtAutoSave.setText(str(settings.autoSaveDelay)) self.txtAutoSaveNoChanges.setText(str(settings.autoSaveNoChangesDelay)) self.chkSaveOnQuit.setChecked(settings.saveOnQuit) + self.chkSaveToZip.setChecked(settings.saveToZip) self.chkAutoSave.stateChanged.connect(self.saveSettingsChanged) self.chkAutoSaveNoChanges.stateChanged.connect(self.saveSettingsChanged) self.chkSaveOnQuit.stateChanged.connect(self.saveSettingsChanged) + self.chkSaveToZip.stateChanged.connect(self.saveSettingsChanged) self.txtAutoSave.textEdited.connect(self.saveSettingsChanged) self.txtAutoSaveNoChanges.textEdited.connect(self.saveSettingsChanged) autoLoad, last = self.mw.welcome.getAutoLoadValues() @@ -241,11 +243,13 @@ class settingsWindow(QWidget, Ui_Settings): sttgs = QSettings() sttgs.setValue("autoLoad", True if self.chkAutoLoad.checkState() else False) + print("Setting Value:", True if self.chkAutoLoad.checkState() else False) sttgs.sync() settings.autoSave = True if self.chkAutoSave.checkState() else False settings.autoSaveNoChanges = True if self.chkAutoSaveNoChanges.checkState() else False settings.saveOnQuit = True if self.chkSaveOnQuit.checkState() else False + settings.saveToZip = True if self.chkSaveToZip.checkState() else False settings.autoSaveDelay = int(self.txtAutoSave.text()) settings.autoSaveNoChangesDelay = int(self.txtAutoSaveNoChanges.text()) self.mw.saveTimer.setInterval(settings.autoSaveDelay * 60 * 1000) diff --git a/manuskript/ui/settings_ui.py b/manuskript/ui/settings_ui.py index 75dfd057..44bb809c 100644 --- a/manuskript/ui/settings_ui.py +++ b/manuskript/ui/settings_ui.py @@ -11,7 +11,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Settings(object): def setupUi(self, Settings): Settings.setObjectName("Settings") - Settings.resize(658, 561) + Settings.resize(658, 585) self.horizontalLayout_8 = QtWidgets.QHBoxLayout(Settings) self.horizontalLayout_8.setObjectName("horizontalLayout_8") self.lstMenu = QtWidgets.QListWidget(Settings) @@ -205,6 +205,15 @@ class Ui_Settings(object): self.chkSaveOnQuit.setChecked(True) self.chkSaveOnQuit.setObjectName("chkSaveOnQuit") self.verticalLayout_6.addWidget(self.chkSaveOnQuit) + self.chkSaveToZip = QtWidgets.QCheckBox(self.groupBox) + font = QtGui.QFont() + font.setBold(False) + font.setWeight(50) + self.chkSaveToZip.setFont(font) + self.chkSaveToZip.setStatusTip("") + self.chkSaveToZip.setChecked(True) + self.chkSaveToZip.setObjectName("chkSaveToZip") + self.verticalLayout_6.addWidget(self.chkSaveToZip) self.verticalLayout_7.addWidget(self.groupBox) self.groupBox_11 = QtWidgets.QGroupBox(self.stackedWidgetPage1) font = QtGui.QFont() @@ -1662,6 +1671,8 @@ class Ui_Settings(object): self.chkAutoSaveNoChanges.setText(_translate("Settings", "If no changes during")) self.label_14.setText(_translate("Settings", "seconds.")) self.chkSaveOnQuit.setText(_translate("Settings", "Save on quit")) + self.chkSaveToZip.setToolTip(_translate("Settings", "

    If you check this option, your project will be save as one single file. Easier to copy or backup, but does not allow collaborative editing, or versionning.
    If this is unchecked, your project will be save as a folder containing many small files.

    ")) + self.chkSaveToZip.setText(_translate("Settings", "Save to one single file")) self.groupBox_11.setTitle(_translate("Settings", "Default text format")) self.label_35.setText(_translate("Settings", "The format set by default when you create a new text item. You can change this on a per item basis.")) self.lblTitleGeneral_2.setText(_translate("Settings", "Revisions")) diff --git a/manuskript/ui/settings_ui.ui b/manuskript/ui/settings_ui.ui index fd5bc9ab..7991b0ee 100644 --- a/manuskript/ui/settings_ui.ui +++ b/manuskript/ui/settings_ui.ui @@ -7,7 +7,7 @@ 0 0 658 - 561 + 585
    @@ -399,6 +399,28 @@ text-align:center;
    + + + + + 50 + false + + + + <html><head/><body><p>If you check this option, your project will be save as one single file. Easier to copy or backup, but does not allow collaborative editing, or versionning.<br/>If this is unchecked, your project will be save as a folder containing many small files.</p></body></html> + + + + + + Save to one single file + + + true + + +
    diff --git a/manuskript/ui/welcome.py b/manuskript/ui/welcome.py index 33314385..86da8828 100644 --- a/manuskript/ui/welcome.py +++ b/manuskript/ui/welcome.py @@ -66,7 +66,7 @@ class welcome(QWidget, Ui_welcome): def getAutoLoadValues(self): sttgns = QSettings() if sttgns.contains("autoLoad"): - autoLoad = True if sttgns.value("autoLoad") == "true" else False + autoLoad = True if sttgns.value("autoLoad") in ["true", True] else False else: autoLoad = False if autoLoad and sttgns.contains("lastProject"): From b676d126babc8098f77ce5f738c5a3e95457c9f4 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 29 Mar 2016 19:24:35 +0200 Subject: [PATCH 074/103] Cleaning up a bit --- manuskript/functions.py | 15 +++++++++++++-- manuskript/models/outlineModel.py | 6 ++---- manuskript/settingsWindow.py | 1 - 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/manuskript/functions.py b/manuskript/functions.py index 28a3ddb9..fa1437b1 100644 --- a/manuskript/functions.py +++ b/manuskript/functions.py @@ -12,7 +12,7 @@ from PyQt5.QtGui import QBrush, QIcon, QPainter from PyQt5.QtGui import QColor from PyQt5.QtGui import QImage from PyQt5.QtGui import QPixmap -from PyQt5.QtWidgets import qApp +from PyQt5.QtWidgets import qApp, QTextEdit from manuskript.enums import Outline @@ -235,4 +235,15 @@ def findFirstFile(regex, path="resources"): lst = os.listdir(p) for l in lst: if re.match(regex, l): - return os.path.join(p, l) \ No newline at end of file + return os.path.join(p, l) + + +def HTML2PlainText(html): + """ + Ressource-inefficient way to convert HTML to plain text. + @param html: + @return: + """ + e = QTextEdit() + e.setHtml(html) + return e.toPlainText() \ No newline at end of file diff --git a/manuskript/models/outlineModel.py b/manuskript/models/outlineModel.py index b71c72f3..c018bc73 100644 --- a/manuskript/models/outlineModel.py +++ b/manuskript/models/outlineModel.py @@ -15,7 +15,7 @@ from manuskript import settings from lxml import etree as ET from manuskript.enums import Outline -from manuskript.functions import mainWindow, toInt, wordCount +from manuskript.functions import mainWindow, toInt, wordCount, HTML2PlainText locale.setlocale(locale.LC_ALL, '') import time, os @@ -531,9 +531,7 @@ class outlineItem(): oldType = self._data[Outline.type] if oldType == "html" and data in ["txt", "t2t", "md"]: # Resource inneficient way to convert HTML to plain text - e = QTextEdit() - e.setHtml(self._data[Outline.text]) - self._data[Outline.text] = e.toPlainText() + self._data[Outline.text] = HTML2PlainText(self._data[Outline.text]) elif oldType in ["txt", "t2t", "md"] and data == "html" and Outline.text in self._data: self._data[Outline.text] = self._data[Outline.text].replace("\n", "
    ") diff --git a/manuskript/settingsWindow.py b/manuskript/settingsWindow.py index 59f41514..9c2b7db4 100644 --- a/manuskript/settingsWindow.py +++ b/manuskript/settingsWindow.py @@ -243,7 +243,6 @@ class settingsWindow(QWidget, Ui_Settings): sttgs = QSettings() sttgs.setValue("autoLoad", True if self.chkAutoLoad.checkState() else False) - print("Setting Value:", True if self.chkAutoLoad.checkState() else False) sttgs.sync() settings.autoSave = True if self.chkAutoSave.checkState() else False From 644d0c1c7b8392866c427d72339f4e392584dd7d Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 30 Mar 2016 09:13:40 +0200 Subject: [PATCH 075/103] Welcome windows: adds templates for fiction and non-fiction --- manuskript/settings.py | 2 +- manuskript/ui/mainWindow.py | 2 +- manuskript/ui/mainWindow.ui | 2 +- manuskript/ui/welcome.py | 54 +++++++++++++++++++++++-------------- manuskript/ui/welcome_ui.py | 4 +-- manuskript/ui/welcome_ui.ui | 4 +-- 6 files changed, 41 insertions(+), 27 deletions(-) diff --git a/manuskript/settings.py b/manuskript/settings.py index c0cc2edb..bc648169 100644 --- a/manuskript/settings.py +++ b/manuskript/settings.py @@ -86,7 +86,7 @@ frequencyAnalyzer = { "phraseMax": 5 } -viewMode = "fiction" +viewMode = "fiction" # simple, fiction saveToZip = True def save(filename=None, protocol=None): diff --git a/manuskript/ui/mainWindow.py b/manuskript/ui/mainWindow.py index 1b2a6149..09580128 100644 --- a/manuskript/ui/mainWindow.py +++ b/manuskript/ui/mainWindow.py @@ -1294,7 +1294,7 @@ class Ui_MainWindow(object): self.actViewTree.setText(_translate("MainWindow", "Tree")) self.actModeSimple.setText(_translate("MainWindow", "&Simple")) self.actModeFiction.setText(_translate("MainWindow", "&Fiction")) - self.actModeSnowflake.setText(_translate("MainWindow", "&Snowflake")) + self.actModeSnowflake.setText(_translate("MainWindow", "S&nowflake")) self.actViewCork.setText(_translate("MainWindow", "Index cards")) self.actViewOutline.setText(_translate("MainWindow", "Outline")) self.actSettings.setText(_translate("MainWindow", "S&ettings")) diff --git a/manuskript/ui/mainWindow.ui b/manuskript/ui/mainWindow.ui index f673905c..111161a1 100644 --- a/manuskript/ui/mainWindow.ui +++ b/manuskript/ui/mainWindow.ui @@ -2291,7 +2291,7 @@ QListView::item:hover { true - &Snowflake + S&nowflake diff --git a/manuskript/ui/welcome.py b/manuskript/ui/welcome.py index 86da8828..ae8739bb 100644 --- a/manuskript/ui/welcome.py +++ b/manuskript/ui/welcome.py @@ -164,32 +164,33 @@ class welcome(QWidget, Ui_welcome): def templates(self): return [ - (self.tr("Empty"), []), + (self.tr("Empty fiction"), [], "Fiction"), (self.tr("Novel"), [ (20, self.tr("Chapter")), (5, self.tr("Scene")), (500, None) # A line with None is word count - ]), + ], "Fiction"), (self.tr("Novella"), [ (10, self.tr("Chapter")), (5, self.tr("Scene")), (500, None) - ]), + ], "Fiction"), (self.tr("Short Story"), [ (10, self.tr("Scene")), (1000, None) - ]), + ], "Fiction"), (self.tr("Trilogy"), [ (3, self.tr("Book")), (3, self.tr("Section")), (10, self.tr("Chapter")), (5, self.tr("Scene")), (500, None) - ]), + ], "Fiction"), + (self.tr("Empty non-fiction"), [], "Non-fiction"), (self.tr("Research paper"), [ (3, self.tr("Section")), (1000, None) - ]) + ], "Non-fiction") ] @classmethod @@ -204,13 +205,17 @@ class welcome(QWidget, Ui_welcome): def changeTemplate(self, item, column): template = [i for i in self.templates() if i[0] == item.text(0)] self.btnCreate.setText(self.btnCreateText) + + # Selected item is a template if len(template): - self.template = template[0][1] + self.template = template[0] self.updateTemplate() + + # Selected item is a sample project elif item.data(0, Qt.UserRole): name = item.data(0, Qt.UserRole) # Clear templates - self.template = self.templates()[0][1] + self.template = self.templates()[0] self.updateTemplate() # Change button text self.btnCreate.setText("Open {}".format(name)) @@ -235,7 +240,7 @@ class welcome(QWidget, Ui_welcome): k = 0 hasWC = False - for d in self.template: + for d in self.template[1]: spin = QSpinBox(self) spin.setRange(0, 999999) spin.setValue(d[0]) @@ -264,29 +269,29 @@ class welcome(QWidget, Ui_welcome): self.lytTemplate.addWidget(txt, k, 2) k += 1 - self.btnAddWC.setEnabled(not hasWC and len(self.template) > 0) + self.btnAddWC.setEnabled(not hasWC and len(self.template[1]) > 0) self.btnAddLevel.setEnabled(True) self.lblTotal.setVisible(hasWC) self.updateWordCount() def templateAddLevel(self): - if len(self.template) > 0 and \ - self.template[len(self.template) - 1][1] == None: + if len(self.template[1]) > 0 and \ + self.template[1][len(self.template[1]) - 1][1] == None: # has word cound, so insert before - self.template.insert(len(self.template) - 1, (10, self.tr("Text"))) + self.template[1].insert(len(self.template[1]) - 1, (10, self.tr("Text"))) else: # No word count, so insert at end - self.template.append((10, self.tr("Something"))) + self.template[1].append((10, self.tr("Something"))) self.updateTemplate() def templateAddWordCount(self): - self.template.append((500, None)) + self.template[1].append((500, None)) self.updateTemplate() def deleteTemplateRow(self): btn = self.sender() row = btn.property("deleteRow") - self.template.pop(row) + self.template[1].pop(row) self.updateTemplate() def updateWordCount(self): @@ -319,11 +324,18 @@ class welcome(QWidget, Ui_welcome): self.tree.setIndentation(0) # Add templates - item = self.addTopLevelItem(self.tr("Templates")) - templates = self.templates() + item = self.addTopLevelItem(self.tr("Fiction")) + templates = [i for i in self.templates() if i[2] == "Fiction"] for t in templates: sub = QTreeWidgetItem(item, [t[0]]) + # Add templates: non-fiction + item = self.addTopLevelItem(self.tr("Non-fiction")) + templates = [i for i in self.templates() if i[2] == "Non-fiction"] + for t in templates: + sub = QTreeWidgetItem(item, [t[0]]) + + # Add Demo project item = self.addTopLevelItem(self.tr("Demo projects")) dir = QDir(appPath("sample-projects")) @@ -342,6 +354,8 @@ class welcome(QWidget, Ui_welcome): # Empty settings imp.reload(settings) + t = [i for i in self.templates() if i[0] == self.template[0]] + if t and t[0][2] == "Non-fiction": settings.viewMode = "simple" # Données self.mw.mdlFlatData = QStandardItemModel(2, 8, self.mw) @@ -419,5 +433,5 @@ class welcome(QWidget, Ui_welcome): # parent.appendChild(item) addElement(item, datas[1:]) - if self.template: - addElement(root, self.template) + if self.template[1]: + addElement(root, self.template[1]) diff --git a/manuskript/ui/welcome_ui.py b/manuskript/ui/welcome_ui.py index 927730dd..f89ca22c 100644 --- a/manuskript/ui/welcome_ui.py +++ b/manuskript/ui/welcome_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'manuskript/ui/welcome_ui.ui' # -# Created by: PyQt5 UI code generator 5.4.1 +# Created by: PyQt5 UI code generator 5.4.2 # # WARNING! All changes made in this file will be lost! @@ -11,7 +11,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_welcome(object): def setupUi(self, welcome): welcome.setObjectName("welcome") - welcome.resize(705, 422) + welcome.resize(728, 459) self.horizontalLayout = QtWidgets.QHBoxLayout(welcome) self.horizontalLayout.setObjectName("horizontalLayout") self.frame_2 = QtWidgets.QFrame(welcome) diff --git a/manuskript/ui/welcome_ui.ui b/manuskript/ui/welcome_ui.ui index 0a5cf2e5..5b1c73d0 100644 --- a/manuskript/ui/welcome_ui.ui +++ b/manuskript/ui/welcome_ui.ui @@ -6,8 +6,8 @@ 0 0 - 705 - 422 + 728 + 459 From 465c387284d52d08259d289fdd382463ba31ac30 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 30 Mar 2016 09:43:18 +0200 Subject: [PATCH 076/103] Fixes bug: tabs are not properly closed --- manuskript/mainWindow.py | 6 ++++-- manuskript/ui/editors/mainEditor.py | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index f92c73eb..2cea3c96 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -374,14 +374,16 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.stack.setCurrentIndex(1) def closeProject(self): + + # Close open tabs in editor + self.mainEditor.closeAllTabs() + # Save datas self.saveDatas() self.currentProject = None QSettings().setValue("lastProject", "") - # FIXME: close all opened tabs in mainEditor - # Clear datas self.loadEmptyDatas() self.saveTimer.stop() diff --git a/manuskript/ui/editors/mainEditor.py b/manuskript/ui/editors/mainEditor.py index 6684e54b..547bbe28 100644 --- a/manuskript/ui/editors/mainEditor.py +++ b/manuskript/ui/editors/mainEditor.py @@ -78,11 +78,15 @@ class mainEditor(QWidget, Ui_mainEditor): self.updateThingsVisible(index) def closeTab(self, index): - # FIXME: submit data if textedit? w = self.tab.widget(index) self.tab.removeTab(index) + w.setCurrentModelIndex(QModelIndex()) w.deleteLater() + def closeAllTabs(self): + while(self.tab.count()): + self.closeTab(0) + def allTabs(self): return [self.tab.widget(i) for i in range(self.tab.count())] From e9fcb967f59abd182eede3c4a4dd7fedfcad3a26 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 30 Mar 2016 10:00:09 +0200 Subject: [PATCH 077/103] Fixes bug in new file format --- manuskript/load_save/version_1.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index 07d9bd0d..177cee9d 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -494,13 +494,13 @@ def addPlotItem(root, mdl, parent=QModelIndex()): def exportOutlineItem(root): """ - Takes an outline item, and returns two lists: + Takes an outline item, and returns three lists: 1. of (`filename`, `content`), representing the whole tree of files to be written, in multimarkdown. - 3. of (`filename`, `filename`) listing files to be moved - 2. of `filename`, representing files to be removed. + 2. of (`filename`, `filename`) listing files to be moved + 3. of `filename`, representing files to be removed. @param root: OutlineItem - @return: [(str, str)], [str] + @return: [(str, str)], [(str, str)], [str] """ files = [] @@ -560,8 +560,10 @@ def outlineItemPath(item): if not item.parent(): return ["outline"] else: + # Count the number of siblings for padding '0' + siblings = item.parent().childCount() name = "{ID}-{name}{ext}".format( - ID=item.row(), + ID=str(item.row()).zfill(len(str(siblings))), name=slugify(item.title()), ext="" if item.type() == "folder" else ".md" # ".{}".format(item.type()) # To have .txt, .t2t, .html, ... ) From bc8b3027e216224f491bcf9a124c17e15fbde3db Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 30 Mar 2016 10:07:38 +0200 Subject: [PATCH 078/103] Purging types from welcome --- manuskript/ui/views/cmbOutlineTypeChoser.py | 2 +- manuskript/ui/welcome.py | 17 +---------------- manuskript/ui/welcome_ui.py | 10 ---------- manuskript/ui/welcome_ui.ui | 14 -------------- 4 files changed, 2 insertions(+), 41 deletions(-) diff --git a/manuskript/ui/views/cmbOutlineTypeChoser.py b/manuskript/ui/views/cmbOutlineTypeChoser.py index 41842f18..b8e19922 100644 --- a/manuskript/ui/views/cmbOutlineTypeChoser.py +++ b/manuskript/ui/views/cmbOutlineTypeChoser.py @@ -25,7 +25,7 @@ class cmbOutlineTypeChoser(QComboBox): def updateItems(self): self.clear() - types = welcome.defaultTextType() + types = [] for t in types: self.addItem(QIcon.fromTheme(t[2]), t[1], t[0]) diff --git a/manuskript/ui/welcome.py b/manuskript/ui/welcome.py index ae8739bb..1dbc0ff8 100644 --- a/manuskript/ui/welcome.py +++ b/manuskript/ui/welcome.py @@ -193,15 +193,6 @@ class welcome(QWidget, Ui_welcome): ], "Non-fiction") ] - @classmethod - def defaultTextType(cls): - return [ - ("t2t", qApp.translate("Welcome", "Txt2Tags"), "text-x-generic"), - ("html", qApp.translate("Welcome", "Rich Text (html)"), "text-html"), - ("txt", qApp.translate("Welcome", "Plain Text"), "text-x-generic"), - ("md", qApp.translate("Welcome", "Multi-Markdown"), "text-x-generic"), - ] - def changeTemplate(self, item, column): template = [i for i in self.templates() if i[0] == item.text(0)] self.btnCreate.setText(self.btnCreateText) @@ -343,11 +334,6 @@ class welcome(QWidget, Ui_welcome): sub = QTreeWidgetItem(item, [f[:-4]]) sub.setData(0, Qt.UserRole, f) - # Populates default text type - self.cmbDefaultType.clear() - for t in self.defaultTextType(): - self.cmbDefaultType.addItem(QIcon.fromTheme(t[2]), t[1], t[0]) - self.tree.expandAll() def loadDefaultDatas(self): @@ -403,8 +389,7 @@ class welcome(QWidget, Ui_welcome): self.mw.mdlWorld = worldModel(self.mw) root = self.mw.mdlOutline.rootItem - _type = self.cmbDefaultType.currentData() - settings.defaultTextType = _type + _type = "md" def addElement(parent, datas): if len(datas) == 2 and datas[1][1] == None or \ diff --git a/manuskript/ui/welcome_ui.py b/manuskript/ui/welcome_ui.py index f89ca22c..a8e83873 100644 --- a/manuskript/ui/welcome_ui.py +++ b/manuskript/ui/welcome_ui.py @@ -78,15 +78,6 @@ class Ui_welcome(object): self.btnAddWC.setObjectName("btnAddWC") self.horizontalLayout_2.addWidget(self.btnAddWC) self.templateLayout.addLayout(self.horizontalLayout_2) - self.horizontalLayout_3 = QtWidgets.QHBoxLayout() - self.horizontalLayout_3.setObjectName("horizontalLayout_3") - self.lblTotal_2 = QtWidgets.QLabel(self.frame_2) - self.lblTotal_2.setObjectName("lblTotal_2") - self.horizontalLayout_3.addWidget(self.lblTotal_2) - self.cmbDefaultType = QtWidgets.QComboBox(self.frame_2) - self.cmbDefaultType.setObjectName("cmbDefaultType") - self.horizontalLayout_3.addWidget(self.cmbDefaultType) - self.templateLayout.addLayout(self.horizontalLayout_3) self.horizontalLayout_23.addLayout(self.templateLayout) self.verticalLayout_32.addLayout(self.horizontalLayout_23) self.line_4 = QtWidgets.QFrame(self.frame_2) @@ -133,7 +124,6 @@ class Ui_welcome(object): self.tree.setSortingEnabled(__sortingEnabled) self.btnAddLevel.setText(_translate("welcome", "Add level")) self.btnAddWC.setText(_translate("welcome", "Add wordcount")) - self.lblTotal_2.setText(_translate("welcome", "Default text type:")) self.chkLoadLastProject.setText(_translate("welcome", "Next time, automatically open last project")) self.btnOpen.setText(_translate("welcome", "Open...")) self.btnRecent.setText(_translate("welcome", "Recent")) diff --git a/manuskript/ui/welcome_ui.ui b/manuskript/ui/welcome_ui.ui index 5b1c73d0..fc07159e 100644 --- a/manuskript/ui/welcome_ui.ui +++ b/manuskript/ui/welcome_ui.ui @@ -190,20 +190,6 @@
    - - - - - - Default text type: - - - - - - - -
    From 76dec8162efe2a0ca1ebc2fa520236f63b6c13c0 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 30 Mar 2016 10:18:09 +0200 Subject: [PATCH 079/103] Purging types from propertiesView --- manuskript/ui/views/propertiesView.py | 5 -- manuskript/ui/views/propertiesView_ui.py | 35 ++++-------- manuskript/ui/views/propertiesView_ui.ui | 68 ++++++++++++------------ 3 files changed, 45 insertions(+), 63 deletions(-) diff --git a/manuskript/ui/views/propertiesView.py b/manuskript/ui/views/propertiesView.py index ce18cf0c..5e637df9 100644 --- a/manuskript/ui/views/propertiesView.py +++ b/manuskript/ui/views/propertiesView.py @@ -16,7 +16,6 @@ class propertiesView(QWidget, Ui_propertiesView): self.cmbPOV.setModels(mdlCharacter, mdlOutline) self.cmbLabel.setModels(mdlLabels, mdlOutline) self.cmbStatus.setModels(mdlStatus, mdlOutline) - self.cmbType.setModel(mdlOutline) self.chkCompile.setModel(mdlOutline) self.txtTitle.setModel(mdlOutline) self.txtGoal.setModel(mdlOutline) @@ -52,8 +51,6 @@ class propertiesView(QWidget, Ui_propertiesView): self.txtTitle.setCurrentModelIndex(idx) self.txtGoal.setCurrentModelIndex(idx) - self.cmbType.setCurrentModelIndex(idx) - else: self.setEnabled(True) self.setLabelsItalic(True) @@ -64,8 +61,6 @@ class propertiesView(QWidget, Ui_propertiesView): self.cmbLabel.setCurrentModelIndexes(indexes) self.cmbStatus.setCurrentModelIndexes(indexes) - self.cmbType.setCurrentModelIndexes(indexes) - def setLabelsItalic(self, value): f = self.lblPOV.font() f.setItalic(value) diff --git a/manuskript/ui/views/propertiesView_ui.py b/manuskript/ui/views/propertiesView_ui.py index 217b3ab5..5b8a7e11 100644 --- a/manuskript/ui/views/propertiesView_ui.py +++ b/manuskript/ui/views/propertiesView_ui.py @@ -2,8 +2,7 @@ # Form implementation generated from reading ui file 'manuskript/ui/views/propertiesView_ui.ui' # -# Created: Thu Mar 3 17:26:11 2016 -# by: PyQt5 UI code generator 5.2.1 +# Created by: PyQt5 UI code generator 5.4.2 # # WARNING! All changes made in this file will be lost! @@ -12,7 +11,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_propertiesView(object): def setupUi(self, propertiesView): propertiesView.setObjectName("propertiesView") - propertiesView.resize(192, 159) + propertiesView.resize(192, 186) self.verticalLayout = QtWidgets.QVBoxLayout(propertiesView) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setObjectName("verticalLayout") @@ -72,14 +71,14 @@ class Ui_propertiesView(object): self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.cmbLabel) self.lblCompile = QtWidgets.QLabel(self.page) self.lblCompile.setObjectName("lblCompile") - self.formLayout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.lblCompile) + self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.lblCompile) self.chkCompile = chkOutlineCompile(self.page) self.chkCompile.setText("") self.chkCompile.setObjectName("chkCompile") - self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.chkCompile) + self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.chkCompile) self.lblGoal = QtWidgets.QLabel(self.page) self.lblGoal.setObjectName("lblGoal") - self.formLayout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.lblGoal) + self.formLayout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.lblGoal) self.txtGoal = lineEditView(self.page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) @@ -90,26 +89,14 @@ class Ui_propertiesView(object): self.txtGoal.setStyleSheet("border-radius: 6px;") self.txtGoal.setFrame(False) self.txtGoal.setObjectName("txtGoal") - self.formLayout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.txtGoal) - self.lblLabel_2 = QtWidgets.QLabel(self.page) - self.lblLabel_2.setObjectName("lblLabel_2") - self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.lblLabel_2) - self.cmbType = cmbOutlineTypeChoser(self.page) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.cmbType.sizePolicy().hasHeightForWidth()) - self.cmbType.setSizePolicy(sizePolicy) - self.cmbType.setFrame(False) - self.cmbType.setObjectName("cmbType") - self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.cmbType) + self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.txtGoal) self.verticalLayout_2.addLayout(self.formLayout) self.stack.addWidget(self.page) self.page_2 = QtWidgets.QWidget() self.page_2.setObjectName("page_2") self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.page_2) - self.verticalLayout_3.setSpacing(0) self.verticalLayout_3.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_3.setSpacing(0) self.verticalLayout_3.setObjectName("verticalLayout_3") self.formLayout_2 = QtWidgets.QFormLayout() self.formLayout_2.setObjectName("formLayout_2") @@ -187,7 +174,6 @@ class Ui_propertiesView(object): self.lblCompile.setText(_translate("propertiesView", "Compile")) self.lblGoal.setText(_translate("propertiesView", "Goal")) self.txtGoal.setPlaceholderText(_translate("propertiesView", "Word count")) - self.lblLabel_2.setText(_translate("propertiesView", "Text type:")) self.lblPOV_2.setText(_translate("propertiesView", "POV")) self.label_31.setText(_translate("propertiesView", "Status")) self.label_34.setText(_translate("propertiesView", "Label")) @@ -195,9 +181,8 @@ class Ui_propertiesView(object): self.label_36.setText(_translate("propertiesView", "Goal")) self.txtGoalMulti.setPlaceholderText(_translate("propertiesView", "Word count")) -from manuskript.ui.views.lineEditView import lineEditView -from manuskript.ui.views.cmbOutlineCharacterChoser import cmbOutlineCharacterChoser -from manuskript.ui.views.cmbOutlineTypeChoser import cmbOutlineTypeChoser from manuskript.ui.views.chkOutlineCompile import chkOutlineCompile -from manuskript.ui.views.cmbOutlineStatusChoser import cmbOutlineStatusChoser +from manuskript.ui.views.cmbOutlineCharacterChoser import cmbOutlineCharacterChoser from manuskript.ui.views.cmbOutlineLabelChoser import cmbOutlineLabelChoser +from manuskript.ui.views.cmbOutlineStatusChoser import cmbOutlineStatusChoser +from manuskript.ui.views.lineEditView import lineEditView diff --git a/manuskript/ui/views/propertiesView_ui.ui b/manuskript/ui/views/propertiesView_ui.ui index d76b439d..4e1549fe 100644 --- a/manuskript/ui/views/propertiesView_ui.ui +++ b/manuskript/ui/views/propertiesView_ui.ui @@ -7,14 +7,23 @@ 0 0 192 - 159 + 186 Form - + + 0 + + + 0 + + + 0 + + 0 @@ -40,7 +49,16 @@ - + + 0 + + + 0 + + + 0 + + 0 @@ -105,28 +123,28 @@ - + Compile - + - + Goal - + @@ -148,26 +166,6 @@ - - - - Text type: - - - - - - - - 0 - 0 - - - - false - - - @@ -177,7 +175,16 @@ 0 - + + 0 + + + 0 + + + 0 + + 0 @@ -319,11 +326,6 @@ QComboBox
    manuskript.ui.views.cmbOutlineLabelChoser.h
    - - cmbOutlineTypeChoser - QComboBox -
    manuskript.ui.views.cmbOutlineTypeChoser.h
    -
    From 8a90f3213f0e901b411d2249304f4e5a7d390bae Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 30 Mar 2016 10:19:44 +0200 Subject: [PATCH 080/103] Purging cmbOutlineTypeChoser --- manuskript/ui/views/cmbOutlineTypeChoser.py | 143 -------------------- 1 file changed, 143 deletions(-) delete mode 100644 manuskript/ui/views/cmbOutlineTypeChoser.py diff --git a/manuskript/ui/views/cmbOutlineTypeChoser.py b/manuskript/ui/views/cmbOutlineTypeChoser.py deleted file mode 100644 index b8e19922..00000000 --- a/manuskript/ui/views/cmbOutlineTypeChoser.py +++ /dev/null @@ -1,143 +0,0 @@ -#!/usr/bin/env python -# --!-- coding: utf8 --!-- -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QIcon, QBrush -from PyQt5.QtWidgets import QComboBox - -from manuskript.enums import Outline -from manuskript.ui.welcome import welcome - - -class cmbOutlineTypeChoser(QComboBox): - def __init__(self, parent=None): - QComboBox.__init__(self, parent) - self.activated[int].connect(self.submit) - self._column = Outline.type.value - self._index = None - self._indexes = None - self._updating = False - self._various = False - - def setModel(self, mdlOutline): - self.mdlOutline = mdlOutline - self.mdlOutline.dataChanged.connect(self.update) - self.updateItems() - - def updateItems(self): - self.clear() - types = [] - - for t in types: - self.addItem(QIcon.fromTheme(t[2]), t[1], t[0]) - - self._various = False - - if self._index or self._indexes: - self.updateSelectedItem() - - def setCurrentModelIndex(self, index): - self._indexes = None - if index.column() != self._column: - index = index.sibling(index.row(), self._column) - self._index = index - # Disabled if item type is not text - self.setEnabled(index.internalPointer().type() in ["t2t", "html", "txt", "md"]) - self.updateItems() - self.updateSelectedItem() - - def setCurrentModelIndexes(self, indexes): - self._indexes = [] - self._index = None - - hasText = False - for i in indexes: - if i.isValid(): - if i.column() != self._column: - i = i.sibling(i.row(), self._column) - self._indexes.append(i) - if i.internalPointer().type() in ["t2t", "html", "txt", "md"]: - hasText = True - - self.setEnabled(hasText) - - self.updateItems() - self.updateSelectedItem() - - def update(self, topLeft, bottomRight): - - if self._updating: - # We are currently putting data in the model, so no updates - return - - if self._index: - if topLeft.row() <= self._index.row() <= bottomRight.row(): - self.updateSelectedItem() - - elif self._indexes: - update = False - for i in self._indexes: - if topLeft.row() <= i.row() <= bottomRight.row(): - update = True - if update: - self.updateSelectedItem() - - def getType(self, index): - item = index.internalPointer() - return item.type() - - def updateSelectedItem(self): - - if self._updating: - return - - if self._index: - _type = self.getType(self._index) - i = self.findData(_type) - if i != -1: - self.setCurrentIndex(i) - - elif self._indexes: - types = [] - same = True - - for i in self._indexes: - types.append(self.getType(i)) - - for t in types[1:]: - if t != types[0]: - same = False - break - - if same: - self._various = False - i = self.findData(types[0]) - if i != -1: - self.setCurrentIndex(i) - - else: - if not self._various: - self.insertItem(0, self.tr("Various")) - f = self.font() - f.setItalic(True) - self.setItemData(0, f, Qt.FontRole) - self.setItemData(0, QBrush(Qt.darkGray), Qt.ForegroundRole) - self._various = True - self.setCurrentIndex(0) - - else: - self.setCurrentIndex(0) - - def submit(self, idx): - if self._index: - self.mdlOutline.setData(self._index, self.currentData()) - - elif self._indexes: - value = self.currentData() - - if self._various and self.currentIndex() == 0: - return - - self._updating = True - for i in self._indexes: - self.mdlOutline.setData(i, value) - self._updating = False From a5b0b6cd952aba84a4923ad501f94446d612fbaa Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 30 Mar 2016 10:30:41 +0200 Subject: [PATCH 081/103] Purging types from textEditView --- manuskript/ui/views/textEditView.py | 155 +++++----------------------- 1 file changed, 27 insertions(+), 128 deletions(-) diff --git a/manuskript/ui/views/textEditView.py b/manuskript/ui/views/textEditView.py index f7061031..1ba1f9f6 100644 --- a/manuskript/ui/views/textEditView.py +++ b/manuskript/ui/views/textEditView.py @@ -13,9 +13,6 @@ from manuskript.functions import toString from manuskript.models.outlineModel import outlineModel from manuskript.ui.editors.MMDHighlighter import MMDHighlighter from manuskript.ui.editors.basicHighlighter import basicHighlighter -from manuskript.ui.editors.t2tFunctions import t2tClearFormat -from manuskript.ui.editors.t2tFunctions import t2tFormatSelection -from manuskript.ui.editors.t2tHighlighter import t2tHighlighter from manuskript.ui.editors.textFormat import textFormat try: @@ -151,40 +148,25 @@ class textEditView(QTextEdit): self.updateText() def setupEditorForIndex(self, index): - # what type of text are we editing? + # In which model are we editing? if type(index.model()) != outlineModel: self._textFormat = "text" return + # what type of text are we editing? if self._column not in [Outline.text.value, Outline.notes.value]: self._textFormat = "text" else: - item = index.internalPointer() - if item.isHTML(): - self._textFormat = "html" - elif item.isT2T(): - self._textFormat = "t2t" - elif item.isMD(): - self._textFormat = "md" - else: - self._textFormat = "text" - - # Accept richtext maybe - if self._textFormat == "html": - self.setAcceptRichText(True) - else: - self.setAcceptRichText(False) + self._textFormat = "md" # Setting highlighter if self._highlighting: item = index.internalPointer() - if self._column == Outline.text.value and not (item.isT2T() or item.isMD()): - self.highlighter = basicHighlighter(self) - elif item.isT2T(): - self.highlighter = t2tHighlighter(self) - elif item.isMD(): + if self._column in [Outline.text.value, Outline.notes.value]: self.highlighter = MMDHighlighter(self) + else: + self.highlighter = basicHighlighter(self) self.highlighter.setDefaultBlockFormat(self._defaultBlockFormat) @@ -244,15 +226,7 @@ class textEditView(QTextEdit): # self.objectName(), self.parent().objectName())) if topLeft.row() <= self._index.row() <= bottomRight.row(): - if self._column == Outline.text.value and \ - topLeft.column() <= Outline.type.value <= bottomRight.column(): - # If item type change, and we display the main text, - # we reset the index to set the proper - # highlighter and other defaults - self.setupEditorForIndex(self._index) - self.updateText() - - elif topLeft.column() <= self._column <= bottomRight.column(): + if topLeft.column() <= self._column <= bottomRight.column(): self.updateText() elif self._indexes: @@ -288,15 +262,9 @@ class textEditView(QTextEdit): self._updating = True if self._index: self.disconnectDocument() - if self._textFormat == "html": - if self.toHtml() != toString(self._model.data(self._index)): - # print(" Updating html") - html = self._model.data(self._index) - self.document().setHtml(toString(html)) - else: - if self.toPlainText() != toString(self._model.data(self._index)): - # print(" Updating plaintext") - self.document().setPlainText(toString(self._model.data(self._index))) + if self.toPlainText() != toString(self._model.data(self._index)): + # print(" Updating plaintext") + self.document().setPlainText(toString(self._model.data(self._index))) self.reconnectDocument() elif self._indexes: @@ -313,7 +281,6 @@ class textEditView(QTextEdit): break if same: - # Assuming that we don't use HTML with multiple items self.document().setPlainText(t[0]) else: self.document().setPlainText("") @@ -332,26 +299,11 @@ class textEditView(QTextEdit): # print("Submitting", self.objectName()) if self._index: # item = self._index.internalPointer() - if self._textFormat == "html": - if self.toHtml() != self._model.data(self._index): - # print(" Submitting html") - self._updating = True - html = self.toHtml() - # We don't store paragraph and font settings - html = re.sub(r"font-family:.*?;\s*", "", html) - html = re.sub(r"font-size:.*?;\s*", "", html) - html = re.sub(r"margin-.*?;\s*", "", html) - html = re.sub(r"text-indent:.*?;\s*", "", html) - html = re.sub(r"line-height:.*?;\s*", "", html) - # print("Submitting:", html) - self._model.setData(self._index, html) - self._updating = False - else: - if self.toPlainText() != self._model.data(self._index): - # print(" Submitting plain text") - self._updating = True - self._model.setData(self._index, self.toPlainText()) - self._updating = False + if self.toPlainText() != self._model.data(self._index): + # print(" Submitting plain text") + self._updating = True + self._model.setData(self._index, self.toPlainText()) + self._updating = False elif self._indexes: self._updating = True @@ -503,70 +455,17 @@ class textEditView(QTextEdit): def applyFormat(self, _format): - if self._textFormat == "html": - - if _format == "Clear": - - cursor = self.textCursor() - - if _format == "Clear": - fmt = self._defaultCharFormat - cursor.setCharFormat(fmt) - bf = self._defaultBlockFormat - cursor.setBlockFormat(bf) - - elif _format in ["Bold", "Italic", "Underline"]: - - cursor = self.textCursor() - - # If no selection, selects the word in which the cursor is now - if not cursor.hasSelection(): - cursor.movePosition(QTextCursor.StartOfWord, - QTextCursor.MoveAnchor) - cursor.movePosition(QTextCursor.EndOfWord, - QTextCursor.KeepAnchor) - - fmt = cursor.charFormat() - - if _format == "Bold": - fmt.setFontWeight(QFont.Bold if fmt.fontWeight() != QFont.Bold else QFont.Normal) - elif _format == "Italic": - fmt.setFontItalic(not fmt.fontItalic()) - elif _format == "Underline": - fmt.setFontUnderline(not fmt.fontUnderline()) - - fmt2 = self._defaultCharFormat - fmt2.setFontWeight(fmt.fontWeight()) - fmt2.setFontItalic(fmt.fontItalic()) - fmt2.setFontUnderline(fmt.fontUnderline()) - - cursor.mergeCharFormat(fmt2) - - elif _format in ["Left", "Center", "Right", "Justify"]: - - cursor = self.textCursor() - - # bf = cursor.blockFormat() - bf = QTextBlockFormat() - bf.setAlignment( - Qt.AlignLeft if _format == "Left" else - Qt.AlignHCenter if _format == "Center" else - Qt.AlignRight if _format == "Right" else - Qt.AlignJustify) - - cursor.setBlockFormat(bf) - self.setTextCursor(cursor) - - elif self._textFormat == "t2t": - if _format == "Bold": - t2tFormatSelection(self, 0) - elif _format == "Italic": - t2tFormatSelection(self, 1) - elif _format == "Underline": - t2tFormatSelection(self, 2) - elif _format == "Clear": - t2tClearFormat(self) - - elif self._textFormat == "md": + if self._textFormat == "md": # FIXME print("Not implemented yet.") + + # Model: from t2tFunctions + # if self._textFormat == "t2t": + # if _format == "Bold": + # t2tFormatSelection(self, 0) + # elif _format == "Italic": + # t2tFormatSelection(self, 1) + # elif _format == "Underline": + # t2tFormatSelection(self, 2) + # elif _format == "Clear": + # t2tClearFormat(self) From 8e7e953a378fc7d1f22d395b216658215f5588fa Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 30 Mar 2016 10:48:18 +0200 Subject: [PATCH 082/103] Purging types from settings --- manuskript/settingsWindow.py | 10 --------- manuskript/ui/settings_ui.py | 28 +---------------------- manuskript/ui/settings_ui.ui | 43 +----------------------------------- 3 files changed, 2 insertions(+), 79 deletions(-) diff --git a/manuskript/settingsWindow.py b/manuskript/settingsWindow.py index 9c2b7db4..686b8434 100644 --- a/manuskript/settingsWindow.py +++ b/manuskript/settingsWindow.py @@ -79,15 +79,6 @@ class settingsWindow(QWidget, Ui_Settings): self.chkAutoLoad.setChecked(autoLoad) self.chkAutoLoad.stateChanged.connect(self.saveSettingsChanged) - dtt = welcome.defaultTextType() - self.cmbDefaultTextType.clear() - for t in dtt: - self.cmbDefaultTextType.addItem(QIcon.fromTheme(t[2]), t[1], t[0]) - i = self.cmbDefaultTextType.findData(settings.defaultTextType) - if i != -1: - self.cmbDefaultTextType.setCurrentIndex(i) - self.cmbDefaultTextType.currentIndexChanged.connect(self.saveSettingsChanged) - # Revisions opt = settings.revisions self.chkRevisionsKeep.setChecked(opt["keep"]) @@ -253,7 +244,6 @@ class settingsWindow(QWidget, Ui_Settings): settings.autoSaveNoChangesDelay = int(self.txtAutoSaveNoChanges.text()) self.mw.saveTimer.setInterval(settings.autoSaveDelay * 60 * 1000) self.mw.saveTimerNoChanges.setInterval(settings.autoSaveNoChangesDelay * 1000) - settings.defaultTextType = self.cmbDefaultTextType.currentData() #################################################################################################### # REVISION # diff --git a/manuskript/ui/settings_ui.py b/manuskript/ui/settings_ui.py index 44bb809c..9c5cedac 100644 --- a/manuskript/ui/settings_ui.py +++ b/manuskript/ui/settings_ui.py @@ -11,7 +11,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Settings(object): def setupUi(self, Settings): Settings.setObjectName("Settings") - Settings.resize(658, 585) + Settings.resize(658, 507) self.horizontalLayout_8 = QtWidgets.QHBoxLayout(Settings) self.horizontalLayout_8.setObjectName("horizontalLayout_8") self.lstMenu = QtWidgets.QListWidget(Settings) @@ -215,30 +215,6 @@ class Ui_Settings(object): self.chkSaveToZip.setObjectName("chkSaveToZip") self.verticalLayout_6.addWidget(self.chkSaveToZip) self.verticalLayout_7.addWidget(self.groupBox) - self.groupBox_11 = QtWidgets.QGroupBox(self.stackedWidgetPage1) - font = QtGui.QFont() - font.setBold(True) - font.setWeight(75) - self.groupBox_11.setFont(font) - self.groupBox_11.setObjectName("groupBox_11") - self.verticalLayout_19 = QtWidgets.QVBoxLayout(self.groupBox_11) - self.verticalLayout_19.setObjectName("verticalLayout_19") - self.label_35 = QtWidgets.QLabel(self.groupBox_11) - font = QtGui.QFont() - font.setBold(False) - font.setWeight(50) - self.label_35.setFont(font) - self.label_35.setWordWrap(True) - self.label_35.setObjectName("label_35") - self.verticalLayout_19.addWidget(self.label_35) - self.cmbDefaultTextType = QtWidgets.QComboBox(self.groupBox_11) - font = QtGui.QFont() - font.setBold(False) - font.setWeight(50) - self.cmbDefaultTextType.setFont(font) - self.cmbDefaultTextType.setObjectName("cmbDefaultTextType") - self.verticalLayout_19.addWidget(self.cmbDefaultTextType) - self.verticalLayout_7.addWidget(self.groupBox_11) spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.verticalLayout_7.addItem(spacerItem2) self.stack.addWidget(self.stackedWidgetPage1) @@ -1673,8 +1649,6 @@ class Ui_Settings(object): self.chkSaveOnQuit.setText(_translate("Settings", "Save on quit")) self.chkSaveToZip.setToolTip(_translate("Settings", "

    If you check this option, your project will be save as one single file. Easier to copy or backup, but does not allow collaborative editing, or versionning.
    If this is unchecked, your project will be save as a folder containing many small files.

    ")) self.chkSaveToZip.setText(_translate("Settings", "Save to one single file")) - self.groupBox_11.setTitle(_translate("Settings", "Default text format")) - self.label_35.setText(_translate("Settings", "The format set by default when you create a new text item. You can change this on a per item basis.")) self.lblTitleGeneral_2.setText(_translate("Settings", "Revisions")) self.label_44.setText(_translate("Settings", "Revisions are a way to keep track of modifications. For each text item, it stores any changes you make to the main text, allowing you to see and restoring previous versions.")) self.chkRevisionsKeep.setText(_translate("Settings", "Keep revisions")) diff --git a/manuskript/ui/settings_ui.ui b/manuskript/ui/settings_ui.ui index 7991b0ee..2d6492af 100644 --- a/manuskript/ui/settings_ui.ui +++ b/manuskript/ui/settings_ui.ui @@ -7,7 +7,7 @@ 0 0 658 - 585 + 507
    @@ -424,47 +424,6 @@ text-align:center; - - - - - 75 - true - - - - Default text format - - - - - - - 50 - false - - - - The format set by default when you create a new text item. You can change this on a per item basis. - - - true - - - - - - - - 50 - false - - - - - - - From 65511660000f04fe66c803af6ecd0640d3bd3f29 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 30 Mar 2016 11:00:27 +0200 Subject: [PATCH 083/103] Purging types from several files --- manuskript/models/references.py | 26 +------------------------- manuskript/ui/editors/editorWidget.py | 2 +- manuskript/ui/revisions.py | 6 +----- 3 files changed, 3 insertions(+), 31 deletions(-) diff --git a/manuskript/models/references.py b/manuskript/models/references.py index a8a86cb7..10cbce6f 100644 --- a/manuskript/models/references.py +++ b/manuskript/models/references.py @@ -167,7 +167,7 @@ def infos(ref): ls=ls.replace("\n", "
    ")) if ls.strip() else "", notes="

    {notesTitle}
    {notes}

    ".format( notesTitle=notesTitle, - notes=linkifyAllRefs(basicT2TFormat(notes))) if notes.strip() else "", + notes=linkifyAllRefs(notes)) if notes.strip() else "", references=listReferences(ref) ) @@ -582,30 +582,6 @@ def listReferences(ref, title=qApp.translate("references", "Referenced in:")): ref=listRefs) if listRefs else "" -def basicT2TFormat(text, formatting=True, EOL=True, titles=True): - """A very basic t2t formatter to display notes and texts.""" - text = text.splitlines() - for n, line in enumerate(text): - if formatting: - line = re.sub("\*\*(.*?)\*\*", "\\1", line) - line = re.sub("//(.*?)//", "\\1", line) - line = re.sub("__(.*?)__", "\\1", line) - - if titles: - for i in range(1, 6): - r1 = '^\s*{s}([^=].*[^=]){s}\s*$'.format(s="=" * i) - r2 = '^\s*{s}([^\+].*[^\+]){s}\s*$'.format(s="\\+" * i) - t = "\\1".format(n=i) - line = re.sub(r1, t, line) - line = re.sub(r2, t, line) - text[n] = line - text = "\n".join(text) - if EOL: - text = text.replace("\n", "
    ") - - return text - - def basicFormat(text): if not text: return "" diff --git a/manuskript/ui/editors/editorWidget.py b/manuskript/ui/editors/editorWidget.py index 20487b38..d3653137 100644 --- a/manuskript/ui/editors/editorWidget.py +++ b/manuskript/ui/editors/editorWidget.py @@ -107,7 +107,7 @@ class editorWidget(QWidget, Ui_editorWidget_ui): autoResize=True) edt.setFrameShape(QFrame.NoFrame) edt.setStyleSheet("background: {};".format(settings.textEditor["background"])) - edt.setStatusTip("{} ({})".format(itm.path(), itm.type())) + edt.setStatusTip("{}".format(itm.path())) self.toggledSpellcheck.connect(edt.toggleSpellcheck, AUC) self.dictChanged.connect(edt.setDict, AUC) # edt.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) diff --git a/manuskript/ui/revisions.py b/manuskript/ui/revisions.py index bdfc0062..0522b4fa 100644 --- a/manuskript/ui/revisions.py +++ b/manuskript/ui/revisions.py @@ -144,8 +144,6 @@ class revisions(QWidget, Ui_revisions): textBefore = [r[1] for r in item.revisions() if r[0] == ts][0] if self.actShowVersion.isChecked(): - if item.type() == "t2t": - textBefore = Ref.basicT2TFormat(textBefore) self.view.setText(textBefore) return @@ -160,7 +158,7 @@ class revisions(QWidget, Ui_revisions): else: _format = lambda x: x - extra = "" if item.type() == "html" else "
    " + extra = "
    " diff = [d for d in diff if d and not d[:2] == "? "] mydiff = "" skip = False @@ -177,8 +175,6 @@ class revisions(QWidget, Ui_revisions): # Same line if op == " " and not self.actDiffOnly.isChecked(): - if item.type() == "t2t": - txt = Ref.basicT2TFormat(txt) mydiff += "{}{}".format(txt, extra) elif op == "- " and op2 == "+ ": From 54ab5f04556c6c207311bb303f8130f5e0c9f2a8 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 30 Mar 2016 11:14:05 +0200 Subject: [PATCH 084/103] Purging types from file loader --- manuskript/load_save/version_1.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index 177cee9d..f706919d 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -18,7 +18,7 @@ from PyQt5.QtGui import QColor, QStandardItem from manuskript import settings from manuskript.enums import Character, World, Plot, PlotStep, Outline -from manuskript.functions import mainWindow, iconColor, iconFromColorString +from manuskript.functions import mainWindow, iconColor, iconFromColorString, HTML2PlainText from lxml import etree as ET from manuskript.load_save.version_0 import loadFilesFromZip @@ -529,12 +529,7 @@ def exportOutlineItem(root): content = outlineToMMD(child) files.append((fpath, content)) - elif child.type() in ["txt", "t2t", "md"]: - content = outlineToMMD(child) - files.append((spath, content)) - - elif child.type() in ["html"]: - # Save as html. Not the most beautiful, but hey. + elif child.type() == "md": content = outlineToMMD(child) files.append((spath, content)) @@ -912,6 +907,14 @@ def outlineFromMMD(text, parent): # Store body item.setData(Outline.text.value, str(body)) + # Set file format to "md" + # (Old version of manuskript had different file formats: text, t2t, html and md) + # If file format is html, convert to plain text: + if item.type() == "html": + item.setData(Outline.text.value, HTML2PlainText(body)) + if item.type() in ["txt", "t2t", "html"]: + item.setData(Outline.type.value, "md") + return item From 7e954eab5e3525c13b7cd51cbd5b5565488c5b2c Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 30 Mar 2016 11:21:54 +0200 Subject: [PATCH 085/103] Purging types from outlineModel and others --- manuskript/exporter/arbo.py | 4 +--- manuskript/load_save/version_1.py | 2 +- manuskript/models/outlineModel.py | 26 ++++-------------------- manuskript/ui/editors/textFormat.py | 4 ---- manuskript/ui/tools/frequencyAnalyzer.py | 12 ----------- 5 files changed, 6 insertions(+), 42 deletions(-) diff --git a/manuskript/exporter/arbo.py b/manuskript/exporter/arbo.py index cece3812..c401eedb 100644 --- a/manuskript/exporter/arbo.py +++ b/manuskript/exporter/arbo.py @@ -29,9 +29,7 @@ class arboExporter(): writeItem(c, path2) else: - ext = ".t2t" if item.isT2T() else \ - ".html" if item.isHTML() else \ - ".txt" + ext = ".md" path2 = os.path.join(path, item.title() + ext) f = open(path2, "w") text = self.formatText(item.text(), item.type()) diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index f706919d..3e646705 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -560,7 +560,7 @@ def outlineItemPath(item): name = "{ID}-{name}{ext}".format( ID=str(item.row()).zfill(len(str(siblings))), name=slugify(item.title()), - ext="" if item.type() == "folder" else ".md" # ".{}".format(item.type()) # To have .txt, .t2t, .html, ... + ext="" if item.type() == "folder" else ".md" ) return outlineItemPath(item.parent()) + [name] diff --git a/manuskript/models/outlineModel.py b/manuskript/models/outlineModel.py index c018bc73..0fa485c4 100644 --- a/manuskript/models/outlineModel.py +++ b/manuskript/models/outlineModel.py @@ -481,12 +481,8 @@ class outlineItem(): elif role == Qt.DecorationRole and column == Outline.title.value: if self.isFolder(): return QIcon.fromTheme("folder") - elif self.isText: + elif self.isMD(): return QIcon.fromTheme("text-x-generic") - elif self.isT2T() or self.isMD(): - return QIcon.fromTheme("text-x-script") - elif self.isHTML(): - return QIcon.fromTheme("text-html") # elif role == Qt.ForegroundRole: # if self.isCompile() in [0, "0"]: @@ -527,15 +523,7 @@ class outlineItem(): updateWordCount = not Outline(column) in self._data or self._data[Outline(column)] != data # Stuff to do before - if column == Outline.type.value: - oldType = self._data[Outline.type] - if oldType == "html" and data in ["txt", "t2t", "md"]: - # Resource inneficient way to convert HTML to plain text - self._data[Outline.text] = HTML2PlainText(self._data[Outline.text]) - elif oldType in ["txt", "t2t", "md"] and data == "html" and Outline.text in self._data: - self._data[Outline.text] = self._data[Outline.text].replace("\n", "
    ") - - elif column == Outline.text.value: + if column == Outline.text.value: self.addRevision() # Setting data @@ -658,8 +646,8 @@ class outlineItem(): def isFolder(self): return self._data[Outline.type] == "folder" - def isT2T(self): - return self._data[Outline.type] == "t2t" + def isText(self): + return self._data[Outline.type] == "md" def isMD(self): return self._data[Outline.type] == "md" @@ -667,12 +655,6 @@ class outlineItem(): def isMMD(self): return self._data[Outline.type] == "md" - def isHTML(self): - return self._data[Outline.type] == "html" - - def isText(self): - return self._data[Outline.type] == "txt" - def text(self): return self.data(Outline.text.value) diff --git a/manuskript/ui/editors/textFormat.py b/manuskript/ui/editors/textFormat.py index e09f6807..2041b5be 100644 --- a/manuskript/ui/editors/textFormat.py +++ b/manuskript/ui/editors/textFormat.py @@ -61,10 +61,6 @@ class textFormat(QWidget, Ui_textFormat): elif item.isText(): self.align.setVisible(False) - self.format.setVisible(False) - - elif item.isT2T() or item.isMD(): - self.align.setVisible(False) def setFormat(self): act = self.sender() diff --git a/manuskript/ui/tools/frequencyAnalyzer.py b/manuskript/ui/tools/frequencyAnalyzer.py index 6ce619f7..e4231034 100644 --- a/manuskript/ui/tools/frequencyAnalyzer.py +++ b/manuskript/ui/tools/frequencyAnalyzer.py @@ -45,12 +45,6 @@ class frequencyAnalyzer(QWidget, Ui_FrequencyAnalyzer): def listPhrases(item, nMin, nMax): txt = item.text() - # Convert to plain text - if item.isHTML(): - e = QTextEdit() - e.setHtml(item.text()) - txt = e.toPlainText() - # Split into words lst = re.findall(r"[\w']+", txt) # Ignores punctuation # lst = re.findall(r"[\w']+|[.,!?;]", txt) # Includes punctuation @@ -92,12 +86,6 @@ class frequencyAnalyzer(QWidget, Ui_FrequencyAnalyzer): def listWords(item): txt = item.text() - # Convert to plain text - if item.isHTML(): - e = QTextEdit() - e.setHtml(item.text()) - txt = e.toPlainText() - lst = re.findall(r"[\w']+", txt) for c in item.children(): lst += listWords(c) From 47d87115e6a827a4d7ba3ec1725021e070d28edc Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 30 Mar 2016 11:36:25 +0200 Subject: [PATCH 086/103] Purging some remaining t2tfiles --- manuskript/ui/editors/MDFunctions.py | 19 + manuskript/ui/editors/t2tFunctions.py | 376 ------------- manuskript/ui/editors/t2tHighlighter.py | 547 ------------------- manuskript/ui/editors/t2tHighlighterStyle.py | 252 --------- manuskript/ui/views/textEditView.py | 21 +- 5 files changed, 28 insertions(+), 1187 deletions(-) create mode 100644 manuskript/ui/editors/MDFunctions.py delete mode 100644 manuskript/ui/editors/t2tFunctions.py delete mode 100644 manuskript/ui/editors/t2tHighlighter.py delete mode 100644 manuskript/ui/editors/t2tHighlighterStyle.py diff --git a/manuskript/ui/editors/MDFunctions.py b/manuskript/ui/editors/MDFunctions.py new file mode 100644 index 00000000..0fbd8b85 --- /dev/null +++ b/manuskript/ui/editors/MDFunctions.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import re + +from PyQt5.QtCore import QRegExp +from PyQt5.QtGui import QTextCursor + + +def MDFormatSelection(editor, style): + """ + Formats the current selection of ``editor`` in the format given by ``style``, + style being: + 0: bold + 1: italic + 2: code + """ + print("Formatting:", style, " (Unimplemented yet !)") + # FIXME \ No newline at end of file diff --git a/manuskript/ui/editors/t2tFunctions.py b/manuskript/ui/editors/t2tFunctions.py deleted file mode 100644 index f226d5d7..00000000 --- a/manuskript/ui/editors/t2tFunctions.py +++ /dev/null @@ -1,376 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import re - -from PyQt5.QtCore import QRegExp -from PyQt5.QtGui import QTextCursor - - -def t2tFormatSelection(editor, style): - """ - Formats the current selection of ``editor`` in the format given by ``style``, - style being: - 0: bold - 1: italic - 2: underline - 3: strike - 4: code - 5: tagged - """ - print("Formatting:", style) - formatChar = "*/_-`'"[style] - - # FIXME: doesn't work if selection spans over several blocks. - - cursor = editor.textCursor() - cursor.beginEditBlock() - - if cursor.hasSelection(): - pass - else: - # If no selection, selects the word in which the cursor is now - cursor.movePosition(QTextCursor.StartOfWord, - QTextCursor.MoveAnchor) - cursor.movePosition(QTextCursor.EndOfWord, - QTextCursor.KeepAnchor) - - if not cursor.hasSelection(): - # No selection means we are outside a word, - # so we insert markup and put cursor in the middle - cursor.insertText(formatChar * 4) - cursor.setPosition(cursor.position() - 2) - cursor.endEditBlock() - editor.setTextCursor(cursor) - # And we are done - return - - # Get start and end of selection - start = cursor.selectionStart() - cursor.block().position() - end = cursor.selectionEnd() - cursor.block().position() - if start > end: - start, end = end, start - - # Whole block - text = cursor.block().text() - - # Adjusts selection to exclude the markup - while text[start:start + 1] == formatChar: - start += 1 - while text[end - 1:end] == formatChar: - end -= 1 - - # Get the text without formatting, and the array of format - fText, fArray = textToFormatArrayNoMarkup(text) - # Get the translated start and end of selection in the unformated text - tStart, tEnd = translateSelectionToUnformattedText(text, start, end) - - # We want only the array that contains the propper formatting - propperArray = fArray[style] - - if 0 in propperArray[tStart:tEnd]: - # have some unformated text in the selection, so we format the - # whole selection - propperArray = propperArray[:tStart] + [1] * \ - (tEnd - tStart) + propperArray[tEnd:] - else: - # The whole selection is already formatted, so we remove the - # formatting - propperArray = propperArray[:tStart] + [0] * \ - (tEnd - tStart) + propperArray[tEnd:] - - fArray = fArray[0:style] + [propperArray] + fArray[style + 1:] - - text = reformatText(fText, fArray) - - # Replaces the whole block - cursor.movePosition(QTextCursor.StartOfBlock) - cursor.movePosition(QTextCursor.EndOfBlock, - QTextCursor.KeepAnchor) - cursor.insertText(text) - - cursor.setPosition(end + cursor.block().position()) - - cursor.endEditBlock() - - editor.setTextCursor(cursor) - - -def t2tClearFormat(editor): - """Clears format on ``editor``'s current selection.""" - - cursor = editor.textCursor() - cursor.beginEditBlock() - - text = cursor.selectedText() - t, a = textToFormatArrayNoMarkup(text) - - cursor.insertText(t) - cursor.endEditBlock() - editor.setTextCursor(cursor) - - -def textToFormatArray(text): - """ - Take some text and returns an array of array containing informations - about how the text is formatted: - r = [ [0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0], - [0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1], - ... - ] - Each sub-array is for one of the beautifier: - 0: bold - 1: italic - 2: underline - 3: strike - 4: code - 5: tagged - - Each sub-array contains an element for each character of the text, with the - value 1 if it is formatted in the specific format, -1 if it is markup, and - 0 otherwise. - - removeMarks returns a both the array and a string, in which all of the - formatting marks have been removed. - """ - - result = [] - - for markup in ["\*", "/", "_", "-", "`", "\'"]: - - rList = [] - - r = QRegExp(r'(' + markup * 2 + ')(.+)(' + markup * 2 + ')') - r.setMinimal(True) - pos = r.indexIn(text, 0) - lastPos = 0 - while pos >= 0: - # We have a winner - rList += [0] * (pos - lastPos) - rList += [2] * 2 - rList += [1] * len(r.cap(2)) - rList += [2] * 2 - lastPos = pos + len(r.cap(0)) - pos = r.indexIn(text, len(rList)) - - if len(rList) < len(text): - rList += [0] * (len(text) - len(rList)) - - result.append(rList) - - return result - - -def textToFormatArrayNoMarkup(text): - """ - Same as textToFormatArray, except that it removes all the markup from the - text and returns two elements: - the array - the text without markup - """ - - r = textToFormatArray(text) - result = [[], [], [], [], [], []] - rText = "" - - for i in range(len(text)): - t = max([k[i] for k in r]) # kind of flattens all the format array - if t != 2: - rText += text[i] - [result[k].append(r[k][i]) for k in range(len(r))] - - return rText, result - - -def translateSelectionToUnformattedText(text, start, end): - """ - Translate the start / end of selection from a formatted text to an - unformatted one. - """ - r = textToFormatArray(text) - - rStart, rEnd = start, end - - for i in range(len(text)): - t = max([k[i] for k in r]) # kind of flattens all the format array - if t == 2: # t == 2 means this character is markup - if i <= start: rStart -= 1 - if i < end: rEnd -= 1 - - return rStart, rEnd - - -def translateSelectionToFormattedText(text, start, end): - """ - Translate the start / end of selection from a formatted text to an - unformatted one. - """ - r = textToFormatArray(text) - - rStart, rEnd = start, end - - for i in range(len(text)): - t = max([k[i] for k in r]) # kind of flattens all the format array - if t == 2: # t == 2 means this character is markup - if i <= start: rStart -= 1 - if i < end: rEnd -= 1 - - return rStart, rEnd - - -def printArray(array): - print(("".join([str(j) for j in array]))) - - -def printArrays(arrays): - for i in arrays: printArray(i) - - -def reformatText(text, markupArray): - """ - Takes a text without formatting markup, and an array generated by - textToFormatArray, and adds the propper markup. - """ - - rText = "" - markup = ["**", "//", "__", "--", "``", "''"] - - for k, m in enumerate(markupArray): - # m = markupArray[k] - _open = False # Are we in an _openned markup - d = 0 - alreadySeen = [] - for i, t in enumerate(text): - insert = False - if not _open and m[i] == 1: - insert = True - _open = True - - if _open and m[i] == 0: - insert = True - _open = False - if _open and m[i] > 1: - z = i - while m[z] == m[i]: z += 1 - if m[z] != 1 and not m[i] in alreadySeen: - insert = True - _open = False - alreadySeen.append(m[i]) - if insert: - rText += markup[k] - for j, m2 in enumerate(markupArray): - # The other array still have the same length - if j > k: - # Insert 2 for bold, 3 for italic, etc. - m2.insert(i + d, k + 2) - m2.insert(i + d, k + 2) - alreadySeen = [] - d += 2 - rText += t - if _open: - rText += markup[k] - for j, m2 in enumerate(markupArray): - # The other array still have the same length - if j > k: - # Insert 2 for bold, 3 for italic, etc. - m2.insert(i + d, k + 2) - m2.insert(i + d, k + 2) - text = rText - rText = "" - - # Clean up - # Exclude first and last space of the markup - for markup in ["\*", "/", "_", "-", "`", "\'"]: - # r = QRegExp(r'(' + markup * 2 + ')(\s+)(.+)(' + markup * 2 + ')') - # r.setMinimal(True) - # text.replace(r, "\\2\\1\\3\\4") - text = re.sub(r'(' + markup * 2 + ')(\s+?)(.+?)(' + markup * 2 + ')', - "\\2\\1\\3\\4", - text) - # r = QRegExp(r'(' + markup * 2 + ')(.+)(\s+)(' + markup * 2 + ')') - # r.setMinimal(True) - # text.replace(r, "\\1\\2\\4\\3") - text = re.sub(r'(' + markup * 2 + ')(.+?)(\s+?)(' + markup * 2 + ')', - "\\1\\2\\4\\3", - text) - - return text - - -def cleanFormat(text): - """Makes markup clean (removes doubles, etc.)""" - t, a = textToFormatArrayNoMarkup(text) - return reformatText(t, a) - - -class State: - NORMAL = 0 - TITLE_1 = 1 - TITLE_2 = 2 - TITLE_3 = 3 - TITLE_4 = 4 - TITLE_5 = 5 - NUMBERED_TITLE_1 = 6 - NUMBERED_TITLE_2 = 7 - NUMBERED_TITLE_3 = 8 - NUMBERED_TITLE_4 = 9 - NUMBERED_TITLE_5 = 10 - TITLES = [TITLE_1, TITLE_2, TITLE_3, TITLE_4, TITLE_5, NUMBERED_TITLE_1, - NUMBERED_TITLE_2, NUMBERED_TITLE_3, NUMBERED_TITLE_4, - NUMBERED_TITLE_5] - # AREA - COMMENT_AREA = 11 - CODE_AREA = 12 - RAW_AREA = 13 - TAGGED_AREA = 14 - # AREA MARKUP - COMMENT_AREA_BEGINS = 15 - COMMENT_AREA_ENDS = 16 - CODE_AREA_BEGINS = 17 - CODE_AREA_ENDS = 18 - RAW_AREA_BEGINS = 19 - RAW_AREA_ENDS = 20 - TAGGED_AREA_BEGINS = 21 - TAGGED_AREA_ENDS = 22 - # LINE - COMMENT_LINE = 30 - CODE_LINE = 31 - RAW_LINE = 32 - TAGGED_LINE = 33 - SETTINGS_LINE = 34 - BLOCKQUOTE_LINE = 35 - HORIZONTAL_LINE = 36 - HEADER_LINE = 37 - # LIST - LIST_BEGINS = 40 - LIST_ENDS = 41 - LIST_EMPTY = 42 - LIST_BULLET = 43 - LIST_BULLET_ENDS = 44 - LIST = [40, 41, 42] + list(range(100, 201)) - # TABLE - TABLE_LINE = 50 - TABLE_HEADER = 51 - # OTHER - MARKUP = 60 - LINKS = 61 - MACRO = 62 - DEFAULT = 63 - - @staticmethod - def titleLevel(state): - """ - Returns the level of the title, from the block state. - """ - return { - State.TITLE_1: 1, - State.TITLE_2: 2, - State.TITLE_3: 3, - State.TITLE_4: 4, - State.TITLE_5: 5, - State.NUMBERED_TITLE_1: 1, - State.NUMBERED_TITLE_2: 2, - State.NUMBERED_TITLE_3: 3, - State.NUMBERED_TITLE_4: 4, - State.NUMBERED_TITLE_5: 5, - }.get(state, -1) diff --git a/manuskript/ui/editors/t2tHighlighter.py b/manuskript/ui/editors/t2tHighlighter.py deleted file mode 100644 index c5f52e83..00000000 --- a/manuskript/ui/editors/t2tHighlighter.py +++ /dev/null @@ -1,547 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf8 -*- - - -# This is aiming at implementing every rule from www.txt2tags.org/rules.html -# But we're not there yet. - -# FIXME: macro words not hilighted properly if at the begining of a line. - -# TODO: parse %!postproc et !%preproc, et si la ligne se termine par une couleur en commentaire (%#FF00FF), -# utiliser cette couleur pour highlighter. Permet des règles customisées par document, facilement. -import re - -from PyQt5.QtCore import QRegExp, QDir, QFileInfo -from PyQt5.QtGui import QTextBlockFormat, QTextCursor, QTextCharFormat, QBrush - -from manuskript.ui.editors.basicHighlighter import basicHighlighter -from manuskript.ui.editors.blockUserData import blockUserData -from manuskript.ui.editors.t2tFunctions import State, textToFormatArray -from manuskript.ui.editors.t2tHighlighterStyle import t2tHighlighterStyle - - -class t2tHighlighter(basicHighlighter): - """Syntax highlighter for the Txt2Tags language. - """ - - def __init__(self, editor, style="Default"): - basicHighlighter.__init__(self, editor) - - # Stupid variable that fixes the loss of QTextBlockUserData. - self.thisDocument = editor.document() - - self.style = t2tHighlighterStyle(self.editor, self._defaultCharFormat, style) - - self.inDocRules = [] - - rules = [ - (r'^\s*[-=_]{20,}\s*$', State.HORIZONTAL_LINE), - (r'^\s*(\+{1})([^\+].*[^\+])(\+{1})(\[[A-Za-z0-9_-]*\])?\s*$', State.NUMBERED_TITLE_1), - (r'^\s*(\+{2})([^\+].*[^\+])(\+{2})(\[[A-Za-z0-9_-]*\])?\s*$', State.NUMBERED_TITLE_2), - (r'^\s*(\+{3})([^\+].*[^\+])(\+{3})(\[[A-Za-z0-9_-]*\])?\s*$', State.NUMBERED_TITLE_3), - (r'^\s*(\+{4})([^\+].*[^\+])(\+{4})(\[[A-Za-z0-9_-]*\])?\s*$', State.NUMBERED_TITLE_4), - (r'^\s*(\+{5})([^\+].*[^\+])(\+{5})(\[[A-Za-z0-9_-]*\])?\s*$', State.NUMBERED_TITLE_5), - (r'^\s*(={1})([^=].*[^=])(={1})(\[[A-Za-z0-9_-]*\])?\s*$', State.TITLE_1), - (r'^\s*(={2})([^=].*[^=])(={2})(\[[A-Za-z0-9_-]*\])?\s*$', State.TITLE_2), - (r'^\s*(={3})([^=].*[^=])(={3})(\[[A-Za-z0-9_-]*\])?\s*$', State.TITLE_3), - (r'^\s*(={4})([^=].*[^=])(={4})(\[[A-Za-z0-9_-]*\])?\s*$', State.TITLE_4), - (r'^\s*(={5})([^=].*[^=])(={5})(\[[A-Za-z0-9_-]*\])?\s*$', State.TITLE_5), - (r'^%!.*$', State.SETTINGS_LINE), - (r'^%[^!]?.*$', State.COMMENT_LINE), - (r'^\t.+$', State.BLOCKQUOTE_LINE), - (r'^(```)(.+)$', State.CODE_LINE), - (r'^(""")(.+)$', State.RAW_LINE), - (r'^(\'\'\')(.+)$', State.TAGGED_LINE), - (r'^\s*[-+:] [^ ].*$', State.LIST_BEGINS), - (r'^\s*[-+:]\s*$', State.LIST_ENDS), - (r'^ *\|\| .*$', State.TABLE_HEADER), - (r'^ *\| .*$', State.TABLE_LINE) - ] - - # Generate rules to identify blocks - State.Rules = [(QRegExp(pattern), state) - for (pattern, state) in rules] - State.Recursion = 0 - - def setDefaultCharFormat(self, cf): - self._defaultCharFormat = cf - self.setStyle() - self.rehighlight() - - def highlightBlock(self, text): - """Apply syntax highlighting to the given block of text. - """ - basicHighlighter.highlightBlockBefore(self, text) - - # Check if syntax highlighting is enabled - if self.style is None: - default = QTextBlockFormat() - QTextCursor(self.currentBlock()).setBlockFormat(default) - print("t2tHighlighter.py: is style supposed to be None?") - return - - block = self.currentBlock() - oldState = blockUserData.getUserState(block) - self.identifyBlock(block) - # formatBlock prevent undo/redo from working - # TODO: find a todo/undo compatible way of formatting block - # self.formatBlock(block) - - state = blockUserData.getUserState(block) - data = blockUserData.getUserData(block) - inList = self.isList(block) - - op = self.style.format(State.MARKUP) - - # self.setFormat(0, len(text), self.style.format(State.DEFAULT)) - - # InDocRules: is it a settings which might have a specific rule, - # a comment which contains color infos, or a include conf? - # r'^%!p[or][se]t?proc[^\s]*\s*:\s*\'(.*)\'\s*\'.*\'' - rlist = [QRegExp(r'^%!p[or][se]t?proc[^\s]*\s*:\s*((\'[^\']*\'|\"[^\"]*\")\s*(\'[^\']*\'|\"[^\"]*\"))'), - # pre/postproc - QRegExp(r'^%.*\s\((.*)\)'), # comment - QRegExp(r'^%!includeconf:\s*([^\s]*)\s*')] # includeconf - for r in rlist: - if r.indexIn(text) != -1: - self.parseInDocRules() - - # Format the whole line: - for lineState in [ - State.BLOCKQUOTE_LINE, - State.HORIZONTAL_LINE, - State.HEADER_LINE, - ]: - if not inList and state == lineState: - self.setFormat(0, len(text), self.style.format(lineState)) - - for (lineState, marker) in [ - (State.COMMENT_LINE, "%"), - (State.CODE_LINE, "```"), - (State.RAW_LINE, "\"\"\""), - (State.TAGGED_LINE, "'''"), - (State.SETTINGS_LINE, "%!") - ]: - if state == lineState and \ - not (inList and state == State.SETTINGS_LINE): - n = 0 - # If it's a comment, we want to highlight all '%'. - if state == State.COMMENT_LINE: - while text[n:n + 1] == "%": - n += 1 - n -= 1 - - # Apply Format - self.setFormat(0, len(marker) + n, op) - self.setFormat(len(marker) + n, - len(text) - len(marker) - n, - self.style.format(lineState)) - - # If it's a setting, we might do something - if state == State.SETTINGS_LINE: - # Target - r = QRegExp(r'^%!([^\s]+)\s*:\s*(\b\w*\b)$') - if r.indexIn(text) != -1: - setting = r.cap(1) - val = r.cap(2) - if setting == "target" and \ - val in self.editor.main.targetsNames: - self.editor.fileWidget.preview.setPreferredTarget(val) - - # Pre/postproc - r = QRegExp(r'^%!p[or][se]t?proc[^\s]*\s*:\s*((\'[^\']*\'|\"[^\"]*\")\s*(\'[^\']*\'|\"[^\"]*\"))') - if r.indexIn(text) != -1: - p = r.pos(1) - length = len(r.cap(1)) - self.setFormat(p, length, self.style.makeFormat(base=self.format(p), - fixedPitch=True)) - - # Tables - for lineState in [State.TABLE_LINE, State.TABLE_HEADER]: - if state == lineState: - for i, t in enumerate(text): - if t == "|": - self.setFormat(i, 1, op) - else: - self.setFormat(i, 1, self.style.format(lineState)) - - # Lists - # if text == " p": print(data.isList()) - if data.isList(): - r = QRegExp(r'^\s*[\+\-\:]? ?') - r.indexIn(text) - self.setFormat(0, r.matchedLength(), self.style.format(State.LIST_BULLET)) - # if state == State.LIST_BEGINS: - # r = QRegExp(r'^\s*[+-:] ') - # r.indexIn(text) - # self.setFormat(0, r.matchedLength(), self.style.format(State.LIST_BULLET)) - - if state == State.LIST_ENDS: - self.setFormat(0, len(text), self.style.format(State.LIST_BULLET_ENDS)) - - # Titles - if not inList and state in State.TITLES: - r = [i for (i, s) in State.Rules if s == state][0] - pos = r.indexIn(text) - if pos >= 0: - f = self.style.format(state) - # Uncomment for markup to be same size as title - # op = self.formats(preset="markup", - # base=self.formats(preset=state)) - self.setFormat(r.pos(2), len(r.cap(2)), f) - self.setFormat(r.pos(1), len(r.cap(1)), op) - self.setFormat(r.pos(3), len(r.cap(3)), op) - - # Areas: comment, code, raw tagged - for (begins, middle, ends) in [ - (State.COMMENT_AREA_BEGINS, State.COMMENT_AREA, State.COMMENT_AREA_ENDS), - (State.CODE_AREA_BEGINS, State.CODE_AREA, State.CODE_AREA_ENDS), - (State.RAW_AREA_BEGINS, State.RAW_AREA, State.RAW_AREA_ENDS), - (State.TAGGED_AREA_BEGINS, State.TAGGED_AREA, State.TAGGED_AREA_ENDS), - ]: - - if state == middle: - self.setFormat(0, len(text), self.style.format(middle)) - elif state in [begins, ends]: - self.setFormat(0, len(text), op) - - # Inline formatting - if state not in [ - # State.COMMENT_AREA, - # State.COMMENT_LINE, - State.RAW_AREA, - State.RAW_LINE, - State.CODE_AREA, - State.CODE_LINE, - State.TAGGED_AREA, - State.TAGGED_LINE, - State.SETTINGS_LINE, - State.HORIZONTAL_LINE, - ] and state not in State.TITLES: - formatArray = textToFormatArray(text) - - # InDocRules - for (r, c) in self.inDocRules: - i = re.finditer(r.decode('utf8'), text, re.UNICODE) - for m in i: - f = self.format(m.start()) - l = m.end() - m.start() - if "," in c: - c1, c2 = c.split(",") - self.setFormat(m.start(), l, - self.style.makeFormat(color=c1, bgcolor=c2, base=f)) - else: - self.setFormat(m.start(), l, - self.style.makeFormat(color=c, base=f)) - - # Links - if state not in [State.COMMENT_LINE, State.COMMENT_AREA]: - r = QRegExp(r'\[(\[[^\]]*\])?[^\]]*\s*([^\s]+)\]') - r.setMinimal(False) - pos = r.indexIn(text) - links = [] - while pos >= 0: - # TODO: The text should not be formatted if [**not bold**] - # if max([k[pos] for k in formatArray]) == 0 or 1 == 1: - self.setFormat(pos, 1, - self.style.format(State.MARKUP)) - self.setFormat(pos + 1, len(r.cap(0)) - 1, - self.style.format(State.LINKS)) - self.setFormat(pos + len(r.cap(0)) - 1, 1, - self.style.format(State.MARKUP)) - if r.pos(2) > 0: - _f = QTextCharFormat(self.style.format(State.LINKS)) - _f.setForeground(QBrush(_f.foreground() - .color().lighter())) - _f.setFontUnderline(True) - self.setFormat(r.pos(2), len(r.cap(2)), _f) - - links.append([pos, len(r.cap(0))]) # To remember for the next highlighter (single links) - pos = r.indexIn(text, pos + 1) - - # Links like www.theologeek.ch, http://www.fsf.org, ... - # FIXME: - "http://adresse et http://adresse" is detected also as italic - # - some error, like "http://adress.htm." also color the final "." - # - also: adresse@email.com, ftp://, www2, www3, etc. - # - But for now, does the job - r = QRegExp(r'http://[^\s]*|www\.[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+[^\s]*') - # r.setMinimal(True) - pos = r.indexIn(text) - while pos >= 0: - for k in links: - # print pos, k[0], k[1] - if k[0] < pos < k[0] + k[1]: # already highlighted - break - else: - self.setFormat(pos, len(r.cap(0)), self.style.format(State.LINKS)) - - pos = r.indexIn(text, pos + 1) - - # Bold, Italic, Underline, Code, Tagged, Strikeout - for i, t in enumerate(text): - f = self.format(i) - beautifiers = [k[i] for k in formatArray] - self.setFormat(i, 1, self.style.beautifyFormat(f, beautifiers)) - - # Macro words - for r in [r'(%%)\b\w+\b', r'(%%)\b\w+\b\(.+\)']: - r = QRegExp(r) - r.setMinimal(True) - pos = r.indexIn(text) - while pos >= 0: - if max([k[pos] for k in formatArray]) == 0: - self.setFormat(pos, len(r.cap(0)), - self.style.format(State.MACRO)) - pos = r.indexIn(text, pos + 1) - - # Highlighted word (for search) - if self.editor.highlightWord: - if self.editor.highligtCS and self.editor.highlightWord in text or \ - not self.editor.highlightCs and self.editor.highlightWord.lower() in text.lower(): - # if self.editor.highlightCS: - # s = self.editor.highlightWord - # else: - # s = self.editor.highlightWord.toLower() - # print(s) - p = text.indexOf(self.editor.highlightWord, cs=self.editor.highlightCS) - while p >= 0: - self.setFormat(p, len(self.editor.highlightWord), - self.style.makeFormat(preset="higlighted", base=self.format(p))) - p = text.indexOf(self.editor.highlightWord, p + 1, cs=self.editor.highlightCS) - - ### Highlight Selection - ### TODO: way to slow, find another way. - ##sel = self.editor.textCursor().selectedText() - ##if len(sel) > 5: self.keywordRules.append((QRegExp(sel), "selected")) - - ## Do keyword formatting - # for expression, style in self.keywordRules: - # expression.setMinimal( True ) - # index = expression.indexIn(text, 0) - - ## There might be more than one on the same line - # while index >= 0: - # length = expression.cap(0).length() - # f = self.formats(preset=style, base=self.formats(index)) - # self.setFormat(index, length, f) - # index = expression.indexIn(text, index + length) - - basicHighlighter.highlightBlockAfter(self, text) - - def identifyBlock(self, block): - """Identifies what block type it is, and set userState and userData - accordingly.""" - - text = block.text() - data = blockUserData.getUserData(block) - - # Header Lines - # No header line here - # if block.blockNumber() == 0: - # block.setUserState(State.HEADER_LINE) - # return - # elif block.blockNumber() in [1, 2] and \ - # self.document().findBlockByNumber(0).text(): - # block.setUserState(State.HEADER_LINE) - # return - - state = 0 - inList = False - blankLinesBefore = 0 - - # if text.contains(QRegExp(r'^\s*[-+:] [^ ].*[^-+]{1}\s*$')): - if QRegExp(r'^\s*[-+:] [^ ].*[^-+]{1}\s*$').indexIn(text) != -1: - state = State.LIST_BEGINS - - # List stuff - if self.isList(block.previous()) or state == State.LIST_BEGINS: - inList = True - - # listLevel and leadingSpaces - # FIXME: not behaving exactly correctly... - lastData = blockUserData.getUserData(block.previous()) - if state == State.LIST_BEGINS: - leadingSpaces = QRegExp(r'[-+:]').indexIn(text, 0) - data.setLeadingSpaces(leadingSpaces) - - data.setListSymbol(text[leadingSpaces]) - if self.isList(block.previous()): - # The last block was also a list. - # We need to check if this is the same level, or a sublist - if leadingSpaces > lastData.leadingSpaces(): - # This is a sublevel list - data.setListLevel(lastData.listLevel() + 1) - else: - # This is same level - data.setListLevel(lastData.listLevel()) - else: - data.setListLevel(1) - else: - data.setListLevel(lastData.listLevel()) - data.setLeadingSpaces(lastData.leadingSpaces()) - data.setListSymbol(lastData.listSymbol()) - - # Blank lines before (two = end of list) - blankLinesBefore = self.getBlankLines(block.previous()) - if not QRegExp(r'^\s*$').indexIn(block.previous().text()) != -1 and \ - not blockUserData.getUserState(block.previous()) in [State.COMMENT_LINE, - State.COMMENT_AREA, State.COMMENT_AREA_BEGINS, - State.COMMENT_AREA_ENDS]: - blankLinesBefore = 0 - elif not blockUserData.getUserState(block.previous()) in \ - [State.COMMENT_LINE, State.COMMENT_AREA, - State.COMMENT_AREA_BEGINS, State.COMMENT_AREA_ENDS]: - blankLinesBefore += 1 - if blankLinesBefore == 2: - # End of list. - blankLinesBefore = 0 - inList = False - if inList and QRegExp(r'^\s*$').indexIn(text) != -1: - state = State.LIST_EMPTY - - # Areas - for (begins, middle, ends, marker) in [ - (State.COMMENT_AREA_BEGINS, State.COMMENT_AREA, State.COMMENT_AREA_ENDS, "^%%%\s*$"), - (State.CODE_AREA_BEGINS, State.CODE_AREA, State.CODE_AREA_ENDS, "^```\s*$"), - (State.RAW_AREA_BEGINS, State.RAW_AREA, State.RAW_AREA_ENDS, "^\"\"\"\s*$"), - (State.TAGGED_AREA_BEGINS, State.TAGGED_AREA, State.TAGGED_AREA_ENDS, '^\'\'\'\s*$'), - ]: - - if QRegExp(marker).indexIn(text) != -1: - if blockUserData.getUserState(block.previous()) in [begins, middle]: - state = ends - break - else: - state = begins - break - if blockUserData.getUserState(block.previous()) in [middle, begins]: - state = middle - break - - # Patterns (for lines) - if not state: - for (pattern, lineState) in State.Rules: - pos = pattern.indexIn(text) - if pos >= 0: - state = lineState - break - - if state in [State.BLOCKQUOTE_LINE, State.LIST_ENDS]: - # FIXME: doesn't work exactly. Closes only the current level, not - # FIXME: the whole list. - inList = False - - if inList and not state == State.LIST_BEGINS: - state += 100 - if blankLinesBefore: - state += 100 - - block.setUserState(state) - block.setUserData(data) - - def formatBlock(self, block): - """ - Formats the block according to its state. - """ - # TODO: Use QTextDocument format presets, and QTextBlock's - # TODO: blockFormatIndex. And move that in t2tHighlighterStyle. - state = block.userState() - blockFormat = QTextBlockFormat() - - if state in [State.BLOCKQUOTE_LINE, - State.HEADER_LINE] + State.LIST: - blockFormat = self.style.formatBlock(block, state) - - QTextCursor(block).setBlockFormat(blockFormat) - - def getBlankLines(self, block): - """Returns if there is a blank line before in the list.""" - state = block.userState() - if state >= 200: - return 1 - else: - return 0 - - def isList(self, block): - """Returns TRUE if the block is in a list.""" - if block.userState() == State.LIST_BEGINS or \ - block.userState() >= 100: - return True - - def setStyle(self, style="Default"): - if style in t2tHighlighterStyle.validStyles: - self.style = t2tHighlighterStyle(self.editor, self._defaultCharFormat, style) - else: - self.style = None - self.rehighlight() - - def setFontPointSize(self, size): - self.defaultFontPointSize = size - self.style = t2tHighlighterStyle(self.editor, self.style.name) - self.rehighlight() - - def parseInDocRules(self): - oldRules = self.inDocRules - self.inDocRules = [] - - t = self.thisDocument.toPlainText() - - # Get all conf files - confs = [] - lines = t.split("\n") - for l in lines: - r = QRegExp(r'^%!includeconf:\s*([^\s]*)\s*') - if r.indexIn(l) != -1: - confs.append(r.cap(1)) - - # Try to load conf files - for c in confs: - try: - import codecs - f = self.editor.fileWidget.file - d = QDir.cleanPath(QFileInfo(f).absoluteDir().absolutePath() + "/" + c) - file = codecs.open(d, 'r', "utf-8") - except: - print(("Error: cannot open {}.".format(c))) - continue - # We add the content to the current lines of the current document - lines += file.readlines() # lines.extend(file.readlines()) - - # b = self.thisDocument.firstBlock() - lastColor = "" - - # while b.isValid(): - for l in lines: - text = l # b.text() - r = QRegExp(r'^%!p[or][se]t?proc[^\s]*\s*:\s*(\'[^\']*\'|\"[^\"]*\")\s*(\'[^\']*\'|\"[^\"]*\")') - if r.indexIn(text) != -1: - rule = r.cap(1)[1:-1] - # Check if there was a color-comment above that post/preproc bloc - if lastColor: - self.inDocRules.append((str(rule), lastColor)) - # Check if previous block is a comment like it should - else: - previousText = lines[lines.indexOf(l) - 1] # b.previous().text() - r = QRegExp(r'^%.*\s\((.*)\)') - if r.indexIn(previousText) != -1: - lastColor = r.cap(1) - self.inDocRules.append((str(rule), lastColor)) - else: - lastColor = "" - # b = b.next() - - if oldRules != self.inDocRules: - # Rules have changed, we need to rehighlight - # print("Rules have changed.", len(self.inDocRules)) - # self.rehighlight() # Doesn't work (seg fault), why? - pass - # b = self.thisDocument.firstBlock() - # while b.isValid(): - # for (r, c) in self.inDocRules: - # r = QRegExp(r) - # pos = r.indexIn(b.text()) - # if pos >= 0: - # print("rehighlighting:", b.text()) - # self.rehighlightBlock(b) - # break - # b = b.next() diff --git a/manuskript/ui/editors/t2tHighlighterStyle.py b/manuskript/ui/editors/t2tHighlighterStyle.py deleted file mode 100644 index 318e217a..00000000 --- a/manuskript/ui/editors/t2tHighlighterStyle.py +++ /dev/null @@ -1,252 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf8 -*- - - -# TODO: creates a general way to generate styles (and edit/import/export) -from PyQt5.QtCore import QRegExp, Qt -from PyQt5.QtGui import QFont, QTextBlockFormat, QColor, QFontMetrics, QTextCharFormat -from PyQt5.QtWidgets import qApp - -from manuskript.ui.editors.blockUserData import blockUserData -from manuskript.ui.editors.t2tFunctions import State - - -class t2tHighlighterStyle(): - """Style for the Syntax highlighter for the Txt2Tags language. - """ - - validStyles = ["Default", "Monospace"] - - def __init__(self, editor, charFormat, name="Default"): - - self.editor = editor - self.name = name - self._defaultCharFormat = charFormat - - # Defaults - # self.defaultFontPointSize = self.editor.defaultFontPointSize - self.defaultFontFamily = qApp.font().family() - self.tabStopWidth = 40 - - self.setupEditor() - - if self.name == "Default": - self.initDefaults() - # Temporary other theme - elif self.name == "Monospace": - self.defaultFontFamily = "Monospace" - self.initDefaults() - for i in self.styles: - f = self.styles[i] - f.setFontFixedPitch(True) - f.setFontFamily(self.defaultFontFamily) - f.setFontPointSize(self._defaultCharFormat.font().pointSize()) - self.styles[i] = f - - def setupEditor(self): - self.editor.setTabStopWidth(self.tabStopWidth) - - def initDefaults(self): - self.styles = {} - for i in [State.CODE_AREA, - State.CODE_LINE, - State.COMMENT_AREA, - State.COMMENT_LINE, - State.SETTINGS_LINE, - State.BLOCKQUOTE_LINE, - State.RAW_AREA, - State.RAW_LINE, - State.TAGGED_AREA, - State.TAGGED_LINE, - State.TITLE_1, - State.TITLE_2, - State.TITLE_3, - State.TITLE_4, - State.TITLE_5, - State.NUMBERED_TITLE_1, - State.NUMBERED_TITLE_2, - State.NUMBERED_TITLE_3, - State.NUMBERED_TITLE_4, - State.NUMBERED_TITLE_5, - State.TABLE_HEADER, - State.TABLE_LINE, - State.HORIZONTAL_LINE, - State.MARKUP, - State.LIST_BULLET, - State.LIST_BULLET_ENDS, - State.LINKS, - State.MACRO, - State.DEFAULT, - State.HEADER_LINE]: - self.styles[i] = self.makeFormat(preset=i) - - def format(self, state): - return self.styles[state] - - def beautifyFormat(self, base, beautifiers): - """Apply beautifiers given in beautifiers array to format""" - if max(beautifiers) == 2: - return self.makeFormat(preset=State.MARKUP, base=base) - else: - if beautifiers[0]: # bold - base.setFontWeight(QFont.Bold) - if beautifiers[1]: # italic - base.setFontItalic(True) - if beautifiers[2]: # underline - base.setFontUnderline(True) - if beautifiers[3]: # strikeout - base.setFontStrikeOut(True) - if beautifiers[4]: # code - base = self.makeFormat(base=base, preset=State.CODE_LINE) - if beautifiers[5]: # tagged - base = self.makeFormat(base=base, preset=State.TAGGED_LINE) - return base - - def formatBlock(self, block, state): - """Apply transformation to given block.""" - blockFormat = QTextBlockFormat() - - if state == State.BLOCKQUOTE_LINE: - # Number of tabs - n = block.text().indexOf(QRegExp(r'[^\t]'), 0) - blockFormat.setIndent(0) - blockFormat.setTextIndent(-self.tabStopWidth * n) - blockFormat.setLeftMargin(self.tabStopWidth * n) - # blockFormat.setRightMargin(self.editor.contentsRect().width() - # - self.editor.lineNumberAreaWidth() - # - fm.width("X") * self.editor.LimitLine - # + self.editor.tabStopWidth()) - blockFormat.setAlignment(Qt.AlignJustify) - if self.name == "Default": - blockFormat.setTopMargin(5) - blockFormat.setBottomMargin(5) - elif state == State.HEADER_LINE: - blockFormat.setBackground(QColor("#EEEEEE")) - elif state in State.LIST: - data = blockUserData.getUserData(block) - if str(data.listSymbol()) in "+-": - blockFormat.setBackground(QColor("#EEFFEE")) - else: - blockFormat.setBackground(QColor("#EEEEFA")) - n = blockUserData.getUserData(block).leadingSpaces() + 1 - - f = QFontMetrics(QFont(self.defaultFontFamily, - self._defaultCharFormat.font().pointSize())) - fm = f.width(" " * n + - blockUserData.getUserData(block).listSymbol()) - blockFormat.setTextIndent(-fm) - blockFormat.setLeftMargin(fm) - if blockUserData.getUserState(block) == State.LIST_BEGINS and \ - self.name == "Default": - blockFormat.setTopMargin(5) - return blockFormat - - def makeFormat(self, color='', style='', size='', base='', fixedPitch='', - preset='', title_level='', bgcolor=''): - """ - Returns a QTextCharFormat with the given attributes, using presets. - """ - - _color = QColor() - # _format = QTextCharFormat() - # _format.setFont(self.editor.font()) - # size = _format.fontPointSize() - _format = QTextCharFormat(self._defaultCharFormat) - - # Base - if base: - _format = base - - # Presets - if preset in [State.CODE_AREA, State.CODE_LINE, "code"]: - style = "bold" - color = "black" - fixedPitch = True - _format.setBackground(QColor("#EEEEEE")) - - if preset in [State.COMMENT_AREA, State.COMMENT_LINE, "comment"]: - style = "italic" - color = "darkGreen" - - if preset in [State.SETTINGS_LINE, "setting", State.MACRO]: - # style = "italic" - color = "magenta" - - if preset in [State.BLOCKQUOTE_LINE]: - color = "red" - - if preset in [State.HEADER_LINE]: - size *= 2 - # print size - - if preset in [State.RAW_AREA, State.RAW_LINE, "raw"]: - color = "blue" - - if preset in [State.TAGGED_AREA, State.TAGGED_LINE, "tagged"]: - color = "purple" - - if preset in State.TITLES: - style = "bold" - color = "darkRed" if State.titleLevel(preset) % 2 == 1 else "blue" - size = (self._defaultCharFormat.font().pointSize() - + 11 - 2 * State.titleLevel(preset)) - - if preset == State.TABLE_HEADER: - style = "bold" - color = "darkMagenta" - - if preset == State.TABLE_LINE: - color = "darkMagenta" - - if preset == State.LIST_BULLET: - color = "red" - style = "bold" - fixedPitch = True - - if preset == State.LIST_BULLET_ENDS: - color = "darkGray" - fixedPitch = True - - if preset in [State.MARKUP, "markup"]: - color = "darkGray" - - if preset in [State.HORIZONTAL_LINE]: - color = "cyan" - fixedPitch = True - - if preset == State.LINKS: - color = "blue" - # style="underline" - - if preset == "selected": - _format.setBackground(QColor("yellow")) - - if preset == "higlighted": - bgcolor = "yellow" - - # if preset == State.DEFAULT: - # size = self.defaultFontPointSize - # _format.setFontFamily(self.defaultFontFamily) - - # Manual formatting - if color: - _color.setNamedColor(color) - _format.setForeground(_color) - if bgcolor: - _color.setNamedColor(bgcolor) - _format.setBackground(_color) - - if 'bold' in style: - _format.setFontWeight(QFont.Bold) - if 'italic' in style: - _format.setFontItalic(True) - if 'strike' in style: - _format.setFontStrikeOut(True) - if 'underline' in style: - _format.setFontUnderline(True) - if size: - _format.setFontPointSize(size) - if fixedPitch: - _format.setFontFixedPitch(True) - - return _format diff --git a/manuskript/ui/views/textEditView.py b/manuskript/ui/views/textEditView.py index 1ba1f9f6..0c3e9d13 100644 --- a/manuskript/ui/views/textEditView.py +++ b/manuskript/ui/views/textEditView.py @@ -11,6 +11,7 @@ from manuskript.enums import Outline from manuskript.functions import AUC from manuskript.functions import toString from manuskript.models.outlineModel import outlineModel +from manuskript.ui.editors.MDFunctions import MDFormatSelection from manuskript.ui.editors.MMDHighlighter import MMDHighlighter from manuskript.ui.editors.basicHighlighter import basicHighlighter from manuskript.ui.editors.textFormat import textFormat @@ -456,16 +457,12 @@ class textEditView(QTextEdit): def applyFormat(self, _format): if self._textFormat == "md": - # FIXME - print("Not implemented yet.") - # Model: from t2tFunctions - # if self._textFormat == "t2t": - # if _format == "Bold": - # t2tFormatSelection(self, 0) - # elif _format == "Italic": - # t2tFormatSelection(self, 1) - # elif _format == "Underline": - # t2tFormatSelection(self, 2) - # elif _format == "Clear": - # t2tClearFormat(self) + if _format == "Bold": + MDFormatSelection(self, 0) + elif _format == "Italic": + MDFormatSelection(self, 1) + elif _format == "Code": + MDFormatSelection(self, 2) + elif _format == "Clear": + MDFormatSelection(self) From f850b4ef0effae9e142ba3c694776bbfcc9238fb Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 30 Mar 2016 11:43:32 +0200 Subject: [PATCH 087/103] Corrects bug: does not append non-exising files to recent files --- manuskript/ui/welcome.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manuskript/ui/welcome.py b/manuskript/ui/welcome.py index 1dbc0ff8..464a33ee 100644 --- a/manuskript/ui/welcome.py +++ b/manuskript/ui/welcome.py @@ -89,7 +89,7 @@ class welcome(QWidget, Ui_welcome): if sttgns.contains("recentFiles"): lst = sttgns.value("recentFiles") self.mw.menuRecents.clear() - for f in lst: + for f in [f for f in lst if os.path.exists(f)]: name = os.path.split(f)[1] a = QAction(name, self) a.setData(f) From bf6cb2906e17ca6f354f553995fa884bca356774 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 30 Mar 2016 11:47:06 +0200 Subject: [PATCH 088/103] Updates sample-project to last version of manuskript --- .../0-Jerusalem/0-Chapter_1/0-Introduction.md | 2 +- .../1-Jesus_taken_up_into_heaven.md | 2 +- .../2-Matthias_chosen_to_replace_Judas.md | 2 +- .../1-Chapter_2/0-The_promised_Spirit.md | 2 +- .../1-Chapter_2/1-Peter_adresses_the_crowd.md | 2 +- .../2-The_life_of_the_first_believers.md | 2 +- .../2-Chapter_3/0-Peter_heals_a_beggar.md | 2 +- .../2-Chapter_3/1-Peter_preaches.md | 2 +- ...eter_and_John_in_front_of_the_Sanhedrin.md | 2 +- .../3-Chapter_4/1-The_believers_pray.md | 2 +- .../3-Chapter_4/2-The_believers_share.md | 2 +- .../4-Chapter_5/0-Ananias_and_Sapphira.md | 2 +- .../1-Many_healings_done_by_the_aposles.md | 2 +- .../4-Chapter_5/2-The_persecutions.md | 2 +- .../5-Chapter_6/0-The_choosing_of_seven.md | 2 +- .../5-Chapter_6/1-Stephen_is_taken.md | 2 +- .../6-Chapter_7/0-Stephen_preaches.md | 2 +- .../6-Chapter_7/1-Stephen_is_killed.md | 2 +- .../0-Chapter_8/0-The_church_scattered.md | 2 +- .../0-Chapter_8/1-Philip_in_Samaria.md | 2 +- .../0-Chapter_8/2-Simon_the_Sorcerer.md | 2 +- .../0-Chapter_8/3-Philip_and_the_Ethiopian.md | 2 +- .../1-Chapter_9/0-Saul-s_conversion.md | 2 +- .../1-Saul_in_Damascus_and_Jerusalem.md | 2 +- .../2-Peter_visits_the_church_in_Judea.md | 2 +- .../0-Cornelius_calls_for_Peter.md | 2 +- .../2-Chapter_10/1-Peter-s_vision.md | 2 +- .../2-Chapter_10/2-The_conversion_of_Peter.md | 2 +- .../0-Peter_explains_his_actions.md | 2 +- .../3-Chapter_11/1-The_Church_in_Antioch.md | 2 +- .../0-Peter_escapes_from_prison.md | 2 +- .../4-Chapter_12/1-Herod_dies.md | 2 +- .../0-Saul_and_Barnabas_are_sent.md | 2 +- .../0-Chapter_13/1-Cyprus.md | 2 +- .../0-Chapter_13/2-Psidian_Antioch.md | 2 +- .../1-Chapter_14/0-Iconium.md | 2 +- .../1-Chapter_14/1-Lystra_and_Derbe.md | 2 +- .../1-Chapter_14/2-Back_to_Antioch.md | 2 +- .../0-The_Council_at_Jerusalem.md | 2 +- ...The_Council_writes_to_gentile_believers.md | 2 +- .../2-Paul_and_Barnabas_fight_and_split.md | 2 +- sample-projects/book-of-acts/revisions.xml | 82 +++++++++---------- sample-projects/book-of-acts/settings.txt | 3 +- 43 files changed, 84 insertions(+), 83 deletions(-) diff --git a/sample-projects/book-of-acts/outline/0-Jerusalem/0-Chapter_1/0-Introduction.md b/sample-projects/book-of-acts/outline/0-Jerusalem/0-Chapter_1/0-Introduction.md index 7625a6a6..df394e3a 100644 --- a/sample-projects/book-of-acts/outline/0-Jerusalem/0-Chapter_1/0-Introduction.md +++ b/sample-projects/book-of-acts/outline/0-Jerusalem/0-Chapter_1/0-Introduction.md @@ -1,6 +1,6 @@ title: Introduction ID: 1 -type: txt +type: md compile: 2 diff --git a/sample-projects/book-of-acts/outline/0-Jerusalem/0-Chapter_1/1-Jesus_taken_up_into_heaven.md b/sample-projects/book-of-acts/outline/0-Jerusalem/0-Chapter_1/1-Jesus_taken_up_into_heaven.md index 6719f299..108d4ccc 100644 --- a/sample-projects/book-of-acts/outline/0-Jerusalem/0-Chapter_1/1-Jesus_taken_up_into_heaven.md +++ b/sample-projects/book-of-acts/outline/0-Jerusalem/0-Chapter_1/1-Jesus_taken_up_into_heaven.md @@ -1,6 +1,6 @@ title: Jesus taken up into heaven ID: 2 -type: txt +type: md notes: {P:0:The good news spreads from Jerusalem to Rome} compile: 2 diff --git a/sample-projects/book-of-acts/outline/0-Jerusalem/0-Chapter_1/2-Matthias_chosen_to_replace_Judas.md b/sample-projects/book-of-acts/outline/0-Jerusalem/0-Chapter_1/2-Matthias_chosen_to_replace_Judas.md index cad95385..0f5be14e 100644 --- a/sample-projects/book-of-acts/outline/0-Jerusalem/0-Chapter_1/2-Matthias_chosen_to_replace_Judas.md +++ b/sample-projects/book-of-acts/outline/0-Jerusalem/0-Chapter_1/2-Matthias_chosen_to_replace_Judas.md @@ -1,6 +1,6 @@ title: Matthias chosen to replace Judas ID: 3 -type: txt +type: md notes: {C:2:Philip} compile: 2 diff --git a/sample-projects/book-of-acts/outline/0-Jerusalem/1-Chapter_2/0-The_promised_Spirit.md b/sample-projects/book-of-acts/outline/0-Jerusalem/1-Chapter_2/0-The_promised_Spirit.md index d92aa32c..9d663e85 100644 --- a/sample-projects/book-of-acts/outline/0-Jerusalem/1-Chapter_2/0-The_promised_Spirit.md +++ b/sample-projects/book-of-acts/outline/0-Jerusalem/1-Chapter_2/0-The_promised_Spirit.md @@ -1,6 +1,6 @@ title: The promised Spirit ID: 4 -type: txt +type: md compile: 2 diff --git a/sample-projects/book-of-acts/outline/0-Jerusalem/1-Chapter_2/1-Peter_adresses_the_crowd.md b/sample-projects/book-of-acts/outline/0-Jerusalem/1-Chapter_2/1-Peter_adresses_the_crowd.md index 54881dff..2fd8b758 100644 --- a/sample-projects/book-of-acts/outline/0-Jerusalem/1-Chapter_2/1-Peter_adresses_the_crowd.md +++ b/sample-projects/book-of-acts/outline/0-Jerusalem/1-Chapter_2/1-Peter_adresses_the_crowd.md @@ -1,6 +1,6 @@ title: Peter adresses the crowd ID: 15 -type: txt +type: md POV: 0 notes: {P:0:The good news spreads from Jerusalem to Rome} compile: 2 diff --git a/sample-projects/book-of-acts/outline/0-Jerusalem/1-Chapter_2/2-The_life_of_the_first_believers.md b/sample-projects/book-of-acts/outline/0-Jerusalem/1-Chapter_2/2-The_life_of_the_first_believers.md index 5a21c727..b0462413 100644 --- a/sample-projects/book-of-acts/outline/0-Jerusalem/1-Chapter_2/2-The_life_of_the_first_believers.md +++ b/sample-projects/book-of-acts/outline/0-Jerusalem/1-Chapter_2/2-The_life_of_the_first_believers.md @@ -1,6 +1,6 @@ title: The life of the first believers ID: 14 -type: txt +type: md compile: 2 diff --git a/sample-projects/book-of-acts/outline/0-Jerusalem/2-Chapter_3/0-Peter_heals_a_beggar.md b/sample-projects/book-of-acts/outline/0-Jerusalem/2-Chapter_3/0-Peter_heals_a_beggar.md index 3ba0a245..9d561eaa 100644 --- a/sample-projects/book-of-acts/outline/0-Jerusalem/2-Chapter_3/0-Peter_heals_a_beggar.md +++ b/sample-projects/book-of-acts/outline/0-Jerusalem/2-Chapter_3/0-Peter_heals_a_beggar.md @@ -1,6 +1,6 @@ title: Peter heals a beggar ID: 13 -type: txt +type: md POV: 0 compile: 2 diff --git a/sample-projects/book-of-acts/outline/0-Jerusalem/2-Chapter_3/1-Peter_preaches.md b/sample-projects/book-of-acts/outline/0-Jerusalem/2-Chapter_3/1-Peter_preaches.md index 68f23084..bee8d53e 100644 --- a/sample-projects/book-of-acts/outline/0-Jerusalem/2-Chapter_3/1-Peter_preaches.md +++ b/sample-projects/book-of-acts/outline/0-Jerusalem/2-Chapter_3/1-Peter_preaches.md @@ -1,6 +1,6 @@ title: Peter preaches ID: 12 -type: txt +type: md POV: 0 compile: 2 diff --git a/sample-projects/book-of-acts/outline/0-Jerusalem/3-Chapter_4/0-Peter_and_John_in_front_of_the_Sanhedrin.md b/sample-projects/book-of-acts/outline/0-Jerusalem/3-Chapter_4/0-Peter_and_John_in_front_of_the_Sanhedrin.md index 6bd6bbca..7b9b3968 100644 --- a/sample-projects/book-of-acts/outline/0-Jerusalem/3-Chapter_4/0-Peter_and_John_in_front_of_the_Sanhedrin.md +++ b/sample-projects/book-of-acts/outline/0-Jerusalem/3-Chapter_4/0-Peter_and_John_in_front_of_the_Sanhedrin.md @@ -1,6 +1,6 @@ title: Peter and John in front of the Sanhedrin ID: 16 -type: txt +type: md POV: 0 compile: 2 diff --git a/sample-projects/book-of-acts/outline/0-Jerusalem/3-Chapter_4/1-The_believers_pray.md b/sample-projects/book-of-acts/outline/0-Jerusalem/3-Chapter_4/1-The_believers_pray.md index 45cd8481..64f88d35 100644 --- a/sample-projects/book-of-acts/outline/0-Jerusalem/3-Chapter_4/1-The_believers_pray.md +++ b/sample-projects/book-of-acts/outline/0-Jerusalem/3-Chapter_4/1-The_believers_pray.md @@ -1,6 +1,6 @@ title: The believers pray ID: 17 -type: txt +type: md notes: Mention: {C:4:Herod} compile: 2 diff --git a/sample-projects/book-of-acts/outline/0-Jerusalem/3-Chapter_4/2-The_believers_share.md b/sample-projects/book-of-acts/outline/0-Jerusalem/3-Chapter_4/2-The_believers_share.md index d99fe89e..9c37d18c 100644 --- a/sample-projects/book-of-acts/outline/0-Jerusalem/3-Chapter_4/2-The_believers_share.md +++ b/sample-projects/book-of-acts/outline/0-Jerusalem/3-Chapter_4/2-The_believers_share.md @@ -1,6 +1,6 @@ title: The believers share ID: 18 -type: txt +type: md notes: {C:5:Barnabas} compile: 2 diff --git a/sample-projects/book-of-acts/outline/0-Jerusalem/4-Chapter_5/0-Ananias_and_Sapphira.md b/sample-projects/book-of-acts/outline/0-Jerusalem/4-Chapter_5/0-Ananias_and_Sapphira.md index 72af522f..57aa71a0 100644 --- a/sample-projects/book-of-acts/outline/0-Jerusalem/4-Chapter_5/0-Ananias_and_Sapphira.md +++ b/sample-projects/book-of-acts/outline/0-Jerusalem/4-Chapter_5/0-Ananias_and_Sapphira.md @@ -1,6 +1,6 @@ title: Ananias and Sapphira ID: 19 -type: txt +type: md compile: 2 diff --git a/sample-projects/book-of-acts/outline/0-Jerusalem/4-Chapter_5/1-Many_healings_done_by_the_aposles.md b/sample-projects/book-of-acts/outline/0-Jerusalem/4-Chapter_5/1-Many_healings_done_by_the_aposles.md index d54f7756..d477a27f 100644 --- a/sample-projects/book-of-acts/outline/0-Jerusalem/4-Chapter_5/1-Many_healings_done_by_the_aposles.md +++ b/sample-projects/book-of-acts/outline/0-Jerusalem/4-Chapter_5/1-Many_healings_done_by_the_aposles.md @@ -1,6 +1,6 @@ title: Many healings done by the aposles ID: 20 -type: txt +type: md compile: 2 diff --git a/sample-projects/book-of-acts/outline/0-Jerusalem/4-Chapter_5/2-The_persecutions.md b/sample-projects/book-of-acts/outline/0-Jerusalem/4-Chapter_5/2-The_persecutions.md index 29ca35bb..62cfd0ec 100644 --- a/sample-projects/book-of-acts/outline/0-Jerusalem/4-Chapter_5/2-The_persecutions.md +++ b/sample-projects/book-of-acts/outline/0-Jerusalem/4-Chapter_5/2-The_persecutions.md @@ -1,6 +1,6 @@ title: The persecutions ID: 21 -type: txt +type: md compile: 2 diff --git a/sample-projects/book-of-acts/outline/0-Jerusalem/5-Chapter_6/0-The_choosing_of_seven.md b/sample-projects/book-of-acts/outline/0-Jerusalem/5-Chapter_6/0-The_choosing_of_seven.md index a050502b..d7988460 100644 --- a/sample-projects/book-of-acts/outline/0-Jerusalem/5-Chapter_6/0-The_choosing_of_seven.md +++ b/sample-projects/book-of-acts/outline/0-Jerusalem/5-Chapter_6/0-The_choosing_of_seven.md @@ -1,6 +1,6 @@ title: The choosing of seven ID: 22 -type: txt +type: md notes: {C:2:Philip} compile: 2 diff --git a/sample-projects/book-of-acts/outline/0-Jerusalem/5-Chapter_6/1-Stephen_is_taken.md b/sample-projects/book-of-acts/outline/0-Jerusalem/5-Chapter_6/1-Stephen_is_taken.md index 75605e52..bd5a6d0b 100644 --- a/sample-projects/book-of-acts/outline/0-Jerusalem/5-Chapter_6/1-Stephen_is_taken.md +++ b/sample-projects/book-of-acts/outline/0-Jerusalem/5-Chapter_6/1-Stephen_is_taken.md @@ -1,6 +1,6 @@ title: Stephen is taken ID: 23 -type: txt +type: md POV: 3 compile: 2 diff --git a/sample-projects/book-of-acts/outline/0-Jerusalem/6-Chapter_7/0-Stephen_preaches.md b/sample-projects/book-of-acts/outline/0-Jerusalem/6-Chapter_7/0-Stephen_preaches.md index a4ea8ff5..154feffd 100644 --- a/sample-projects/book-of-acts/outline/0-Jerusalem/6-Chapter_7/0-Stephen_preaches.md +++ b/sample-projects/book-of-acts/outline/0-Jerusalem/6-Chapter_7/0-Stephen_preaches.md @@ -1,6 +1,6 @@ title: Stephen preaches ID: 24 -type: txt +type: md POV: 3 compile: 2 diff --git a/sample-projects/book-of-acts/outline/0-Jerusalem/6-Chapter_7/1-Stephen_is_killed.md b/sample-projects/book-of-acts/outline/0-Jerusalem/6-Chapter_7/1-Stephen_is_killed.md index d6fdf592..7e1b3480 100644 --- a/sample-projects/book-of-acts/outline/0-Jerusalem/6-Chapter_7/1-Stephen_is_killed.md +++ b/sample-projects/book-of-acts/outline/0-Jerusalem/6-Chapter_7/1-Stephen_is_killed.md @@ -1,6 +1,6 @@ title: Stephen is killed ID: 25 -type: txt +type: md POV: 3 compile: 2 diff --git a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/0-Chapter_8/0-The_church_scattered.md b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/0-Chapter_8/0-The_church_scattered.md index e3fe1f60..538ec02a 100644 --- a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/0-Chapter_8/0-The_church_scattered.md +++ b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/0-Chapter_8/0-The_church_scattered.md @@ -1,6 +1,6 @@ title: The church scattered ID: 26 -type: txt +type: md notes: {P:0:The good news spreads from Jerusalem to Rome} compile: 2 diff --git a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/0-Chapter_8/1-Philip_in_Samaria.md b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/0-Chapter_8/1-Philip_in_Samaria.md index 5d331240..dd41833b 100644 --- a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/0-Chapter_8/1-Philip_in_Samaria.md +++ b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/0-Chapter_8/1-Philip_in_Samaria.md @@ -1,6 +1,6 @@ title: Philip in Samaria ID: 32 -type: txt +type: md POV: 2 notes: {P:0:The good news spreads from Jerusalem to Rome} compile: 2 diff --git a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/0-Chapter_8/2-Simon_the_Sorcerer.md b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/0-Chapter_8/2-Simon_the_Sorcerer.md index 6420d90c..fb84e5a5 100644 --- a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/0-Chapter_8/2-Simon_the_Sorcerer.md +++ b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/0-Chapter_8/2-Simon_the_Sorcerer.md @@ -1,6 +1,6 @@ title: Simon the Sorcerer ID: 33 -type: txt +type: md POV: 2 compile: 2 diff --git a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/0-Chapter_8/3-Philip_and_the_Ethiopian.md b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/0-Chapter_8/3-Philip_and_the_Ethiopian.md index e3b32142..d41312c9 100644 --- a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/0-Chapter_8/3-Philip_and_the_Ethiopian.md +++ b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/0-Chapter_8/3-Philip_and_the_Ethiopian.md @@ -1,6 +1,6 @@ title: Philip and the Ethiopian ID: 36 -type: txt +type: md POV: 2 compile: 2 diff --git a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/1-Chapter_9/0-Saul-s_conversion.md b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/1-Chapter_9/0-Saul-s_conversion.md index 6f6eab4c..22f74fb6 100644 --- a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/1-Chapter_9/0-Saul-s_conversion.md +++ b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/1-Chapter_9/0-Saul-s_conversion.md @@ -1,6 +1,6 @@ title: Saul's conversion ID: 37 -type: txt +type: md POV: 1 compile: 2 diff --git a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/1-Chapter_9/1-Saul_in_Damascus_and_Jerusalem.md b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/1-Chapter_9/1-Saul_in_Damascus_and_Jerusalem.md index 6adc0299..fba2db5f 100644 --- a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/1-Chapter_9/1-Saul_in_Damascus_and_Jerusalem.md +++ b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/1-Chapter_9/1-Saul_in_Damascus_and_Jerusalem.md @@ -1,6 +1,6 @@ title: Saul in Damascus and Jerusalem ID: 38 -type: txt +type: md POV: 1 notes: {P:2:Paul and Barnabas fight} {C:5:Barnabas} diff --git a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/1-Chapter_9/2-Peter_visits_the_church_in_Judea.md b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/1-Chapter_9/2-Peter_visits_the_church_in_Judea.md index c00102d3..deccebe2 100644 --- a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/1-Chapter_9/2-Peter_visits_the_church_in_Judea.md +++ b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/1-Chapter_9/2-Peter_visits_the_church_in_Judea.md @@ -1,6 +1,6 @@ title: Peter visits the church in Judea ID: 39 -type: txt +type: md POV: 0 compile: 2 diff --git a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/2-Chapter_10/0-Cornelius_calls_for_Peter.md b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/2-Chapter_10/0-Cornelius_calls_for_Peter.md index f6732812..87de5c5e 100644 --- a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/2-Chapter_10/0-Cornelius_calls_for_Peter.md +++ b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/2-Chapter_10/0-Cornelius_calls_for_Peter.md @@ -1,6 +1,6 @@ title: Cornelius calls for Peter ID: 40 -type: txt +type: md POV: 0 notes: {P:1} compile: 2 diff --git a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/2-Chapter_10/1-Peter-s_vision.md b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/2-Chapter_10/1-Peter-s_vision.md index c6cb5aa5..2fb24c3c 100644 --- a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/2-Chapter_10/1-Peter-s_vision.md +++ b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/2-Chapter_10/1-Peter-s_vision.md @@ -1,6 +1,6 @@ title: Peter's vision ID: 41 -type: txt +type: md POV: 0 compile: 2 diff --git a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/2-Chapter_10/2-The_conversion_of_Peter.md b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/2-Chapter_10/2-The_conversion_of_Peter.md index 808121c0..687a4853 100644 --- a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/2-Chapter_10/2-The_conversion_of_Peter.md +++ b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/2-Chapter_10/2-The_conversion_of_Peter.md @@ -1,6 +1,6 @@ title: The conversion of Peter ID: 42 -type: txt +type: md POV: 0 compile: 2 diff --git a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/3-Chapter_11/0-Peter_explains_his_actions.md b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/3-Chapter_11/0-Peter_explains_his_actions.md index c6416781..abaa4b5e 100644 --- a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/3-Chapter_11/0-Peter_explains_his_actions.md +++ b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/3-Chapter_11/0-Peter_explains_his_actions.md @@ -1,6 +1,6 @@ title: Peter explains his actions ID: 43 -type: txt +type: md POV: 0 notes: {P:1:Peter needs to broaden his understanding of the Gospel} compile: 2 diff --git a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/3-Chapter_11/1-The_Church_in_Antioch.md b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/3-Chapter_11/1-The_Church_in_Antioch.md index 2c5cd39b..281ee95c 100644 --- a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/3-Chapter_11/1-The_Church_in_Antioch.md +++ b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/3-Chapter_11/1-The_Church_in_Antioch.md @@ -1,6 +1,6 @@ title: The Church in Antioch ID: 44 -type: txt +type: md notes: {P:2:Paul and Barnabas fight} {C:5:Barnabas} compile: 2 diff --git a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/4-Chapter_12/0-Peter_escapes_from_prison.md b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/4-Chapter_12/0-Peter_escapes_from_prison.md index d767703b..db21b528 100644 --- a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/4-Chapter_12/0-Peter_escapes_from_prison.md +++ b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/4-Chapter_12/0-Peter_escapes_from_prison.md @@ -1,6 +1,6 @@ title: Peter escapes from prison ID: 45 -type: txt +type: md POV: 0 notes: Mention: {C:4:Herod} compile: 2 diff --git a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/4-Chapter_12/1-Herod_dies.md b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/4-Chapter_12/1-Herod_dies.md index 8a246d32..b61d96bb 100644 --- a/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/4-Chapter_12/1-Herod_dies.md +++ b/sample-projects/book-of-acts/outline/1-Judea_and_Samaria/4-Chapter_12/1-Herod_dies.md @@ -1,6 +1,6 @@ title: Herod dies ID: 46 -type: txt +type: md POV: 4 notes: Present: - {C:1:Paul} diff --git a/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/0-Chapter_13/0-Saul_and_Barnabas_are_sent.md b/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/0-Chapter_13/0-Saul_and_Barnabas_are_sent.md index f505ce20..1a893301 100644 --- a/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/0-Chapter_13/0-Saul_and_Barnabas_are_sent.md +++ b/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/0-Chapter_13/0-Saul_and_Barnabas_are_sent.md @@ -1,6 +1,6 @@ title: Saul and Barnabas are sent ID: 51 -type: txt +type: md notes: {P:2:Paul and Barnabas fight} {C:5:Barnabas} diff --git a/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/0-Chapter_13/1-Cyprus.md b/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/0-Chapter_13/1-Cyprus.md index 0d495fe0..c36f9515 100644 --- a/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/0-Chapter_13/1-Cyprus.md +++ b/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/0-Chapter_13/1-Cyprus.md @@ -1,6 +1,6 @@ title: Cyprus ID: 52 -type: txt +type: md notes: {P:0:The good news spreads from Jerusalem to Rome} {C:5:Barnabas} compile: 2 diff --git a/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/0-Chapter_13/2-Psidian_Antioch.md b/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/0-Chapter_13/2-Psidian_Antioch.md index 90534031..e1b65aff 100644 --- a/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/0-Chapter_13/2-Psidian_Antioch.md +++ b/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/0-Chapter_13/2-Psidian_Antioch.md @@ -1,6 +1,6 @@ title: Psidian Antioch ID: 53 -type: txt +type: md notes: {C:5:Barnabas} compile: 2 diff --git a/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/1-Chapter_14/0-Iconium.md b/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/1-Chapter_14/0-Iconium.md index f1272416..2d6bc16a 100644 --- a/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/1-Chapter_14/0-Iconium.md +++ b/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/1-Chapter_14/0-Iconium.md @@ -1,6 +1,6 @@ title: Iconium ID: 54 -type: txt +type: md compile: 2 diff --git a/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/1-Chapter_14/1-Lystra_and_Derbe.md b/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/1-Chapter_14/1-Lystra_and_Derbe.md index fd52faa3..33a2ddce 100644 --- a/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/1-Chapter_14/1-Lystra_and_Derbe.md +++ b/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/1-Chapter_14/1-Lystra_and_Derbe.md @@ -1,6 +1,6 @@ title: Lystra and Derbe ID: 55 -type: txt +type: md notes: {C:5:Barnabas} compile: 2 diff --git a/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/1-Chapter_14/2-Back_to_Antioch.md b/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/1-Chapter_14/2-Back_to_Antioch.md index 653cf75f..dbcc9953 100644 --- a/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/1-Chapter_14/2-Back_to_Antioch.md +++ b/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/1-Chapter_14/2-Back_to_Antioch.md @@ -1,6 +1,6 @@ title: Back to Antioch ID: 56 -type: txt +type: md compile: 2 diff --git a/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/2-Chapter_15/0-The_Council_at_Jerusalem.md b/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/2-Chapter_15/0-The_Council_at_Jerusalem.md index 310118cd..02386eb8 100644 --- a/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/2-Chapter_15/0-The_Council_at_Jerusalem.md +++ b/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/2-Chapter_15/0-The_Council_at_Jerusalem.md @@ -1,6 +1,6 @@ title: The Council at Jerusalem ID: 57 -type: txt +type: md notes: {P:1:Peter needs to broaden his understanding of the Gospel} {C:5:Barnabas} compile: 2 diff --git a/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/2-Chapter_15/1-The_Council_writes_to_gentile_believers.md b/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/2-Chapter_15/1-The_Council_writes_to_gentile_believers.md index 0316c36d..d71bcfbf 100644 --- a/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/2-Chapter_15/1-The_Council_writes_to_gentile_believers.md +++ b/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/2-Chapter_15/1-The_Council_writes_to_gentile_believers.md @@ -1,6 +1,6 @@ title: The Council writes to gentile believers ID: 58 -type: txt +type: md notes: {C:5:Barnabas} compile: 2 diff --git a/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/2-Chapter_15/2-Paul_and_Barnabas_fight_and_split.md b/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/2-Chapter_15/2-Paul_and_Barnabas_fight_and_split.md index 67ea8d19..37bf86cf 100644 --- a/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/2-Chapter_15/2-Paul_and_Barnabas_fight_and_split.md +++ b/sample-projects/book-of-acts/outline/2-To_the_extremities_of_the_world/0-Asia_Minor_and_Greece/2-Chapter_15/2-Paul_and_Barnabas_fight_and_split.md @@ -1,6 +1,6 @@ title: Paul and Barnabas fight and split ID: 59 -type: txt +type: md notes: {P:2:Paul and Barnabas fight} {C:5:Barnabas} compile: 2 diff --git a/sample-projects/book-of-acts/revisions.xml b/sample-projects/book-of-acts/revisions.xml index 4ee7b177..84fab665 100644 --- a/sample-projects/book-of-acts/revisions.xml +++ b/sample-projects/book-of-acts/revisions.xml @@ -2,92 +2,92 @@ - + - + - + - + - - + + - - + + - - - + + + - - - + + + - - + + - - + + - - - - + + + + - - + + - + - + - - + + - - + + - - + + - - - + + + - - - + + + - - - + + + diff --git a/sample-projects/book-of-acts/settings.txt b/sample-projects/book-of-acts/settings.txt index 1eca0915..4d2ed963 100644 --- a/sample-projects/book-of-acts/settings.txt +++ b/sample-projects/book-of-acts/settings.txt @@ -24,7 +24,6 @@ ], "outlineViewColumns": [ 0, - 5, 8, 9, 11, @@ -44,6 +43,7 @@ "smartremove": true }, "saveOnQuit": true, + "saveToZip": false, "spellcheck": false, "textEditor": { "background": "#fff", @@ -56,6 +56,7 @@ "spacingBelow": 5, "tabWidth": 20 }, + "viewMode": "fiction", "viewSettings": { "Cork": { "Background": "Nothing", From 29ef36dd9a0fa2f7aabd2956eaea866d6e784f07 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 30 Mar 2016 14:07:21 +0200 Subject: [PATCH 089/103] Updates i18l and french translation --- i18n/manuskript.pro | 70 +- i18n/manuskript_es.qm | Bin 23231 -> 24746 bytes i18n/manuskript_es.ts | 2152 ++++++++++++++++++++++++++++------ i18n/manuskript_fr.qm | Bin 43288 -> 48032 bytes i18n/manuskript_fr.ts | 2153 ++++++++++++++++++++--------------- manuskript/ui/mainWindow.py | 9 +- manuskript/ui/mainWindow.ui | 11 +- 7 files changed, 3049 insertions(+), 1346 deletions(-) diff --git a/i18n/manuskript.pro b/i18n/manuskript.pro index e8e8c6e5..833c926c 100644 --- a/i18n/manuskript.pro +++ b/i18n/manuskript.pro @@ -1,58 +1,64 @@ -FORMS += ../manuskript/ui/mainWindow.ui -FORMS += ../manuskript/ui/settings_ui.ui -FORMS += ../manuskript/ui/welcome_ui.ui -FORMS += ../manuskript/ui/sldImportance_ui.ui -FORMS += ../manuskript/ui/cheatSheet_ui.ui -FORMS += ../manuskript/ui/compileDialog_ui.ui FORMS += ../manuskript/ui/revisions_ui.ui +FORMS += ../manuskript/ui/mainWindow.ui +FORMS += ../manuskript/ui/compileDialog_ui.ui +FORMS += ../manuskript/ui/search_ui.ui +FORMS += ../manuskript/ui/tools/frequency_ui.ui +FORMS += ../manuskript/ui/welcome_ui.ui +FORMS += ../manuskript/ui/cheatSheet_ui.ui +FORMS += ../manuskript/ui/settings_ui.ui FORMS += ../manuskript/ui/editors/editorWidget_ui.ui +FORMS += ../manuskript/ui/editors/textFormat_ui.ui FORMS += ../manuskript/ui/editors/locker_ui.ui +FORMS += ../manuskript/ui/editors/completer_ui.ui +FORMS += ../manuskript/ui/editors/mainEditor_ui.ui FORMS += ../manuskript/ui/views/propertiesView_ui.ui -FORMS += ../manuskript/ui/views/basicItemView_ui.ui FORMS += ../manuskript/ui/views/metadataView_ui.ui +FORMS += ../manuskript/ui/views/basicItemView_ui.ui +FORMS += ../manuskript/ui/views/sldImportance_ui.ui +FORMS += ../manuskript/ui/views/storylineView_ui.ui + +SOURCES += ../manuskript/exporter/__init__.py +SOURCES += ../manuskript/load_save/version_0.py SOURCES += ../manuskript/main.py -SOURCES += ../manuskript/loadSave.py SOURCES += ../manuskript/mainWindow.py -SOURCES += ../manuskript/settingsWindow.py - +SOURCES += ../manuskript/models/characterModel.py SOURCES += ../manuskript/models/outlineModel.py SOURCES += ../manuskript/models/persosProxyModel.py SOURCES += ../manuskript/models/plotModel.py -SOURCES += ../manuskript/models/worldModel.py -SOURCES += ../manuskript/models/persosModel.py +SOURCES += ../manuskript/models/plotsProxyModel.py SOURCES += ../manuskript/models/references.py - -SOURCES += ../manuskript/exporter/__init__.py - -SOURCES += ../manuskript/ui/helpLabel.py -SOURCES += ../manuskript/ui/sldImportance.py -SOURCES += ../manuskript/ui/welcome.py +SOURCES += ../manuskript/models/worldModel.py +SOURCES += ../manuskript/settingsWindow.py SOURCES += ../manuskript/ui/cheatSheet.py -SOURCES += ../manuskript/ui/compileDialog.py -SOURCES += ../manuskript/ui/revisions.py SOURCES += ../manuskript/ui/collapsibleDockWidgets.py - -SOURCES += ../manuskript/ui/editors/editorWidget.py +SOURCES += ../manuskript/ui/compileDialog.py SOURCES += ../manuskript/ui/editors/fullScreenEditor.py SOURCES += ../manuskript/ui/editors/locker.py -SOURCES += ../manuskript/ui/editors/textFormat.py -SOURCES += ../manuskript/ui/editors/completer.py SOURCES += ../manuskript/ui/editors/mainEditor.py - -SOURCES += ../manuskript/ui/views/corkDelegate.py -SOURCES += ../manuskript/ui/views/outlineDelegates.py -SOURCES += ../manuskript/ui/views/outlineBasics.py +SOURCES += ../manuskript/ui/editors/textFormat.py +SOURCES += ../manuskript/ui/helpLabel.py +SOURCES += ../manuskript/ui/revisions.py +SOURCES += ../manuskript/ui/search.py +SOURCES += ../manuskript/ui/tools/frequencyAnalyzer.py +SOURCES += ../manuskript/ui/views/characterTreeView.py +SOURCES += ../manuskript/ui/views/cmbOutlineCharacterChoser.py SOURCES += ../manuskript/ui/views/cmbOutlineLabelChoser.py -SOURCES += ../manuskript/ui/views/cmbOutlinePersoChoser.py SOURCES += ../manuskript/ui/views/cmbOutlineStatusChoser.py -SOURCES += ../manuskript/ui/views/treeView.py +SOURCES += ../manuskript/ui/views/corkDelegate.py SOURCES += ../manuskript/ui/views/lineEditView.py -SOURCES += ../manuskript/ui/views/textEditView.py -SOURCES += ../manuskript/ui/views/plotTreeView.py +SOURCES += ../manuskript/ui/views/outlineBasics.py +SOURCES += ../manuskript/ui/views/outlineDelegates.py SOURCES += ../manuskript/ui/views/plotDelegate.py +SOURCES += ../manuskript/ui/views/plotTreeView.py +SOURCES += ../manuskript/ui/views/sldImportance.py +SOURCES += ../manuskript/ui/views/storylineView.py +SOURCES += ../manuskript/ui/views/textEditCompleter.py +SOURCES += ../manuskript/ui/views/textEditView.py +SOURCES += ../manuskript/ui/views/treeView.py +SOURCES += ../manuskript/ui/welcome.py TRANSLATIONS += manuskript_fr.ts TRANSLATIONS += manuskript_es.ts diff --git a/i18n/manuskript_es.qm b/i18n/manuskript_es.qm index 8a53b807a03e0577469453c6c3cc7a967cd274aa..26cd9a415251e68a71405b71292e02bae5675b61 100644 GIT binary patch delta 3379 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 PNllfBdl6ZYaoqm^R_n<% delta 3188 zcmah~c~nzp7QZif?`2Qe1Z53^prTZ8B?^dvum}ic#(ow0kt(B?mRIS$0Ixf|Ae&KmM$2n)t{E_#2@B8j|@BRJm zy#)IEXB9h-HqCrI7+kjC-8Kp$*Ux-w116KnN z16zPsh{ET=ZVAk^M21(1(vA^L3@6GxK{PN_#EDHr(@gljMQmF=QT$zE_p?O)vx#-| znUKA9F>xuQf!Byjk0VOl4g7^jH<`G+B%*`_;%Z8W+~*P3 zf%V~E61Q(RQNM#CMvW)!+i64t3Me9JGZ9lt5v7NSByx&q0#nI2idcOdxS1l(KpA!n z#e6%GDBzrfl25}x89^y^F+@IQO6%N9q$s5Hv5&AmlhTtri83Ei`oTe9rl1T<0}c+M zjN?nd#FsMjA40h{%06Hq(icv@UAT~@S+SqxPI=3aXo8~_ zFI_|_rzn4n6AU-g^N+zWypE=yyi4SHh6>!45e=P5GbA4njrfLUsE|m%wI_BnIo#ucLeTO_%K zHlmS6$@DWwIDCs_&h{^fCT*8A^j;)NHAxoC0kH{7C0qR#5e>@S5M={?DVFfq7F%y>T(0`Ifc-LT89VC#x@ zJ5ZAyY}3vnBEF7Yor>BFXcf`$KD)Ny2)@5y+r|_S`Hlw)z;br$zRd_2z;0{)9ph?b zx2<4+b|4(fH;d?JXFG<$&R-?s#OFm!cV~Al%1248?5+_GFmn%J_jC?|p@H2yb}G>r zEBmnx7}v~pQzGsFV!I7UL>+G)}WiesQXOdGYEUx7hH0R(Y+=fgL89J0}Z$yB! zYT!|fYp{sMAGn=9@1i&KT+alId%tU(lQ$1mHUg+#0QcNTt+Ip4&$JwUH` z2ktke53qHf#XUj5oc46M%l2tj8;P_3l z>h7~h;IwQTbBZX$O|~ojCTc!e_MvAh#_Fu>V!%U4y;OGNCX@}@`B04O1^I$#PmJ#>5gkca-Dpk|4^f1cAx=Fx)ja}C&7G;;%}#?A%SkiN7KAf(|by# zvjLK;ly2Rti2UAF#vB1dO|OVSOO*+$P;=8gWl5C<i z0;66Rj{Vp{>i35^aB;YwS9k3Sfn%L|KZ8UD?^AzaM3;un(8r}VgX8|7@#TY@I{Z@2A)NrlN#$+BQf_6HLq^{8cp|_<~=_&+qf@8jQgkN z10H|FK5uA_Io8`@FkW-) zku>r6$-_+5=3=|qDtxZ;65LcJ!Y0)&y_Ui#lPYnd;u&jMSY~B)g=p-Ys*Yh9v#{K) z#2M;d!Z;6k1WN*4CzGgz%KG9|KaImBg_V@q5yacs>($N(x74g~S?R-?%EGL|8-D$q zjo#xJ!LIV+*c6MksxOo10&oj|dYOb}~^>)iVyS~)L(k?Ke zUIL#Ug%do5P= z_|;VDLr>sU_X@gyrrCRSY(HEI(%7l%vUe1n~YjP)V#HmV zAuhYWV44)`d@d(XA{@wV5^AP7oX_R;vVyH3+-WR~XR=2<>jIAcZx4{+sVW}NbU|NG Q=iHfcsq8h3iiBbQ1DAeA00000 diff --git a/i18n/manuskript_es.ts b/i18n/manuskript_es.ts index 48d629ab..a87f7f68 100644 --- a/i18n/manuskript_es.ts +++ b/i18n/manuskript_es.ts @@ -1,10 +1,56 @@ - - + + + FrequencyAnalyzer + + + Frequency Analyzer + + + + + Word frequency + + + + + Settings + Preferencias + + + + Minimum size: + + + + + Exclude words (coma seperated): + + + + + Analyze + + + + + Phrase frequency + + + + + Number of words: from + + + + + to + + + MainWindow - General General @@ -50,8 +96,6 @@ Autor - - Name Nombre @@ -62,8 +106,6 @@ Email - - Summary Resumen @@ -74,42 +116,36 @@ Situación: - Summary: Resumen: - One sentance - Una frase + One sentence + Una frase - One paragraph Un párrafo - One page Una página - Full Lleno - - One sentance summary - Resumen de una frase + One sentence summary + Resumen de una frase - One paragraph summary Resumen de un párrafo @@ -130,12 +166,6 @@ Resumen completo - - - - - - Next Siguiente @@ -156,21 +186,16 @@ Nombres - - - Filter Filtro - Basic infos Informaciónes básicas - Importance Importancia @@ -197,8 +222,8 @@ - <html><head/><body><p align="right">One sentance<br/>summary</p></body></html> - <html><head/><body><p align="right">Resumen de<br/>una frase</p></body></html> + <html><head/><body><p align="right">One sentence<br/>summary</p></body></html> + <html><head/><body><p align="right">Resumen de<br/>una frase</p></body></html> @@ -216,8 +241,6 @@ Informaciones detalladas - - Plots Tramas @@ -233,7 +256,6 @@ Personaje(s) - Description Descripción @@ -249,13 +271,11 @@ Pasos para la resolución - World Mundo - Populates with empty data Rellenas con datos vacíos @@ -276,9 +296,7 @@ Fuente del conflicto - - - + Outline @@ -309,8 +327,8 @@ - Fi&le - &Archivo + &File + &Archivo @@ -318,170 +336,347 @@ &Recientes - + &Mode &Modo - - Help - Ayuda + + &Help + Ayuda - + &Tools &Herramientas - E&dit - &Editar - - - &View &Ver - + &Cheat sheet &Chuleta - + Sea&rch &Buscar - + &Navigation &Navegación - + &Open &Abrir - + Ctrl+O Ctrl+O - + &Save &Guardar - + Ctrl+S Ctrl+S - + Sa&ve as... G&uardar Como... - + Ctrl+Shift+S Ctrl+Shift+S - + &Quit &Quitar - + Ctrl+Q Ctrl+Q - + &Show help texts &Ver textos de ayuda - + Ctrl+Shift+B Ctrl+Shift+B - + &Spellcheck &Corrector Ortográfico - + F9 F9 - + &Labels... &Etiquetas... - + &Status... E&stado... - + Tree Árbol - - &Normal - &Normal - - - + &Simple &Simple - - &Fractal - &Fractal - - - + Index cards Fichas - + S&ettings &Preferencias - + F8 F8 - + &Close project &Cerrar proyecto - + Co&mpile C&ompilar - + F6 F6 - + &Frequency Analyzer A&nalizador de frecuencia + + + &Fiction + + + + + S&nowflake + + + + + The file {} does not exist. Try again. + + + + + Project {} saved. + + + + + Project {} loaded. + + + + + Project {} loaded with some errors: + + + + + * {} wasn't found in project file. + + + + + Project {} loaded with some errors. + + + + + (~{} pages) + + + + + Words: {}{} + + + + + Book summary + + + + + Project tree + + + + + Metadata + + + + + Story line + + + + + Enter infos about your book, and yourself. + + + + + The basic situation, in the form of a 'What if...?' question. Ex: 'What if the most dangerous + evil wizard could wasn't abled to kill a baby?' (Harry Potter) + + + + + Take time to think about a one sentence (~50 words) summary of your book. Then expand it to + a paragraph, then to a page, then to a full summary. + + + + + Create your characters. + + + + + Develop plots. + + + + + Create the outline of your masterpiece. + + + + + Write. + + + + + Debug infos. Sometimes useful. + + + + + Dictionary + + + + + Install PyEnchant to use spellcheck + + + + + Nothing + Ninguno + + + + POV + + + + + Label + Etiqueta + + + + Progress + Progreso + + + + Compile + Compilar + + + + Icon color + + + + + Text color + + + + + Background color + + + + + Icon + + + + + Text + Texto + + + + Background + Fondo + + + + Border + + + + + Corner + + + + + &Edit + + Settings @@ -496,8 +691,7 @@ General - - + Revisions Revisiones @@ -507,21 +701,17 @@ Vistas - - + Labels Etiquetas - - - + Status Estado - - + Fullscreen Pantalla Completa @@ -541,574 +731,516 @@ Podrias necesitar reiniciar manuskript para evitar problemas visuales. - + Loading Cargando - + Automatically load last project on startup Cargar automáticamente el último proyecto al iniciar - + Saving Guardando - + Automatically save every Grabar automáticamente en - + minutes. minutos. - + If no changes during si no hay cambios durante - + seconds. segundos. - + Save on quit Guardar al salir - - Default text format - Formato de texto por defecto - - - - The format set by default when you create a new text item. You can change this on a per item basis. - El formato se indica por defecto cuando creas un nuevo elemento de texto. Puedes cambiar esta opción para cada elemento. - - - + Revisions are a way to keep track of modifications. For each text item, it stores any changes you make to the main text, allowing you to see and restoring previous versions. Las revisiones son una manera de realizar un seguimiento de las modificaciones. Para cada elemento de texto, almacena cualquier cambio que hagas en el texto principal, permientiendote ver y restaurar versiones anteriores. - + Keep revisions Alamacenar revisiones - + S&mart remove Al&macenamiento inteligente - + Keep: Almacenar: - + Smart remove allows you to keep only a certain number of revisions. It is strongly recommended to use it, lest you file will becomes full of thousands of insignificant changes. El almacenamiento inteligente te permite almacenar solo un cierto número de revisiones. Se recomienda encarecidamente que lo uses, para que tu archivo no se llene de miles de cambios insignificantes. - + revisions per day for the last month revisiones por día durante el último mes - + revisions per minute for the last 10 minutes revisiones por minuto durante los últimos 10 minutos - + revisions per hour for the last day revisiones por hora durante el último día - + revisions per 10 minutes for the last hour revisiones cada 10 minutos durante la última hora - + revisions per week till the end of time revisiones por semana hasta el fin de los tiempos - + Views settings Preferencias de visualización - + Tree Árbol - - + Colors Colores - - - + Icon color: Color del icono: - - - - - - - - - - - - - + Nothing Ninguno - - - - - - - - - - - - + POV - - - - - - - - - - - - + Label Etiqueta - - - - - - - - - - - + Progress Progreso - - - - - - - - - - - - + Compile Compilar - - - + Text color: Color del texto: - - - + Background color: Color del fondo: - + Folders Carpetas - + Show ite&m count Ver número de ele&mentos - - + Show wordcount Ver número de palabras - - + Show progress Ver progreso - - + Show summary Ver resumen - + Text Texto - + Outline - + Visible columns Columnas visibles - + Goal Meta - + Word count Número de palabras - + Percentage Porcentaje - + Title Título - + Index cards Fichas - + Item colors Color de los elementos - + Border color: Color del borde: - + Corner color: Color de la esquina: - + Background Fondo - - - - - + Color: Color: - - + Ctrl+S Ctrl+S - - + Image: Imagen: - + Text editor Editor de texto - + Font Fuente - + Family: Familia: - - + Size: Tamaño: - - + Misspelled: Errata: - + Background: Fondo: - + Paragraphs Párrafos - - + Line spacing: Espaciado de linea: - - + Single Sencilla - - + 1.5 lines 1.5 lineas - - + Double Doble - - + Proportional Proporcional - - - + % % - - + Tab width: Anchura del tabulador: - - - - - - - - - - + px px - - + Indent 1st line Indentar la 1ª linea - - + Spacing: Espaciado: - + New Nuevo - + Edit Editar - + Delete Borrar - + Theme name: Nombre del tema - + Apply Aplicar - + Cancel Cancelar - + Window Background Fondo de la ventana - + Text Background Fondo del texto - + Text Options Opciones de texto - + Paragraph Options Opciones de párrafo - + Type: Tipo - + No Image Sin Imagen - + Tiled Tileado - + Centered Centrado - - + Stretched Ajustado - + Scaled Escalado - + Zoomed Agrandado - + Opacity: Opacidad: - + Position: Posición: - + Left Izquierda - + Center Centro - + Right Derecha - + Width: Anchura: - + Corner radius: Radio de la esquina: - + Margins: Margenes: - + Padding: - Bad translate? Relleno: - + Font: Fuente: + + + Application language + + + + + <html><head/><body><p>If you check this option, your project will be save as one single file. Easier to copy or backup, but does not allow collaborative editing, or versionning.<br/>If this is unchecked, your project will be save as a folder containing many small files.</p></body></html> + + + + + Save to one single file + + + + + &Nothing + + + + + You will need to restart manuskript for the translation to take effect. + + + + + SpellAction + + + Spelling Suggestions + + + + + app + + + Loaded translation: {}. + + + + + Warning: failed to load translator for locale {}... + + basicItemView @@ -1143,6 +1275,42 @@ Resumen de unas pocas frases: + + characterModel + + + New character + + + + + Name + Nombre + + + + Value + + + + + characterTreeView + + + Main + + + + + Secondary + + + + + Minor + + + cheatSheet @@ -1155,6 +1323,93 @@ Filter (type the name of anything in your project) Filtro (escribe el nombre de cualquier elemento de tu proyecto) + + + Minor + + + + + Secondary + + + + + Main + + + + + Characters + Personajes + + + + Texts + + + + + Plots + Tramas + + + + World + Mundo + + + + cmbOutlineCharacterChoser + + + None + Ninguno + + + + Main + + + + + Secondary + + + + + Minor + + + + + Various + + + + + cmbOutlineLabelChoser + + + Various + + + + + cmbOutlineStatusChoser + + + Various + + + + + collapsibleDockWidgets + + + Dock Widgets Toolbar + + compileDialog @@ -1174,7 +1429,6 @@ Carpeta: - ... ... @@ -1194,6 +1448,42 @@ Cancel Cancelar + + + Working... + + + + + Chose export folder + + + + + Chose export target + + + + + completer + + + Form + + + + + corkDelegate + + + One line summary + Resumen de una línea + + + + Full summary + Resumen completo + editorWidget_ui @@ -1203,6 +1493,86 @@ + + exporter + + + HTML + + + + + HTML Document (*.html) + + + + + Arborescence + + + + + OpenDocument (LibreOffice) + + + + + OpenDocument (*.odt) + + + + + frequencyAnalyzer + + + Phrases + + + + + Frequency + + + + + Word + + + + + fullScreenEditor + + + Theme: + + + + + {} words / {} + + + + + {} words + {} palabras + + + + helpLabel + + + If you don't wanna see me, you can hide me in Help menu. + + + + + lineEditView + + + Various + + + locker @@ -1240,6 +1610,79 @@ Lock ! ¡ Bloquear ! + + + ~{} h. + + + + + ~{} mn. + + + + + {}:{} + + + + + {} s. + + + + + {} remaining + + + + + {} words remaining + + + + + mainEditor + + + Form + + + + + Text + Texto + + + + Index cards + Fichas + + + + Outline + + + + + F11 + + + + + Root + + + + + {} words / {} + + + + + {} words + {} palabras + metadataView @@ -1269,7 +1712,6 @@ Resumen completo - Notes / References Noras / Referencias @@ -1337,19 +1779,372 @@ New Nuevo + + + Main + + + + + Secondary + + + + + Minor + + + + + outlineCharacterDelegate + + + None + Ninguno + + + + Main + + + + + Secondary + + + + + Minor + + outlineModel - + {} words / {} ({}) {} palabras / {} ({}) - + {} words {} palabras + + + Title + Título + + + + POV + + + + + Label + Etiqueta + + + + Status + Estado + + + + Compile + Compilar + + + + Word count + Número de palabras + + + + Goal + Meta + + + + persosProxyModel + + + Main + + + + + Secundary + + + + + Minors + + + + + plotDelegate + + + General + General + + + + Promise + + + + + Problem + + + + + Progress + Progreso + + + + Resolution + + + + + Try / Fail + + + + + No and + + + + + Yes but + + + + + Freytag's pyramid + + + + + Exposition + + + + + Rising action + + + + + Climax + + + + + Falling action + + + + + Three acts + + + + + 1. Setup + + + + + 1. Inciting event + + + + + 1. Turning point + + + + + 2. Choice + + + + + 2. Reversal + + + + + 2. Disaster + + + + + 3. Stand up + + + + + 3. Climax + + + + + 3. Ending + + + + + Hero's journey + + + + + Ordinary world + + + + + Call to adventure + + + + + Refusal of the call + + + + + Meeting with mentor + + + + + Corssing the Threshold + + + + + Tests + + + + + Approach + + + + + Abyss + + + + + Reward / Revelation + + + + + Transformation + + + + + Atonement + + + + + Return + + + + + plotModel + + + New plot + + + + + Name + Nombre + + + + Meta + + + + + New step + + + + + Main + + + + + Secondary + + + + + Minor + + + + + plotTreeView + + + Main + + + + + Secondary + + + + + Minor + + + + + **Plot:** {} + + + + + plotsProxyModel + + + Main + + + + + Secundary + + + + + Minors + + propertiesView @@ -1359,59 +2154,45 @@ Formulario - - + POV - - + Status Estado - - + Label Etiqueta - - + Compile Compilar - - + Goal Meta - - + Word count Número de palabras - - - Text type: - Tipo de texto: - references - - + Not a reference: {}. No es una referencia: {}. - - - + Unknown reference: {}. Referencia desconocida: {}. @@ -1456,93 +2237,115 @@ Notas: - + Basic infos Informaciones básicas - + Detailed infos Informaciones detalladas - + POV of: - - - + Go to {}. Ir a {}. - - + Description Descripción - + Result Resultado - + Characters Personajes - + Resolution steps Pasos para la resolución - + Passion Pasión - + Conflict Conflicto - + Folder: <b>{}</b> Carpeta: <b>{}</b> - + Text: <b>{}</b> Texto: <b>{}</b> - + Character: <b>{}</b> Personaje: <b>{}</b> - + Plot: <b>{}</b> Trama: <b>{}</b> - + World: <b>{name}</b>{path} Mundo: <b>{name}</b>{path} - + <b>Unknown reference:</b> {}. <b>Referencia desconocida:</b> {}. - + Referenced in: Referenciado en: + + + Motivation + Motivación + + + + Goal + Meta + + + + Epiphany + Epifanía + + + + Short summary + + + + + Longer summary + + revisions @@ -1557,28 +2360,324 @@ Opciones - + Restore Restaurar - + Delete Borrar + + + Show modifications + + + + + Show ancient version + + + + + Show spaces + + + + + Show modifications only + + + + + {} years ago + + + + + {} months ago + + + + + {} days ago + + + + + 1 day ago + + + + + {} hours ago + + + + + {} minutes ago + + + + + {} seconds ago + + + + + Line {}: + + + + + Clear all + + + + + search + + + Form + + + + + Search in: + + + + + All + + + + + Title + Título + + + + Text + Texto + + + + Summary + Resumen + + + + Notes + Notas + + + + POV + + + + + Status + Estado + + + + Label + Etiqueta + + + + Options: + + + + + Case sensitive + + + + + settingsWindow + + + New status + + + + + New label + + + + + newtheme + + + + + New theme + + sldImportance - + Form Formulario - + TextLabel EtiquetadeTexto + + + Minor + + + + + Secondary + + + + + Main + + + + + storylineView + + + Form + + + + + + + + + + + - + + + + + Show Plots + + + + + Show Characters + + + + + textEditCompleter + + + Insert reference + + + + + textEditView + + + Various + + + + + textFormat + + + Form + + + + + CTRL+B + + + + + CTRL+I + + + + + CTRL+U + + + + + CTRL+P + + + + + CTRL+L + + + + + CTRL+E + + + + + CTRL+R + + + + + CTRL+J + + + + + treeView + + + Root + + + + + Open {} items in new tabs + + + + + Open {} in a new tab + + + + + Expand {} + + + + + Collapse {} + + + + + Expand All + + + + + Collapse All + + welcome @@ -1603,30 +2702,27 @@ Vacía - + Novel Novela - + Novella - Extracted from crosslanguage wikipedia Novela Corta - + Short Story - Extracted from crosslanguage wikipedia Cuento - + Research paper - Bad translation Árticulo de investigación - + Demo projects Proyectos de ejemplo @@ -1641,29 +2737,367 @@ Añadir cuenta de palabras - - Default text type: - Tipo de texto por defecto: - - - + Next time, automatically open last project La proxima vez, abrir el último proyecto automáticamente - + Open... Abrir... - + Recent Reciente - + Create Crear + + + Open project + + + + + Manuskript project (*.msk);;All files (*) + + + + + Save project as... + + + + + Manuskript project (*.msk) + + + + + Create New Project + + + + + Empty fiction + + + + + Chapter + + + + + Scene + + + + + Trilogy + + + + + Book + + + + + Section + + + + + Empty non-fiction + + + + + words each. + + + + + of + + + + + Text + Texto + + + + Something + + + + + <b>Total:</b> {} words (~ {} pages) + + + + + Fiction + + + + + Non-fiction + + + + + Idea + + + + + Note + + + + + Research + + + + + TODO + + + + + First draft + + + + + Second draft + + + + + Final + + + + + worldModel + + + New item + + + + + Fantasy world building + + + + + Physical + + + + + Climate + + + + + Topography + + + + + Astronomy + + + + + Natural ressources + + + + + Wild life + + + + + Flora + + + + + History + + + + + Races + + + + + Diseases + + + + + Cultural + + + + + Customs + + + + + Food + + + + + Languages + + + + + Education + + + + + Dresses + + + + + Science + + + + + Calendar + + + + + Bodily language + + + + + Ethics + + + + + Religion + + + + + Government + + + + + Politics + + + + + Gender roles + + + + + Music and arts + + + + + Architecture + + + + + Military + + + + + Technology + + + + + Courtship + + + + + Demography + + + + + Transportation + + + + + Medicine + + + + + Magic system + + + + + Rules + + + + + Organization + + + + + Magical objects + + + + + Magical places + + + + + Magical races + + + + + Important places + + + + + Important objects + + diff --git a/i18n/manuskript_fr.qm b/i18n/manuskript_fr.qm index 8babc02c7e3115590551ecb66a60e2e6d814e79b..cd51be1876d502c74d79b70878a3d33584a5c302 100644 GIT binary patch delta 10510 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 AlmGw# delta 6891 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 VhvUr7G^y|Z?hvcPea{m0e*?1waf|=} diff --git a/i18n/manuskript_fr.ts b/i18n/manuskript_fr.ts index 6a308d39..82bee146 100644 --- a/i18n/manuskript_fr.ts +++ b/i18n/manuskript_fr.ts @@ -1,618 +1,683 @@ - + - MainWindow + FrequencyAnalyzer - - Overview - Aperçu + + Frequency Analyzer + Analyseur de fréquence - - Book infos - Informations sur le livre + + Word frequency + Fréquence des mots - - Title - Titre - - - - Subtitle - Sous-titre - - - - Serie - Série - - - - Volume - Volume - - - - Genre - Genre - - - - License - License - - - - Author - Informations sur l'auteur - - - - Name - Nom - - - - Email - Email - - - - Summary - Résumé - - - - One sentence - Une phrase - - - - One sentence summary - Résumé en une phrase - - - - Next - Suivant - - - - One paragraph - Un paragraphe - - - - One paragraph summary - Résumé en un paragraphe - - - - One page - Une page - - - - Expand each sentence of your one paragraph summary to a paragraph - Développez chaque phrase du paragraphe précédent en un paragraphe complet - - - - Full - Complet - - - - One page summary - Résumé en une page - - - - Full summary - Résumé complet - - - - Characters - Personnages - - - - Names - Noms - - - - Filter - Filtre - - - - Basic infos - Informations générales - - - - Importance - Importance - - - - Motivation - Motivation - - - - Goal - Goal - - - - Conflict - Conflit - - - - Epiphany - Épiphanie - - - - <html><head/><body><p align="right">One sentence<br/>summary</p></body></html> - <html><head/><body><p align="right">Résumé<br/>en une phrase</p></body></html> - - - - <html><head/><body><p align="right">One paragraph<br/>summary</p></body></html> - <html><head/><body><p align="right">Résumé<br/>en un paragraphe</p></body></html> - - - - Notes - Notes - - - - Detailed infos - Informations détaillées - - - - Plots - Intrigues - - - - Plot - Intrigue - - - - Character(s) - Personnage(s) - - - - Description - Description - - - - Result - Résultat - - - - Resolution steps - Étapes de résolution - - - - Outline - Plan - - - - Redaction - Rédaction - - - - Tools - Outils - - - - Cheat sheet - Aide-mémoire - - - - Debug - Debug - - - - FlatData - FlatData - - - - Persos - Persos - - - - File - Fichier - - - - Help - Aide - - - - Open - Ouvrir - - - - Ctrl+O - - - - - Recents - Récents - - - - Save - Enregistrer - - - - Ctrl+S - Ctrl+S - - - - Save as... - Enregistrer sous... - - - - Ctrl+Shift+S - - - - - Quit - Quitter - - - - Ctrl+Q - - - - - Show help texts - Montrer les bulles d'aides - - - - Ctrl+Shift+B - - - - - Spellcheck - Correcteur orthographique - - - - F8 - - - - - Mode - Mode - - - - Labels - Labels - - - - Edit - Édition - - - - Labels... - Labels... - - - - Status... - Status... - - - - Situation: - Situation: - - - - Summary: - Résumé: - - - - What if...? - Et si...? - - - - Index cards - Cartes - - - - View - Vue - - - - F9 - F9 - - - - Tree - Arbre - - - - Normal - Normal - - - - Simple - Simple - - - - Fractal - Fractal - - - + Settings Réglages - + + Minimum size: + Taille minimum: + + + + Exclude words (coma seperated): + Exclure les mots<br>(séparés par des virgules): + + + + Analyze + Analyser + + + + Phrase frequency + Fréquence des phrases + + + + Number of words: from + Nombre de mots: de + + + + to + à + + + + MainWindow + + + Book infos + Informations sur le livre + + + + Title + Titre + + + + Subtitle + Sous-titre + + + + Serie + Série + + + + Volume + Volume + + + + Genre + Genre + + + + License + License + + + + Author + Informations sur l'auteur + + + + Name + Nom + + + + Email + Email + + + + Summary + Résumé + + + + One sentence + Une phrase + + + + One sentence summary + Résumé en une phrase + + + + Next + Suivant + + + + One paragraph + Un paragraphe + + + + One paragraph summary + Résumé en un paragraphe + + + + One page + Une page + + + + Expand each sentence of your one paragraph summary to a paragraph + Développez chaque phrase du paragraphe précédent en un paragraphe complet + + + + Full + Complet + + + + One page summary + Résumé en une page + + + + Full summary + Résumé complet + + + + Characters + Personnages + + + + Names + Noms + + + + Filter + Filtre + + + + Basic infos + Informations générales + + + + Importance + Importance + + + + Motivation + Motivation + + + + Goal + Cible + + + + Conflict + Conflit + + + + Epiphany + Épiphanie + + + + <html><head/><body><p align="right">One sentence<br/>summary</p></body></html> + <html><head/><body><p align="right">Résumé<br/>en une phrase</p></body></html> + + + + <html><head/><body><p align="right">One paragraph<br/>summary</p></body></html> + <html><head/><body><p align="right">Résumé<br/>en un paragraphe</p></body></html> + + + + Notes + Notes + + + + Detailed infos + Informations détaillées + + + + Plots + Intrigues + + + + Plot + Intrigue + + + + Character(s) + Personnage(s) + + + + Description + Description + + + + Result + Résultat + + + + Resolution steps + Étapes de résolution + + + + Outline + Plan + + + + Redaction + Rédaction + + + + Debug + Debug + + + + FlatData + FlatData + + + + Persos + Persos + + + + &Help + &Aide + + + + Ctrl+O + + + + + Ctrl+S + Ctrl+S + + + + Ctrl+Shift+S + + + + + Ctrl+Q + + + + + Ctrl+Shift+B + + + + + F8 + + + + + Labels + Labels + + + + Situation: + Situation: + + + + Summary: + Résumé: + + + + What if...? + Et si...? + + + + Index cards + Cartes + + + + F9 + F9 + + + + Tree + Arbre + + + Compile Compilation - - Close project - Fermer le projet - - - - Search - Recherche - - - + F6 F6 - + World Monde - + Populates with empty data Remplir avec des catégories vides - + General Général - + More Plus - + Source of passion Source de passion - + Source of conflict Source de conflit - + The file {} does not exist. Try again. Le fichier {} n'existe pas. Essayez encore. - + Project {} saved. Le projet {} a été enregistré. - + Project {} loaded. Le projet {} a été chargé. - + Project {} loaded with some errors: Le projet {} a été chargé, avec des erreurs: - + * {} wasn't found in project file. * {} n'a pas été trouvé dans le fichier du projet. - + Project {} loaded with some errors. Le projet {} a été chargé avec des erreurs. - + (~{} pages) (~{} pages) - + Words: {}{} Mots: {}{} - + Book summary Résumé du livre - + Project tree Arborescence - + Metadata Métadonnées - + Enter infos about your book, and yourself. Entrez toutes les informations relatives au livre, ainsi qu'à vous. - - The basic situation, in the form of a 'What if...?' question. Ex: 'What if the most dangerous evil wizard could wasn't abled to kill a baby?' (Harry Potter) - La situation de base, sous la forme d'une question: "Et si...?" Par exemple: "Et si le plus dangereux magiciens mauvais n'était pas capable de tuer un bébé?" (Harry Potter) - - - - Take time to think about a one sentence (~50 words) summary of your book. Then expand it to a paragraph, then to a page, then to a full summary. - Prenez le temps de réfléchir à un résumé de votre livre, en une phrase (~50 mots). Puis augmentez cette phrase en un paragraphe, puis en une page, puis en un résumé complet. - - - + Create your characters. Créez ici vos personnage. - + Develop plots. Développez vos intrigues. - + Create the outline of your masterpiece. Créez le plan de votre chef-d'œuvre. - + Write. Écrivez. - + Debug infos. Sometimes useful. Des infos pour débugger des fois pendant qu'on code c'est utile. - + Dictionary Dictionnaire - + Install PyEnchant to use spellcheck Installez PyEnchant pour profiter du correcteur orthographique - + Nothing Rien - + POV POV - + Label Label - + Progress Progrès - + Icon color Couleur de l'icone - + Text color Couleur du texte - + Background color Couleur de l'arrière-plan - + Icon Icone - + Text Texte - + Background Arrière-plan - + Border Bordure - + Corner Coin + + + &File + &Fichier + + + + &Recents + &Récents + + + + &Tools + &Outils + + + + &View + &Vue + + + + &Mode + &Mode + + + + &Cheat sheet + &Feuille de triche + + + + Sea&rch + &Recherche + + + + &Navigation + &Navigation + + + + &Open + &Ouvrir + + + + &Save + &Enregistrer + + + + Sa&ve as... + Enre&gistrer sous… + + + + &Quit + &Quitter + + + + &Show help texts + &Afficher les bulles d'aides + + + + &Spellcheck + &Correcteur orthographique + + + + &Labels... + &Labels… + + + + &Status... + &Status… + + + + &Simple + &Simple + + + + &Fiction + &Fiction + + + + S&nowflake + S&nowflage + + + + S&ettings + &Réglages + + + + &Close project + &Fermer le projet + + + + Co&mpile + Co&mpiler + + + + &Frequency Analyzer + &Analyseur de fréquence + + + + Story line + + + + + The basic situation, in the form of a 'What if...?' question. Ex: 'What if the most dangerous + evil wizard could wasn't abled to kill a baby?' (Harry Potter) + La situation de base, sous la forme d'une question "Et si…?". Par exemple: "Et si le plus dangereux des sorciers maléfiques n'était pas capable de tuer un petit bébé?" (Harry Potter) + + + + Take time to think about a one sentence (~50 words) summary of your book. Then expand it to + a paragraph, then to a page, then to a full summary. + Prenez le temps de penser à une phrase (~50 mots) qui résume votre livre. Ensuite, développez-là en un paragraphe, puis une page, puis un résumé complet. + + + + &Edit + &Édition + Settings @@ -627,403 +692,398 @@ Apparence - + Labels Labels - + Status Status - + Fullscreen Plein écran - + General settings Réglages généraux - + Application style Style de l'application - + Saving Enregistrement - + Automatically save every Enregistrer automatiquement toutes les - + minutes. minutes. - + If no changes during S'il n'y a pas de modification durant - + seconds. secondes. - + Save on quit Enregistrer en quittant - + Views settings Apparence - + Tree Arbre - + Colors Couleurs - + Icon color: Icone: - + Nothing Rien - + POV POV - + Label Label - + Progress Progrès - + Compile Compilation - + Text color: Texte: - + Background color: Arrière-plan: - + Folders Dossiers - - Show item count - Afficher le nombre de sous-éléments - - - + Show wordcount Afficher le nombre de mots - + Show progress Afficher le progrès - + Text Texte - + Outline Plan - + Visible columns Colonnes visibles - + Goal - Goal + Cible - + Word count Nombre de mots - + Percentage Pourcentage - + Title Titre - + Index cards Cartes - + Item colors Couleurs des cartes - + Border color: Bordure: - + Corner color: Coin: - + Background Arrière-plan - + Color: Couleur: - + Ctrl+S Ctrl+S - + Image: Image: - + New Nouveau - + Edit Modifier - + Delete Supprimer - + Theme name: Nom du thème: - + Apply Enregistrer - + Cancel Annuler - + Window Background Arrière plan de la fenêtre - + Text Background Arrière plan du texte - + Text Options Options du texte - + Paragraph Options Options des paragraphes - + Type: Type: - + No Image Pas d'image - + Tiled Mosaïque - + Centered Centrée - + Stretched Étirée - + Scaled Mise à l'échelle - + Zoomed Zoomée - + Opacity: Opacité: - + % % - + Position: Position: - + Left Gauche - + Center Centre - + Right Droite - + Width: Largeur: - + px px - + Corner radius: Arrondi: - + Margins: Marges: - + Padding: Intérieur: - + Font: Police: - + Size: Taille: - + Misspelled: Orthographe: - + Line spacing: Espacement des lignes: - + Single Simple - + 1.5 lines 1.5 lignes - + Double Double - + Proportional Proportionnel - + Tab width: Tabulation: - + Spacing: Espacement: - + Indent 1st line Retrait 1ère ligne @@ -1033,115 +1093,140 @@ des lignes: Réglages - + You might need to restart manuskript in order to avoid some visual issues. Il sera nécessaire de rédemarrer manuskript pour éviter des problèmes d'affichages après avoir changé de style. - + Loading Chargement - + Automatically load last project on startup Charger au démarrage le dernier projet ouvert - - Default text format - Format de texte par défaut - - - - The format set by default when you create a new text item. You can change this on a per item basis. - Le format définit par défaut lorsque vous créez un nouveau élément texte. Vous pouvez changer ce format pour chaque élément. - - - + Text editor Éditeur de texte - + Font Police - + Family: Famille: - + Paragraphs Paragraphes - + Background: Arrière-plan: - + Revisions Révisions - + Revisions are a way to keep track of modifications. For each text item, it stores any changes you make to the main text, allowing you to see and restoring previous versions. Les révisions sont un moyen de garder une trace des modifications apportées à un texte. Pour chaque texte, chaque changement que vous apportez est enregistré, vous permettant de comparer la version actuelle avec des versions antérieures, et de les restaurer. - + Keep revisions Garder les révisions - - Smart remove - Suppression intelligente - - - + Keep: Garder: - + Smart remove allows you to keep only a certain number of revisions. It is strongly recommended to use it, lest you file will becomes full of thousands of insignificant changes. La suppression intelligente vous permet de ne garder qu'un certain nombre de révisions. Il est fortement recommander de l'utiliser, sous peine de voir ses documents envahis de millieurs de modifications insignifiantes. - + revisions per day for the last month révision(s) par jour pour le dernier mois - + revisions per minute for the last 10 minutes révision(s) par minute pour les dernières 10 minutes - + revisions per hour for the last day révision(s) par heure pour le dernier jour - + revisions per 10 minutes for the last hour révision(s) par tranche de 10 minutes pour la dernière heure - + revisions per week till the end of time révision(s) par semaine jusqu'à la fin des temps + + + Application language + Language de l'application + + + + You will need to restart manuskript for the translation to take effect. + Vous aurez besoin de rédemarrer manuskript pour que la traduction soit chargée. + + + + <html><head/><body><p>If you check this option, your project will be save as one single file. Easier to copy or backup, but does not allow collaborative editing, or versionning.<br/>If this is unchecked, your project will be save as a folder containing many small files.</p></body></html> + <html><head/><body><p>Si vous cochez cette option, le projet sera enregistrer en un seul fichier. Plus facile à copier, mais ne permet pas de travailler en collaboration, ou d'utiliser un gestionnaire de version extérieur.<br/>Si l'option n'est pas cochée, le projet sera sauvegardé en un dossier contenant de nombreux petits fichiers.</p></body></html> + + + + Save to one single file + Enregistrer dans un seul fichier + + + + S&mart remove + &Supression intelligente + + + + Show ite&m count + Montrer le &nombre d'éléments + + + + Show summary + Montrer le résummé + + + + &Nothing + &Rien + SpellAction - + Spelling Suggestions Suggestions @@ -1149,14 +1234,14 @@ des lignes: app - - Loaded transation: {}. + + Loaded translation: {}. Traduction chargée: {}. - - Failed to load translator for {}... - La traduction pour {} n'a pas pu être chargée... + + Warning: failed to load translator for locale {}... + Attention: la traduction {} n'a pas pu être chargée… @@ -1174,7 +1259,7 @@ des lignes: Goal: - Goal: + Cible: @@ -1192,6 +1277,42 @@ des lignes: Résumé en quelques phrases: + + characterModel + + + New character + Nouveau personnage + + + + Name + Nom + + + + Value + Valeur + + + + characterTreeView + + + Main + Principal + + + + Secondary + Secondaire + + + + Minor + Mineur + + cheatSheet @@ -1200,86 +1321,86 @@ des lignes: Form - - Filter - Filtre - - - + Minor Mineur - + Secondary Secondaire - + Main Principal - + Characters Personnages - + Texts Textes - + Plots Intrigues - + World Monde + + + Filter (type the name of anything in your project) + Filtrer (taper le nom de quoi que ce soit dans votre projet) + + + + cmbOutlineCharacterChoser + + + None + Aucun + + + + Main + Principal + + + + Secondary + Secondaire + + + + Minor + Mineur + + + + Various + Différentes valeurs + cmbOutlineLabelChoser - + Various Différentes valeurs - - cmbOutlinePersoChoser - - - Various - Différentes valeurs - - - - None - Aucun - - - - Main - Principal - - - - Secondary - Secondaire - - - - Minor - Mineur - - cmbOutlineStatusChoser - + Various Différentes valeurs @@ -1287,7 +1408,7 @@ des lignes: collapsibleDockWidgets - + Dock Widgets Toolbar @@ -1330,30 +1451,38 @@ des lignes: Annuler - + Working... En cours... - + Chose export folder Choisir le dossier - + Chose export target Choisir la cible + + completer + + + Form + Form + + corkDelegate - + One line summary Résumé en une ligne - + Full summary Résumé complet @@ -1369,45 +1498,63 @@ des lignes: exporter - + HTML HTML - + HTML Document (*.html) Document HTML (*.html) - + Arborescence Arborescence - + OpenDocument (LibreOffice) OpenDocument (LibreOffice) - + OpenDocument (*.odt) OpenDocument (*.odt) + + frequencyAnalyzer + + + Phrases + Phrases + + + + Frequency + Fréquence + + + + Word + Mot + + fullScreenEditor - + Theme: Thème: - + {} words / {} {} mots / {} - + {} words {} mots @@ -1415,7 +1562,7 @@ des lignes: helpLabel - + If you don't wanna see me, you can hide me in Help menu. Infobulle: Si tu me trouve dérengant, tu peux me cacher via le menu Aide. @@ -1466,32 +1613,32 @@ des lignes: Bloquer ! - + ~{} h. ~{} h. - + ~{} mn. ~{} mn. - + {}:{} {}:{} - + {} s. {} s. - + {} remaining {} restant - + {} words remaining {} mots restants @@ -1499,20 +1646,45 @@ des lignes: mainEditor - + Root Racine - + {} words / {} {} mots / {} - + {} words {} mots + + + Form + Form + + + + Text + Texte + + + + Index cards + Cartes + + + + Outline + Plan + + + + F11 + F11 + metadataView @@ -1522,100 +1694,128 @@ des lignes: Form - + Properties Propriétés - + Summary Résumé - + One line summary Résumé en une ligne - + Notes / References Notes / Références - + Revisions Révisions + + + Full summary + Résumé complet + outlineBasics - + Copy Copier - + Cut Couper - + Paste Coller - + Delete Supprimer - + Set POV Choisir le POV - + Set Status Choisir le status - + Set Label Choisir le label - + New Folder Nouveau Dossier - + None Aucun - + New Nouveau - + New Text Nouveau text - + Main Principal - + Secondary Secondaire - + + Minor + Mineur + + + + outlineCharacterDelegate + + + None + Aucun + + + + Main + Principal + + + + Secondary + Secondaire + + + Minor Mineur @@ -1623,106 +1823,65 @@ des lignes: outlineModel - + Title Titre - + POV POV - + Label Label - + Status Status - + Compile Compilation - + Word count Nombre de mots - + Goal - Goal + Cible - + {} words / {} ({}) {} mots / {} ({}) - + {} words {} mots - - outlineCharacterDelegate - - - None - Aucun - - - - Main - Principal - - - - Secondary - Secondaire - - - - Minor - Mineur - - - - persosModel - - - New character - Nouveau personnage - - - - Name - Nom - - - - Value - Valeur - - persosProxyModel - + Main Principal - + Secundary Secondaire - + Minors Mineurs @@ -1730,182 +1889,182 @@ des lignes: plotDelegate - + General Général - + Promise Promesse - + Problem Problème - + Progress Progrès - + Resolution Résolution - + Try / Fail Essayer / Échouer - + Freytag's pyramid Pyramide de Freytag - + Exposition Exposition - + Rising action Action montante - + Climax Apogée - + Falling action Action en chute - + Three acts Trois actes - + 1. Setup 1. Mise en place - + 1. Inciting event 1. Catalyseur - + 1. Turning point 1. Tournant - + 2. Choice 2. Choix - + 2. Reversal 2. Renversement - + 2. Disaster 2. Désastre - + 3. Stand up 3. Redressement - + 3. Climax 3. Apogée - + 3. Ending 3. Résolution - + Hero's journey Voyage du héros - + Ordinary world Monde ordinaire - + Call to adventure Appel à l'aventure - + Refusal of the call Refus de l'appel - + Meeting with mentor Rencontre du mentor - + Corssing the Threshold Franchir le seuil - + Tests Épreuves - + Approach Approche - + Abyss Abysse - + Reward / Revelation Récompense / Révélation - + Transformation Transformation - + Atonement Expiation - + Return Retour - + No and Non et - + Yes but Oui mai @@ -1913,64 +2072,82 @@ des lignes: plotModel - + New plot Nouvelle intrigue - - New subplot - Nouvelle sous-intrigue - - - + Main Principale - + Secondary Secondaire - + Minor Mineure - + Name Nom - + Meta Meta + + + New step + Nouvelle étape + plotTreeView - + Main Principale - + Secondary Secondaire - + Minor Mineure - + **Plot:** {} **Intrigue:** {} + + plotsProxyModel + + + Main + Principale + + + + Secundary + Secondaire + + + + Minors + Mineurs + + propertiesView @@ -1979,198 +2156,198 @@ des lignes: Form - + POV POV - + Status Status - + Label Label - + Compile Compile - + Goal - Goal + Cible - + Word count Nombre de mots - - - Text type: - Format: - references - + Unknown reference: {}. Référence inconnue: {}. - + Text: <b>{}</b> Texte: <b>{}</b> - + Character: <b>{}</b> Personnage: <b>{}</b> - + Basic infos Informations générales - + Detailed infos Informations détaillées - + POV of: POV de: - + Referenced in: Référencé dans: - + Motivation Motivation - + Goal - Goal + Cible - + Conflict Conflit - + Epiphany Épiphanie - + Short summary Résumé court - + Longer summary Résumé long - + Path: Chemin: - + Stats: Stats: - + POV: POV: - + Status: Status: - + Label: Label: - + Short summary: Résumé court: - + Long summary: Résumé long: - + Notes: Notes: - + Not a reference: {}. Pas une référence: {}. - + Go to {}. Aller à {}. - + Description Description - + Result Résultat - + Characters Personnages - + Resolution steps Étapes de résolution - + Plot: <b>{}</b> Intrigue: <b>{}</b> - + Passion Passion - + World: <b>{name}</b>{path} Monde: <b>{name}</b>{path} - + <b>Unknown reference:</b> {}. <b>Référence inconnue:</b> {}. + + + Folder: <b>{}</b> + Dossier: <b>{}</b> + revisions @@ -2180,37 +2357,37 @@ des lignes: Form - + Restore Restaurer - + Delete Supprimer - + 1 day ago Il y a un jour - + {} days ago Il y a {} jours - + {} hours ago Il y a {} heures - + {} minutes ago Il y a {} minutes - + {} seconds ago Il y a {} secondes @@ -2220,116 +2397,200 @@ des lignes: Options - + Show modifications Montrer les modifications - + Show ancient version Montrer la version ancienne - + Show spaces Montrer les espaces - + Show modifications only Montrer les modifications seulement - + Line {}: Ligne {}: - + {} years ago Il y a {} ans - + {} months ago Il y a {} mois - + Clear all Effacer tout + + search + + + Form + Form + + + + Search in: + Rechercher dans: + + + + All + Tout + + + + Title + Titre + + + + Text + Texte + + + + Summary + Résumé + + + + Notes + Notes + + + + POV + POV + + + + Status + Status + + + + Label + Label + + + + Options: + Options: + + + + Case sensitive + Sensible à la casse + + settingsWindow - + New status Nouveau status - + New label Nouveau label - + newtheme nouveautheme - + New theme Nouveau Thème - - - Txt2Tags - Txt2Tags - - - - Rich Text (html) - Texte riche (html) - - - - Plain Text - Texte simple - sldImportance - + Form Form - + TextLabel TextLabel - + Minor Mineur - + Secondary Secondaire - + Main Principal + + storylineView + + + Form + Form + + + + + + + + + + + - + - + + + + Show Plots + Montrer les intrigues + + + + Show Characters + Montrer les personnages + + + + textEditCompleter + + + Insert reference + Insérer une référence + + textEditView - + Various Différentes valeurs @@ -2376,41 +2637,46 @@ des lignes: CTRL+J CTRL+J + + + Form + Form + treeView - + Expand {} Développer {} - + Collapse {} Fermer {} - + Expand All Tout développer - + Collapse All Tout fermer - + Root Racine - + Open {} items in new tabs Ouvrir {} éléments dans des nouveaux onglets - + Open {} in a new tab Ouvrir {} dans un nouvel onglet @@ -2428,37 +2694,37 @@ des lignes: - + Templates Modèles - + Empty Vide - + Novel Roman - + Novella Nouvelle - + Short Story Histoire courte - + Research paper - + Article académique - + Demo projects Projets de démonstration @@ -2473,360 +2739,365 @@ des lignes: Ajouter le nombre de mots - + Next time, automatically open last project La prochaine fois, ouvrir automatiquement le dernier projet - + Open... Ouvrir... - + Recent Récents - + Create Créer - + Open project Ouvrir le projet - + Manuskript project (*.msk) Projet Manuskript (*.msk) - + Save project as... Enregistrer le projer sous... - + Create New Project Créer un nouveau projet - + Chapter Chapitre - + Scene Scène - + Trilogy Trilogie - + Book Livre - + Section Section - + words each. mots chacun(e). - + of de - + Text Texte - + Something Quelque chose - + <b>Total:</b> {} words (~ {} pages) <b>Total:</b> {} mots (~ {} pages) - + Idea Idée - + Note Note - + Research Recherche - + TODO TODO - + First draft Premier brouillon - + Second draft Second brouillon - + Final Final - - Default text type: - Type de texte par défaut: + + Manuskript project (*.msk);;All files (*) + Projet manuskript (*.msk);;Tous les fichiers (*) - - Txt2Tags - Txt2Tags + + Empty fiction + Fiction vide - - Rich Text (html) - Texte riche (html) + + Empty non-fiction + Non-fiction vide - - Plain Text - Texte simple + + Fiction + Fiction + + + + Non-fiction + Non-fiction worldModel - + New item Nouvel élément - + Fantasy world building Fantasy - + Physical Physique - + Climate Climat - + Topography Topographie - + Astronomy Astronomie - + Natural ressources Ressources naturelles - + Wild life Faune - + Flora Flore - + History Histoire - + Races Races - + Diseases Maladies - + Cultural Culture - + Customs Coutumes - + Food Nourriture - + Languages Langues - + Education Éducation - + Dresses Habits - + Science Science - + Calendar Calendrier - + Bodily language Language corporel - + Ethics Éthique - + Religion Religion - + Government Gouvernement - + Politics Politique - + Gender roles Rôles de genres - + Music and arts Musique et arts - + Architecture Architecture - + Military Militaire - + Technology Technologie - + Courtship Relations - + Demography Démographie - + Transportation Transport - + Medicine Médecine - + Magic system Magie - + Rules Lois - + Organization Organisation - + Magical objects Objets magiques - + Magical places Endroits magiques - + Magical races Races magiques - + Important places Lieux importants - + Important objects Objets importants diff --git a/manuskript/ui/mainWindow.py b/manuskript/ui/mainWindow.py index 09580128..2d57efe7 100644 --- a/manuskript/ui/mainWindow.py +++ b/manuskript/ui/mainWindow.py @@ -1137,8 +1137,6 @@ class Ui_MainWindow(object): self.actCompile.setObjectName("actCompile") self.actToolFrequency = QtWidgets.QAction(MainWindow) self.actToolFrequency.setObjectName("actToolFrequency") - self.actionTest = QtWidgets.QAction(MainWindow) - self.actionTest.setObjectName("actionTest") self.menuFile.addAction(self.actOpen) self.menuFile.addAction(self.menuRecents.menuAction()) self.menuFile.addAction(self.actSave) @@ -1267,11 +1265,11 @@ class Ui_MainWindow(object): self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_20), _translate("MainWindow", "Outline")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("MainWindow", "Labels")) self.tabMain.setTabText(self.tabMain.indexOf(self.lytTabDebug), _translate("MainWindow", "Debug")) - self.menuFile.setTitle(_translate("MainWindow", "Fi&le")) + self.menuFile.setTitle(_translate("MainWindow", "&File")) self.menuRecents.setTitle(_translate("MainWindow", "&Recents")) - self.menuHelp.setTitle(_translate("MainWindow", "Help")) + self.menuHelp.setTitle(_translate("MainWindow", "&Help")) self.menuTools.setTitle(_translate("MainWindow", "&Tools")) - self.menuEdit.setTitle(_translate("MainWindow", "E&dit")) + self.menuEdit.setTitle(_translate("MainWindow", "&Edit")) self.menuView.setTitle(_translate("MainWindow", "&View")) self.menuMode.setTitle(_translate("MainWindow", "&Mode")) self.dckCheatSheet.setWindowTitle(_translate("MainWindow", "&Cheat sheet")) @@ -1303,7 +1301,6 @@ class Ui_MainWindow(object): self.actCompile.setText(_translate("MainWindow", "Co&mpile")) self.actCompile.setShortcut(_translate("MainWindow", "F6")) self.actToolFrequency.setText(_translate("MainWindow", "&Frequency Analyzer")) - self.actionTest.setText(_translate("MainWindow", "test")) 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 111161a1..05e7a4e7 100644 --- a/manuskript/ui/mainWindow.ui +++ b/manuskript/ui/mainWindow.ui @@ -1984,7 +1984,7 @@
    - Fi&le + &File @@ -2008,7 +2008,7 @@ - Help + &Help @@ -2021,7 +2021,7 @@ - E&dit + &Edit @@ -2339,11 +2339,6 @@ QListView::item:hover { &Frequency Analyzer
    - - - test - - From f2538a586b26bf211fdf907251fbc8812cd634f2 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 30 Mar 2016 14:16:58 +0200 Subject: [PATCH 090/103] Adds a reminder --- i18n/tool.txt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 i18n/tool.txt diff --git a/i18n/tool.txt b/i18n/tool.txt new file mode 100644 index 00000000..6e98ed85 --- /dev/null +++ b/i18n/tool.txt @@ -0,0 +1,17 @@ +List all forms: + + find .. -iname *.ui + +List all files containing ".tr": + + grep -rin "\.tr(" ../manuskript > list.txt + +List all files containing ".translate": + + grep -rin "\.translate(" ../manuskript >> list.txt + +Then clean list.txt, and copy it in LibreOffice Calc, then: +- Data, Filter, Advanced +- From: whole column +- No duplication +- → filter \ No newline at end of file From 1cda95f43c1a2b6706c907bb495b5abc044ed914 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 30 Mar 2016 15:47:52 +0200 Subject: [PATCH 091/103] Bug correction... --- manuskript/ui/views/textEditView.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manuskript/ui/views/textEditView.py b/manuskript/ui/views/textEditView.py index 0c3e9d13..bfc49deb 100644 --- a/manuskript/ui/views/textEditView.py +++ b/manuskript/ui/views/textEditView.py @@ -357,7 +357,7 @@ class textEditView(QTextEdit): if enchant and self.spellcheck and not self._dict: if self.currentDict: self._dict = enchant.Dict(self.currentDict) - elif enchant.get_default_language() in enchant.get_default_language(): + elif enchant.dict_exists(enchant.get_default_language()): self._dict = enchant.Dict(enchant.get_default_language()) else: self.spellcheck = False From 3e698c8eeaf7d4cfcbadcb2eb5b65b721f460008 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 30 Mar 2016 22:36:02 +0200 Subject: [PATCH 092/103] New spellcheck feature: add word to dict --- manuskript/ui/views/textEditView.py | 38 ++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/manuskript/ui/views/textEditView.py b/manuskript/ui/views/textEditView.py index bfc49deb..12984339 100644 --- a/manuskript/ui/views/textEditView.py +++ b/manuskript/ui/views/textEditView.py @@ -398,15 +398,19 @@ class textEditView(QTextEdit): return popup_menu # Select the word under the cursor. + # But only if there is no selection (otherwise it's impossible to select more text to copy/cut) cursor = self.textCursor() - # cursor = self.cursorForPosition(pos) - cursor.select(QTextCursor.WordUnderCursor) - self.setTextCursor(cursor) + if not cursor.hasSelection(): + cursor.select(QTextCursor.WordUnderCursor) + self.setTextCursor(cursor) + # Check if the selected word is misspelled and offer spelling # suggestions if it is. if cursor.hasSelection(): text = str(cursor.selectedText()) - if not self._dict.check(text): + valid = self._dict.check(text) + selectedWord = cursor.selectedText() + if not valid: spell_menu = QMenu(self.tr('Spelling Suggestions'), self) for word in self._dict.suggest(text): action = self.SpellAction(word, spell_menu) @@ -416,7 +420,23 @@ class textEditView(QTextEdit): # suggestions. if len(spell_menu.actions()) != 0: popup_menu.insertSeparator(popup_menu.actions()[0]) + # Adds: add to dictionary + addAction = QAction(self.tr("&Add to dictionary"), popup_menu) + addAction.triggered.connect(self.addWordToDict) + addAction.setData(selectedWord) + popup_menu.insertAction(popup_menu.actions()[0], addAction) + # Adds: suggestions popup_menu.insertMenu(popup_menu.actions()[0], spell_menu) + # popup_menu.insertSeparator(popup_menu.actions()[0]) + + # If word was added to custom dict, give the possibility to remove it + elif valid and self._dict.is_added(selectedWord): + popup_menu.insertSeparator(popup_menu.actions()[0]) + # Adds: remove from dictionary + rmAction = QAction(self.tr("&Remove from custom dictionary"), popup_menu) + rmAction.triggered.connect(self.rmWordFromDict) + rmAction.setData(selectedWord) + popup_menu.insertAction(popup_menu.actions()[0], rmAction) return popup_menu @@ -432,6 +452,16 @@ class textEditView(QTextEdit): cursor.endEditBlock() + def addWordToDict(self): + word = self.sender().data() + self._dict.add(word) + self.highlighter.rehighlight() + + def rmWordFromDict(self): + word = self.sender().data() + self._dict.remove(word) + self.highlighter.rehighlight() + ############################################################################### # FORMATTING ############################################################################### From 574660dc193b2027a8e9d04b2260e8c6db81f8dc Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 31 Mar 2016 10:19:18 +0200 Subject: [PATCH 093/103] Corrects stylesheet bug in textEditView --- manuskript/ui/views/textEditView.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/manuskript/ui/views/textEditView.py b/manuskript/ui/views/textEditView.py index 12984339..c81349aa 100644 --- a/manuskript/ui/views/textEditView.py +++ b/manuskript/ui/views/textEditView.py @@ -182,11 +182,12 @@ class textEditView(QTextEdit): f = QFont() f.fromString(opt["font"]) # self.setFont(f) - self.setStyleSheet(""" + self.setStyleSheet("""QTextEdit{{ background: {bg}; color: {foreground}; font-family: {ff}; font-size: {fs}; + }} """.format( bg=opt["background"], foreground=opt["fontColor"], From cf6e021ed195476d89160ceb24ab8a93a2e12a7d Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 31 Mar 2016 10:45:58 +0200 Subject: [PATCH 094/103] Minor tweak in file format --- manuskript/loadSave.py | 8 +++++++- manuskript/load_save/version_1.py | 9 ++------- sample-projects/book-of-acts/MANUSKRIPT | 1 + 3 files changed, 10 insertions(+), 8 deletions(-) create mode 100644 sample-projects/book-of-acts/MANUSKRIPT diff --git a/manuskript/loadSave.py b/manuskript/loadSave.py index 908bc371..93ede035 100644 --- a/manuskript/loadSave.py +++ b/manuskript/loadSave.py @@ -39,10 +39,16 @@ def loadProject(project): isZip = False # Does it have a VERSION in zip root? + # Was used in transition between 0.2.0 and 0.3.0 + # So VERSION part can be deleted for manuskript 0.4.0 if isZip and "VERSION" in zf.namelist(): version = int(zf.read("VERSION")) - # Zip but no VERSION: oldest file format + # Does it have a MANUSKRIPT in zip root? + elif isZip and "MANUSKRIPT" in zf.namelist(): + version = int(zf.read("MANUSKRIPT")) + + # Zip but no VERSION/MANUSKRIPT: oldest file format elif isZip: version = 0 diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index 3e646705..913dffc6 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -118,9 +118,8 @@ def saveProject(zip=None): mw = mainWindow() - if zip: - # File format version - files.append(("VERSION", "1")) + # File format version + files.append(("MANUSKRIPT", "1")) # General infos (book and author) # Saved in plain text, in infos.txt @@ -378,10 +377,6 @@ def saveProject(zip=None): if os.path.isdir(filename): shutil.rmtree(filename) - elif path == "VERSION": - # If loading from zip, but saving to path, file VERSION is not needed. - continue - else: # elif os.path.exists(filename) os.remove(filename) diff --git a/sample-projects/book-of-acts/MANUSKRIPT b/sample-projects/book-of-acts/MANUSKRIPT new file mode 100644 index 00000000..56a6051c --- /dev/null +++ b/sample-projects/book-of-acts/MANUSKRIPT @@ -0,0 +1 @@ +1 \ No newline at end of file From 26ffc47350dd232612fa8e96e22a348ab092b3d6 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 31 Mar 2016 10:50:20 +0200 Subject: [PATCH 095/103] Fixes bug when switching projects --- manuskript/mainWindow.py | 3 +++ manuskript/ui/welcome.py | 1 + 2 files changed, 4 insertions(+) diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index 2cea3c96..a0779558 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -375,6 +375,9 @@ class MainWindow(QMainWindow, Ui_MainWindow): def closeProject(self): + if not self.currentProject: + return + # Close open tabs in editor self.mainEditor.closeAllTabs() diff --git a/manuskript/ui/welcome.py b/manuskript/ui/welcome.py index 464a33ee..0aefdf3d 100644 --- a/manuskript/ui/welcome.py +++ b/manuskript/ui/welcome.py @@ -115,6 +115,7 @@ class welcome(QWidget, Ui_welcome): def loadRecentFile(self): act = self.sender() self.appendToRecentFiles(act.data()) + self.mw.closeProject() self.mw.loadProject(act.data()) ############################################################################### From b5577fa9a8768b4d2a1d5d9dd74d6898d053e5a2 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 31 Mar 2016 11:08:44 +0200 Subject: [PATCH 096/103] Update settings to last version for sample project --- sample-projects/book-of-acts/settings.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample-projects/book-of-acts/settings.txt b/sample-projects/book-of-acts/settings.txt index 4d2ed963..3485637a 100644 --- a/sample-projects/book-of-acts/settings.txt +++ b/sample-projects/book-of-acts/settings.txt @@ -5,7 +5,7 @@ "autoSaveNoChangesDelay": 5, "corkBackground": { "color": "#926239", - "image": "/home/olivier/Dropbox/Documents/Travail/Geekeries/Python/PyCharmProjects/manuskript/resources/backgrounds/spacedreams.jpg" + "image": "writingdesk.jpg" }, "corkSizeFactor": 84, "defaultTextType": "txt", From bbcb9a33e58ce7c5740d6249a56b726f2686a962 Mon Sep 17 00:00:00 2001 From: Olivier Date: Thu, 31 Mar 2016 11:20:26 +0200 Subject: [PATCH 097/103] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fab019c8..78ab527e 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,11 @@ [Manuskript](http://www.theologeek.ch/manuskript) is an open-source tool for writers. -![Main view](http://www.theologeek.ch/manuskript/wp-content/uploads/2016/02/index-cards-1.jpg) +![Main view](http://www.theologeek.ch/manuskript/wp-content/uploads/2016/03/manuskript-0.3.0.jpg) +## [Download](http://www.theologeek.ch/manuksript/download) + ## Running from sources To run the application without installing just: @@ -23,4 +25,4 @@ Be sure to have all **dependencies** installed ! Optional: - pyenchant -- zlib \ No newline at end of file +- zlib From 2e6cff73d563575572c312518498b600ff14384f Mon Sep 17 00:00:00 2001 From: Olivier Date: Thu, 31 Mar 2016 11:21:25 +0200 Subject: [PATCH 098/103] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 78ab527e..750bb303 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ![Main view](http://www.theologeek.ch/manuskript/wp-content/uploads/2016/03/manuskript-0.3.0.jpg) -## [Download](http://www.theologeek.ch/manuksript/download) +## [Download](http://www.theologeek.ch/manuskript/download) ## Running from sources From 3571ec3f16068c51bbf084f57cf823d34d79a1eb Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 31 Mar 2016 15:31:07 +0200 Subject: [PATCH 099/103] Bug correction: need to convert from html to plain text when loading old file format --- manuskript/models/outlineModel.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/manuskript/models/outlineModel.py b/manuskript/models/outlineModel.py index 0fa485c4..ab6dba0b 100644 --- a/manuskript/models/outlineModel.py +++ b/manuskript/models/outlineModel.py @@ -767,6 +767,15 @@ class outlineItem(): if "lastPath" in root.attrib: self._lastPath = root.attrib["lastPath"] + # If loading from an old file format, convert to md and remove html markup + if self.type() in ["txt", "t2t"]: + self.setData(Outline.type.value, "md") + + elif self.type() == "html": + self.setData(Outline.type.value, "md") + self.setData(Outline.text.value, HTML2PlainText(self.data(Outline.text.value))) + self.setData(Outline.notes.value, HTML2PlainText(self.data(Outline.notes.value))) + for child in root: if child.tag == "outlineItem": item = outlineItem(self._model, xml=ET.tostring(child), parent=self) From 2cf7d99217a6057c6b7aafb671580aa3a82aa33e Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 31 Mar 2016 15:58:13 +0200 Subject: [PATCH 100/103] Optimization in storyline view (wip) --- makefile | 2 +- manuskript/ui/views/storylineView.py | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/makefile b/makefile index 46e7e6e3..b79b732f 100644 --- a/makefile +++ b/makefile @@ -16,7 +16,7 @@ lineprof: kernprof -l -v manuskript/main.py profile: - python3 -m cProfile -s 'cumtime' manuskript/main.py | more + python3 -m cProfile -s 'cumtime' bin/manuskript | more compile: cd manuskript && python3 setup.py build_ext --inplace diff --git a/manuskript/ui/views/storylineView.py b/manuskript/ui/views/storylineView.py index 3c4b0792..df89f14f 100644 --- a/manuskript/ui/views/storylineView.py +++ b/manuskript/ui/views/storylineView.py @@ -5,6 +5,7 @@ from PyQt5.QtGui import QBrush, QPen, QFontMetrics, QFontMetricsF, QColor from PyQt5.QtWidgets import QWidget, QGraphicsScene, QGraphicsSimpleTextItem, QMenu, QAction, QGraphicsRectItem, \ QGraphicsLineItem, QGraphicsEllipseItem +from manuskript.enums import Outline from manuskript.models import references from manuskript.ui.views.storylineView_ui import Ui_storylineView @@ -50,11 +51,15 @@ class storylineView(QWidget, Ui_storylineView): # self._mdlPlots.rowsInserted.connect(self.refresh) self._mdlOutline = mdlOutline - self._mdlOutline.dataChanged.connect(self.reloadTimer.start) + self._mdlOutline.dataChanged.connect(self.updateMaybe) self._mdlCharacter = mdlCharacter self._mdlCharacter.dataChanged.connect(self.reloadTimer.start) + def updateMaybe(self, topLeft, bottomRight): + if topLeft.column() <= Outline.notes.value <= bottomRight.column(): + self.reloadTimer.start + def plotReferences(self): "Returns a list of plot references" if not self._mdlPlots: @@ -85,7 +90,10 @@ class storylineView(QWidget, Ui_storylineView): def refresh(self): if not self._mdlPlots or not self._mdlOutline or not self._mdlCharacter: - pass + return + + if not self.isVisible(): + return LINE_HEIGHT = 18 SPACING = 3 From 747f169a2b64b0257012ba31e12b714869fc3993 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 31 Mar 2016 16:09:37 +0200 Subject: [PATCH 101/103] Corrects bug when saving empty plots --- manuskript/load_save/version_1.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index 913dffc6..e612d8d8 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -475,10 +475,11 @@ def addPlotItem(root, mdl, parent=QModelIndex()): step = ET.SubElement(outline, "step") for cY in range(mdl.columnCount(index)): cIndex = mdl.index(cX, cY, index) - val = mdl.data(cIndex) + # If empty, returns None, which creates trouble later with lxml, so default to "" + val = mdl.data(cIndex) or "" for w in PlotStep: - if cY == w.value: + if cY == w.value and w.name: step.attrib[w.name] = val elif Plot.steps.name in outline.attrib: From 8aec06ce233fbd2903f570dc805013fe7bd8a76e Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 31 Mar 2016 16:10:06 +0200 Subject: [PATCH 102/103] Updates spansih translation, thanks to @vmpajares --- i18n/manuskript_es.qm | Bin 24746 -> 47696 bytes i18n/manuskript_es.ts | 653 +++++++++++++++++++++--------------------- 2 files changed, 330 insertions(+), 323 deletions(-) diff --git a/i18n/manuskript_es.qm b/i18n/manuskript_es.qm index 26cd9a415251e68a71405b71292e02bae5675b61..eeab46463d28241d2c0c019e15af73cfdd5aae44 100644 GIT binary patch literal 47696 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 delta 3013 zcmXYydtA-;AIIO{bH3m2xpdAcNhnV1rW;3zR4XanZz{Q^nTjKT8wqmuncpd z)kKM>h$7pFGTVvVHgiV#5{-=|a-U9O-Fuj4Bpwxs`gN0N?aUlbod|^R&)ACk!e#Rky8lycAy0G4f+1=PbB1$Ura6dBl+bX zCsM_bUj-yoEhN9HHt-txeGAFO0t&f;@|cq-zA%KS&qzu<(n6$HQS$I-;CGZ9cZ6t6 z8>QqFL3wKprL>hosw_$!{}d%?qV!{-L~nFa#^Pg8#Fx|6jx%~MXIvp?x{5MOPC}6> zG&b)Q+d0#R(Aa$J&+MXcWe9v!Pq39J`8JIYQi$x|;IxLkpb5_*mDfF*c=}HwhxaMV z4#xAnM3Ynphz4J$Nj9EPD4)~cfitErXZkhDma*P{7G*b>i2~hXJf z!=z8ulPhqL-E;MVLpZ>{O})xC0~zmBZyjHWT6U;+G?k#X3)GzsY9dRUy3^$hQSf6? zI{oUxbaB8>2rSuE%&)N^klx}ve=82`Q6^DYi9Ef%O zR&YlCB{o`LeMs{cn@h%{Ht&mvj(B3hKCxx^7@}bX;+fgt$bn)9MdLNZiXEW{z_3jG zE*F_cO%(4{7eFaTNxcjna9J#Ql|Lm)%8|S(0-@w&PS+^O`)fv|apa6YC=J`<3PtWn z)^Uw_kYtsV_Zv2*#!Gn*AhqqEoNh0q{FC$0y)@D?hKyb7rSj@bj2B83jYElgmT{)` zkk;$)Ed4~Osv{0HZw@;+i?rv!&+V zpW(S2mpa3S!S!dPE3K_C+hSQgMTZipF=677gyRX+Y2xk%S zYR$gnd+>xxbE4-Clp;~n)&D6Hdak*9uK@4g!%p+C5>k$st@-N)7K}KoeM{?0)VD=j zJftTi6gh*BawbgERuUq2vuJDH!9IuIwL84A&i$D7NIR5K?bIIs-yWFqZ|#YP#YpHA z?b$;=K(TY$v!_s!LG9Xe8%v-_srK=r_aIqc7oB*NiL8w$V2B6j|+wix|HcJ zpukYhgf89W50IICE2qaljLp|gH-{p_YMt`aX#|v_`|{jaB9A!T*JB+~^YwbYQiSme zeaKe@DAglQm*=1rA0lLZsi;vW69xjD$Vh|~EWhQ8$E=p@eAi~6F+kktOa`dzR7 zD^ff4r-IRFdR^j-e2X*v1O4eFoUdQNndoGb--Lul>upwg-Xa>BX0y(ki<&t!+ibQg z#Q~dbYT_hFH_N6j`5P#4*5Huw1PiYkMpfj&Tv|icc@H$88-|tYGITr}!}{GQLDV+G z7B9??a5wB9h{j`|W@z2#j@NL)a8y75=oqi*GuZIsD45#gzEO9a!KD61>+2tpE=FS! zBzN9pjGp)hI@VHSbQWr<`@;BcqZ3h>2E2$1BaHdK1fr4T8FxFwoWr|0!`5<+EHxgG z@ps*4uJP1Xn3di)o;n={$p>(n`*Owx8LyRNzuh#e@nOzSL^eUDA?-dm_=G9s#bHc* zZd&em8*%@_88(tL;)-c)3ly*{F?Dw%V<*XU|9mStCfV9K;=+u(w&Cg6=WAm-;v$sn zG0C=g3Nj5haYoGKjM-%Su^ak9hB@ALXCvI&E5^2ddM%#Kb=!xxw!+2IOmv%#?14if zJLtHE)%C7Y>nTzh)u%~f;pPTqnv1u3m=10EK3=|gYjjOS{Bt@FCf zikCUFN^d)6@3W8%^4+dTex7P&gGCUO;{o=9VhpSjSohG+*puNFR`}*wwmQth?u5m% zobV83Q{)Nx)d5Oze66UAO_?ewZexCw1v7g((afB(_Ug2hNi%6K%~5{HYF8<1CZAF( z-%lMXuz@+Nl$*Ju1XeM_pLx%;u~g~lwx%(M`88#;@;!m9 OfAia#H87V-5dIJ7`eLU5 diff --git a/i18n/manuskript_es.ts b/i18n/manuskript_es.ts index a87f7f68..c3ff41dd 100644 --- a/i18n/manuskript_es.ts +++ b/i18n/manuskript_es.ts @@ -1,51 +1,52 @@ - + + FrequencyAnalyzer Frequency Analyzer - + Analizador de Frecuencia Word frequency - + Frecuencia de palabras Settings - Preferencias + Preferencias Minimum size: - + Tamaño mínimo: Exclude words (coma seperated): - + Palabras a excluir (separadas por comas): Analyze - + Analizar Phrase frequency - + Frecuencia de frases Number of words: from - + Numero de palabras: desde to - + hasta @@ -123,7 +124,7 @@ One sentence - Una frase + Una frase @@ -143,7 +144,7 @@ One sentence summary - Resumen de una frase + Resumen de una frase @@ -208,7 +209,7 @@ Goal - Meta + Objetivo @@ -223,7 +224,7 @@ <html><head/><body><p align="right">One sentence<br/>summary</p></body></html> - <html><head/><body><p align="right">Resumen de<br/>una frase</p></body></html> + <html><head/><body><p align="right">Resumen de<br/>una frase</p></body></html> @@ -298,7 +299,8 @@ Outline - + Need to search a good word for this one + @@ -313,12 +315,12 @@ FlatData - + Datos Planos Persos - + @@ -328,7 +330,7 @@ &File - &Archivo + &Archivo @@ -343,7 +345,7 @@ &Help - Ayuda + A&yuda @@ -488,194 +490,196 @@ &Fiction - + &Ficción S&nowflake - + S&nowflake The file {} does not exist. Try again. - + El archivo {} no existe. Inténtelo de nuevo. Project {} saved. - + Proyecto {} guardado. Project {} loaded. - + Proyecto {} cargado. Project {} loaded with some errors: - + Proyecto {} cargado con algunos errores: * {} wasn't found in project file. - + * {} no se encontró en el archivo del proyecto. Project {} loaded with some errors. - + Proyecto {} cargado con algunos errores. (~{} pages) - + (~{} páginas) Words: {}{} - + Palabras: {}{} Book summary - + Resumen del libro Project tree - + Árbol del proyecto Metadata - + Metadata Story line - + Historia Enter infos about your book, and yourself. - + Introduzca información acerca de tu libro y sobre ti mismo. The basic situation, in the form of a 'What if...?' question. Ex: 'What if the most dangerous evil wizard could wasn't abled to kill a baby?' (Harry Potter) - + La situación básica en la forma de una pregunta tipo "¿Que pasaría sí...?'. Ej:"¿Que pasaría si el más peligroso + hechicero malvado no pudiera ser capaz de matar un bebe?" (Harry Potter) Take time to think about a one sentence (~50 words) summary of your book. Then expand it to a paragraph, then to a page, then to a full summary. - + Tómate tu tiempo para pensar en resumen de una linea (apróximadamente 50 palabras) de tu libro. Después expándelo hasta + un párrafo, después hasta una página, y por último hasta un resumen completo. Create your characters. - + Crea tus personajes. Develop plots. - + Desarrolla las tramas. Create the outline of your masterpiece. - + Crea el esquema de tu obra maestra. Write. - + Escribe. Debug infos. Sometimes useful. - + Depura la información. A veces es útil. Dictionary - + Diccionario Install PyEnchant to use spellcheck - + Instala PyEnchant para usar el chequeo ortográfico Nothing - Ninguno + Ninguno POV - + Label - Etiqueta + Etiqueta Progress - Progreso + Progreso Compile - Compilar + Compilar Icon color - + Color del icono Text color - + Color del texto Background color - + Color del Fondo Icon - + Icono Text - Texto + Texto Background - Fondo + Fondo Border - + Borde Corner - + Esquina &Edit - + &Editar @@ -908,7 +912,7 @@ Outline - + Esquema @@ -918,7 +922,7 @@ Goal - Meta + Objetivo @@ -1078,7 +1082,7 @@ Theme name: - Nombre del tema + Nombre del tema: @@ -1113,7 +1117,7 @@ Type: - Tipo + Tipo: @@ -1198,27 +1202,27 @@ Application language - + Idioma de la aplicación <html><head/><body><p>If you check this option, your project will be save as one single file. Easier to copy or backup, but does not allow collaborative editing, or versionning.<br/>If this is unchecked, your project will be save as a folder containing many small files.</p></body></html> - + <html><head/><body><p>Si marcas esta opción, tu proyecto se grabará en un único archivo. Más facil de copiar o guardar, pero no permitirá edición colaborativa o distintas versiones.<br/>Si está desmarcada, tu proyecto se grabará como una carpeta conteniendo multiples archivos pequeños.</p></body></html> Save to one single file - + Guardar en un único fichero &Nothing - + &Ninguno You will need to restart manuskript for the translation to take effect. - + Necesitarás reiniciar manuskript para hacer funcionar la traducción. @@ -1226,7 +1230,7 @@ Spelling Suggestions - + Sugerencias de Ortografía @@ -1234,12 +1238,12 @@ Loaded translation: {}. - + Cargada la traducción: {}. Warning: failed to load translator for locale {}... - + Aviso: error al cargar la traducción para el idioma {}... @@ -1252,7 +1256,7 @@ POV: - + @@ -1280,17 +1284,17 @@ New character - + Nuevo personaje Name - Nombre + Nombre Value - + Valor @@ -1298,17 +1302,17 @@ Main - + Principal Secondary - + Secundario Minor - + Menor @@ -1326,37 +1330,37 @@ Minor - + Menor Secondary - + Secundario Main - + Principal Characters - Personajes + Personajes Texts - + Textos Plots - Tramas + Tramas World - Mundo + Mundo @@ -1364,27 +1368,27 @@ None - Ninguno + Ninguno Main - + Principal Secondary - + Secundario Minor - + Menor Various - + Varios @@ -1392,7 +1396,7 @@ Various - + Varios @@ -1400,7 +1404,7 @@ Various - + Varios @@ -1408,7 +1412,8 @@ Dock Widgets Toolbar - + I must search the oficial translation for QT DockWidgets + @@ -1451,17 +1456,17 @@ Working... - + Trabajando... Chose export folder - + Elige carpeta para exportar Chose export target - + Elige nombre para exportar @@ -1469,7 +1474,7 @@ Form - + Formulario @@ -1477,12 +1482,12 @@ One line summary - Resumen de una línea + Resumen de una línea Full summary - Resumen completo + Resumen completo @@ -1498,27 +1503,27 @@ HTML - + HTML HTML Document (*.html) - + Documento HTML (*.html) Arborescence - + OpenDocument (LibreOffice) - + OpenDocument (*.odt) - + @@ -1526,17 +1531,17 @@ Phrases - + Frases Frequency - + Frecuencia Word - + Palabra @@ -1544,17 +1549,17 @@ Theme: - + Tema: {} words / {} - + {} palabras / {} {} words - {} palabras + {} palabras @@ -1562,7 +1567,7 @@ If you don't wanna see me, you can hide me in Help menu. - + Si no quieres verme, puedes ocultarme en el menú Ayuda. @@ -1570,7 +1575,7 @@ Various - + Varios @@ -1613,32 +1618,32 @@ ~{} h. - + ~{} h. ~{} mn. - + {}:{} - + {} s. - + {} remaining - + {} restantes {} words remaining - + {} palabras restantes @@ -1646,42 +1651,42 @@ Form - + Formulario Text - Texto + Texto Index cards - Fichas + Fichas Outline - + Esquema F11 - + Root - + Raíz {} words / {} - + {} palabras / {} {} words - {} palabras + {} palabras @@ -1782,17 +1787,17 @@ Main - + Principal Secondary - + Secundario Minor - + Menor @@ -1800,22 +1805,22 @@ None - Ninguno + Ninguno Main - + Principal Secondary - + Secundario Minor - + Menor @@ -1833,37 +1838,37 @@ Title - Título + Título POV - + Label - Etiqueta + Etiqueta Status - Estado + Estado Compile - Compilar + Compilar Word count - Número de palabras + Número de palabras Goal - Meta + Meta @@ -1871,17 +1876,17 @@ Main - + Principal Secundary - + Secundario Minors - + Menor @@ -1889,182 +1894,183 @@ General - General + General Promise - + Promesa Problem - + Problema Progress - Progreso + Progreso Resolution - + without compilating I don't know if it means 'resolución' or 'propósito' + Resolución Try / Fail - + Intento / Fallo No and - + No y Yes but - + Si pero Freytag's pyramid - + Piramide de Freytag Exposition - + Exposición Rising action - + Aumento de la acción Climax - + Climax Falling action - + Caída de la acción Three acts - + Tres actos 1. Setup - + 1. Inciting event - + 1. Evento incitador 1. Turning point - + 1. Punto crítico 2. Choice - + 2. Elección 2. Reversal - + 2 Inversión 2. Disaster - + 2. Desastre 3. Stand up - + 3. Climax - + 3. Climax 3. Ending - + 3. Final Hero's journey - + El Viaje del Héroe Ordinary world - + Mundo ordinario Call to adventure - + Llamada de la aventura Refusal of the call - + Rechazo de la llamada Meeting with mentor - + Encuentro con el mentor Corssing the Threshold - + Cruce del primer umbral Tests - + Pruebas, aliados y enemigos Approach - + Acercamiento Abyss - + Prueba difícil o traumática Reward / Revelation - + Recompensa / Revelación Transformation - + Transformación Atonement - + Expiación Return - + Regreso @@ -2072,37 +2078,37 @@ New plot - + Nueva trama Name - Nombre + Nombre Meta - + Meta New step - + Siguiente paso Main - + Principal Secondary - + Secundario Minor - + Menor @@ -2110,22 +2116,22 @@ Main - + Principal Secondary - + Secundario Minor - + Menor **Plot:** {} - + **Trama:** {} @@ -2133,17 +2139,17 @@ Main - + Principal Secundary - + Secundario Minors - + Menor @@ -2156,7 +2162,7 @@ POV - + @@ -2176,7 +2182,7 @@ Goal - Meta + Objetivo @@ -2209,7 +2215,7 @@ POV: - + @@ -2249,7 +2255,7 @@ POV of: - + @@ -2324,27 +2330,27 @@ Motivation - Motivación + Motivación Goal - Meta + Objetivo Epiphany - Epifanía + Epifanía Short summary - + Resumen corto Longer summary - + Resumen largo @@ -2372,67 +2378,67 @@ Show modifications - + Ver modificaciones Show ancient version - + Ver versión antigua Show spaces - + Ver espacios Show modifications only - + Ver sólo modificaciones {} years ago - + Hace {} años {} months ago - + Hace {} meses {} days ago - + Hace {} días 1 day ago - + Hace 1 día {} hours ago - + Hace {} horas {} minutes ago - + Hace {} minutos {} seconds ago - + Hace {} segundos Line {}: - + Linea {}: Clear all - + Limpiar todo @@ -2440,62 +2446,62 @@ Form - + Formulario Search in: - + Buscar en: All - + Todo Title - Título + Título Text - Texto + Texto Summary - Resumen + Resumen Notes - Notas + Notas POV - + Status - Estado + Estado Label - Etiqueta + Etiqueta Options: - + Opciones: Case sensitive - + Distingue mayúsculas y minúsculas @@ -2503,22 +2509,22 @@ New status - + Nuevo estado New label - + Nueva etiqueta newtheme - + nuevotema New theme - + Nuevo Tema @@ -2536,17 +2542,17 @@ Minor - + Menor Secondary - + Secundario Main - + Principal @@ -2554,27 +2560,27 @@ Form - + Formulario + - + - - + Show Plots - + Ver Tramas Show Characters - + Ver Personajes @@ -2582,7 +2588,7 @@ Insert reference - + Insertar referencia @@ -2590,7 +2596,7 @@ Various - + Varios @@ -2598,47 +2604,47 @@ Form - + Formulario CTRL+B - + CTRL+I - + CTRL+U - + CTRL+P - + CTRL+L - + CTRL+E - + CTRL+R - + CTRL+J - + @@ -2646,37 +2652,37 @@ Root - + Raíz Open {} items in new tabs - + Abrir {} elemntos en pestañas nuevas Open {} in a new tab - + Abrir {} en una nueva pestaña Expand {} - + Expandir {} Collapse {} - + Contraer {} Expand All - + Expandir Todo Collapse All - + Contraer Todo @@ -2759,132 +2765,133 @@ Open project - + Abrir proyecto Manuskript project (*.msk);;All files (*) - + Proyecto de Manuskript (*.msk);;Todos los ficheros (*) Save project as... - + Guardar proyecto como... Manuskript project (*.msk) - + Proyecto de Manuskript (*.msk) Create New Project - + Crear un Proyecto Nuevo Empty fiction - + Ficción vacía Chapter - + Capítulo Scene - + Escena Trilogy - + Trilogía Book - + Libro Section - + Sección Empty non-fiction - + No-Ficción vacía words each. - + I need to see the context + of - + de Text - Texto + Texto Something - + Algo <b>Total:</b> {} words (~ {} pages) - + <b>Total:</b> {} palabras (~ {} páginas) Fiction - + Ficción Non-fiction - + No-ficción Idea - + Note - + Nota Research - + Investigación TODO - + POR HACER First draft - + Primer borrador Second draft - + Segundo borrador Final - + @@ -2892,212 +2899,212 @@ New item - + Nuevo elemento Fantasy world building - + Construcción del mundo de fantasía Physical - + Fisico Climate - + Clima Topography - + Topografia Astronomy - + Astronomía Natural ressources - + Recursos naturales Wild life - + Vida salvaje Flora - + Flora History - + Historia Races - + Razas Diseases - + Enfermedades Cultural - + Customs - + Aduanas Food - + Comida Languages - + Idiomas Education - + Educación Dresses - + Vestidos Science - + Ciencia Calendar - + Calendario Bodily language - + Lenguaje corporal Ethics - + Ética Religion - + Religión Government - + Gobierno Politics - + Política Gender roles - + Roles de género Music and arts - + Música y artes Architecture - + Arquitectura Military - + Ejercitos Technology - + Tenologia Courtship - + Cortejo Demography - + Demografía Transportation - + Medios de transporte Medicine - + Medicina Magic system - + Sistema de magia Rules - + Reglas Organization - + Organización Magical objects - + Objetos mágicos Magical places - + Lugares mágicos Magical races - + Razas mágicas Important places - + Lugares importantes Important objects - + Objetos importantes From 85cb90d592a774e816108f84105e7c4055e4d79c Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 31 Mar 2016 16:12:54 +0200 Subject: [PATCH 103/103] Bumping version number --- manuskript/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manuskript/main.py b/manuskript/main.py index f765a1af..f9a092b2 100644 --- a/manuskript/main.py +++ b/manuskript/main.py @@ -10,7 +10,7 @@ from PyQt5.QtWidgets import QApplication, qApp from manuskript.functions import appPath, writablePath -_version = "0.2.0" +_version = "0.3.0" faulthandler.enable()