manuskript/manuskript/models/plotModel.py

386 lines
15 KiB
Python
Raw Normal View History

#!/usr/bin/env python
2016-02-07 00:34:22 +13:00
# --!-- 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
2019-12-22 04:42:49 +13:00
from manuskript.enums import Plot, PlotStep, Model
2016-02-07 00:34:22 +13:00
from manuskript.functions import toInt, mainWindow
2019-12-22 04:42:49 +13:00
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
2016-02-07 00:34:22 +13:00
2019-12-22 04:42:49 +13:00
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()
2016-02-07 00:34:22 +13:00
self.updatePlotPersoButton()
2016-02-07 00:34:22 +13:00
###############################################################################
# QUERIES
###############################################################################
2015-06-22 23:11:45 +12:00
def getPlotsByImportance(self):
plots = [[], [], []]
for i in range(self.rowCount()):
2017-11-16 09:05:48 +13:00
importance = self.item(i, Plot.importance).text()
ID = self.item(i, Plot.ID).text()
2016-02-07 00:34:22 +13:00
plots[2 - toInt(importance)].append(ID)
2015-06-22 23:11:45 +12:00
return plots
2016-02-07 00:34:22 +13:00
2015-06-23 06:30:43 +12:00
def getSubPlotsByID(self, ID):
index = self.getIndexFromID(ID)
if not index.isValid():
return
2017-11-16 09:05:48 +13:00
index = index.sibling(index.row(), Plot.steps)
2015-06-23 06:30:43 +12:00
item = self.itemFromIndex(index)
lst = []
for i in range(item.rowCount()):
2017-11-16 09:05:48 +13:00
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)
2017-11-16 09:05:48 +13:00
if item.child(i, PlotStep.name):
name = item.child(i, PlotStep.name).text()
else:
name = ""
# Don't know why sometimes summary is None
2017-11-16 09:05:48 +13:00
if item.child(i, PlotStep.summary):
summary = item.child(i, PlotStep.summary).text()
else:
summary = ""
lst.append((_ID, name, summary))
2015-06-23 06:30:43 +12:00
return lst
2016-02-07 00:34:22 +13:00
2015-06-23 06:30:43 +12:00
def getPlotNameByID(self, ID):
2015-06-22 23:11:45 +12:00
for i in range(self.rowCount()):
2017-11-16 09:05:48 +13:00
_ID = self.item(i, Plot.ID).text()
2015-06-22 23:11:45 +12:00
if _ID == ID or toInt(_ID) == ID:
2017-11-16 09:05:48 +13:00
name = self.item(i, Plot.name).text()
2015-06-22 23:11:45 +12:00
return name
return None
2016-02-07 00:34:22 +13:00
def getPlotImportanceByRow(self, row):
for i in range(self.rowCount()):
if i == row:
2017-11-16 09:05:48 +13:00
importance = self.item(i, Plot.importance).text()
return importance
return "0" # Default to "Minor"
2015-07-03 03:45:27 +12:00
def getSubPlotTextsByID(self, plotID, subplotRaw):
"""Returns a tuple (name, summary) for the subplot whose raw in the model
2015-07-03 03:45:27 +12:00
is ``subplotRaw``, of plot whose ID is ``plotID``.
"""
plotIndex = self.getIndexFromID(plotID)
2017-11-16 09:05:48 +13:00
name = plotIndex.child(subplotRaw, PlotStep.name).data()
summary = plotIndex.child(subplotRaw, PlotStep.summary).data()
2016-02-07 00:34:22 +13:00
return name, summary
2015-06-22 23:11:45 +12:00
def getIndexFromID(self, ID):
for i in range(self.rowCount()):
2017-11-16 09:05:48 +13:00
_ID = self.item(i, Plot.ID).text()
2015-06-22 23:11:45 +12:00
if _ID == ID or toInt(_ID) == ID:
return self.index(i, 0)
return QModelIndex()
2016-02-07 00:34:22 +13:00
2015-06-22 23:11:45 +12:00
def currentIndex(self):
2015-06-23 06:30:43 +12:00
i = self.mw.lstPlots.currentIndex()
2016-02-07 00:34:22 +13:00
if i.isValid():
2015-06-22 23:11:45 +12:00
return i
else:
return None
2016-02-07 00:34:22 +13:00
###############################################################################
# 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))
2016-03-07 04:10:25 +13:00
self.appendRow([p, _id, importance, QStandardItem("Characters"),
QStandardItem(), QStandardItem(), QStandardItem("Resolution steps")])
return p, _id
2016-02-07 00:34:22 +13:00
def getUniqueID(self, parent=QModelIndex()):
2016-02-07 00:34:22 +13:00
"""Returns an unused ID"""
parentItem = self.itemFromIndex(parent)
vals = []
for i in range(self.rowCount(parent)):
2017-11-16 09:05:48 +13:00
index = self.index(i, Plot.ID, parent)
# item = self.item(i, Plot.ID)
if index.isValid() and index.data():
vals.append(int(index.data()))
2016-02-07 00:34:22 +13:00
k = 0
2016-02-07 00:34:22 +13:00
while k in vals:
k += 1
return str(k)
2016-02-07 00:34:22 +13:00
def removePlot(self, index):
self.takeRow(index.row())
2016-02-07 00:34:22 +13:00
###############################################################################
# SUBPLOTS
###############################################################################
2015-07-07 01:00:22 +12:00
def headerData(self, section, orientation, role=Qt.DisplayRole):
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
2017-11-16 09:05:48 +13:00
if section == PlotStep.name:
2015-07-07 01:00:22 +12:00
return self.tr("Name")
2017-11-16 09:05:48 +13:00
elif section == PlotStep.meta:
2015-07-07 01:00:22 +12:00
return self.tr("Meta")
else:
return ""
else:
return ""
else:
return QStandardItemModel.headerData(self, section, orientation, role)
2016-02-07 00:34:22 +13:00
2015-07-07 01:00:22 +12:00
def data(self, index, role=Qt.DisplayRole):
if index.parent().isValid() and \
index.parent().column() == Plot.steps and \
index.column() == PlotStep.meta:
2015-07-07 01:00:22 +12:00
if role == Qt.TextAlignmentRole:
return Qt.AlignRight | Qt.AlignVCenter
elif role == Qt.ForegroundRole:
return QBrush(Qt.gray)
else:
return QStandardItemModel.data(self, index, role)
2016-02-07 00:34:22 +13:00
2015-07-07 01:00:22 +12:00
else:
return QStandardItemModel.data(self, index, role)
2016-02-07 00:34:22 +13:00
def addSubPlot(self):
2015-06-23 06:30:43 +12:00
index = self.mw.lstPlots.currentPlotIndex()
2015-06-22 23:11:45 +12:00
if not index.isValid():
return
2016-02-07 00:34:22 +13:00
2017-11-16 09:05:48 +13:00
parent = index.sibling(index.row(), Plot.steps)
parentItem = self.item(index.row(), Plot.steps)
2016-02-07 00:34:22 +13:00
2015-06-22 23:11:45 +12:00
if not parentItem:
return
2016-02-07 00:34:22 +13:00
2016-03-07 04:10:25 +13:00
p = QStandardItem(self.tr("New step"))
_id = QStandardItem(self.getUniqueID(parent))
summary = QStandardItem()
2016-02-07 00:34:22 +13:00
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))
2016-02-07 00:34:22 +13:00
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())
2016-02-07 00:34:22 +13:00
2015-06-22 23:11:45 +12:00
def flags(self, index):
parent = index.parent()
2016-02-07 00:34:22 +13:00
if parent.isValid(): # this is a subitem
return Qt.ItemIsEnabled | Qt.ItemIsEditable | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled
2015-06-22 23:11:45 +12:00
else:
return QStandardItemModel.flags(self, index)
2015-07-03 03:45:27 +12:00
2016-02-07 00:34:22 +13:00
###############################################################################
# PLOT PERSOS
###############################################################################
2015-07-03 03:45:27 +12:00
def addPlotPerso(self, v):
2015-06-23 06:30:43 +12:00
index = self.mw.lstPlots.currentPlotIndex()
2015-06-22 23:11:45 +12:00
if index.isValid():
2017-11-16 09:05:48 +13:00
if not self.item(index.row(), Plot.characters):
self.setItem(index.row(), Plot.characters, QStandardItem())
2016-02-07 00:34:22 +13:00
2017-11-16 09:05:48 +13:00
item = self.item(index.row(), Plot.characters)
2016-02-07 00:34:22 +13:00
# 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
2016-02-07 00:34:22 +13:00
item.appendRow(QStandardItem(str(v)))
2016-02-07 00:34:22 +13:00
def removePlotPerso(self):
index = self.mw.lstPlotPerso.currentIndex()
if not index.isValid():
return
parent = index.parent()
parentItem = self.itemFromIndex(parent)
parentItem.takeRow(index.row())
2016-02-07 00:34:22 +13:00
def updatePlotPersoButton(self):
2015-06-25 06:41:23 +12:00
menu = QMenu(self.mw)
2016-02-07 00:34:22 +13:00
menus = []
for i in [self.tr("Main"), self.tr("Secondary"), self.tr("Minor")]:
m = QMenu(i, menu)
menus.append(m)
menu.addMenu(m)
2016-02-07 00:34:22 +13:00
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))
2015-06-29 20:22:18 +12:00
a.triggered.connect(mpr.map)
mpr.setMapping(a, int(self.mw.mdlCharacter.ID(i)))
2016-02-07 00:34:22 +13:00
imp = toInt(self.mw.mdlCharacter.importance(i))
2016-02-07 00:34:22 +13:00
menus[2 - imp].addAction(a)
# Disabling empty menus
for m in menus:
if not m.actions():
m.setEnabled(False)
mpr.mapped.connect(self.addPlotPerso)
2016-02-07 00:34:22 +13:00
self.mw.btnAddPlotPerso.setMenu(menu)
2019-12-22 04:42:49 +13:00
#######################################################################
# 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