mirror of
https://github.com/olivierkes/manuskript.git
synced 2024-09-28 07:21:31 +12:00
386 lines
No EOL
15 KiB
Python
386 lines
No EOL
15 KiB
Python
#!/usr/bin/env python
|
|
# --!-- coding: utf8 --!--
|
|
from PyQt5.QtCore import QModelIndex
|
|
from PyQt5.QtCore import QSignalMapper
|
|
from PyQt5.QtCore import Qt
|
|
from PyQt5.QtGui import QBrush
|
|
from PyQt5.QtGui import QStandardItem
|
|
from PyQt5.QtGui import QStandardItemModel
|
|
from PyQt5.QtWidgets import QAction, QMenu
|
|
|
|
from manuskript.enums import Plot, PlotStep, Model
|
|
from manuskript.functions import toInt, mainWindow
|
|
from manuskript.models.searchResultModel import searchResultModel
|
|
from manuskript.searchLabels import PlotSearchLabels, PLOT_STEP_COLUMNS_OFFSET
|
|
from manuskript.functions import search
|
|
from manuskript.models.searchableModel import searchableModel
|
|
from manuskript.models.searchableItem import searchableItem
|
|
|
|
class plotModel(QStandardItemModel, searchableModel):
|
|
def __init__(self, parent):
|
|
QStandardItemModel.__init__(self, 0, 3, parent)
|
|
self.setHorizontalHeaderLabels([i.name for i in Plot])
|
|
self.mw = mainWindow()
|
|
|
|
self.updatePlotPersoButton()
|
|
|
|
###############################################################################
|
|
# QUERIES
|
|
###############################################################################
|
|
|
|
def getPlotsByImportance(self):
|
|
plots = [[], [], []]
|
|
for i in range(self.rowCount()):
|
|
importance = self.item(i, Plot.importance).text()
|
|
ID = self.item(i, Plot.ID).text()
|
|
plots[2 - toInt(importance)].append(ID)
|
|
return plots
|
|
|
|
def getSubPlotsByID(self, ID):
|
|
index = self.getIndexFromID(ID)
|
|
if not index.isValid():
|
|
return
|
|
index = index.sibling(index.row(), Plot.steps)
|
|
item = self.itemFromIndex(index)
|
|
lst = []
|
|
for i in range(item.rowCount()):
|
|
if item.child(i, PlotStep.ID):
|
|
_ID = item.child(i, PlotStep.ID).text()
|
|
|
|
# Don't know why sometimes name is None (while drag'n'dropping
|
|
# several items)
|
|
if item.child(i, PlotStep.name):
|
|
name = item.child(i, PlotStep.name).text()
|
|
else:
|
|
name = ""
|
|
|
|
# Don't know why sometimes summary is None
|
|
if item.child(i, PlotStep.summary):
|
|
summary = item.child(i, PlotStep.summary).text()
|
|
else:
|
|
summary = ""
|
|
|
|
lst.append((_ID, name, summary))
|
|
return lst
|
|
|
|
def getPlotNameByID(self, ID):
|
|
for i in range(self.rowCount()):
|
|
_ID = self.item(i, Plot.ID).text()
|
|
if _ID == ID or toInt(_ID) == ID:
|
|
name = self.item(i, Plot.name).text()
|
|
return name
|
|
return None
|
|
|
|
def getPlotImportanceByRow(self, row):
|
|
for i in range(self.rowCount()):
|
|
if i == row:
|
|
importance = self.item(i, Plot.importance).text()
|
|
return importance
|
|
return "0" # Default to "Minor"
|
|
|
|
def getSubPlotTextsByID(self, plotID, subplotRaw):
|
|
"""Returns a tuple (name, summary) for the subplot whose raw in the model
|
|
is ``subplotRaw``, of plot whose ID is ``plotID``.
|
|
"""
|
|
plotIndex = self.getIndexFromID(plotID)
|
|
name = plotIndex.child(subplotRaw, PlotStep.name).data()
|
|
summary = plotIndex.child(subplotRaw, PlotStep.summary).data()
|
|
return name, summary
|
|
|
|
def getIndexFromID(self, ID):
|
|
for i in range(self.rowCount()):
|
|
_ID = self.item(i, Plot.ID).text()
|
|
if _ID == ID or toInt(_ID) == ID:
|
|
return self.index(i, 0)
|
|
return QModelIndex()
|
|
|
|
def currentIndex(self):
|
|
i = self.mw.lstPlots.currentIndex()
|
|
if i.isValid():
|
|
return i
|
|
else:
|
|
return None
|
|
|
|
###############################################################################
|
|
# ADDING / REMOVING
|
|
###############################################################################
|
|
|
|
def addPlot(self, name="New plot"):
|
|
if not name:
|
|
name="New Plot"
|
|
p = QStandardItem(self.tr(name))
|
|
_id = QStandardItem(self.getUniqueID())
|
|
importance = QStandardItem(str(0))
|
|
self.appendRow([p, _id, importance, QStandardItem("Characters"),
|
|
QStandardItem(), QStandardItem(), QStandardItem("Resolution steps")])
|
|
return p, _id
|
|
|
|
def getUniqueID(self, parent=QModelIndex()):
|
|
"""Returns an unused ID"""
|
|
parentItem = self.itemFromIndex(parent)
|
|
vals = []
|
|
for i in range(self.rowCount(parent)):
|
|
index = self.index(i, Plot.ID, parent)
|
|
# item = self.item(i, Plot.ID)
|
|
if index.isValid() and index.data():
|
|
vals.append(int(index.data()))
|
|
|
|
k = 0
|
|
while k in vals:
|
|
k += 1
|
|
return str(k)
|
|
|
|
def removePlot(self, index):
|
|
self.takeRow(index.row())
|
|
|
|
###############################################################################
|
|
# SUBPLOTS
|
|
###############################################################################
|
|
|
|
def headerData(self, section, orientation, role=Qt.DisplayRole):
|
|
if role == Qt.DisplayRole:
|
|
if orientation == Qt.Horizontal:
|
|
if section == PlotStep.name:
|
|
return self.tr("Name")
|
|
elif section == PlotStep.meta:
|
|
return self.tr("Meta")
|
|
else:
|
|
return ""
|
|
else:
|
|
return ""
|
|
else:
|
|
return QStandardItemModel.headerData(self, section, orientation, role)
|
|
|
|
def data(self, index, role=Qt.DisplayRole):
|
|
if index.parent().isValid() and \
|
|
index.parent().column() == Plot.steps and \
|
|
index.column() == PlotStep.meta:
|
|
if role == Qt.TextAlignmentRole:
|
|
return Qt.AlignRight | Qt.AlignVCenter
|
|
elif role == Qt.ForegroundRole:
|
|
return QBrush(Qt.gray)
|
|
else:
|
|
return QStandardItemModel.data(self, index, role)
|
|
|
|
else:
|
|
return QStandardItemModel.data(self, index, role)
|
|
|
|
def addSubPlot(self):
|
|
index = self.mw.lstPlots.currentPlotIndex()
|
|
if not index.isValid():
|
|
return
|
|
|
|
parent = index.sibling(index.row(), Plot.steps)
|
|
parentItem = self.item(index.row(), Plot.steps)
|
|
|
|
if not parentItem:
|
|
return
|
|
|
|
p = QStandardItem(self.tr("New step"))
|
|
_id = QStandardItem(self.getUniqueID(parent))
|
|
summary = QStandardItem()
|
|
|
|
currentIndex = self.mw.lstSubPlots.selectionModel().selectedIndexes()
|
|
if currentIndex:
|
|
# We use last item of selection in case of many
|
|
currentIndex = currentIndex[-1]
|
|
row = currentIndex.row() + 1
|
|
parentItem.insertRow(row, [p, _id, QStandardItem(), summary])
|
|
# Select last index
|
|
self.mw.lstSubPlots.setCurrentIndex(currentIndex.sibling(row, 0))
|
|
else:
|
|
# Don't know why, if summary is in third position, then drag/drop deletes it...
|
|
parentItem.appendRow([p, _id, QStandardItem(), summary])
|
|
# Select last index
|
|
self.mw.lstSubPlots.setCurrentIndex(
|
|
parent.child(self.rowCount(parent) - 1, 0))
|
|
|
|
def removeSubPlot(self):
|
|
"""
|
|
Remove all selected subplots / plot steps, in mw.lstSubPlots.
|
|
"""
|
|
parent = self.mw.lstSubPlots.rootIndex()
|
|
if not parent.isValid():
|
|
return
|
|
parentItem = self.itemFromIndex(parent)
|
|
|
|
while self.mw.lstSubPlots.selectionModel().selectedRows():
|
|
i = self.mw.lstSubPlots.selectionModel().selectedRows()[0]
|
|
parentItem.takeRow(i.row())
|
|
|
|
def flags(self, index):
|
|
parent = index.parent()
|
|
if parent.isValid(): # this is a subitem
|
|
return Qt.ItemIsEnabled | Qt.ItemIsEditable | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled
|
|
else:
|
|
return QStandardItemModel.flags(self, index)
|
|
|
|
###############################################################################
|
|
# PLOT PERSOS
|
|
###############################################################################
|
|
|
|
def addPlotPerso(self, v):
|
|
index = self.mw.lstPlots.currentPlotIndex()
|
|
if index.isValid():
|
|
if not self.item(index.row(), Plot.characters):
|
|
self.setItem(index.row(), Plot.characters, QStandardItem())
|
|
|
|
item = self.item(index.row(), Plot.characters)
|
|
|
|
# We check that the PersoID is not in the list yet
|
|
for i in range(item.rowCount()):
|
|
if item.child(i).text() == str(v):
|
|
return
|
|
|
|
item.appendRow(QStandardItem(str(v)))
|
|
|
|
def removePlotPerso(self):
|
|
index = self.mw.lstPlotPerso.currentIndex()
|
|
if not index.isValid():
|
|
return
|
|
parent = index.parent()
|
|
parentItem = self.itemFromIndex(parent)
|
|
parentItem.takeRow(index.row())
|
|
|
|
def updatePlotPersoButton(self):
|
|
menu = QMenu(self.mw)
|
|
|
|
menus = []
|
|
for i in [self.tr("Main"), self.tr("Secondary"), self.tr("Minor")]:
|
|
m = QMenu(i, menu)
|
|
menus.append(m)
|
|
menu.addMenu(m)
|
|
|
|
mpr = QSignalMapper(menu)
|
|
for i in range(self.mw.mdlCharacter.rowCount()):
|
|
a = QAction(self.mw.mdlCharacter.name(i), menu)
|
|
a.setIcon(self.mw.mdlCharacter.icon(i))
|
|
a.triggered.connect(mpr.map)
|
|
mpr.setMapping(a, int(self.mw.mdlCharacter.ID(i)))
|
|
|
|
imp = toInt(self.mw.mdlCharacter.importance(i))
|
|
|
|
menus[2 - imp].addAction(a)
|
|
|
|
# Disabling empty menus
|
|
for m in menus:
|
|
if not m.actions():
|
|
m.setEnabled(False)
|
|
|
|
mpr.mapped.connect(self.addPlotPerso)
|
|
self.mw.btnAddPlotPerso.setMenu(menu)
|
|
|
|
#######################################################################
|
|
# Search
|
|
#######################################################################
|
|
def searchableItems(self):
|
|
items = []
|
|
|
|
for i in range(self.rowCount()):
|
|
items.append(plotItemSearchWrapper(i, self.item, self.mw.mdlCharacter.getCharacterByID))
|
|
|
|
return items
|
|
|
|
|
|
class plotItemSearchWrapper(searchableItem):
|
|
def __init__(self, rowIndex, getItem, getCharacterByID):
|
|
self.rowIndex = rowIndex
|
|
self.getItem = getItem
|
|
self.getCharacterByID = getCharacterByID
|
|
super().__init__(PlotSearchLabels)
|
|
|
|
def searchOccurrences(self, searchRegex, column):
|
|
results = []
|
|
|
|
plotName = self.getItem(self.rowIndex, Plot.name).text()
|
|
if column >= PLOT_STEP_COLUMNS_OFFSET:
|
|
results += self.searchInPlotSteps(self.rowIndex, plotName, column, column - PLOT_STEP_COLUMNS_OFFSET, searchRegex, False)
|
|
else:
|
|
item_name = self.getItem(self.rowIndex, Plot.name).text()
|
|
if column == Plot.characters:
|
|
charactersList = self.getItem(self.rowIndex, Plot.characters)
|
|
|
|
for i in range(charactersList.rowCount()):
|
|
characterID = charactersList.child(i).text()
|
|
|
|
character = self.getCharacterByID(characterID)
|
|
if character:
|
|
columnText = character.name()
|
|
|
|
characterResults = search(searchRegex, columnText)
|
|
if len(characterResults):
|
|
# We will highlight the full character row in the plot characters list, so we
|
|
# return the row index instead of the match start and end positions.
|
|
results += [
|
|
searchResultModel(Model.Plot, self.getItem(self.rowIndex, Plot.ID).text(), column,
|
|
self.translate(item_name),
|
|
self.searchPath(column),
|
|
[(i, 0)], context) for start, end, context in
|
|
search(searchRegex, columnText)]
|
|
else:
|
|
results += super().searchOccurrences(searchRegex, column)
|
|
if column == Plot.name:
|
|
results += self.searchInPlotSteps(self.rowIndex, plotName, Plot.name, PlotStep.name,
|
|
searchRegex, False)
|
|
elif column == Plot.summary:
|
|
results += self.searchInPlotSteps(self.rowIndex, plotName, Plot.summary, PlotStep.summary,
|
|
searchRegex, True)
|
|
|
|
return results
|
|
|
|
def searchModel(self):
|
|
return Model.Plot
|
|
|
|
def searchID(self):
|
|
return self.getItem(self.rowIndex, Plot.ID).text()
|
|
|
|
def searchTitle(self, column):
|
|
return self.getItem(self.rowIndex, Plot.name).text()
|
|
|
|
def searchPath(self, column):
|
|
def _path(item):
|
|
path = []
|
|
|
|
if item.parent():
|
|
path += _path(item.parent())
|
|
path.append(item.text())
|
|
|
|
return path
|
|
|
|
return [self.translate("Plot")] + _path(self.getItem(self.rowIndex, Plot.name)) + [self.translate(self.searchColumnLabel(column))]
|
|
|
|
def searchData(self, column):
|
|
return self.getItem(self.rowIndex, column).text()
|
|
|
|
def plotStepPath(self, plotName, plotStepName, column):
|
|
return [self.translate("Plot"), plotName, plotStepName, self.translate(self.searchColumnLabel(column))]
|
|
|
|
def searchInPlotSteps(self, plotIndex, plotName, plotColumn, plotStepColumn, searchRegex, searchInsidePlotStep):
|
|
results = []
|
|
|
|
# Plot step info can be found in two places: the own list of plot steps (this is the case for ie. name and meta
|
|
# fields) and "inside" the plot step once it is selected in the list (as it's the case for the summary).
|
|
if searchInsidePlotStep:
|
|
# We are searching *inside* the plot step, so we return both the row index (for selecting the right plot
|
|
# step in the list), and (start, end) positions of the match inside the text field for highlighting it.
|
|
getSearchData = lambda rowIndex, start, end, context: ([(rowIndex, 0), (start, end)], context)
|
|
else:
|
|
# We are searching *in the plot step row*, so we only return the row index for selecting the right plot
|
|
# step in the list when highlighting search results.
|
|
getSearchData = lambda rowIndex, start, end, context: ([(rowIndex, 0)], context)
|
|
|
|
item = self.getItem(plotIndex, Plot.steps)
|
|
for i in range(item.rowCount()):
|
|
if item.child(i, PlotStep.ID):
|
|
plotStepName = item.child(i, PlotStep.name).text()
|
|
plotStepText = item.child(i, plotStepColumn).text()
|
|
|
|
# We will highlight the full plot step row in the plot steps list, so we
|
|
# return the row index instead of the match start and end positions.
|
|
results += [searchResultModel(Model.PlotStep, self.getItem(plotIndex, Plot.ID).text(), plotStepColumn,
|
|
self.translate(plotStepName),
|
|
self.plotStepPath(plotName, plotStepName, plotColumn),
|
|
*getSearchData(i, start, end, context)) for start, end, context in
|
|
search(searchRegex, plotStepText)]
|
|
|
|
return results |