diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py
index b3825a6c..ab2f3dca 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 36faa59a..bbeefb8f 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 8afa7289..a82475e0 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 058be9db..e682aa7c 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)