From e73a89c079f38466a16dc0c58efaedae7410ad64 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Sat, 27 Feb 2016 19:56:55 +0100 Subject: [PATCH 1/7] New feature: story line (work in progress) --- manuskript/mainWindow.py | 11 +- manuskript/ui/mainWindow.py | 56 ++++---- manuskript/ui/mainWindow.ui | 176 +++++++++++++----------- manuskript/ui/views/storylineView.py | 89 ++++++++++++ manuskript/ui/views/storylineView_ui.py | 63 +++++++++ manuskript/ui/views/storylineView_ui.ui | 133 ++++++++++++++++++ 6 files changed, 417 insertions(+), 111 deletions(-) create mode 100644 manuskript/ui/views/storylineView.py create mode 100644 manuskript/ui/views/storylineView_ui.py create mode 100644 manuskript/ui/views/storylineView_ui.ui diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index 7fc3a51..2d84793 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -699,6 +699,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.treeOutlineOutline.setModel(self.mdlOutline) # self.redacEditor.setModel(self.mdlOutline) + self.storylineView.setModel(self.mdlPlots) self.treeOutlineOutline.selectionModel().selectionChanged.connect(lambda: self.outlineItemEditor.selectionChanged( @@ -799,6 +800,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.toolbar.addCustomWidget(self.tr("Book summary"), self.grpPlotSummary, self.TabPlots) self.toolbar.addCustomWidget(self.tr("Project tree"), self.treeRedacWidget, self.TabRedac) self.toolbar.addCustomWidget(self.tr("Metadata"), self.redacMetadata, self.TabRedac) + self.toolbar.addCustomWidget(self.tr("Story line"), self.storylineView, self.TabRedac) # Custom "tab" bar on the left self.lstTabs.setIconSize(QSize(48, 48)) @@ -838,9 +840,12 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.splitterOutlineV.setStretchFactor(0, 75) self.splitterOutlineV.setStretchFactor(1, 25) - self.splitterRedac.setStretchFactor(0, 30) - self.splitterRedac.setStretchFactor(1, 40) - self.splitterRedac.setStretchFactor(2, 30) + # self.splitterRedacV.setStretchFactor(0, 55) + # self.splitterRedacV.setStretchFactor(1, 25) + + self.splitterRedacH.setStretchFactor(0, 30) + self.splitterRedacH.setStretchFactor(1, 40) + self.splitterRedacH.setStretchFactor(2, 30) # QFormLayout stretch for w in [self.txtWorldDescription, self.txtWorldPassion, self.txtWorldConflict]: diff --git a/manuskript/ui/mainWindow.py b/manuskript/ui/mainWindow.py index 4abf5c1..dba45bc 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: Mon Feb 8 11:20:10 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) @@ -869,10 +867,13 @@ class Ui_MainWindow(object): self.verticalLayout_15 = QtWidgets.QVBoxLayout(self.lytTabRedac) self.verticalLayout_15.setContentsMargins(0, 0, 0, 0) self.verticalLayout_15.setObjectName("verticalLayout_15") - self.splitterRedac = QtWidgets.QSplitter(self.lytTabRedac) - self.splitterRedac.setOrientation(QtCore.Qt.Horizontal) - self.splitterRedac.setObjectName("splitterRedac") - self.treeRedacWidget = QtWidgets.QWidget(self.splitterRedac) + self.splitterRedacV = QtWidgets.QSplitter(self.lytTabRedac) + self.splitterRedacV.setOrientation(QtCore.Qt.Vertical) + self.splitterRedacV.setObjectName("splitterRedacV") + self.splitterRedacH = QtWidgets.QSplitter(self.splitterRedacV) + self.splitterRedacH.setOrientation(QtCore.Qt.Horizontal) + self.splitterRedacH.setObjectName("splitterRedacH") + self.treeRedacWidget = QtWidgets.QWidget(self.splitterRedacH) self.treeRedacWidget.setObjectName("treeRedacWidget") self.verticalLayout_30 = QtWidgets.QVBoxLayout(self.treeRedacWidget) self.verticalLayout_30.setContentsMargins(0, 0, 0, 0) @@ -905,11 +906,13 @@ class Ui_MainWindow(object): spacerItem16 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_31.addItem(spacerItem16) self.verticalLayout_30.addLayout(self.horizontalLayout_31) - self.mainEditor = mainEditor(self.splitterRedac) + self.mainEditor = mainEditor(self.splitterRedacH) self.mainEditor.setObjectName("mainEditor") - self.redacMetadata = metadataView(self.splitterRedac) + self.redacMetadata = metadataView(self.splitterRedacH) self.redacMetadata.setObjectName("redacMetadata") - self.verticalLayout_15.addWidget(self.splitterRedac) + self.storylineView = storylineView(self.splitterRedacV) + self.storylineView.setObjectName("storylineView") + self.verticalLayout_15.addWidget(self.splitterRedacV) self.tabMain.addTab(self.lytTabRedac, "") self.lytTabDebug = QtWidgets.QWidget() self.lytTabDebug.setObjectName("lytTabDebug") @@ -989,7 +992,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") @@ -1159,7 +1162,7 @@ class Ui_MainWindow(object): self.retranslateUi(MainWindow) self.stack.setCurrentIndex(1) - self.tabMain.setCurrentIndex(4) + self.tabMain.setCurrentIndex(6) self.tabSummary.setCurrentIndex(0) self.tabPersos.setCurrentIndex(0) self.tabPlot.setCurrentIndex(0) @@ -1294,19 +1297,20 @@ class Ui_MainWindow(object): self.actCloseProject.setText(_translate("MainWindow", "&Close project")) self.actCompile.setText(_translate("MainWindow", "Co&mpile")) self.actCompile.setShortcut(_translate("MainWindow", "F6")) - self.actToolFrequency.setText(_translate("MainWindow", "Frequency Analyzer")) + self.actToolFrequency.setText(_translate("MainWindow", "&Frequency Analyzer")) -from manuskript.ui.welcome import welcome -from manuskript.ui.views.metadataView import metadataView -from manuskript.ui.views.basicItemView import basicItemView -from manuskript.ui.editors.mainEditor import mainEditor -from manuskript.ui.views.plotTreeView import plotTreeView -from manuskript.ui.sldImportance import sldImportance -from manuskript.ui.views.textEditCompleter import textEditCompleter -from manuskript.ui.views.treeView import treeView -from manuskript.ui.views.persoTreeView import persoTreeView -from manuskript.ui.search import search from manuskript.ui.cheatSheet import cheatSheet -from manuskript.ui.views.outlineView import outlineView -from manuskript.ui.views.textEditView import textEditView +from manuskript.ui.editors.mainEditor import mainEditor +from manuskript.ui.search import search +from manuskript.ui.sldImportance import sldImportance +from manuskript.ui.views.basicItemView import basicItemView from manuskript.ui.views.lineEditView import lineEditView +from manuskript.ui.views.metadataView import metadataView +from manuskript.ui.views.outlineView import outlineView +from manuskript.ui.views.persoTreeView import persoTreeView +from manuskript.ui.views.plotTreeView import plotTreeView +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 2d2f1cd..1dd7cb5 100644 --- a/manuskript/ui/mainWindow.ui +++ b/manuskript/ui/mainWindow.ui @@ -124,7 +124,7 @@ QTabWidget::Rounded - 4 + 6 true @@ -1770,88 +1770,94 @@ 0 - + - Qt::Horizontal + Qt::Vertical - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QAbstractItemView::EditKeyPressed - - - false - - - - - - - - - - - - - ../../../../../../../.designer/backup../../../../../../../.designer/backup - - - - - - - - - - - ../../../../../../../.designer/backup../../../../../../../.designer/backup - - - - - - - - - - - ../../../../../../../.designer/backup../../../../../../../.designer/backup - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - + + + Qt::Horizontal + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QAbstractItemView::EditKeyPressed + + + false + + + + + + + + + + + + + ../../../../../../../.designer/backup../../../../../../../.designer/backup + + + + + + + + + + + ../../../../../../../.designer/backup../../../../../../../.designer/backup + + + + + + + + + + + ../../../../../../../.designer/backup../../../../../../../.designer/backup + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + - - + @@ -1967,7 +1973,7 @@ 0 0 1112 - 20 + 30 @@ -2323,7 +2329,7 @@ QListView::item:hover { - Frequency Analyzer + &Frequency Analyzer @@ -2405,6 +2411,12 @@ QListView::item:hover { QTextEdit
manuskript.ui.views.textEditCompleter.h
+ + storylineView + QWidget +
manuskript.ui.views.storylineView.h
+ 1 +
diff --git a/manuskript/ui/views/storylineView.py b/manuskript/ui/views/storylineView.py new file mode 100644 index 0000000..e1285bb --- /dev/null +++ b/manuskript/ui/views/storylineView.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QBrush, QPen +from PyQt5.QtWidgets import QWidget, QGraphicsScene, QGraphicsSimpleTextItem, QMenu, QAction + +from manuskript.functions import randomColor +from manuskript.ui.views.storylineView_ui import Ui_storylineView + + +class storylineView(QWidget, Ui_storylineView): + def __init__(self, parent=None): + QWidget.__init__(self, parent) + self.setupUi(self) + self._mdlPlots = None + self.btnRefresh.clicked.connect(self.refresh) + self.scene = QGraphicsScene() + self.view.setScene(self.scene) + + self.generateMenu() + + 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.btnSettings.setMenu(m) + + def setModel(self, mdlPlots): + self._mdlPlots = mdlPlots + self._mdlPlots.dataChanged.connect(self.refresh) + self._mdlPlots.rowsInserted.connect(self.refresh) + + def refresh(self): + if not self._mdlPlots: + pass + + LINE_HEIGHT = 32 + LINE_SPACING = 6 + RECT_WIDTH = 200 + TOTAL_WIDTH = 4000 + + s = self.scene + s.clear() + plotsID = self._mdlPlots.getPlotsByImportance() + i = 0 + + plots = [] + + # Add Plots + for importance in plotsID: + for ID in importance: + name = self._mdlPlots.getPlotNameByID(ID) + print(ID, name) + color = randomColor() + + # Rect + r = s.addRect(0, 0, RECT_WIDTH, LINE_HEIGHT) + r.setPen(QPen(Qt.NoPen)) + r.setBrush(QBrush(color)) + r.setPos(0, i * LINE_HEIGHT + i * LINE_SPACING) + i += 1 + + # Text + txt = QGraphicsSimpleTextItem(name, r) + txt.setPos(r.boundingRect().center() - txt.boundingRect().center()) + + # Line + line = s.addLine(10, LINE_HEIGHT / 2, TOTAL_WIDTH, LINE_HEIGHT / 2) + line.setParentItem(r) + line.setPen(QPen(color, 5)) + line.setZValue(-10) + + plots.append((ID, r)) + + + # Add Folders and Texts + + + # self.view.fitInView(0, 0, TOTAL_WIDTH, i * LINE_HEIGHT, Qt.KeepAspectRatioByExpanding) # KeepAspectRatio + self.view.setSceneRect() diff --git a/manuskript/ui/views/storylineView_ui.py b/manuskript/ui/views/storylineView_ui.py new file mode 100644 index 0000000..c9413c2 --- /dev/null +++ b/manuskript/ui/views/storylineView_ui.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'manuskript/ui/views/storylineView_ui.ui' +# +# Created by: PyQt5 UI code generator 5.4.2 +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore, QtGui, QtWidgets + +class Ui_storylineView(object): + def setupUi(self, storylineView): + storylineView.setObjectName("storylineView") + storylineView.resize(1040, 130) + self.horizontalLayout = QtWidgets.QHBoxLayout(storylineView) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout.setSpacing(0) + self.horizontalLayout.setObjectName("horizontalLayout") + self.verticalLayout = QtWidgets.QVBoxLayout() + self.verticalLayout.setSpacing(0) + self.verticalLayout.setObjectName("verticalLayout") + self.btnZoomIn = QtWidgets.QPushButton(storylineView) + self.btnZoomIn.setMaximumSize(QtCore.QSize(32, 32)) + self.btnZoomIn.setFlat(True) + self.btnZoomIn.setObjectName("btnZoomIn") + self.verticalLayout.addWidget(self.btnZoomIn) + self.btnZoomOut = QtWidgets.QPushButton(storylineView) + self.btnZoomOut.setMaximumSize(QtCore.QSize(32, 32)) + self.btnZoomOut.setFlat(True) + self.btnZoomOut.setObjectName("btnZoomOut") + self.verticalLayout.addWidget(self.btnZoomOut) + self.btnRefresh = QtWidgets.QPushButton(storylineView) + self.btnRefresh.setMaximumSize(QtCore.QSize(32, 32)) + self.btnRefresh.setText("") + icon = QtGui.QIcon.fromTheme("view-refresh") + self.btnRefresh.setIcon(icon) + self.btnRefresh.setFlat(True) + self.btnRefresh.setObjectName("btnRefresh") + self.verticalLayout.addWidget(self.btnRefresh) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout.addItem(spacerItem) + self.btnSettings = QtWidgets.QPushButton(storylineView) + self.btnSettings.setMaximumSize(QtCore.QSize(32, 32)) + self.btnSettings.setText("") + icon = QtGui.QIcon.fromTheme("preferences-system") + self.btnSettings.setIcon(icon) + self.btnSettings.setFlat(True) + self.btnSettings.setObjectName("btnSettings") + self.verticalLayout.addWidget(self.btnSettings) + self.horizontalLayout.addLayout(self.verticalLayout) + self.view = QtWidgets.QGraphicsView(storylineView) + self.view.setObjectName("view") + self.horizontalLayout.addWidget(self.view) + + self.retranslateUi(storylineView) + QtCore.QMetaObject.connectSlotsByName(storylineView) + + def retranslateUi(self, storylineView): + _translate = QtCore.QCoreApplication.translate + storylineView.setWindowTitle(_translate("storylineView", "Form")) + self.btnZoomIn.setText(_translate("storylineView", "+")) + self.btnZoomOut.setText(_translate("storylineView", "-")) + diff --git a/manuskript/ui/views/storylineView_ui.ui b/manuskript/ui/views/storylineView_ui.ui new file mode 100644 index 0000000..71741da --- /dev/null +++ b/manuskript/ui/views/storylineView_ui.ui @@ -0,0 +1,133 @@ + + + storylineView + + + + 0 + 0 + 1040 + 130 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + + 32 + 32 + + + + + + + + true + + + + + + + + 32 + 32 + + + + - + + + true + + + + + + + + 32 + 32 + + + + + + + + + + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 32 + 32 + + + + + + + + + + + + true + + + + + + + + + + + + + From 998c38eb2a6b2479b9989b68a10b349016bb36c9 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Sat, 27 Feb 2016 21:48:38 +0100 Subject: [PATCH 2/7] Working on storyline, begining to look like something. --- manuskript/mainWindow.py | 2 +- manuskript/ui/views/storylineView.py | 171 +++++++++++++++++++++------ 2 files changed, 139 insertions(+), 34 deletions(-) diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index 2d84793..b3825a6 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -699,7 +699,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.treeOutlineOutline.setModel(self.mdlOutline) # self.redacEditor.setModel(self.mdlOutline) - self.storylineView.setModel(self.mdlPlots) + self.storylineView.setModels(self.mdlOutline, self.mdlPersos, self.mdlPlots) self.treeOutlineOutline.selectionModel().selectionChanged.connect(lambda: self.outlineItemEditor.selectionChanged( diff --git a/manuskript/ui/views/storylineView.py b/manuskript/ui/views/storylineView.py index e1285bb..6708868 100644 --- a/manuskript/ui/views/storylineView.py +++ b/manuskript/ui/views/storylineView.py @@ -1,8 +1,9 @@ #!/usr/bin/env python # --!-- coding: utf8 --!-- -from PyQt5.QtCore import Qt +from PyQt5.QtCore import Qt, QTimer from PyQt5.QtGui import QBrush, QPen -from PyQt5.QtWidgets import QWidget, QGraphicsScene, QGraphicsSimpleTextItem, QMenu, QAction +from PyQt5.QtWidgets import QWidget, QGraphicsScene, QGraphicsSimpleTextItem, QMenu, QAction, QGraphicsRectItem, \ + QGraphicsLineItem from manuskript.functions import randomColor from manuskript.ui.views.storylineView_ui import Ui_storylineView @@ -17,6 +18,11 @@ class storylineView(QWidget, Ui_storylineView): self.scene = QGraphicsScene() self.view.setScene(self.scene) + self.reloadTimer = QTimer() + self.reloadTimer.timeout.connect(self.refresh) + self.reloadTimer.setSingleShot(True) + self.reloadTimer.setInterval(500) + self.generateMenu() def generateMenu(self): @@ -34,56 +40,155 @@ class storylineView(QWidget, Ui_storylineView): self.btnSettings.setMenu(m) - def setModel(self, mdlPlots): + def setModels(self, mdlOutline, mdlPersos, mdlPlots): self._mdlPlots = mdlPlots - self._mdlPlots.dataChanged.connect(self.refresh) - self._mdlPlots.rowsInserted.connect(self.refresh) + # self._mdlPlots.dataChanged.connect(self.refresh) + # self._mdlPlots.rowsInserted.connect(self.refresh) + + self._mdlOutline = mdlOutline + self._mdlOutline.dataChanged.connect(self.reloadTimer.start) + + self._mdlPersos = mdlPersos + self._mdlPersos.dataChanged.connect(self.reloadTimer.start) + def refresh(self): - if not self._mdlPlots: + if not self._mdlPlots or not self._mdlOutline or not self._mdlPersos: pass LINE_HEIGHT = 32 - LINE_SPACING = 6 + SPACING = 6 RECT_WIDTH = 200 - TOTAL_WIDTH = 4000 + TEXT_WIDTH = 25 + LEVEL_HEIGHT = 12 s = self.scene s.clear() + + # Get Max Level + root = self._mdlOutline.rootItem + def maxLevel(item, level=0, max=0): + if level > max: + max = level + for c in item.children(): + m = maxLevel(c, level + 1) + if m > max: + max = m + return max + + MAX_LEVEL = maxLevel(root) + + # Get plots plotsID = self._mdlPlots.getPlotsByImportance() - i = 0 - plots = [] - - # Add Plots for importance in plotsID: for ID in importance: name = self._mdlPlots.getPlotNameByID(ID) - print(ID, name) - color = randomColor() + plots.append((ID, name)) - # Rect - r = s.addRect(0, 0, RECT_WIDTH, LINE_HEIGHT) - r.setPen(QPen(Qt.NoPen)) - r.setBrush(QBrush(color)) - r.setPos(0, i * LINE_HEIGHT + i * LINE_SPACING) - i += 1 - - # Text - txt = QGraphicsSimpleTextItem(name, r) - txt.setPos(r.boundingRect().center() - txt.boundingRect().center()) - - # Line - line = s.addLine(10, LINE_HEIGHT / 2, TOTAL_WIDTH, LINE_HEIGHT / 2) - line.setParentItem(r) - line.setPen(QPen(color, 5)) - line.setZValue(-10) - - plots.append((ID, r)) + ROWS_HEIGHT = len(plots) * (LINE_HEIGHT + SPACING ) # Add Folders and Texts + outline = OutlineRect(0, 0, 0, ROWS_HEIGHT + SPACING + MAX_LEVEL * LEVEL_HEIGHT) + s.addItem(outline) + outline.setPos(RECT_WIDTH + SPACING, 0) + # A Function to add a rect with centered text + def addRectText(x, w, parent, text="", level=0, tooltip=""): + deltaH = LEVEL_HEIGHT if level else 0 + r = OutlineRect(0, 0, w, parent.rect().height()-deltaH, parent) + r.setPos(x, deltaH) + r.setToolTip(tooltip) + + txt = QGraphicsSimpleTextItem(text, r) + f = txt.font() + f.setPointSize(8) + txt.setFont(f) + txt.setPos(r.boundingRect().center() - txt.boundingRect().center()) + txt.setY(0) + return r + + def itemWidth(item): + if item.isFolder(): + r = 0 + for c in item.children(): + r += itemWidth(c) + return r + else: + return TEXT_WIDTH + + def listItems(item, rect, level=0): + delta = 0 + for child in item.children(): + w = itemWidth(child) + if child.isFolder(): + parent = addRectText(delta, w, rect, child.title(), level, tooltip=child.title()) + listItems(child, parent, level + 1) + else: + addRectText(delta, TEXT_WIDTH, rect, "", level, tooltip=child.title()) + delta += w + + listItems(root, outline) + + OUTLINE_WIDTH = itemWidth(root) + + # Add Plots + i = 0 + itemsRect = s.addRect(0, 0, 0, 0) + itemsRect.setPos(0, MAX_LEVEL * LEVEL_HEIGHT + SPACING) + + for ID, name in plots: + color = randomColor() + + # Rect + r = QGraphicsRectItem(0, 0, RECT_WIDTH, LINE_HEIGHT, itemsRect) + r.setPen(QPen(Qt.NoPen)) + r.setBrush(QBrush(color)) + r.setPos(0, i * LINE_HEIGHT + i * SPACING) + i += 1 + + # Text + txt = QGraphicsSimpleTextItem(name, r) + txt.setPos(r.boundingRect().center() - txt.boundingRect().center()) + + # Line + line = PlotLine(RECT_WIDTH, + r.mapToScene(r.rect().center()).y(), + OUTLINE_WIDTH + RECT_WIDTH + SPACING, + r.mapToScene(r.rect().center()).y()) + s.addItem(line) + line.setPen(QPen(color, 5)) + line.setToolTip(self.tr("Plot: ") + name) # self.view.fitInView(0, 0, TOTAL_WIDTH, i * LINE_HEIGHT, Qt.KeepAspectRatioByExpanding) # KeepAspectRatio - self.view.setSceneRect() + self.view.setSceneRect(0, 0, 0, 0) + + +class OutlineRect(QGraphicsRectItem): + def __init__(self, x, y, w, h, parent=None): + QGraphicsRectItem.__init__(self, x, y, w, h, parent) + self.setBrush(Qt.white) + self.setAcceptHoverEvents(True) + + def hoverEnterEvent(self, QGraphicsSceneHoverEvent): + self.setBrush(Qt.lightGray) + + def hoverLeaveEvent(self, QGraphicsSceneHoverEvent): + self.setBrush(Qt.white) + + +class PlotLine(QGraphicsLineItem): + def __init__(self, x1, y1, x2, y2, parent=None): + QGraphicsLineItem.__init__(self, x1, y1, x2, y2, parent) + self.setAcceptHoverEvents(True) + + def hoverEnterEvent(self, QGraphicsSceneHoverEvent): + p = self.pen() + p.setWidth(10) + self.setPen(p) + + def hoverLeaveEvent(self, QGraphicsSceneHoverEvent): + p = self.pen() + p.setWidth(5) + self.setPen(p) From 5d69b467861447947e183dbfd5b3a5407dd2415e Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Sun, 28 Feb 2016 09:24:30 +0100 Subject: [PATCH 3/7] Adds slider, and adapts title's rect to text --- manuskript/ui/mainWindow.py | 28 +++++++++++++------------ manuskript/ui/views/storylineView.py | 26 ++++++++++++++--------- manuskript/ui/views/storylineView_ui.py | 14 +++++++++---- manuskript/ui/views/storylineView_ui.ui | 19 ++++++++++------- 4 files changed, 52 insertions(+), 35 deletions(-) diff --git a/manuskript/ui/mainWindow.py b/manuskript/ui/mainWindow.py index dba45bc..dbcda03 100644 --- a/manuskript/ui/mainWindow.py +++ b/manuskript/ui/mainWindow.py @@ -2,7 +2,8 @@ # Form implementation generated from reading ui file 'manuskript/ui/mainWindow.ui' # -# Created by: PyQt5 UI code generator 5.4.2 +# Created: Sun Feb 28 09:13:29 2016 +# by: PyQt5 UI code generator 5.2.1 # # WARNING! All changes made in this file will be lost! @@ -16,8 +17,8 @@ class Ui_MainWindow(object): self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.centralwidget) - self.horizontalLayout_2.setContentsMargins(0, 6, 0, 0) self.horizontalLayout_2.setSpacing(0) + self.horizontalLayout_2.setContentsMargins(0, 6, 0, 0) self.horizontalLayout_2.setObjectName("horizontalLayout_2") self.stack = QtWidgets.QStackedWidget(self.centralwidget) self.stack.setObjectName("stack") @@ -808,6 +809,7 @@ 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) @@ -1299,18 +1301,18 @@ class Ui_MainWindow(object): self.actCompile.setShortcut(_translate("MainWindow", "F6")) self.actToolFrequency.setText(_translate("MainWindow", "&Frequency Analyzer")) -from manuskript.ui.cheatSheet import cheatSheet -from manuskript.ui.editors.mainEditor import mainEditor -from manuskript.ui.search import search -from manuskript.ui.sldImportance import sldImportance -from manuskript.ui.views.basicItemView import basicItemView -from manuskript.ui.views.lineEditView import lineEditView -from manuskript.ui.views.metadataView import metadataView -from manuskript.ui.views.outlineView import outlineView -from manuskript.ui.views.persoTreeView import persoTreeView from manuskript.ui.views.plotTreeView import plotTreeView 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.basicItemView import basicItemView from manuskript.ui.views.treeView import treeView +from manuskript.ui.views.outlineView import outlineView +from manuskript.ui.search import search +from manuskript.ui.sldImportance import sldImportance +from manuskript.ui.views.textEditCompleter import textEditCompleter +from manuskript.ui.views.metadataView import metadataView +from manuskript.ui.views.persoTreeView import persoTreeView +from manuskript.ui.cheatSheet import cheatSheet from manuskript.ui.welcome import welcome +from manuskript.ui.editors.mainEditor import mainEditor +from manuskript.ui.views.lineEditView import lineEditView +from manuskript.ui.views.textEditView import textEditView diff --git a/manuskript/ui/views/storylineView.py b/manuskript/ui/views/storylineView.py index 6708868..058be9d 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 -from PyQt5.QtGui import QBrush, QPen +from PyQt5.QtGui import QBrush, QPen, QFontMetrics from PyQt5.QtWidgets import QWidget, QGraphicsScene, QGraphicsSimpleTextItem, QMenu, QAction, QGraphicsRectItem, \ QGraphicsLineItem @@ -14,7 +14,6 @@ class storylineView(QWidget, Ui_storylineView): QWidget.__init__(self, parent) self.setupUi(self) self._mdlPlots = None - self.btnRefresh.clicked.connect(self.refresh) self.scene = QGraphicsScene() self.view.setScene(self.scene) @@ -23,6 +22,9 @@ class storylineView(QWidget, Ui_storylineView): self.reloadTimer.setSingleShot(True) self.reloadTimer.setInterval(500) + self.btnRefresh.clicked.connect(self.refresh) + self.sldTxtSize.sliderMoved.connect(self.reloadTimer.start) + self.generateMenu() def generateMenu(self): @@ -58,14 +60,13 @@ class storylineView(QWidget, Ui_storylineView): LINE_HEIGHT = 32 SPACING = 6 - RECT_WIDTH = 200 - TEXT_WIDTH = 25 + TEXT_WIDTH = self.sldTxtSize.value() LEVEL_HEIGHT = 12 s = self.scene s.clear() - # Get Max Level + # Get Max Level (max depth) root = self._mdlOutline.rootItem def maxLevel(item, level=0, max=0): if level > max: @@ -81,18 +82,23 @@ class storylineView(QWidget, Ui_storylineView): # Get plots plotsID = self._mdlPlots.getPlotsByImportance() plots = [] + fm = QFontMetrics(s.font()) + max_name = 0 + for importance in plotsID: for ID in importance: name = self._mdlPlots.getPlotNameByID(ID) plots.append((ID, name)) + max_name = max(fm.width(name), max_name) ROWS_HEIGHT = len(plots) * (LINE_HEIGHT + SPACING ) + TITLE_WIDTH = max_name + 2 * SPACING # Add Folders and Texts outline = OutlineRect(0, 0, 0, ROWS_HEIGHT + SPACING + MAX_LEVEL * LEVEL_HEIGHT) s.addItem(outline) - outline.setPos(RECT_WIDTH + SPACING, 0) + outline.setPos(TITLE_WIDTH + SPACING, 0) # A Function to add a rect with centered text def addRectText(x, w, parent, text="", level=0, tooltip=""): @@ -114,7 +120,7 @@ class storylineView(QWidget, Ui_storylineView): r = 0 for c in item.children(): r += itemWidth(c) - return r + return r or TEXT_WIDTH else: return TEXT_WIDTH @@ -142,7 +148,7 @@ class storylineView(QWidget, Ui_storylineView): color = randomColor() # Rect - r = QGraphicsRectItem(0, 0, RECT_WIDTH, LINE_HEIGHT, itemsRect) + 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) @@ -153,9 +159,9 @@ class storylineView(QWidget, Ui_storylineView): txt.setPos(r.boundingRect().center() - txt.boundingRect().center()) # Line - line = PlotLine(RECT_WIDTH, + line = PlotLine(TITLE_WIDTH, r.mapToScene(r.rect().center()).y(), - OUTLINE_WIDTH + RECT_WIDTH + SPACING, + OUTLINE_WIDTH + TITLE_WIDTH + SPACING, r.mapToScene(r.rect().center()).y()) s.addItem(line) line.setPen(QPen(color, 5)) diff --git a/manuskript/ui/views/storylineView_ui.py b/manuskript/ui/views/storylineView_ui.py index c9413c2..9f886ff 100644 --- a/manuskript/ui/views/storylineView_ui.py +++ b/manuskript/ui/views/storylineView_ui.py @@ -2,7 +2,8 @@ # Form implementation generated from reading ui file 'manuskript/ui/views/storylineView_ui.ui' # -# Created by: PyQt5 UI code generator 5.4.2 +# Created: Sun Feb 28 09:13:29 2016 +# by: PyQt5 UI code generator 5.2.1 # # WARNING! All changes made in this file will be lost! @@ -13,8 +14,8 @@ class Ui_storylineView(object): storylineView.setObjectName("storylineView") storylineView.resize(1040, 130) self.horizontalLayout = QtWidgets.QHBoxLayout(storylineView) - self.horizontalLayout.setContentsMargins(0, 0, 0, 0) self.horizontalLayout.setSpacing(0) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) self.horizontalLayout.setObjectName("horizontalLayout") self.verticalLayout = QtWidgets.QVBoxLayout() self.verticalLayout.setSpacing(0) @@ -37,8 +38,13 @@ class Ui_storylineView(object): self.btnRefresh.setFlat(True) self.btnRefresh.setObjectName("btnRefresh") self.verticalLayout.addWidget(self.btnRefresh) - spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - self.verticalLayout.addItem(spacerItem) + self.sldTxtSize = QtWidgets.QSlider(storylineView) + self.sldTxtSize.setMinimum(1) + self.sldTxtSize.setMaximum(100) + self.sldTxtSize.setProperty("value", 20) + self.sldTxtSize.setOrientation(QtCore.Qt.Vertical) + self.sldTxtSize.setObjectName("sldTxtSize") + self.verticalLayout.addWidget(self.sldTxtSize) self.btnSettings = QtWidgets.QPushButton(storylineView) self.btnSettings.setMaximumSize(QtCore.QSize(32, 32)) self.btnSettings.setText("") diff --git a/manuskript/ui/views/storylineView_ui.ui b/manuskript/ui/views/storylineView_ui.ui index 71741da..069de66 100644 --- a/manuskript/ui/views/storylineView_ui.ui +++ b/manuskript/ui/views/storylineView_ui.ui @@ -88,17 +88,20 @@ - + + + 1 + + + 100 + + + 20 + Qt::Vertical - - - 20 - 40 - - - + From c8391f771aecdff43535a2e521ae6fc9f195d0f9 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Sun, 28 Feb 2016 12:43:23 +0100 Subject: [PATCH 4/7] Ugly but working --- manuskript/mainWindow.py | 4 +- manuskript/models/outlineModel.py | 13 +++- manuskript/models/references.py | 47 ++++++++---- manuskript/ui/views/storylineView.py | 108 +++++++++++++++++++++------ 4 files changed, 132 insertions(+), 40 deletions(-) diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index b3825a6..ab2f3dc 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -840,8 +840,8 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.splitterOutlineV.setStretchFactor(0, 75) self.splitterOutlineV.setStretchFactor(1, 25) - # self.splitterRedacV.setStretchFactor(0, 55) - # self.splitterRedacV.setStretchFactor(1, 25) + self.splitterRedacV.setStretchFactor(0, 10) + self.splitterRedacV.setStretchFactor(1, 100) self.splitterRedacH.setStretchFactor(0, 30) self.splitterRedacH.setStretchFactor(1, 40) diff --git a/manuskript/models/outlineModel.py b/manuskript/models/outlineModel.py index 36faa59..bbeefb8 100644 --- a/manuskript/models/outlineModel.py +++ b/manuskript/models/outlineModel.py @@ -836,11 +836,19 @@ class outlineItem(): return lst - def findItemsContaining(self, text, columns, mainWindow, caseSensitive=False): + def findItemsContaining(self, text, columns, mainWindow=mainWindow(), caseSensitive=False): """Returns a list if IDs of all subitems containing ``text`` in columns ``columns`` (being a list of int). """ + lst = self.itemContains(text, columns, mainWindow, caseSensitive) + + for c in self.children(): + lst.extend(c.findItemsContaining(text, columns, mainWindow, caseSensitive)) + + return lst + + def itemContains(self, text, columns, mainWindow=mainWindow(), caseSensitive=False): lst = [] text = text.lower() if not caseSensitive else text for c in columns: @@ -863,9 +871,6 @@ class outlineItem(): if not self.ID() in lst: lst.append(self.ID()) - for c in self.children(): - lst.extend(c.findItemsContaining(text, columns, mainWindow, caseSensitive)) - return lst ############################################################################### diff --git a/manuskript/models/references.py b/manuskript/models/references.py index 8afa728..a82475e 100644 --- a/manuskript/models/references.py +++ b/manuskript/models/references.py @@ -21,30 +21,47 @@ RegEx = r"{(\w):(\d+):?.*?}" RegExNonCapturing = r"{\w:\d+:?.*?}" # The basic format of the references EmptyRef = "{{{}:{}:{}}}" +EmptyRefSearchable = "{{{}:{}:" PersoLetter = "C" TextLetter = "T" PlotLetter = "P" WorldLetter = "W" -def plotReference(ID): - """Takes the ID of a plot and returns a reference for that plot.""" - return EmptyRef.format(PlotLetter, ID, "") +def plotReference(ID, searchable=False): + """Takes the ID of a plot and returns a reference for that plot. + @searchable: returns a stripped version that allows simple text search.""" + if not searchable: + return EmptyRef.format(PlotLetter, ID, "") + else: + return EmptyRefSearchable.format(PlotLetter, ID, "") -def persoReference(ID): - """Takes the ID of a character and returns a reference for that character.""" - return EmptyRef.format(PersoLetter, ID, "") +def persoReference(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, "") + else: + return EmptyRefSearchable.format(PersoLetter, ID, "") -def textReference(ID): - """Takes the ID of an outline item and returns a reference for that item.""" - return EmptyRef.format(TextLetter, ID, "") +def textReference(ID, searchable=False): + """Takes the ID of an outline item and returns a reference for that item. + @searchable: returns a stripped version that allows simple text search.""" + if not searchable: + return EmptyRef.format(TextLetter, ID, "") + else: + return EmptyRefSearchable.format(TextLetter, ID, "") -def worldReference(ID): - """Takes the ID of a world item and returns a reference for that item.""" - return EmptyRef.format(WorldLetter, ID, "") +def worldReference(ID, searchable=False): + """Takes the ID of a world item and returns a reference for that item. + @searchable: returns a stripped version that allows simple text search.""" + if not searchable: + return EmptyRef.format(WorldLetter, ID, "") + else: + return EmptyRefSearchable.format(WorldLetter, ID, "") ############################################################################### @@ -375,7 +392,10 @@ def tooltip(ref): item = idx.internalPointer() - tt = qApp.translate("references", "Text: {}").format(item.title()) + if item.isFolder(): + tt = qApp.translate("references", "Folder: {}").format(item.title()) + else: + tt = qApp.translate("references", "Text: {}").format(item.title()) tt += "
{}".format(item.path()) return tt @@ -454,6 +474,7 @@ def linkifyAllRefs(text): def listReferences(ref, title=qApp.translate("references", "Referenced in:")): oM = mainWindow().mdlOutline listRefs = "" + # Removes everything after the second ':': '{L:ID:random text}' → '{L:ID:' ref = ref[:ref.index(":", ref.index(":") + 1)] lst = oM.findItemsContaining(ref, [Outline.notes.value]) for t in lst: diff --git a/manuskript/ui/views/storylineView.py b/manuskript/ui/views/storylineView.py index 058be9d..e682aa7 100644 --- a/manuskript/ui/views/storylineView.py +++ b/manuskript/ui/views/storylineView.py @@ -1,11 +1,13 @@ #!/usr/bin/env python # --!-- coding: utf8 --!-- -from PyQt5.QtCore import Qt, QTimer -from PyQt5.QtGui import QBrush, QPen, QFontMetrics +from PyQt5.QtCore import Qt, QTimer, QRectF +from PyQt5.QtGui import QBrush, QPen, QFontMetrics, QFontMetricsF from PyQt5.QtWidgets import QWidget, QGraphicsScene, QGraphicsSimpleTextItem, QMenu, QAction, QGraphicsRectItem, \ - QGraphicsLineItem + 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 @@ -58,9 +60,10 @@ class storylineView(QWidget, Ui_storylineView): if not self._mdlPlots or not self._mdlOutline or not self._mdlPersos: pass - LINE_HEIGHT = 32 - SPACING = 6 + LINE_HEIGHT = 18 + SPACING = 3 TEXT_WIDTH = self.sldTxtSize.value() + CIRCLE_WIDTH = 10 LEVEL_HEIGHT = 12 s = self.scene @@ -79,19 +82,22 @@ class storylineView(QWidget, Ui_storylineView): MAX_LEVEL = maxLevel(root) - # Get plots + # Generate left entries + # (As of now, plot only) plotsID = self._mdlPlots.getPlotsByImportance() - plots = [] + trackedItems = [] fm = QFontMetrics(s.font()) max_name = 0 for importance in plotsID: for ID in importance: name = self._mdlPlots.getPlotNameByID(ID) - plots.append((ID, name)) + ref = references.plotReference(ID, searchable=True) + + trackedItems.append((ID, ref, name)) max_name = max(fm.width(name), max_name) - ROWS_HEIGHT = len(plots) * (LINE_HEIGHT + SPACING ) + ROWS_HEIGHT = len(trackedItems) * (LINE_HEIGHT + SPACING ) TITLE_WIDTH = max_name + 2 * SPACING @@ -100,21 +106,26 @@ class storylineView(QWidget, Ui_storylineView): s.addItem(outline) outline.setPos(TITLE_WIDTH + SPACING, 0) - # A Function to add a rect with centered text + refCircles = [] # a list of all references, to be added later on the lines + + # A Function to add a rect with centered elided text def addRectText(x, w, parent, text="", level=0, tooltip=""): deltaH = LEVEL_HEIGHT if level else 0 - r = OutlineRect(0, 0, w, parent.rect().height()-deltaH, parent) + r = OutlineRect(0, 0, w, parent.rect().height()-deltaH, parent, title=text) r.setPos(x, deltaH) - r.setToolTip(tooltip) txt = QGraphicsSimpleTextItem(text, r) f = txt.font() f.setPointSize(8) + fm = QFontMetricsF(f) + elidedText = fm.elidedText(text, Qt.ElideMiddle, w) txt.setFont(f) + txt.setText(elidedText) txt.setPos(r.boundingRect().center() - txt.boundingRect().center()) txt.setY(0) return r + # A function to returns an item's width, by counting its children def itemWidth(item): if item.isFolder(): r = 0 @@ -128,11 +139,34 @@ class storylineView(QWidget, Ui_storylineView): delta = 0 for child in item.children(): w = itemWidth(child) + if child.isFolder(): parent = addRectText(delta, w, rect, child.title(), level, tooltip=child.title()) + parent.setToolTip(references.tooltip(references.textReference(child.ID()))) listItems(child, parent, level + 1) + else: - addRectText(delta, TEXT_WIDTH, rect, "", level, tooltip=child.title()) + rectChild = addRectText(delta, TEXT_WIDTH, rect, "", level, tooltip=child.title()) + rectChild.setToolTip(references.tooltip(references.textReference(child.ID()))) + + # Find tracked references in that scene (or parent folders) + for ID, ref, name in trackedItems: + + result = [] + c = child + while c: + result += c.itemContains(ref, [Outline.notes.value]) + c = c.parent() + + if result: + ref2 = result[0] + + # Create a RefCircle with the reference + c = RefCircle(TEXT_WIDTH / 2, - CIRCLE_WIDTH / 2, CIRCLE_WIDTH, ID=ref2) + + # 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()))) + delta += w listItems(root, outline) @@ -144,7 +178,7 @@ class storylineView(QWidget, Ui_storylineView): itemsRect = s.addRect(0, 0, 0, 0) itemsRect.setPos(0, MAX_LEVEL * LEVEL_HEIGHT + SPACING) - for ID, name in plots: + for ID, ref, name in trackedItems: color = randomColor() # Rect @@ -159,31 +193,63 @@ class storylineView(QWidget, Ui_storylineView): txt.setPos(r.boundingRect().center() - txt.boundingRect().center()) # Line - line = PlotLine(TITLE_WIDTH, - r.mapToScene(r.rect().center()).y(), - OUTLINE_WIDTH + TITLE_WIDTH + SPACING, - r.mapToScene(r.rect().center()).y()) + line = PlotLine(0, 0, + OUTLINE_WIDTH + SPACING, 0) + line.setPos(TITLE_WIDTH, r.mapToScene(r.rect().center()).y()) s.addItem(line) line.setPen(QPen(color, 5)) line.setToolTip(self.tr("Plot: ") + name) + # We add the circles / references to text, on the line + for ref2, circle, pos in refCircles: + if ref2 == ref: + circle.setParentItem(line) + circle.setPos(pos.x(), 0) + # self.view.fitInView(0, 0, TOTAL_WIDTH, i * LINE_HEIGHT, Qt.KeepAspectRatioByExpanding) # KeepAspectRatio self.view.setSceneRect(0, 0, 0, 0) class OutlineRect(QGraphicsRectItem): - def __init__(self, x, y, w, h, parent=None): + def __init__(self, x, y, w, h, parent=None, title=None): QGraphicsRectItem.__init__(self, x, y, w, h, parent) self.setBrush(Qt.white) self.setAcceptHoverEvents(True) + self._title = title - def hoverEnterEvent(self, QGraphicsSceneHoverEvent): + def hoverEnterEvent(self, event): self.setBrush(Qt.lightGray) - def hoverLeaveEvent(self, QGraphicsSceneHoverEvent): + def hoverLeaveEvent(self, event): self.setBrush(Qt.white) +class RefCircle(QGraphicsEllipseItem): + def __init__(self, x, y, diameter, parent=None, ID=None): + 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) + + def multiplyDiameter(self, factor): + r1 = self.rect() + r2 = QRectF(0, 0, r1.width() * factor, r1.height() * factor) + self.setRect(r2) + self.setPos(self.pos() + r1.center() - r2.center()) + + def mouseDoubleClickEvent(self, event): + print("Good News!", self._ref) + references.open(self._ref) + + def hoverEnterEvent(self, event): + self.multiplyDiameter(2) + + def hoverLeaveEvent(self, event): + self.multiplyDiameter(.5) + + class PlotLine(QGraphicsLineItem): def __init__(self, x1, y1, x2, y2, parent=None): QGraphicsLineItem.__init__(self, x1, y1, x2, y2, parent) From e9c74129dfda00b33a3bcdc651c05e64d08736ec Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Sun, 28 Feb 2016 13:01:46 +0100 Subject: [PATCH 5/7] Corrects a bug that prevented all references to be found --- manuskript/mainWindow.py | 4 ++-- manuskript/models/outlineModel.py | 7 ++++--- manuskript/models/references.py | 26 +++++++++++++++++++++++--- manuskript/ui/views/storylineView.py | 3 +-- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index ab2f3dc..5fcc3bd 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -840,8 +840,8 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.splitterOutlineV.setStretchFactor(0, 75) self.splitterOutlineV.setStretchFactor(1, 25) - self.splitterRedacV.setStretchFactor(0, 10) - self.splitterRedacV.setStretchFactor(1, 100) + self.splitterRedacV.setStretchFactor(0, 75) + self.splitterRedacV.setStretchFactor(1, 25) self.splitterRedacH.setStretchFactor(0, 30) self.splitterRedacH.setStretchFactor(1, 40) diff --git a/manuskript/models/outlineModel.py b/manuskript/models/outlineModel.py index bbeefb8..a3450a8 100644 --- a/manuskript/models/outlineModel.py +++ b/manuskript/models/outlineModel.py @@ -836,15 +836,16 @@ class outlineItem(): return lst - def findItemsContaining(self, text, columns, mainWindow=mainWindow(), caseSensitive=False): + def findItemsContaining(self, text, columns, mainWindow=mainWindow(), caseSensitive=False, recursive=True): """Returns a list if IDs of all subitems containing ``text`` in columns ``columns`` (being a list of int). """ lst = self.itemContains(text, columns, mainWindow, caseSensitive) - for c in self.children(): - lst.extend(c.findItemsContaining(text, columns, mainWindow, caseSensitive)) + if recursive: + for c in self.children(): + lst.extend(c.findItemsContaining(text, columns, mainWindow, caseSensitive)) return lst diff --git a/manuskript/models/references.py b/manuskript/models/references.py index a82475e..ac29a06 100644 --- a/manuskript/models/references.py +++ b/manuskript/models/references.py @@ -471,12 +471,32 @@ def linkifyAllRefs(text): return re.sub(RegEx, lambda m: refToLink(m.group(0)), text) +def findReferencesTo(ref, parent=None, recursive=True): + """List of text items containing references ref, and returns IDs. + Starts from item parent. If None, starts from root.""" + oM = mainWindow().mdlOutline + + if parent == None: + parent = oM.rootItem + + # Removes everything after the second ':': '{L:ID:random text}' → '{L:ID:' + ref = ref[:ref.index(":", ref.index(":") + 1)+1] + + # Bare form '{L:ID}' + ref2 = ref[:-1] + "}" + + # Since it's a simple search (no regex), we search for both. + lst = parent.findItemsContaining(ref, [Outline.notes.value], recursive=recursive) + lst += parent.findItemsContaining(ref2, [Outline.notes.value], recursive=recursive) + + return lst + def listReferences(ref, title=qApp.translate("references", "Referenced in:")): oM = mainWindow().mdlOutline listRefs = "" - # Removes everything after the second ':': '{L:ID:random text}' → '{L:ID:' - ref = ref[:ref.index(":", ref.index(":") + 1)] - lst = oM.findItemsContaining(ref, [Outline.notes.value]) + + lst = findReferencesTo(ref) + for t in lst: idx = oM.getIndexByID(t) listRefs += "
  • {text}
  • ".format( diff --git a/manuskript/ui/views/storylineView.py b/manuskript/ui/views/storylineView.py index e682aa7..ac149f7 100644 --- a/manuskript/ui/views/storylineView.py +++ b/manuskript/ui/views/storylineView.py @@ -155,7 +155,7 @@ class storylineView(QWidget, Ui_storylineView): result = [] c = child while c: - result += c.itemContains(ref, [Outline.notes.value]) + result += references.findReferencesTo(ref, c, recursive=False) c = c.parent() if result: @@ -240,7 +240,6 @@ class RefCircle(QGraphicsEllipseItem): self.setPos(self.pos() + r1.center() - r2.center()) def mouseDoubleClickEvent(self, event): - print("Good News!", self._ref) references.open(self._ref) def hoverEnterEvent(self, event): From 2387138bd6f0f8668018fcc98a9883a2a67d3229 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Sun, 28 Feb 2016 13:12:17 +0100 Subject: [PATCH 6/7] Adds some references to plot for the StoryLine --- sample-projects/book-of-acts.msk | Bin 30416 -> 30596 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 48f0092d66a9d1895d5db6052013f211f460da94..0bc4d5b2941d67bf74b5461027eb7ab4d80b0b4d 100644 GIT binary patch delta 28239 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 L0#oMkpZ>o9k=)oP delta 28024 zcmaIdLv-HX8!r5$Ns}}U8r!z*#^w{Nv2A^mHg0U&wrv}Y`NXz!erx>)@4L?6jP_@6 zuRYq6YhCvayo8)NheTA8g@Q(hfPjF5aQh)si)aN2_1{_V1$p{ESU~;#A0D8Y{s(Fp zxc`9-HU$#+Z&)g7nVe_Q0s^8W5)$J3e^=SNy4YCSnKF9V+MIW;#o;fdYbn4_A;C?GZMg~ z8z>8BT6NQUD{DLb>$e61*D;+GjWFInq<@A)vw8aOMPEIats{XnGET_&-=MdW?uqwQ zber{9M&>)#mh=Q7F4XT9uf%n#K97rw6|?wj(}}v@)6I@n!FDVoBd2H?FK7r`idDR7 zTgW$$UyL2}+~1WZ)*hNXRM%b4l_sw|fp)#cQ$3;htg(~B>ZJyV`{5E zu9UE53vx?g?Yu6sFfGM0t*va+R#)+kY{h@@F!0eE%1<^}-^Bv!9>`&?-%NLZ8XLUT z`!v{aQHjfy%2{dnmekstNN|Awp@j6xiP=FgIR5swzPwn_dhq!39mo{)yyo`&e2#Zl z_EEo{_BF6QIE5$kP#tY-->o8i8cN5>8ViOp*p>u7-n0ESIh|^AHPzSZ@WM|O^abgo z`8(Gy70yJlVLc<|8TyH%!+?*v0b4Upx>q-&H$zae(l)QWP>d9?$IH=i$%4%a4-I1 zA3C96vnQ;k%j9-bkkhX&IxuzZ8*&c=v7ZB34s0YF_x8w`@0yWQ2{&pc-( z;JRS!I6+MEt-d8=Qo*pUrMtPF`0vebjzzbE!Cj`cn$7K|bzfQ2zdnR*wkDBO*{kvSuo4jd5hl0Q}wx8QF^1UiD5;BQZr{no)o{ip508<=mu(O_oIAvSW zWLa{>?Gvfx2zbF;8$JBgkNf*&FVbLq&zD5qSP%41&>LH5q5xahlLn}H`=viNmLx9R z&$AaqMXAIE#TfT#Q&c~$m7V-oXG_($wb$W>z;o{=O4NN`!5)9!%`14)RHv*wm_WYG zhXpfC@QYuw<)BNlQ*}@U&hru%bIczdc+Y5wB(bhG89+!mBuzej)0BtvJ2CE)4vGxw ztxU^paihY6&bnxGm*1MJ(B}h3-Kjk(eVnM95w`U#^IZzI0=(X(ZLeV(lZwKf_0Wx2>yIcdHxXELtw|sy4gR!2n^JaMbYsof!=N*3UEf3RwIpHc zs=}hcXKr0=OI*1#&S{Asw!4Gu!larp3x9x#&XN5NfSGF64+uWjvyb3g=r`FP9K3`b zk8J21p6=7`om!(*jkF&Bdj0LczqFv)&o^sM1y1GhqVSXI2<<&K7LooiZ4Fdhd~QaG zT;$Bt+2l>1l@3rNzS)wkEoCf7JDx8J))e&rdbx3eR8 zwe_aM6Gx6|jh;cebBs4q`W)b7Kary@dj2`wfSYH+eJy8CGQ6bJuU9saYvsvf{9Um9oMs|)rd z-s`#nw|kLL5k_=uUTV8h>5E03QzVVaQFZEFfR%z(m+gFedmi?p>-zWA654lsUlZ5W zf+Y!$JxLBXI<$MLwj->WeVomc=9HLy%}f&~L<@+6$R=3_MncI{Ph%@+NS>3nepG1uYLQGTFXcDq%{SEP zenVdwHC|+p^g0g9GM1s5Q81^95>oZlecLgU#krum{x#^uJ!kjZTbU4sAP#KE8SIr{`0ndtC!^6Gnp+^*pwF3o2!E^ z`w5Uv?>)|4UtNc<&t3KK1e#upYf#D<(S3o`&aTkMBYpXUXey^(;ziH#FNQt3<;9M* z6)3>7d06R?Wx*4{l~6SElSwo)dVWsv$@q^yBR)g#U#k+HWME=;r^UQK4Rl{s=P2JF~Rj@m~4f%(uPw zOXHvY>hedgY`|f`R2M$l>DqrwHDhiSrY>XyqMbyM_In3WAHg?+g7Oy4qQmA1fsaBq zPzJ`O+qmgs#bevz_q3^he+PO^Al!NiPu656TQzm@VpKE&)~oE2cwHePJlldyWBx?L z&JIAL@=ZZw zBi)uC_doJ_U*!To5SQ>t$)nf0&vMl2+MMs>yQy+(1Xpu>zC&7GCiG`|!^8bCqXSQ> zUG!O4_B*W0PKb^vrJ+GrRu@@?U2dtVNIl%bmpr1)bIjV{AVw0(PeV;Y$<)!Vz_$#^t?(?oJUYzRlX#sZMVTzf$)_;<1Nnp*b z8StrKS_LZ_)^jxRrc7vQv!PCnQ2T!NJHId>g_@41@5IEWdXJ80V{N4jcUWS z{1r$$rW&YB3OO?+;p%LU0YrqFPDgwaUw2SUrEReMnm z?R(95z$0JhCXURE3qwd57K3Rn=+iCPfD01d`MEkw4sz2`c|)7iv7A^>%)CpKN9Jc5 zPTAUCL`m;&BfD|e^^Dh=_v3cU>!?^Y_y=}$$^rZi2C*o)J8sryxXlS0t-DU$4}07D zXofu4gn}r2&M;gQSx8#?9bEJhWm~a9je4VWJ}c^{imv|Iqz?d_80E&#S63QIAmWRf zz!7|%2|K1a#Jxm!7Z|EH@axOGRoOdkZVJqEcW?3tde4vZ>uOUPWOBCEHPa`xcfr># zA9)GL+kX3z5_B(%hn3`kn1X7l5_P+~Pu_)xil@j+H&1%cp{^6bgvl9mqDrL$a_c%P zFW>v603Q9lO{_?62jGkV0{kx?V4Jc1HU27PxEGVzOjMTIk3Vw9m13_q%dE$WB@aR> z$9hA$#)it+?`XJ{wVT;=lzhZXweEaeK}$-yZ>Kz1a4;inAFpet@WOah_m84Zebc)v z-~ydN1KqKX1RnG0u4{`hTdO6}LWSf?3Y&$Z8XV5d>ihV!)2sg1H%aPvq5!0?= zr>4yDpY`j4a}O(lg{8{ekU~039bd3v;b9zKtxc-aajbRE9${HRYf$QU&va9HoiEN! zzhYOb>XAZ7tur zV!+A%j?jR>0MnfAKpP@WN@erAR3CLgYCNThLgaw2v>rnI`8x!CIuqEZv0@&`xPZ)K?# zGW)i3lX?#L_Ms&o!6}-K*zcnypobAn<H5L5Vy7=<}Pz*BNwhEe-|eOouIAy_kOy1 zMb*H)-OJ%0#?{BfmkPDGvU1@SeO>!?oqyn`y~A>abtr|ztau+1o{Sx|tn6J44<~1c zh3VqMJS_rwaq@=cY+0t$KPA1rxPOYHhT7T)Z^ZBIS(4gJ2GfqQ=3i*0rdj)z7NkaQ z%NaV@l559-f9^MH&U-GDXPqxyy2y#QlXd4fFp<#(LsSmeH3CDl_((h%`TkLuHHXrb z-!PDVDO^G0M>ea4Hl6MNVq!H$CsTg-Tc%aSUvhKiP2n8}P_7N1B=_rfx{HE!Wmab3 zgT{wEsL-N}DVycrcAg6{th#42hY=|cKZ=s-^AqlY86+u$s?4AjjAanVkGv_jEgm+n ziI@n|D{jjAQDSPQ2h5eKd%-QZPTcPm-gaUf?V&nGL4#@A#{n#nw-o#jx5Z@ZMntg` zlw`S(e;tMw4~h^d1eY>O_^7t)UNZPR1o80jy}I7RpF%43IG-vHr}=w%!7U$ct8oOS zQo_uDJ44T112poZ*IC&S_CG&2zuGq@>%(94J28uqty!@28ZWA-75G|1b>!LJEkDXL z-I%`Oylm;!YD%+jdD=NzV7x(EqltjRq;`0pDaZ-&l6q4IDVVQENmTPgK$BnHt_z+p zIb*xkGy@4Sn;>qJmw%;|UuxKLLG$U5iR(QB+-t1Bh+AXFT%NU#Qi{FF2rnt2&^1px zw`)@1p}!+S<>TJ^1wp3x7d%c^DyvV13_V)C4ijuN2{i>$U5#BHdM=hkqk2Yqgsx=# z7$*KAc}V_ikAP_P+3yAcyW5_#Q+bXiDkL}NF;F}(PTjY=$qqVU^AJP3zx(@B`U6k^ zq21r8ihXSpqHPKCZA!aPL)MIpWmHC1Q|@hB z)$4iR?fscRk;v$hv7nZQdVycIwLba@aI^6iO-}MQm9OOdElw%G1^BpAtHzNl7b#j& zK?q^vQxk=ZysASpWiVYOo%T%yh;e&=+2&{j)y4OZSB}Y~-r!J$eh)+wU;mE~s6s{q z(b(6Y&)C;w!f$XtguV=X-b3t4x;&u}_=$yls?O|4T0EiP`UM6Pkp(LM!===>A_^{za#ywP6B>}?YJ=)uPsAkr|LCkbn%8pT`pW^M5Jg$t0#%6u2g$vRSx+cawPi1mGgwvPp2^^2bwz7_I(4Ep!MlXhjX z?*Wwq7RvmUINoz@wL`<<^i`CO_O^V_A}zLjWhbRId0H9nGqMG{o%plRP|FvR{*33O z%O;;MjO}#vyl9esYn&_Bg^DcVI|Pg4?45}~g-#1$PRFfmtK^$jWcZ+hnTnf2ND&^H z_xN!$?Zp&B*T4Y8veG5V7vhS^WW&JSh4-K7yj^MT;IahMry{2DAjW&9adE!o+up80 zh{}c%6LIL$b9C?gnA|VZsnD%M$~?FhRf^)4+}Yp%=tz}Sljw5I|FAAp%pwVC@UJ5Q zUS%}$C=~7(JItU7TBfUi+0t&eY_a2Fmx~N<()^vQf`U!AHb%N@wu#uC^4Py)6-D>l zXs$!JW7%PwM*1-ndYtT{C8^YhEg>ue1wr(X?b!ay#QfTT#tiXVjKx(=Fv9WPkm%QC zm>J|z|81FLT3hws(!-3}$C5swANCb(fHjYP!pPLUlZiS)0-y9~g3vpBNp-p1m$CRi z{S8Ml=`miGJn2P0KHJZ?#T{36nV?Zyi`qAE`!aw0eVgzncCJJt)#Hrh-y+SW&viX^ ziVpsBZzg>n!>Xo+m*td>M5l#FQjHT95t@pYVOV=ug~qj0r1n+Aw=oKZnMD|S88~&r zMvsrJ?_|*3{^4NV+T4Y@I=!Qa1a5zFFpa&}2w zk$YUq8ec}H*R*C1f0_G{f|!*-x&RTcc0yE7A-8$bWbm$Af(^r$}(h%+a!aB~`Jj0Dui<9jAI!B!XN#S-n8LA!isaqB@U?#xV?R|4wUE z8Mg6k`0g0sUy8#mVrHCnAbnl*!3VkF2<3Nyd@5vi`+a~76-Uup01OwSGn)9hYGkX0UYjG49u0k58OSRf3~>>G0c@yP-^yH_(u=9>B(KnNu-F z8i5`mSex`$@GDKE2u34jz(`|8l(_9D12GSbLM^I!kNy{zs!y%f3CJZJqt=lb>TsJn zVc3`7lZx4yx3PQaB7YM2xTa_X>|Z-B8sE;-uhz;^2e$sy9PAl$OB)H80Yj!?GH|xZ z^(J+_M@PsrE%j&<|Ywtm0l-myi%+ z$*@lRSmfU$_$8loSn8aQZAy3`5tOnUh@>vwBp6>=^h=`J5ig_lP^Su`5>%rUErK

    $Oa*WZ1GL3_MzkSF|YhE@s}0X`VweKla{GgRQILK5+PeJOx8@bV#Di7_ItDt z)2UPY@1F9P+v=r7;E|%kzCK_y=DKP3TirkUGch+?wc$wDe;oMyy30O0y0AXRMYVQ* z?d4i|uNMqM+(^O2aF>p z&vImKM!7GAi!Nn>pQQpi##;d}unKH?ehotXgo!YRzCP8n^%e&T^NX@_D1++Q>aEMB zkm(tq&OF>#0DdAFEOjo(ODn{OgrJWXj<+E1sQlu>vV${Ek8;NDHk^Gq2PXzrmajHn zmC58uc6PYu!D>OR8Nx^}gacc_QL=Kst|DIhVEE2(!!}0ddNmlRYwC3T#!uB1k%Z%1 zDAogHl}I?|E4m#5qhFtYaKfqRE#2D96nrIv*6NSs1P)Z}yNRR@Vu-KU`b^@JkhJpo z{~(VO=Dn2GV{0+~^&)7tB!~W`*VyijH;GrPC7h)G;ud$o_Ge~3rkh^S*(ls=mV&Sy zn$A?!9c-jm$iOS_YIF3RQ3ig{emRZ&oDRI~@teVdz;pV0j~91nCyrsh999@K3pdTd zF9o6%Xn=A2RBt$l=iYvliQFk70mP#wKOxB@HZI5~1Nlc2Cj09sGi4+e0Wu-u{4lSt znPtfwnVa{+s86qIw92gt+Bjkp`rm)iD|v8eXkBT<`$gUJ{nL?68GK%FPPa6>#|7LU zqMnc*!RX9AA1(@Cl^WuzPTl#g^7w@==p=rN`Tz;?V`bX*qf*xb%;CA0=xHui!ebV) z9^8VIzH>fSyMs@N6v65iP$Btp?rQUO${GCE>ONDO%W1E=`F~EoX7j-hqxlCyDz~d6 zrB)8&MqSM|yRCWH1$?=0JLC8^xIBy;VBJ8_!SiOFk1q6F>BAjbm?Ux}Qu&qwZCN0i z$_B7RKOT!3otaOe_on=6SwJACYE&Ok;G>|{Uj*q)a-4cv=2Q*nFWv{}*n|KlF(r0T z>99V0B-9IkrVVOB?jc8rni@GLaVVF*XxwuJ%bqdN?wGAE3Mk=LWw9Rm+Mr~jhK zH;~h@LfKLsG7aIv>ns8}ok=mW2XSbEV}KKVf)vB^v2?46rv-5;v=G5Brq{V@`LpXC z6D^K5>pb5$SKx9NRE>S4orj;kvR%`NRBScelKtZG@qR_OR@cMBf5{GP!Bgg_a%%1O z!#uEs)cXF(5)$-JCSnrZmLVOpQZcE>`U7HNJoMNc zo)DVl7M+#GBojA#Q<#T!f;Dec9)f|)K*LeHkt`2GlSaQ-2QDz;zKd4>8*aPt(%6H6~M4lJHg zk(E(#+y|y82rm1KuCCtO@l&Ou0WmX~H3rQ1?y+YUrQ~-%U=QBH_1q9Xo9fBTeytiG zvVG$jYxQ|@&q8;6p+_*KLZSmE!F-v?2JA*P4spcD;aU2F8jB&gFseKcH;L1aI@gho z1`|mg&#IExmDP)KA@zhvLn=k(tHq9GA+Hk0lJi=Ou0(>_KZAvkV0{)7Y`{tjuE!+|4SxW3>045^;RtLkZ*c7Ttgui9s7xbO-j+ zoVQ2bcZt|8y2ZE@w1okm0;;t(Urz^>sc2>TxzmaVgzn8mC|!AuAusZoz}Y?I8Mezq zv%_gyknN=>-zO~R#rSSt8fcGwFJ}Av_pT1vaD8w3IRndEokx(3e}6=q@gH$LHeuxp zzKK;?8G&ATyzr4j%VvN&M+$S5t<^$C@|ePa)!v^8Pqck$Pd@ogVGt0$cP{9Q z#kQae$9_|{z|}5pmHlA{GCl7zq=@#lMxU_C_HIA*bvD?nu`v@XE#cJJpw-7oM&cC4 zC}R#*lmg@A|7$6VEM?5r6DmIZ2jY>p(GCnQC0QB@_JhSxIy#n<2fHe9%67 z$iy2YoxEahH8vVc(A8=Xb@=_u=vl1A> z2VrGd0S`#@2}r@nq(H{c0BPq222k*0esVFjiI3%&E@-~rvIScLvti=N6}e_l+7mUf zfIHMBIk)&Xv3@2@tj`!Y9*A5hos9}W)Ql=)W25iJ}$?3cg(j0l2l|pEBG;0Zc<@WIB||KNdi-8VY#ddQmYHJ8cfnF-cNf&t#h_5B~Krr7kEB01h{K`ipUyzNOQaY`EXuIuKKamWN~C!!NfP zgcf$0V}#9Dgqn+%GYJ{Np|d&tIjORQa7e6Cv&C=foHL!GZ!7S%zWI!*U0;EAsEOI4 zMyCgQMbr0njb{Sn0WZj(m8QlOX3CE$V;!15sd%Dy{L+Ht=Q>LbmnAn%Vg@@b0B(-U zyd@XaGVk6opm2uLnnV690-x}4%)IF^h1vFSaWig8bl>KP%p&yyWz0rGD9xy;dFfP# z(a9LJdk6EK8-=PQYY^NqlR&O-?~{dc61lm<4oc(XsWkz+kmtuOZ^eS)=6(o{A*PE( zaERJ6T^kpkHOo-Wy1^3zJMhh5swbeU?OyW&Ocg{Yw=c;tu*7pafWN_CGZA) zhJtKiQKd)BZ=3fPMcIl)!a1j7R{;d=@BMWcNrCvLL2^g?&M8S%Uv7l}GB1QI&L&co z=?$@!@j8kW7#kK__dN(BocdblCwaU|toola%W0|B9Pm~{2GoZ*8AB0&243+_rS>|^ zM&F^iHt9nsIknP7e7>w4WBKt$b4O^>>a*|M?7N^Q`>k6CPu4wC2_K@rI`A>q2QT(? z(%9=g)4i~2rujg8v|F_SZk?NAJPhri_;<;WHDA)rfSDfa+eUD%)m>(`@4EHfUniyJ z67+I8w-q+R3j3cH!q;`C8k<3<7xUv}-SAXmZ;Jc!E2^7sY*-r>w#39=e~N!g&*8mR zLs27cbzH77!%8pga;wdf%O&%uHc2ea#_nd|2lv>wDFySQCgMNxd+k0GxG!5vO<|OusHxm#5J!7{E^vw$|2r{OC@T7ZWziS6Aq+c3b&Ib(mZl z=T5xy{tmML;9Q#^HEuar92bE%LJMHvtG6=gcu@@JKYwkD`*Bc}4IY@sE!d_gh~R#g zcdbtvVNL9{@Fky1YitVXX-9Tvj@W-~OXiTz1WyZ&{p$vB5{NiU7-L#&;OuKk)U?Gu z%9*0czi^Blmvph2o8Ea+l8|viLtdwNWGDVULVtc1%gcpftdG%6ZGlxF=`MdYZD%px zQII9vAB~+V(o$~c*t%~tqrS)PE=Ry@*uAwpZ(V9lGcMWFFdk|fnb_H2<9_iC7YJvt zj*PID9bp7uwn!VkCNH(;u2xxuq=K)o7F9ALNQ9iiH^)e#^B$Y^=roRNAOR557|g}0 z_de3>0z`|9rG)gyYL#qS$wG=hSm)-%DH3ZCr0!DQad@?(n)%xK?Z_7bsoUS!YkUeqRBu@hr<;k_3W)7^H$Q^Vw`jC+zW)<(Bj#6?sjaW{@%Okp#xns_S#3R zIH9>4xHA(!6|`)iw3_l!5!{+)^zk0TVoUhmnY&hL*-o9ce*bOTKRhbUg|U+;w&H>| zm;-`|6RUS=FejVGw?HK$=!V9>5wK})D?D;bGQN?YO|Z3L#_k{9M5d#e2&TEM7Nwt zR%GgUM;zBUi*{~o<+203!t}X0eR>xr0I<*@FFeqd@`X4i&5LXnQ<6P)KgW#KNYPy< z$65sCJTWB-aT82C8IZFc2Fzqg1D5Y`ejysoHGkc(MJjq>dYHMUI%i6y26IGDgD#7c zNjhe!I6V;5%gQvy*~`k8!%wq}EVXYzlD`7{LRmSX6oCef+l7v_`R+)KJxWVmfZW&Z zI+n7OTL8jHg^|~YJKyq7n(1gFe>Aoy#;l`*RTig|iYFK{?m+N9=yyC(N$|40crRnH zKBdFL@UQ|2yGC4U4?g7R!Ll1o*oUtwK{5rKI^PKdOt9Swam%S8!m!&_KW8sdDi#juBgBc@qao*!q1GhM0N@6aT!Z&lu+=QdFZYPp9O+g;A{e6;_ z(uqb&R&lbW1!{j-^M2lMgJR)D_dC8953xMe)3&2u7OB{>l#HAu41|Sc0c5vXC*lN~ z@jP!0a6HW}(!Tf+%sfK7j3wGMnkfXf;ZYhjx9kZwBPv~;65sw5Ep2ZHbXVZq;JYbZ zFyZ6MFgw{&k7|pGpvL97c!xIBmkmvUO<>2-QE-wO{`_i1O)l<5xVAY-J~>G<^}DBx z4Cee(&+%--zGDSlS)j7C2jpE~wom?suSNYLc~1TZfsUcJQPPB(_IWR)>5JzO$%TGt z9dWnU8!hD*|MDxLXuw)3R;Gjq*YczFmHrr+Z4F^hH#J*_*v(dB2qOHwFG-(jOqKqX zzTdgbbnaNwaY#ZPPha2QZ*o6+9*eYZC@5ZPnvs{@tPJ8Kv64Ti98g+$H_(ul+<{^F z*5@)r`S!CQ{_ZTXCmIPUZzc{@ne}H|5ueJ%JD%~{b0xuauR#=fXAT?YfgImXGS0U zlm{QNqdgF$EBZzo06Y=%y6rC4h{1*v@?N+ooz?j;+Yv+u*veC?^5I2#yU{YE6Fhu6 zRL1e3i=ml|N(R$l7lseO*Ta896*Gf$aq_#l0ne99KP+zus5I{zU8*mVwj{{VvZcUi zOJ_v>>v)l^Z#VH8b(b%vGCiud*|+{i*>Zly*h&wjI>_so0i>%{$+rqHb`5G9BUaBU zkmh;{xH*N_LN~gZ;E$~A^7tl&YX>mUwaNtlFvJ|vGqmr|rE~cH z@`yTdta9(~ITP|X73X0#0vpRJcF}|e-V*1hBioR(th)|Q9qIscZ9w=;A}a>YA(x&-s}q&9bu&bexB0ggykc59Rv}yk zAwd(@jcl2Zw$-k5PHG+ zF~bruM^XDZP5h6fVSPgtE78yuO?5N;-x#A35ysxvqd;%lvM%g*-HIh9ssKC>uVPY~ zfBlQD;_F-~5ZmON9$zMd*qU^l9C}s~p-z@qOo<>4qINjTT{^uLSK_TE3(ewmom`4u zRyr-&w=9>+-O8FW)vxyS$qziwklGDb$iJEMi7AhizaTYsnqDN)9oXLAFE`M=?6zM} zu3QYW}t{2wtnPN>pS_Q%r1DQLUOY`Q1i&pp8Xy}xG zrYi(r6^@JYH|sf0X0__~S||>vb$IdZ{B|pK_6M?743@{+Y}UgJK#xz7?hNTrM=n`H zwY0sC9BFf*x~XPv8Q1ZO&Bou?JK!Wc86T}pFvHzlRhQWAbE=n`3y=bHGuHNJbE>w`&|1vz1(Ph2=azR=# zPX%PteaGh41uT4#c}O{L8;ds+IHi0sYf~!rJi60HaJ-M63H@6J$kmO>g_xFo9T+=) zQjv0g?ZCHaB>|$onW=7^y6b_(o1lO7M;dS1UQ*epW11J!62t~h9-&ZjQG;-IBj;;- zDOladE5ae09C#aP8ew}9DfU=Hv?V7S(*Oe77hWqm1Q`jVR>e$nA**Jm`1cRMb53X< zZy?LCThnH>!duZg1?wEQ^9xl?VGA!$-MVH22eIIDz8_J>jHWy$CzL7Lcqd z;4@52b$nzkkYU|~En;e7!5*GQ`@S8VyHwHxBk<9mKhx2-b~PN$Ln!^^FI6i-Y)d5~)t zBWO8GZu$U^&VHGwKmM$X{$zT68>q;Rj9*Ilmd~6B&T3th*@vtu#+l4uqoe}03QOOU zYNp@UAqGctx`(bjFfM2fg>1AqMAiSygi0NpSXU9A(3$>W6yTCBwS3p?4cfVkWO7(u zgpyhJE$%bVw370azMQt^P+}0^S&E=W6C=#oRH8|?$n7+|>}1)66VM#22X@m-^*hu* z^fT(^f*2c2A%dM4g>JNOs@GiGH9fhAb9B@QmftC@Mdb$6Vd#B#+PYATFAX0z`BV)# znh6#v6>s$UN<$&-eo%VppQ9)P>Qj%_jw^bfAG^N?msj+KTil*H-pH+xq7efB7FKt3 zw%eKtu%OBIk=K+gN3WkX0N)0Z*b^z0#3Z7fx;p*e8oT(9naJpVwEv*9!YpreLy2xO zI3J5RbnW?};9t;Pf#GYuUPs}z<*;XJsi+7U@s7}0GVfE-@wPj5H}3{LAk?sP4z5G2 z5;y7V*}qI;g}?PuI-BtgiuL|tYI8>CX9xG$<4v zwXKwpch_jJ%e{#_TT6zIxrnF!47I=ER_aa6kM20lWHzQAge^h=)uU4=b%ch1!|CHiGS(J>U?o?R2s5^F{3^A2KbXjD6UDL5Jr`Ggb> z7KLE~7c){0O8-5}as)VXpDMeW7kQJ@ep4aq@K>sG$A$%m1FY<=eH(wIJ$w~qJqeCu z-1)(-o{oyAkqzjCcD=pbTkI6ZGfq-++H4AS1B1~W9Sy=u7P64yk>0e5Zmx6X3;^)xG^AF7$xLW-%-*dPvMHpAmi*bNA=WPo{pW^qO-io zgIa~q_phM429;BP28VlSsaac9k-qR>sbB-{K-hjPKqE6qv}Nub2|drwNn$0K%fW*= ztSjbQXIL>sDfVU3ul^>)Wmf%Ge|S{r6j_@;2 z$39BrNXY!v^n?}uoz;`|NDv=&0PO41g~L5W^Hsw4Fm)#qeK>Tr1#zQx#)cT#h@y(X z$=T}&fEJrX6IB2=w!q4{F{RX7s4OpCCHT84x%7L39ky}fatIqUnOmp4fFG*RS;dp* zeSj~qh{^29(m#?3bIrvK zjVX7j%~D3eXndFLZbYxJ`R~M_AG7fU=^J1C;wB$Z>b9>}{4QXN1%{>Gjnt12m9j=x z2Rp|5o9M8hkQTF=qp%sHesY?pz_SmoKKHI7;2|~b^7*2|jpxA6oN9{ICpav48S99> z@ti)_-7?p3!gjLi;X6|&Y?#7HY_1FdUO}{6tF#7EF=BO-NG*3=TCMR&3i62W^iA^S!#=A*<3dhVyDS)U>y2`g3sFg-*=yvIH=2b~ce4s|XpDM) zsydq|cdbw6vpu5d5?4>3(l#?S?+1;RU9(_!jrKydcs*1SQx_c96%H7(ozwgQ2AZi} zYVMJdZe_ZE_(voXxpOp$8xo5;U{;bPHeoO_v_c2%ix~#atM;D zohD`cn$erzW{y{1A5x1bu6MFA{r6X25}JL8ru~T6c)!z2{Nb?jaetwg53WMur~!*4 z9GCV<%bqrVN&NOcl4wbPV*r8&GM=+?7iJJv?rrA%lph%v=W7U^)5Q?zgQw_<6?Zx7)2-cUo4{3t|;2AIQOBpE$V1C zgiE?+0DiAO+mk+VB+|9gVg;qiOGi{>+#4(an41qaumvt- zOc$wK1|ZUieBd(CR+V3~oOR-hE#Rf+WVmZaWI=Ho3$%^XKIZu%(#4UeDx*c+R@p$- z#}x%LEy%yG5lnSe=<6(Rm(<+b4lg)q1PT_G8eKiCL^(y1?qq0bK&mz?pK)$nJE;h9 zaKO(xDQ6Z}_vz&qnq(#H9_AFMd(7c%z zaCr7K{rSwRTgWEM3~zaCZl`UG3%#CLOXU`dYFwmdTX8*L8YGEh^2axW<=bvl9naH) za`x>vbOOBo)H%8Cn(jJuO)5$9X{56*&RUqLo??_L8>e+H z2dVm<7nM#uhk4ThtCh4AwWw5E*|OObKu4HZxg#m6uZixx^|EKGrE=HQ({_ySZ|=Kv z9M`tjtSU0laJTurjy?OYy!OxETX}R^H_iKs8QA$`_xCX%o2=|>xCMltTj`Fwf`L~Z z6RL^35k8y=@F2;gBSp^U0vkh;2fCaX#`bznw*-Zi@+@88YLcJ(2z^EbsWpG7&1TkY zM)r|GG^1xVpg^l!RBM<@Kj}k4NcVguKQOdg1ts>*?37)Q8O#^E>Nv9AUZjD#G3wkA zOz-Uy0-C_1+)7l-)28v<7dxnRAT^#Q{^zb28e;k#wYhTmTR<=0d@kW@VQGqIvpG7f zb*`J-ds{Uy_d85;=}yGRC4<3Pl}mnU4Nkt-$IU#|cL};~b2Ov!w)_a8Pk6<(t_KCz z32ZXN=1$v)DgB+`H|pZ)maWnXRdY+~S}4Oau@mGNK? z0bX@!_S`=gj`YvBUNwV|=}95QC=6fNt=CcU!HZd7*fR@s;mB^1Z<DDO>cUfvw2^KSw>QfQGgn>bzJbDBA5u5{F}?W2l-dC=Z;tK7HH zST_3pgKf<-tW2JRE$X-|Jm|_(>%J~=j7V?ccD<(S&jqhgT}knJB`^esmZp2wDf+p6 zoC^RBFHl~oE&u&Yp~?Bfj;$?DV!}~jwXL52@Vp5?KJ8=VX;%eDg`D7CyOq48%H5iCQlFsL2sZTcl==M2y!!ovV5 zCrcUM^JY+^AzeajOfe^>T9HC^Xz-X6WHp%*KTW9yYNn^DzRF;nke*i3mbRrNRCf}o zoP@y$wTZEtTtfU)ng~wSGa6$D>X!S0epCMLbla}F=vzNMu+No%+ zg<^HW*o~6vKQwUTA??F|%^MOm+5H+K+5Skd*=CQ+B<@ZREfv8&y_x#=S4!Z}(g~-0 z=xjAAa!X{Ayit>M>uC?FlWghb|8Wk)*odUTvFAFWE4%iIEs7fBRrjIk3a6Pk-NyD5hR9fR}X9@7YO{JJ=A`kg^&`cGdb1X?ieu- z&>qN=M5|SdMipXnAQb2R*FVp1u{L!(sya9``W$H$H2dJrV|p?CLUVSCMRDkAv#+c` zDmSxbF+V&D%#_fXsL(GV*Y zzi<<=ex>*5mY5SA|GYLT*(*(|ct06yjO#G!VNFd&fK?psU5THC;QgfI5y6!yjDi9s zktqE&f=otXLKo$a{PKvtbUOKMMRE6u3p2p#TesPa1kbNZ5qhD9zm92>so%*+IyHD3 z1JW&K1`&5?9#bYB1_E*9zAYkCT=cqF*?vjus%?qpN0yC1eKU4ejN9zB*e=S%31 zHRM#4IJ7d=m)kR4W8yUE%oXWHvGp&O&$a?q8oREf>kH;s`kt!pt3*RGTzNjqrKETC ziwG>EH3DuM?r%zWlZ9zF#j#7|Et%O$(dSzyt4x$Mr*2&*;a%F zx~dE|F{@ow?jwp<__8r{6Ju$9V|nV6_0X?6PSi` zi%>B+VzTnw)*ukQ$mNX~9%j2>gL6^uVa`JzwdaI~Bo1jY@2@nYG?t$R$?(Xiv z9fCVI?(V_e9`wN-f~1e(V2p zWfHPhWp@3l`Ke6jt~v5(@k2((&9Hh7gD;e;WPoI-x@GhQjnu0eO1Rwst2X@)PUUZpErry*~? z4@MkuUfXTN=ou`}V_y#Hnx*ener_sfn9-Fz58~&|*20ETOI(>+y0(3V@@x_PUND-p zf0i$1RX(0A|5wYexftQ*@^I#x;lio{r`X zBRNzYn*l3s+we>oNG7^pIgL|Bl`d<_FWR-yuEEvGi4LC!W&cM+iX@ON&o+XkVvH zv${&glfaQ3HiVekK*mixgNL0}tGd!NyZ}mBIiG#}D ziNY@YdVOa5)^!PW6`dW|^tuhWbZf?H@KEec`8=%Ch;|~&6M~)*{rJRG?0=H=J z{$He!KT1p27oV?WzFx8;`P1I1X1>Q)(!A#<*k+|dew|ss@?fT^_p24!3rO6& z)pWb`>Nm^|!-tyMxzL4TUtgh=V3`#S*uN(f>u&eE9upa`8qy>g?<@>tNl#%i9obx{ zsl;((Fc+|DjLpI`(r4_FLcec{(!-Sj>Dp+-&cJ5JbXBq@Uj}>MsI(404`TWF2|C;i zD}1z$ezI{s$0Okbi))1q$!~WQfRZz(nMZz6Vmzilg16zJJ^g9TQ;mdvgU5&P(0IYH zY_|-yZ4fxE9wyA)wn@ouwt@g;t*3zx5TOm(EUmlxh}n4euSQi8cG~N7O}-XKPqi=< zu^;GiVD!?MW;7~mOfH=S=oU14Rqp@L9i;3nEkv>Ek!Ggif>o<5QOOYjM2Wsu%y{Kq zYPh%uO3dD4ILsX5r82R5x=eoyLWt?P>IVYeph%?Ll>W9ZUy_|}15gzHeDjcI6Q_(E z=Vy+ORnp$(n5BuN>2_yE$Zq?z(;jAAycHTh^*keZb zjXjf@l}f&r<5YMuA5stkUg8o3b}eD+R$xO$hCy4ET-s&1ujkUh$VD*&i|vB2zKtn6 zs;}oJUcvY1XD7WPo*zUjth;CEm@pqz=(DTD^nIA&in{kXYO96WI9@j6j$eFcFmYuH+2&fNPj?luM$*V+T&GwpRQKbt_bF4vrjlP2h}E19$EKU0*t zfB@E;T;+%cyK5@1R_|iw*CVpWi9OE-)*+R9xuh=K!g(gZwjqmBxM@*Rr6bgxIZ|`m z5@+zvus?7}g`ADLCuljA8|H4iPHfK% zykaG5HSa3WG})Y5V$ON(uC)TWy%po8HdUFinMl>M_95OpY*8CpPtZh4d3~(wbz=6< z*)NMl-m(u6Ah|!JT6GrV$g!j3w{>o`&0X028ByNVY?8w6=NN%_rz4v?ooVR zJ~xi03i5#m$u@0Vu4j053YFj7dlH_QIUE!V^wq$}rMYHy%A%xpnbDtmvJs8VMz)S` zP-uvmbRwY0U*;Fqb?mS4K>9x&tI%nuJOWIi8`mo-%T$EJsU}%9{t1x||J=Md`D+qZ z=ys)gIpe7Q@gq48_J|o(nn=BaW`PChQOaKJiMtwAvwsplBw1o?QiTZrTVctdiGT3q zXF~#h5(W*?s*kAAq@2YR$%~hdO6U%y#Xqu8Wf5u+kX}nqg@AR!kMz>GU;JsI&GDB0Sc(n}Xilqz0AQ zy0hiR17Bs^mSrbMZ;J9q9{I%==e$S2&VTtn3`amg9=m zdT8%OhVk$(!?RYU6(x%nF2KVH6~}r;>+S)cN?}@x;E!CI>X4vQH*5nRbpo(wW`zc} zsR)+;I2(tTpl_-``us!DSyf;j!6Ki>5Y%72oG&}LiwOA)C99+UyM4O3zh8RXF?o|1 zY4=#N+Fa{QyS28WHB0hfrbJWDLg8t8Z)vfTP?&!4fomOa0AiZ&ggQE@5uw`PWk56l?BE#LR2yX^`z z$qz{)U51P+bf<^48ACqo1fm&5Z&D!R(aEtZ59h>&B{rel)0tU?P(@3M1t_-$5$7a@urxp!}%(PL0r_ z2%!qESjF|#6khhV_aA%dHi2SXg?*AT!U|7*6f_hnD)VWHgO3}W8{T_~^hsgEpI=KD z0?6fp_gkmkFUl}9pI3m`(FsYkW%fK5jn$}##=R_J5pN6(o-FNrX7JanE^DlNKhYDZt2Wh*kV+_$+$4w1c(ShWv^8T+A zYqY@_3idQ2*UAIb4t5w5NmBjez(nS%=_eeyDHiOULX6WdC+BRs4sKLvCErw9vw6>st`+2x0ecTJPJZM($qQ5nK;XcTBP!z}Pt zWb+CIzgRHAkU*@p49DiUl%=@o4R4{|>-|^!kWXoW9Y!^}lrKk3oSZP9 z{*DG8z}0Ec&rrns{M(h{Rd+@siEG6R?xy49M}bu#@1gQ*)^BLZ%81m=^DzeK+W%Gu z)GjaPqzpA|wOGfc>rkmR;i-6EC~|~+8d5vlxSYlR`&bI#QaecH&=Zq<39SXiD~AnX zzwzT%FOIJ2heyMyYgfBzm(t%uv*u&p>)=Y4LWj+x8nwUGIOB6%9J70O*nnWGlm4@d zc(p%thJNU5NL;G1RSpvd%^6syD#Ad$zz!gd>mZI5Z(&fId9mHXo_MouMt9|;&i_)^ zP^!rji2DxIw3!SeUev?d48cm>kGw9lJ|cYbo1kT~w6JY@`w#1u4!cXcF|dysS8uuC zNnT(~y{ya-9-c$*v`Q|k!@EU9zqKYS$iY8HH=+ zT1k#X z=u0vfd0DYbssHBf>B<2+uXcO`eBkaMC&o44c}r#r|D~}ue-!b~JaP`$o%zxLN%Y{V zeio9P+iY4&n>?{X>OERV*mSy^>)+q zr5he?28-uKDX-zV6$kgBgtkl)HB2V4jZKLjLZZ`(X<6TE*>dRf3Q98^S8$98zHo{V z(n*fKNcrl_ZcmoV7jgMyB;Nu$=fgKnx&J0NV<5!CsNqCxi`#rulokRISj;kqHB>@Q+eZ42A zI+}WA3C;jMqY}e%-OAx)*(Erour@%B?{G?JTxFn1qW;#Vb=P?4BAAz+4b(B8`YT*= zBfwrUiiugiF%3MB6>y(c&5vuu+O-{dy>GbR)?Nfo)6od_L82Wi=9Q0=j_`3Vq>DH{4AXUXz)i)|bGFROuR#q5P3IMdUO9SX( zB$#f)0;o%YuWuxv&wDb<8~X>)V0$XFEXLB&hV^PWdZFH+9bxY=U#`(ohuQfb`SPWR zhA0Ep&4<*IJqZnEeS!hU*@osBq6M;1MhQgO9J=Z=r71PIEkvd*i_EV#+fy7ixx(bL z!+LtV;b`BKQNE7<^9rCuF#u3TiljDj;zGhFCfobIRZ`73m8NyOjdZp~twe%Ii29#h zo7JC~qftJ;u)Rx~Koq0TB@9<0uV8b{dSBLu%GO1ZxI8&JnBE>;h4859#WI*Bx?EPj z`0Fu5a3%B&fm5mAxo>7vQ#TWiSFD!O9USNDXJk;FO4{UZy7VrMWdWg6VibnT??{}K zHi<~pF=_7l_(|7wWXfSW$06GA5gkoDiamx+EIH)|zhRpuVq_7E zpE`8i4-uZb;Y_9!L5LR9&An!A_9qh(zF?6 zj+s=daA*(0V%&0Pa1#l}rTy*2INO562*?|ueTrmZ2X1u_efW92EsC0n+BVF;Y0dg- zE*_pyy3=6?u>J`oYS}1dDpX&FIW~x89G@h^?pBkg7`u`&=KqT2qVS{yj_^ZF40R5l zU_d4(Ce>Z2*)mAdX}!#Nv+c0$YGHozxhpSiv_EdlO!Rz|F%@aMBl4x zZvQwq!5J3C+&GhOeVR=Z_XwgL2{G~z=UB<$)0!W?<5T^-ukuC151M2Lv{9((pR(I& zl&)ZX@z-sQ?Kum|yNS9H-Rf$&&9yPmjjVKn6X+UmOg{E0w6XKb@QrMECPLQsR1Wu1 zped}N;Qi_(`xfSLX+Mgw#K-bvg<9I%Bq*C0HSwJH$fbt|b*XW5?=XPfW7&iz+VU*q zWA(ulC9k<5KYFv0j@7=-dh?F5XE zo`Y7k(6st^Q46uouhZK7W-mP`{@nD<#Qh+cPrLy_^^qs*v!FfHk=JGgC7J!ojy?-d zIyyxVsXQBJ6K5S}Ra3rdw|x`8ex2fK8L+!YIX|l6Vlw(iOcdBu=h?#R3&GtB^=AR(1&VL9B- z%5l|q-q4iN1f12dBG||n#+7Y~g2vDhpAJoiQ$Pov`^2l)1OT3sv1J7Yvs@hnK$)}y zM>oH3FLjAX=-PjW{u`+8+>I&=mRH{(s6;2cNGVJ8>rrp|fsuJ2et~a#6U@B9DN>2| z%s*JlS!YUP$*s*m#L@gVnqx98zw~Gj)()G}P+WT$$<8J6 zT{mTMQ%2*w%ls1TG#VOu5m^5Uct?UyRD$kr-umXQvWK_zgfz8uhg)EzryF`)?RCKp zw2eXXrq3$D-^*i7f4eW}b>RD>({MmBepiYA=2O`OM!L5BBV zlD{->b6m`5NGq7LE^K?~4sTUM7qjIH!rnvHLm@)C7$RNd#yfgP3iG-Ef2WojRdTis zrlJ5xN$ygbZ$)}9ZYLmd_96MyQleplp`6k2Hy7IS&RH4)>LS%fp}3oVdS3yHqG4n< z{7wterseUqBCjl$^Y269&*G!~yJ@Gg+H^~497qXtisawOMp?>1R8KaxY>+*zD~?3i zX7yaS;-@c}7h+{9j+hw$-owwXCH8NTrKT@c_>*EqwzVg!xvtCh4PSdNP@*z8m%+M2 zDXrR?=r~P`kSiev_j{v4I6^h^X>hX0EEdum$68jo5Q?MH3$l5wagy}%HdO;h5FTx5 zOHEQ1DMaI}yPh!@L8a`@h*lbt9nK9P$rQ_l=mCjNkUBAXdW;L;HiDAD;0k+RyqEcG zS+BApq9C@g;3Aoqr!nCXKw*JR?Xe=49V@ABQurX22G2U#@ppXc`>F@O?64wv}3!pB5(;@DCz=Rg_DrP%rSu}uGwpnK$KYPIC^O2lFa%UNh%r&`dQ(OBT zaaW8-Z)lsj-joDrD3?@j){d$tvqnuytT0ZL6<@9wd6ilE4e?$c7`0?P z(h=MCpBYfx*a{nKJ$t7H3-c2uBU$~=g^{l)TAca!%|{-3Ndz&m>JElBU37{Y8g8O| z=WyJLlGxv$muX>lWiYWIj8h@33Z3xk-TQ57DRm;_-Fj<*C^O>QKX`_YRyZwB!yqrM znlV%ZirVm>n+0g&0>bAl=V}8%RWYaqFhfeQRTtu0`#Dhv@;=kYv+8R?qR9`*>~S=D zm1xw87?u(PiD_poiw2rGW^fC3h*r~=!cnEG8H6KY?4gfzk0%>| znSzbK74*-)HPkHjnTu$X%VF7%B7KP^tH~xMa2RM-X!rSOwQtDF;L+YDSDTqvh&b4a z)>&+!d#dRLqC>eV_rTACj9PoM{)C5n&t5EhXKTJd+Ue%EUx>MlvCPp_!xYKZ#Wh-8 z>8*bRUtD8Cv6^p!2rla$;HJzYMtu&CHy;@g(*JpO5kp@2@ov}4HFTTNn@s&MERt$K zC>^abRvTGe*Jdec57PBocI&9-&}}#%*(7sb0xB(X(_V%$smh^iu+GW{#UU~9=YE-k zqZ0{%FAoFdA3h{Be!68{NLbOa3>CfCG>XxF|L(Bsl>QVD@aRV{-H9k;mCKL_Fg6fT zsu}0FzJbt@lE+D21~^L%xz1$pQm*3=ir@Hwc`8OP3K#wDd~BOAT0Y)VT3=5k9gQh` zg70O{Gn9O$NT7yEe0j5SlV1;P-^RA8DHv#ddY0167Yr(JV|&)xPhxY6c!+IN_WXX? z?ZfPfG2EBwmO4ej?3}sfrhRkYbK4LbGdu!p;ns29a%h!NrRna0DwksCxhJC6{t#~f ziOP6f(T-C$hKM;Rt@1W4)({%8^IyyHAYKYyp-5w;`D-5+lHT0_Vreah{~cmddWDx< zZD+0CP)O-VV2)8$FM(URB3V)uQ;M3sr4F^1PcI@0GeZf(Q^%=53OJbO+C2)YM|shb z{Mh@g>C^#?&Y)EP@^*l!?Dg}!C8Gy`0mEf2;$q0UnE4^C6v7?f`fQg)kE_tR_-WLd z7{8b~IKTRQwnXph9GMNa;RRjc$HhJxI%#%nwYP*;2n9>5t~xv6cf zyo8cPSQuy}op%%S*A_3}wPBUC>G2dBd6O0GsXGg$C=U*PO^QR%$}yn)Q%es? zs?ugrkF9sqHpe6OIX)tN2Cq#&IWqTGNk`>X(RZUc>T=w-k{~}iF~}wt6U`cY$PSX! z0zy-J&DVL4NngI6?hneBwa-^>GK7AE$OQ|X2|r+QYm322I5vq==}cD`F_k%NGs7XugU z703Z0q@M^FtDBXoJjA%soc_HK)N|_pVvNCDVDr-)?z42u;olQ0nvTFDJk)Q9ULjcn zepM%&w5V6KO7L*4ouj$*u2>0S*>rky-g4%BR7G9h@N|cDi62R!AN>Whq}#GG!;bQ5cqk(|XNwR>&r%nLRmi*Ce|3tU>RiJTjcv1{Ce z9qD$bDEmIGkK?|T8rMdHnCAK=63-^iH7Cp&&FiK1AzC%fiU&ykOd22i{OCM6&=~mm z%9%ml`=i6&$Hw_7*y28pqtg$!XX*7?u(QM-G#(D|W{FCFPZHm#>%$CpI?+t@-_Ui= zi*~w+2Vqac4hA$^^O23*`hsFH;couqPR{CG@B>vyT z6{l6PQJM4| zJhZq>{oC=4W-6^4BUkn3`7h5MgEecnw=!y>u5r#_>3)D;D=5(&DQtUw+}j!F)y}}n zTvIXgi*O&svdZ*>4_}Srn8SG}l54}v;gBh%rvxF)+N@t&04NCGVzr-{WRximQH6WD zqHX*KS=D9@uhjZUpoE*qGZfl*p5%J^EN^v;W2CN=B7MbJS$Z13iVTD8nQbAvSlDDw zaVcVSNCzba1BZgyoSnFKSdv``1ZF@i#U)Li4}o>4*5o>uJ5djby0iUV;;=y zr~6ilFPXx+7(+QDPxps&-af=LFqC9gh>NQJYcbHJ_5o}{=vFLscn3^oS%mmZRG!+o zBYdv$i>t|bPRc`?mo)bXj}6P!#HXx`kq5usPCK+%T+Bt?3)YYAO)h$(zAC07_fjM} zzyaGU3jE1Dat9JNYu^}5_VoLwHd4of_25?Jy2YbD(+oy4{6#kw7^><3hZL`qq6{e- zUx2E2rDM+Q19U*5)^o1&1KLaRCZC>|i=RhXUf)~-Jv*~=J{fK&m_2`LY|uW9tQq?5 zBoO-U%oWPx-@kf8m7)mVLV|FpsR|c(rqr_bjr6aNI6_L_L6$l~3;7&-4i@hA3TRRW z=VBfC@O=Y+;| zZHzNBA!@H75@xMs{nLIg75$Spj{H?lTVXe!f(VO) zk7#?w21? z@MI|$98LQ>(kCAW3Fg^Q23-X7LVY9O9U+;iV&MxWKI_>a6PDzb40jhQ^=y|C{o$uy zz>}qmDp^JbXDlwpnRbhS$2Du3xWk_xOCC;CQ-uqYpF<{d_dR3F1RNjV2)eWmA;s`!cE`+vaxm2OgnT+SD>@K^m>1#-K5V-XDCK8mq1;3t#Z`>uV)e29GhfE?QE4#_0BRf{LMre14o)a->M!oy6jjhjnLElG3FUo9i9_wkv66F8WNy#!ytWDL_;vPf@td>`j6E>=z|9 zDLTmtiCwj3alKhM_Ru2AcA`%hGTi(sgFfIwEE-Bdmk_m2J|=ewo;kqqKl2<{22tJV z3gOrho`)3wP%5eU2Gr(g*Vn9^C65o&Vnh+KPW&Dfoh;~_uyx{+hb(_GU4tphqF0(7 zm8&1pkd#{*I0(s1uBVMrW^viv&jUWg#dHm?hs;H`??Ho&@#@e>=TZ!MT9b*UI#%}C zDzNgx89|)WH*JpyM`*O$)!b^&TnV|AGL9+2e3ifX#9TS;0GG}se}qw)pDrj0f@@<4 z+>2h!VZ0S9#^Kcdd6-dq<;hG7CN4|p+oiyW z2yUBFhvtd0xPHhn#_s5ydv%6V;t4B!P8sqZ#h;Z??XgqirS#xpRe9bT2ME5}L9d4d zYF~@H=gbs;Wf@O$;@c-@C8~1+i@juOxIE(p6T*ltV4p{TqKOY8#|ta2tsLQE-ryZm z(TnyVDc&b0{aNku#H}JmVqBqgFzt;G%sLfY!kDLNI>$0L>}wNwD74@G_bh`XeJ7|0 zLC_qXHP$?@ILTtL!|zb#=I|ar_oD1d4HQU#^JQTQk*^yRLz&^KeGD5W&Sm{qyNs{D z#U8^Oc!u3`*Vx0AFEiFAI9Q<>OY{jZHbP6yP%}lDu9|t8J=|Z)SXmC&tH-K+_8xK)JLx>i zoCcwP3nSfwoKOhZ!rQqUN~GtgP;6Y`1%y)oZu?L^cBm+tmlqd6WkCY1v>%G-Un)h* z6ybdiqv+j=y)Kf6@$i#g%*nqLM{jfAi7tW6x zv^DP@0jg`cZ>+7o&aeo3U#eare1F1SDn%KMef*K4)?teEpjhdiAE@Z+&XukVna;Qe z+zBB$MtibIIb{4ovhruS_k9t5R@Re1Z9doRw}#GUct+nu?JI;Ti1MEHk?#b}6j4N$ z4HssX$k9`W!Rz7veuKR)Vp1gF3FqPn8+@c z=M+wna7#?p+OdoAt>4sRhf{4y#HH3&2Ii*EIHE=G_}^78`vAw+b5kucyY$Bi-P&gNizf?x(tl6vFF^5! znwvdZR3!2r&pnX;7tBez=*(C3D>MWI_)D^qGd7@hs@yFH~}zfxx7=LiD&qYJe=CI2E`Mj7S6P$9GKdT zWkRk^g~U%Vc2sbld6h4Ac(_iR8pAvtz?(8o_qcXfZoCT_TUMalgb!OD=-oUE`y(FU zVPNFxmNs6_D~EZJ16Lp>1lvEf-?*OgQCzW8Wt+VpC&VMZ0NKVsainT4M6yHJVFjSz zlHrrZ>JbklKy7+dc!h_fn)l8Tl5lTzLW_p5gh)MIed~At&Q~G&e4Zjse)Hlmb_#ki z>?6h{^Q*895Bt59pst|_!vO*=(Bm-F#eZ~>u$lD?D`g$7XWyw0!WRBfHrA%S`R@|}_y>SgyYnuEXNYDbVbXyK22e~q>P z_N&#PEWd(m;WE!aFG}Z6_8){W`#;Gu5KDucn3X>Ms}x8mOz8g)e3sne#{ECwv*a5$ z_W!Lk?*B)Bx^w*RIOG4(X?J$y|C@Q1jP5~4`ah}vzuX`oQ2xW9{l6ywvd1aez=I6- J&h>v%{|oM%VW$89 From 647563df6e078eea5b305c70feefe7f059935192 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Sun, 28 Feb 2016 13:54:11 +0100 Subject: [PATCH 7/7] Fixes some issues with the custom ^Colbar, though behaviour is not perfect --- manuskript/mainWindow.py | 18 +++++++++++++- manuskript/ui/collapsibleDockWidgets.py | 17 +++++++++++++ manuskript/ui/mainWindow.py | 32 +++++++++++++------------ manuskript/ui/mainWindow.ui | 8 ++++++- 4 files changed, 58 insertions(+), 17 deletions(-) diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index 5fcc3bd..ed6bc4c 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -322,6 +322,8 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.mainEditor.setFolderView(settings.folderView) self.mainEditor.updateFolderViewButtons(settings.folderView) self.tabMain.setCurrentIndex(settings.lastTab) + # We force to emit even if it opens on the current tab + self.tabMain.currentChanged.emit(settings.lastTab) self.mainEditor.updateCorkBackground() # Set autosave @@ -406,6 +408,16 @@ class MainWindow(QMainWindow, Ui_MainWindow): if sttgns.contains("revisionsState"): state = [False if v == "false" else True for v in sttgns.value("revisionsState")] self.redacMetadata.revisions.restoreState(state) + if sttgns.contains("splitterRedacH"): + self.splitterRedacH.restoreState(sttgns.value("splitterRedacH")) + if sttgns.contains("splitterRedacV"): + self.splitterRedacV.restoreState(sttgns.value("splitterRedacV")) + if sttgns.contains("toolbar"): + # self.toolbar is not initialized yet, so we just store balue + self._toolbarState = sttgns.value("toolbar") + else: + self._toolbarState = "" + def closeEvent(self, event): # Save State and geometry and other things @@ -413,8 +425,10 @@ class MainWindow(QMainWindow, Ui_MainWindow): sttgns.setValue("geometry", self.saveGeometry()) sttgns.setValue("windowState", self.saveState()) sttgns.setValue("metadataState", self.redacMetadata.saveState()) - sttgns.setValue("metadataState", self.redacMetadata.saveState()) sttgns.setValue("revisionsState", self.redacMetadata.revisions.saveState()) + sttgns.setValue("splitterRedacH", self.splitterRedacH.saveState()) + sttgns.setValue("splitterRedacV", self.splitterRedacV.saveState()) + sttgns.setValue("toolbar", self.toolbar.saveState()) # Specific settings to save before quitting settings.lastTab = self.tabMain.currentIndex() @@ -801,6 +815,8 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.toolbar.addCustomWidget(self.tr("Project tree"), self.treeRedacWidget, self.TabRedac) self.toolbar.addCustomWidget(self.tr("Metadata"), self.redacMetadata, self.TabRedac) self.toolbar.addCustomWidget(self.tr("Story line"), self.storylineView, self.TabRedac) + if self._toolbarState: + self.toolbar.restoreState(self._toolbarState) # Custom "tab" bar on the left self.lstTabs.setIconSize(QSize(48, 48)) diff --git a/manuskript/ui/collapsibleDockWidgets.py b/manuskript/ui/collapsibleDockWidgets.py index f6e64e1..5afebf0 100644 --- a/manuskript/ui/collapsibleDockWidgets.py +++ b/manuskript/ui/collapsibleDockWidgets.py @@ -69,6 +69,7 @@ class collapsibleDockWidgets(QToolBar): # widget.installEventFilter(self) b = verticalButton(self) b.setDefaultAction(a) + #b.setChecked(widget.isVisible()) a2 = self.addWidget(b) self.otherWidgets.append((b, a2, widget, group)) @@ -87,6 +88,22 @@ class collapsibleDockWidgets(QToolBar): else: action.setVisible(True) + def saveState(self): + # We just need to save states of the custom widgets. + state = [] + for btn, act, w, grp in self.otherWidgets: + state.append( + (grp, btn.text(), btn.isChecked()) + ) + return state + + def restoreState(self, state): + for group, title, status in state: + for btn, act, widget, grp in self.otherWidgets: + if group == grp and title == btn.text(): + btn.setChecked(status) + widget.setVisible(status) + class verticalButton(QToolButton): def __init__(self, parent): diff --git a/manuskript/ui/mainWindow.py b/manuskript/ui/mainWindow.py index dbcda03..8eac15f 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 09:13:29 2016 +# Created: Sun Feb 28 13:22:17 2016 # by: PyQt5 UI code generator 5.2.1 # # WARNING! All changes made in this file will be lost! @@ -871,9 +871,11 @@ class Ui_MainWindow(object): self.verticalLayout_15.setObjectName("verticalLayout_15") self.splitterRedacV = QtWidgets.QSplitter(self.lytTabRedac) self.splitterRedacV.setOrientation(QtCore.Qt.Vertical) + self.splitterRedacV.setChildrenCollapsible(False) self.splitterRedacV.setObjectName("splitterRedacV") self.splitterRedacH = QtWidgets.QSplitter(self.splitterRedacV) self.splitterRedacH.setOrientation(QtCore.Qt.Horizontal) + self.splitterRedacH.setChildrenCollapsible(False) self.splitterRedacH.setObjectName("splitterRedacH") self.treeRedacWidget = QtWidgets.QWidget(self.splitterRedacH) self.treeRedacWidget.setObjectName("treeRedacWidget") @@ -994,7 +996,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, 30)) + self.menubar.setGeometry(QtCore.QRect(0, 0, 1112, 20)) self.menubar.setObjectName("menubar") self.menuFile = QtWidgets.QMenu(self.menubar) self.menuFile.setObjectName("menuFile") @@ -1301,18 +1303,18 @@ class Ui_MainWindow(object): self.actCompile.setShortcut(_translate("MainWindow", "F6")) self.actToolFrequency.setText(_translate("MainWindow", "&Frequency Analyzer")) -from manuskript.ui.views.plotTreeView import plotTreeView -from manuskript.ui.views.storylineView import storylineView -from manuskript.ui.views.basicItemView import basicItemView -from manuskript.ui.views.treeView import treeView -from manuskript.ui.views.outlineView import outlineView -from manuskript.ui.search import search -from manuskript.ui.sldImportance import sldImportance -from manuskript.ui.views.textEditCompleter import textEditCompleter -from manuskript.ui.views.metadataView import metadataView -from manuskript.ui.views.persoTreeView import persoTreeView -from manuskript.ui.cheatSheet import cheatSheet -from manuskript.ui.welcome import welcome -from manuskript.ui.editors.mainEditor import mainEditor 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.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.textEditCompleter import textEditCompleter +from manuskript.ui.sldImportance import sldImportance +from manuskript.ui.views.metadataView import metadataView diff --git a/manuskript/ui/mainWindow.ui b/manuskript/ui/mainWindow.ui index 1dd7cb5..2720d37 100644 --- a/manuskript/ui/mainWindow.ui +++ b/manuskript/ui/mainWindow.ui @@ -1774,10 +1774,16 @@ Qt::Vertical + + false + Qt::Horizontal + + false + @@ -1973,7 +1979,7 @@ 0 0 1112 - 30 + 20