diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py
index 7fc3a51..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()
@@ -699,6 +713,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.treeOutlineOutline.setModel(self.mdlOutline)
# self.redacEditor.setModel(self.mdlOutline)
+ self.storylineView.setModels(self.mdlOutline, self.mdlPersos, self.mdlPlots)
self.treeOutlineOutline.selectionModel().selectionChanged.connect(lambda:
self.outlineItemEditor.selectionChanged(
@@ -799,6 +814,9 @@ 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)
+ if self._toolbarState:
+ self.toolbar.restoreState(self._toolbarState)
# Custom "tab" bar on the left
self.lstTabs.setIconSize(QSize(48, 48))
@@ -838,9 +856,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, 75)
+ 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/models/outlineModel.py b/manuskript/models/outlineModel.py
index 36faa59..a3450a8 100644
--- a/manuskript/models/outlineModel.py
+++ b/manuskript/models/outlineModel.py
@@ -836,11 +836,20 @@ class outlineItem():
return lst
- def findItemsContaining(self, text, columns, 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)
+
+ if recursive:
+ 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 +872,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..ac29a06 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
@@ -451,11 +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 = ""
- 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/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 4abf5c1..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: Mon Feb 8 11:20:10 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!
@@ -869,10 +869,15 @@ 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.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")
self.verticalLayout_30 = QtWidgets.QVBoxLayout(self.treeRedacWidget)
self.verticalLayout_30.setContentsMargins(0, 0, 0, 0)
@@ -905,11 +910,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")
@@ -1159,7 +1166,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 +1301,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.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 2d2f1cd..2720d37 100644
--- a/manuskript/ui/mainWindow.ui
+++ b/manuskript/ui/mainWindow.ui
@@ -124,7 +124,7 @@
QTabWidget::Rounded
- 4
+ 6
true
@@ -1770,88 +1770,100 @@
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
-
-
-
-
-
-
-
+
+ false
+
+
+
+ Qt::Horizontal
+
+
+ false
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+ QAbstractItemView::EditKeyPressed
+
+
+ false
+
+
+
+ -
+
+
-
+
+
+
+
+
+
+ ../../../../../../../.designer/backup../../../../../../../.designer/backup
+
+
+
+ -
+
+
+
+
+
+
+ ../../../../../../../.designer/backup../../../../../../../.designer/backup
+
+
+
+ -
+
+
+
+
+
+
+ ../../../../../../../.designer/backup../../../../../../../.designer/backup
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+
+
-
-
+
@@ -2323,7 +2335,7 @@ QListView::item:hover {
- Frequency Analyzer
+ &Frequency Analyzer
@@ -2405,6 +2417,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..ac149f7
--- /dev/null
+++ b/manuskript/ui/views/storylineView.py
@@ -0,0 +1,265 @@
+#!/usr/bin/env python
+# --!-- coding: utf8 --!--
+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, 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
+
+
+class storylineView(QWidget, Ui_storylineView):
+ def __init__(self, parent=None):
+ QWidget.__init__(self, parent)
+ self.setupUi(self)
+ self._mdlPlots = None
+ 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.btnRefresh.clicked.connect(self.refresh)
+ self.sldTxtSize.sliderMoved.connect(self.reloadTimer.start)
+
+ 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 setModels(self, mdlOutline, mdlPersos, mdlPlots):
+ self._mdlPlots = mdlPlots
+ # 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 or not self._mdlOutline or not self._mdlPersos:
+ pass
+
+ LINE_HEIGHT = 18
+ SPACING = 3
+ TEXT_WIDTH = self.sldTxtSize.value()
+ CIRCLE_WIDTH = 10
+ LEVEL_HEIGHT = 12
+
+ s = self.scene
+ s.clear()
+
+ # Get Max Level (max depth)
+ 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)
+
+ # Generate left entries
+ # (As of now, plot only)
+ plotsID = self._mdlPlots.getPlotsByImportance()
+ 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)
+
+ trackedItems.append((ID, ref, name))
+ max_name = max(fm.width(name), max_name)
+
+ ROWS_HEIGHT = len(trackedItems) * (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(TITLE_WIDTH + SPACING, 0)
+
+ 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, title=text)
+ r.setPos(x, deltaH)
+
+ 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
+ for c in item.children():
+ r += itemWidth(c)
+ return r or TEXT_WIDTH
+ 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())
+ parent.setToolTip(references.tooltip(references.textReference(child.ID())))
+ listItems(child, parent, level + 1)
+
+ else:
+ 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 += references.findReferencesTo(ref, c, recursive=False)
+ 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)
+
+ 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, ref, name in trackedItems:
+ 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)
+ i += 1
+
+ # Text
+ txt = QGraphicsSimpleTextItem(name, r)
+ txt.setPos(r.boundingRect().center() - txt.boundingRect().center())
+
+ # Line
+ 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, title=None):
+ QGraphicsRectItem.__init__(self, x, y, w, h, parent)
+ self.setBrush(Qt.white)
+ self.setAcceptHoverEvents(True)
+ self._title = title
+
+ def hoverEnterEvent(self, event):
+ self.setBrush(Qt.lightGray)
+
+ 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):
+ 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)
+ 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)
diff --git a/manuskript/ui/views/storylineView_ui.py b/manuskript/ui/views/storylineView_ui.py
new file mode 100644
index 0000000..9f886ff
--- /dev/null
+++ b/manuskript/ui/views/storylineView_ui.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'manuskript/ui/views/storylineView_ui.ui'
+#
+# 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!
+
+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.setSpacing(0)
+ self.horizontalLayout.setContentsMargins(0, 0, 0, 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)
+ 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("")
+ 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..069de66
--- /dev/null
+++ b/manuskript/ui/views/storylineView_ui.ui
@@ -0,0 +1,136 @@
+
+
+ storylineView
+
+
+
+ 0
+ 0
+ 1040
+ 130
+
+
+
+ Form
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+ 0
+
+
-
+
+
+
+ 32
+ 32
+
+
+
+ +
+
+
+ true
+
+
+
+ -
+
+
+
+ 32
+ 32
+
+
+
+ -
+
+
+ true
+
+
+
+ -
+
+
+
+ 32
+ 32
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+ -
+
+
+ 1
+
+
+ 100
+
+
+ 20
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+
+ 32
+ 32
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+
+
+ -
+
+
+
+
+
+
+
diff --git a/sample-projects/book-of-acts.msk b/sample-projects/book-of-acts.msk
index 48f0092..0bc4d5b 100644
Binary files a/sample-projects/book-of-acts.msk and b/sample-projects/book-of-acts.msk differ