manuskript/src/models/outlineModel.py

925 lines
30 KiB
Python
Raw Normal View History

2015-06-02 10:06:17 +12:00
#!/usr/bin/env python
#--!-- coding: utf8 --!--
2015-06-04 04:40:19 +12:00
from qt import *
2015-06-04 12:04:47 +12:00
from enums import *
2015-06-02 10:06:17 +12:00
from enum import Enum
2015-06-03 00:40:48 +12:00
from lxml import etree as ET
2015-06-05 06:22:37 +12:00
from functions import *
2015-07-04 09:00:54 +12:00
import settings
2015-06-30 22:27:43 +12:00
import locale
locale.setlocale(locale.LC_ALL, '')
2015-07-04 04:41:18 +12:00
import time
2015-06-05 06:22:37 +12:00
2015-06-02 10:06:17 +12:00
class outlineModel(QAbstractItemModel):
2015-06-04 12:51:37 +12:00
def __init__(self, parent):
QAbstractItemModel.__init__(self, parent)
2015-06-02 10:06:17 +12:00
2015-06-27 03:33:55 +12:00
self.rootItem = outlineItem(self, title="root", ID="0")
2015-06-02 10:06:17 +12:00
def index(self, row, column, parent):
if not self.hasIndex(row, column, parent):
return QModelIndex()
if not parent.isValid():
parentItem = self.rootItem
else:
parentItem = parent.internalPointer()
childItem = parentItem.child(row)
if childItem:
return self.createIndex(row, column, childItem)
else:
return QModelIndex()
2015-06-05 21:37:01 +12:00
def indexFromItem(self, item, column=0):
if item == self.rootItem:
2015-06-16 06:30:18 +12:00
return QModelIndex()
2015-06-05 21:37:01 +12:00
parent = item.parent()
if not parent:
parent = self.rootItem
if len(parent.children()) == 0:
return None
#print(item.title(), [i.title() for i in parent.children()])
row = parent.children().index(item)
col = column
return self.createIndex(row, col, item)
def ID(self, index):
if index.isValid():
item = index.internalPointer()
return item.ID()
2015-07-01 00:01:32 +12:00
def findItemsByPOV(self, POV):
"Returns a list of IDs of all items whose POV is ``POV``."
return self.rootItem.findItemsByPOV(POV)
2015-07-01 01:38:14 +12:00
def findItemsContaining(self, text, columns, caseSensitive=False):
"""Returns a list of IDs of all items containing ``text``
in columns ``columns`` (being a list of int)."""
return self.rootItem.findItemsContaining(text, columns, mainWindow(), caseSensitive)
2015-07-01 00:01:32 +12:00
def getIndexByID(self, ID):
"Returns the index of item whose ID is ``ID``. If none, returns QModelIndex()."
def search(item):
if item.ID() == ID:
return item
for c in item.children():
r = search(c)
if r:
return r
item = search(self.rootItem)
if not item:
return QModelIndex()
else:
return self.indexFromItem(item)
2015-06-02 10:06:17 +12:00
def parent(self, index=QModelIndex()):
if not index.isValid():
return QModelIndex()
childItem = index.internalPointer()
try:
parentItem = childItem.parent()
except AttributeError:
import traceback, sys
print(traceback.print_exc())
print(sys.exc_info()[0])
return QModelIndex()
2015-06-02 10:06:17 +12:00
if parentItem == self.rootItem:
return QModelIndex()
return self.createIndex(parentItem.row(), 0, parentItem)
def rowCount(self, parent=QModelIndex()):
if parent.column() > 0:
return 0
if not parent.isValid():
parentItem = self.rootItem
else:
parentItem = parent.internalPointer()
return parentItem.childCount()
def columnCount(self, parent=QModelIndex()):
if parent.isValid():
return parent.internalPointer().columnCount()
else:
return self.rootItem.columnCount()
def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return QVariant()
item = index.internalPointer()
return item.data(index.column(), role)
def setData(self, index, value, role=Qt.EditRole):
item = index.internalPointer()
2015-06-18 04:40:55 +12:00
if item.data(index.column(), role) != value:
2015-06-18 04:40:55 +12:00
item.setData(index.column(), value, role)
2015-06-04 12:51:37 +12:00
#self.dataChanged.emit(index.sibling(index.row(), 0),
#index.sibling(index.row(), max([i.value for i in Outline])))
#print("Model emit", index.row(), index.column())
self.dataChanged.emit(index, index)
if index.column() == Outline.type.value:
# If type changed, then the icon of title changed.
# Some views might be glad to know it.
self.dataChanged.emit(index.sibling(index.row(), Outline.title.value),
index.sibling(index.row(), Outline.title.value))
2015-06-02 10:06:17 +12:00
return True
2015-06-03 00:40:48 +12:00
def headerData(self, section, orientation, role=Qt.DisplayRole):
2015-06-18 04:40:55 +12:00
if orientation == Qt.Horizontal and role in [Qt.DisplayRole, Qt.ToolTipRole]:
2015-06-06 11:13:27 +12:00
if section == Outline.title.value:
2015-06-18 04:40:55 +12:00
return self.tr("Title")
elif section == Outline.POV.value:
return self.tr("POV")
elif section == Outline.label.value:
return self.tr("Label")
elif section == Outline.status.value:
return self.tr("Status")
elif section == Outline.compile.value:
return self.tr("Compile")
2015-06-06 11:13:27 +12:00
elif section == Outline.wordCount.value:
2015-06-18 04:40:55 +12:00
return self.tr("Word count")
2015-06-06 11:13:27 +12:00
elif section == Outline.goal.value:
2015-06-18 04:40:55 +12:00
return self.tr("Goal")
2015-06-06 11:13:27 +12:00
elif section == Outline.goalPercentage.value:
return "%"
else:
return [i.name for i in Outline][section]
elif role == Qt.SizeHintRole:
if section == Outline.compile.value:
return QSize(40, 30)
elif section == Outline.goalPercentage.value:
return QSize(100, 30)
else:
return QVariant()
2015-06-03 00:40:48 +12:00
else:
return QVariant()
2015-06-06 11:13:27 +12:00
2015-06-03 00:40:48 +12:00
return True
#################### DRAG AND DROP ########################
# http://doc.qt.io/qt-5/model-view-programming.html#using-drag-and-drop-with-item-views
2015-06-02 10:06:17 +12:00
def flags(self, index):
2015-06-04 12:04:47 +12:00
#FIXME when dragging folders, sometimes flags is not called
2015-06-03 00:40:48 +12:00
flags = QAbstractItemModel.flags(self, index) | Qt.ItemIsEditable
2015-06-02 10:06:17 +12:00
2015-06-08 23:53:33 +12:00
if index.isValid() and index.internalPointer().isFolder() and index.column() == 0:
2015-06-04 12:04:47 +12:00
flags |= Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled
2015-06-03 00:40:48 +12:00
2015-06-08 23:53:33 +12:00
elif index.isValid() and index.column() == 0:
2015-06-03 00:40:48 +12:00
flags |= Qt.ItemIsDragEnabled
2015-06-08 23:53:33 +12:00
elif not index.isValid():
2015-06-02 10:06:17 +12:00
flags |= Qt.ItemIsDropEnabled
2015-06-04 12:04:47 +12:00
if index.isValid() and index.column() == Outline.compile.value:
flags |= Qt.ItemIsUserCheckable
2015-06-05 21:37:01 +12:00
if index.column() in [i.value for i in [Outline.wordCount, Outline.goalPercentage]]:
flags &= ~ Qt.ItemIsEditable
2015-06-02 10:06:17 +12:00
return flags
2015-06-03 00:40:48 +12:00
def mimeTypes(self):
return ["application/xml"]
def mimeData(self, indexes):
mimeData = QMimeData()
encodedData = ""
2015-06-02 10:06:17 +12:00
2015-06-03 00:40:48 +12:00
root = ET.Element("outlineItems")
2015-06-04 12:04:47 +12:00
2015-06-03 00:40:48 +12:00
for index in indexes:
2015-06-04 12:04:47 +12:00
if index.isValid() and index.column() == 0:
2015-06-03 00:40:48 +12:00
item = ET.XML(index.internalPointer().toXML())
root.append(item)
encodedData = ET.tostring(root)
mimeData.setData("application/xml", encodedData)
return mimeData
2015-06-02 10:06:17 +12:00
2015-06-03 00:40:48 +12:00
def supportedDropActions(self):
2015-06-09 22:32:43 +12:00
return Qt.CopyAction | Qt.MoveAction
2015-06-02 10:06:17 +12:00
2015-06-08 23:53:33 +12:00
#def canDropMimeData(self, data, action, row, column, parent):
#if not data.hasFormat("application/xml"):
#return False
2015-06-04 04:40:19 +12:00
2015-06-08 23:53:33 +12:00
#if column > 0:
#return False
2015-06-04 04:40:19 +12:00
2015-06-08 23:53:33 +12:00
#return True
2015-06-04 04:40:19 +12:00
2015-06-03 00:40:48 +12:00
def dropMimeData(self, data, action, row, column, parent):
2015-06-02 10:06:17 +12:00
2015-06-03 00:40:48 +12:00
if action == Qt.IgnoreAction:
return True # What is that?
if not data.hasFormat("application/xml"):
return False
if column > 0:
column = 0
2015-06-08 08:06:57 +12:00
if row != -1:
2015-06-03 00:40:48 +12:00
beginRow = row
elif parent.isValid():
beginRow = self.rowCount(parent) + 1
else:
beginRow = self.rowCount() + 1
2015-06-02 10:06:17 +12:00
2015-06-08 23:53:33 +12:00
encodedData = bytes(data.data("application/xml")).decode()
2015-06-04 12:04:47 +12:00
2015-06-03 00:40:48 +12:00
root = ET.XML(encodedData)
2015-06-02 10:06:17 +12:00
2015-06-08 08:06:57 +12:00
if root.tag != "outlineItems":
2015-06-03 00:40:48 +12:00
return False
items = []
for child in root:
if child.tag == "outlineItem":
item = outlineItem(xml=ET.tostring(child))
items.append(item)
2015-06-03 00:40:48 +12:00
if not items:
return False
r = self.insertItems(items, beginRow, parent)
if action == Qt.CopyAction:
for item in items:
item.getUniqueID()
return r
2015-06-02 10:06:17 +12:00
2015-06-03 00:40:48 +12:00
################# ADDING AND REMOVING #################
def insertItem(self, item, row, parent=QModelIndex()):
return self.insertItems([item], row, parent)
def insertItems(self, items, row, parent=QModelIndex()):
2015-06-02 10:06:17 +12:00
if not parent.isValid():
parentItem = self.rootItem
else:
parentItem = parent.internalPointer()
2015-06-03 00:40:48 +12:00
2015-06-08 08:06:57 +12:00
if parent.isValid() and parent.column() != 0:
2015-06-05 21:37:01 +12:00
parent = parentItem.index()
2015-06-03 00:40:48 +12:00
# Insert only if parent is folder
2015-06-02 10:06:17 +12:00
if parentItem.isFolder():
2015-06-03 00:40:48 +12:00
self.beginInsertRows(parent, row, row + len(items) - 1)
for i in items:
parentItem.insertChild(row + items.index(i), i)
2015-06-02 10:06:17 +12:00
self.endInsertRows()
2015-06-03 00:40:48 +12:00
2015-06-06 11:13:27 +12:00
return True
else:
return False
2015-06-03 00:40:48 +12:00
def appendItem(self, item, parent=QModelIndex()):
if not parent.isValid():
parentItem = self.rootItem
else:
parentItem = parent.internalPointer()
2015-06-08 08:06:57 +12:00
if parent.isValid() and parent.column() != 0:
2015-06-05 21:37:01 +12:00
parent = parentItem.index()
2015-06-03 00:40:48 +12:00
# If parent is folder, write into
if parentItem.isFolder():
self.insertItem(item, self.rowCount(parent), parent)
# If parent is not folder, write next to
else:
self.insertItem(item, parent.row()+1, parent.parent())
2015-06-02 10:06:17 +12:00
def removeIndex(self, index):
item = index.internalPointer()
2015-06-03 00:40:48 +12:00
self.removeRow(item.row(), index.parent())
2015-06-02 10:06:17 +12:00
def removeIndexes(self, indexes):
levels = {}
for i in indexes:
item = i.internalPointer()
level = item.level()
if not level in levels:
levels[level] = []
levels[level].append([i.row(), i])
# Sort by level then by row
for l in reversed(sorted(levels.keys())):
rows = levels[l]
rows = list(reversed(sorted(rows, key=lambda x:x[0])))
for r in rows:
self.removeIndex(r[1])
2015-06-03 00:40:48 +12:00
def removeRow(self, row, parent=QModelIndex()):
return self.removeRows(row, 1, parent)
def removeRows(self, row, count, parent=QModelIndex()):
2015-06-02 10:06:17 +12:00
if not parent.isValid():
parentItem = self.rootItem
else:
parentItem = parent.internalPointer()
2015-06-03 00:40:48 +12:00
self.beginRemoveRows(parent, row, row + count - 1)
for i in range(count):
parentItem.removeChild(row)
2015-06-02 10:06:17 +12:00
self.endRemoveRows()
return True
2015-06-03 00:40:48 +12:00
#def insertRow(self, row, item, parent=QModelIndex()):
#self.beginInsertRows(parent, row, row)
#if not parent.isValid():
#parentItem = self.rootItem
#else:
#parentItem = parent.internalPointer()
#parentItem.insertChild(row, item)
#self.endInsertRows()
2015-06-02 10:06:17 +12:00
2015-06-03 00:40:48 +12:00
2015-06-16 06:30:18 +12:00
################# XML / saving / loading #################
2015-06-03 00:40:48 +12:00
2015-06-17 23:25:46 +12:00
def saveToXML(self, xml=None):
"If xml (filename) is given, saves the items to xml. Otherwise returns as string."
2015-06-03 00:40:48 +12:00
root = ET.XML(self.rootItem.toXML())
2015-06-17 23:25:46 +12:00
if xml:
ET.ElementTree(root).write(xml, encoding="UTF-8", xml_declaration=True, pretty_print=True)
else:
return ET.tostring(root, encoding="UTF-8", xml_declaration=True, pretty_print=True)
2015-06-03 00:40:48 +12:00
2015-06-17 23:25:46 +12:00
def loadFromXML(self, xml, fromString=False):
"Load from xml. Assume that xml is a filename. If fromString=True, xml is the content."
if not fromString:
2015-06-04 04:40:19 +12:00
root = ET.parse(xml)
2015-06-17 23:25:46 +12:00
else:
root = ET.fromstring(xml)
2015-06-27 03:33:55 +12:00
self.rootItem = outlineItem(model=self, xml=ET.tostring(root), ID="0")
self.rootItem.checkIDs()
2015-06-16 06:30:18 +12:00
def pathToIndex(self, index, path=""):
# FIXME: Use item's ID instead of rows
2015-06-16 06:30:18 +12:00
if not index.isValid():
return ""
if index.parent().isValid():
path = self.pathToIndex(index.parent())
if path:
path = "{},{}".format(path, str(index.row()))
else:
path = str(index.row())
return path
2015-06-04 12:51:37 +12:00
2015-06-16 06:30:18 +12:00
def indexFromPath(self, path):
path = path.split(",")
item = self.rootItem
for p in path:
if p != "" and int(p) < item.childCount():
2015-06-16 06:30:18 +12:00
item = item.child(int(p))
return self.indexFromItem(item)
2015-06-03 00:40:48 +12:00
class outlineItem():
2015-06-27 03:33:55 +12:00
def __init__(self, model=None, title="", _type="folder", xml=None, parent=None, ID=None):
2015-06-02 10:06:17 +12:00
self._data = {}
self.childItems = []
2015-06-05 21:37:01 +12:00
self._parent = None
self._model = model
self.defaultTextType = None
2015-06-27 03:33:55 +12:00
self.IDs = [] # used by root item to store unique IDs
if title:
self._data[Outline.title] = title
2015-06-02 10:06:17 +12:00
self._data[Outline.type] = _type
2015-06-05 21:37:01 +12:00
self._data[Outline.compile] = Qt.Checked
2015-06-04 04:40:19 +12:00
if xml is not None:
2015-06-03 00:40:48 +12:00
self.setFromXML(xml)
2015-06-27 03:33:55 +12:00
if parent:
parent.appendChild(self)
if ID:
self._data[Outline.ID] = ID
2015-06-03 00:40:48 +12:00
2015-06-02 10:06:17 +12:00
def child(self, row):
return self.childItems[row]
def childCount(self):
return len(self.childItems)
2015-06-04 12:51:37 +12:00
def children(self):
return self.childItems
2015-06-02 10:06:17 +12:00
def columnCount(self):
return len(Outline)
def data(self, column, role=Qt.DisplayRole):
2015-06-05 06:22:37 +12:00
#print("Data: ", column, role)
2015-06-02 10:06:17 +12:00
if role == Qt.DisplayRole or role == Qt.EditRole:
2015-06-27 01:55:34 +12:00
#if column == Outline.compile.value:
#return self.data(column, Qt.CheckStateRole)
2015-06-05 06:22:37 +12:00
2015-06-27 01:55:34 +12:00
if Outline(column) in self._data:
2015-06-02 10:06:17 +12:00
return self._data[Outline(column)]
2015-06-05 21:37:01 +12:00
2015-07-04 04:41:18 +12:00
elif column == Outline.revisions.value:
return []
2015-06-02 10:06:17 +12:00
else:
return ""
2015-06-04 17:08:49 +12:00
2015-06-02 10:06:17 +12:00
elif role == Qt.DecorationRole and column == Outline.title.value:
if self.isFolder():
return QIcon.fromTheme("folder")
2015-06-16 01:46:31 +12:00
elif self.isText():
2015-06-21 21:32:42 +12:00
return QIcon.fromTheme("text-x-generic")
elif self.isT2T():
return QIcon.fromTheme("text-x-script")
elif self.isHTML():
return QIcon.fromTheme("text-html")
2015-06-04 12:04:47 +12:00
2015-06-18 03:15:13 +12:00
#elif role == Qt.ForegroundRole:
#if self.isCompile() in [0, "0"]:
#return QBrush(Qt.gray)
2015-06-04 17:08:49 +12:00
2015-06-04 12:04:47 +12:00
elif role == Qt.CheckStateRole and column == Outline.compile.value:
2015-06-27 01:55:34 +12:00
#print(self.title(), self.compile())
#if self._data[Outline(column)] and not self.compile():
#return Qt.PartiallyChecked
#else:
return self._data[Outline(column)]
2015-06-06 11:13:27 +12:00
elif role == Qt.FontRole:
f = QFont()
if column == Outline.wordCount.value and self.isFolder():
f.setItalic(True)
elif column == Outline.goal.value and self.isFolder() and self.data(Outline.setGoal) == None:
f.setItalic(True)
if self.isFolder():
f.setBold(True)
return f
2015-06-02 10:06:17 +12:00
2015-06-05 06:22:37 +12:00
def setData(self, column, data, role=Qt.DisplayRole):
if role not in [Qt.DisplayRole, Qt.EditRole, Qt.CheckStateRole]:
print(column, column == Outline.text.value, data, role)
return
2015-06-05 21:37:01 +12:00
if column == Outline.text.value and self.isFolder():
# Folder have no text
return
2015-06-05 06:22:37 +12:00
2015-06-05 21:37:01 +12:00
if column == Outline.goal.value:
self._data[Outline.setGoal] = toInt(data) if toInt(data) > 0 else ""
# Checking if we will have to recount words
2015-06-05 21:37:01 +12:00
updateWordCount = False
if column in [Outline.wordCount.value, Outline.goal.value, Outline.setGoal.value]:
2015-06-08 08:06:57 +12:00
updateWordCount = not Outline(column) in self._data or self._data[Outline(column)] != data
# Stuff to do before
if column == Outline.type.value:
oldType = self._data[Outline.type]
if oldType == "html" and data in ["txt", "t2t"]:
# Resource inneficient way to convert HTML to plain text
e = QTextEdit()
e.setHtml(self._data[Outline.text])
self._data[Outline.text] = e.toPlainText()
elif oldType in ["txt", "t2t"] and data == "html" and Outline.text in self._data:
2015-06-25 23:41:55 +12:00
self._data[Outline.text] = self._data[Outline.text].replace("\n", "<br>")
2015-06-07 05:10:44 +12:00
2015-07-04 04:41:18 +12:00
elif column == Outline.text.value:
self.addRevision()
# Setting data
self._data[Outline(column)] = data
# Stuff to do afterwards
if column == Outline.text.value:
wc = wordCount(data)
self.setData(Outline.wordCount.value, wc)
2015-06-27 01:55:34 +12:00
if column == Outline.compile.value:
self.emitDataChanged(cols=[Outline.title.value, Outline.compile.value], recursive=True)
2015-06-05 21:37:01 +12:00
if updateWordCount:
self.updateWordCount()
def updateWordCount(self):
if not self.isFolder():
setGoal = toInt(self.data(Outline.setGoal.value))
goal = toInt(self.data(Outline.goal.value))
2015-06-08 08:06:57 +12:00
if goal != setGoal:
2015-06-05 21:37:01 +12:00
self._data[Outline.goal] = setGoal
if setGoal:
wc = toInt(self.data(Outline.wordCount.value))
self.setData(Outline.goalPercentage.value, wc / float(setGoal))
else:
wc = 0
for c in self.children():
wc += toInt(c.data(Outline.wordCount.value))
self._data[Outline.wordCount] = wc
setGoal = toInt(self.data(Outline.setGoal.value))
goal = toInt(self.data(Outline.goal.value))
if setGoal:
2015-06-08 08:06:57 +12:00
if goal != setGoal:
2015-06-05 21:37:01 +12:00
self._data[Outline.goal] = setGoal
goal = setGoal
2015-06-05 21:37:01 +12:00
else:
goal = 0
for c in self.children():
goal += toInt(c.data(Outline.goal.value))
self._data[Outline.goal] = goal
if goal:
self.setData(Outline.goalPercentage.value, wc / float(goal))
2015-06-21 20:44:11 +12:00
else:
self.setData(Outline.goalPercentage.value, "")
2015-06-05 21:37:01 +12:00
self.emitDataChanged([Outline.goal.value, Outline.setGoal.value,
Outline.wordCount.value, Outline.goalPercentage.value])
2015-06-05 21:37:01 +12:00
if self.parent():
self.parent().updateWordCount()
2015-06-02 10:06:17 +12:00
def row(self):
if self.parent:
return self.parent().childItems.index(self)
def appendChild(self, child):
2015-06-03 00:40:48 +12:00
self.insertChild(self.childCount(), child)
2015-06-02 10:06:17 +12:00
def insertChild(self, row, child):
self.childItems.insert(row, child)
2015-06-03 00:40:48 +12:00
child._parent = self
2015-06-06 11:13:27 +12:00
child.setModel(self._model)
2015-06-27 03:33:55 +12:00
if not child.data(Outline.ID.value):
child.getUniqueID()
2015-06-05 21:37:01 +12:00
self.updateWordCount()
2015-06-06 11:13:27 +12:00
def setModel(self, model):
self._model = model
for c in self.children():
c.setModel(model)
2015-06-05 21:37:01 +12:00
def index(self, column=0):
2015-06-06 11:13:27 +12:00
if self._model:
return self._model.indexFromItem(self, column)
else:
return QModelIndex()
2015-06-05 21:37:01 +12:00
2015-06-27 01:55:34 +12:00
def emitDataChanged(self, cols=None, recursive=False):
2015-06-05 21:37:01 +12:00
idx = self.index()
2015-06-06 11:13:27 +12:00
if idx and self._model:
if not cols:
# Emit data changed for the whole item (all columns)
self._model.dataChanged.emit(idx, self.index(len(Outline)))
2015-06-27 01:55:34 +12:00
else:
# Emit only for the specified columns
for c in cols:
self._model.dataChanged.emit(self.index(c), self.index(c))
2015-06-27 01:55:34 +12:00
if recursive:
for c in self.children():
c.emitDataChanged(cols, recursive=True)
2015-06-02 10:06:17 +12:00
def removeChild(self, row):
self.childItems.pop(row)
self.updateWordCount()
2015-06-02 10:06:17 +12:00
def parent(self):
return self._parent
def type(self):
return self._data[Outline.type]
2015-06-02 10:06:17 +12:00
def isFolder(self):
return self._data[Outline.type] == "folder"
def isT2T(self):
return self._data[Outline.type] == "t2t"
def isHTML(self):
return self._data[Outline.type] == "html"
def isText(self):
return self._data[Outline.type] == "txt"
2015-07-01 23:14:03 +12:00
def text(self):
return self.data(Outline.text.value)
2015-06-27 01:55:34 +12:00
def compile(self):
if self._data[Outline.compile] in ["0", 0]:
return False
elif self.parent():
return self.parent().compile()
else:
return True # rootItem always compile
2015-06-04 17:08:49 +12:00
2015-06-05 21:37:01 +12:00
def title(self):
if Outline.title in self._data:
return self._data[Outline.title]
else:
return ""
def ID(self):
return self.data(Outline.ID.value)
2015-06-05 21:37:01 +12:00
2015-06-30 22:27:43 +12:00
def POV(self):
return self.data(Outline.POV.value)
2015-07-01 00:01:32 +12:00
def status(self):
return self.data(Outline.status.value)
def label(self):
return self.data(Outline.label.value)
2015-06-06 11:32:52 +12:00
def path(self):
2015-06-30 22:27:43 +12:00
"Returns path to item as string."
2015-06-06 11:32:52 +12:00
if self.parent().parent():
return "{} > {}".format(self.parent().path(), self.title())
else:
return self.title()
2015-06-30 22:27:43 +12:00
def pathID(self):
"Returns path to item as list of (ID, title)."
if self.parent().parent():
return self.parent().pathID() + [(self.ID(), self.title())]
else:
return [(self.ID(), self.title())]
2015-06-05 21:37:01 +12:00
def level(self):
if self.parent():
return self.parent().level() + 1
else:
return -1
2015-06-30 22:27:43 +12:00
def stats(self):
wc = self.data(Outline.wordCount.value)
goal = self.data(Outline.goal.value)
progress = self.data(Outline.goalPercentage.value)
if not wc:
wc = 0
if goal:
return qApp.translate("outlineModel", "{} words / {} ({})").format(
locale.format("%d", wc, grouping=True),
locale.format("%d", goal, grouping=True),
"{}%".format(str(int(progress * 100))))
else:
return qApp.translate("outlineModel", "{} words").format(
locale.format("%d", wc, grouping=True))
2015-07-04 04:41:18 +12:00
###############################################################################
# XML
###############################################################################
2015-06-03 00:40:48 +12:00
def toXML(self):
item = ET.Element("outlineItem")
2015-06-06 11:13:27 +12:00
# We don't want to write some datas (computed)
2015-07-04 04:41:18 +12:00
exclude = [Outline.wordCount, Outline.goal, Outline.goalPercentage, Outline.revisions]
2015-06-06 11:13:27 +12:00
# We want to force some data even if they're empty
force = [Outline.compile]
2015-06-05 21:37:01 +12:00
2015-06-03 00:40:48 +12:00
for attrib in Outline:
2015-06-05 21:37:01 +12:00
if attrib in exclude: continue
2015-06-05 06:22:37 +12:00
val = self.data(attrib.value)
2015-06-06 11:13:27 +12:00
if val or attrib in force:
2015-06-08 08:06:57 +12:00
item.set(attrib.name, str(val))
2015-07-04 04:41:18 +12:00
# Saving revisions
rev = self.revisions()
for r in rev:
revItem = ET.Element("revision")
revItem.set("timestamp", str(r[0]))
revItem.set("text", r[1])
item.append(revItem)
2015-06-03 00:40:48 +12:00
for i in self.childItems:
item.append(ET.XML(i.toXML()))
return ET.tostring(item)
def setFromXML(self, xml):
root = ET.XML(xml)
for k in root.attrib:
if k in Outline.__members__:
2015-06-06 11:13:27 +12:00
#if k == Outline.compile:
#self.setData(Outline.__members__[k].value, unicode(root.attrib[k]), Qt.CheckStateRole)
#else:
2015-06-08 08:06:57 +12:00
self.setData(Outline.__members__[k].value, str(root.attrib[k]))
2015-06-03 00:40:48 +12:00
for child in root:
2015-07-04 04:41:18 +12:00
if child.tag == "outlineItem":
item = outlineItem(self._model, xml=ET.tostring(child), parent=self)
elif child.tag == "revision":
self.appendRevision(child.attrib["timestamp"], child.attrib["text"])
###############################################################################
# IDS
###############################################################################
2015-06-27 03:33:55 +12:00
def getUniqueID(self):
self.setData(Outline.ID.value, self._model.rootItem.findUniqueID())
def checkIDs(self):
"""This is called when a model is loaded.
Makes a list of all sub-items IDs, that is used to generate unique IDs afterwards.
"""
self.IDs = self.listAllIDs()
if max([self.IDs.count(i) for i in self.IDs if i]) != 1:
print("There are some doublons:", [i for i in self.IDs if i and self.IDs.count(i) != 1])
def checkChildren(item):
for c in item.children():
_id = c.data(Outline.ID.value)
if not _id or _id == "0":
c.getUniqueID()
checkChildren(c)
checkChildren(self)
def listAllIDs(self):
IDs = [self.data(Outline.ID.value)]
for c in self.children():
IDs.extend(c.listAllIDs())
return IDs
def findUniqueID(self):
k = 0
while str(k) in self.IDs:
k += 1
self.IDs.append(str(k))
return str(k)
def pathToItem(self):
path = self.data(Outline.ID.value)
if self.parent().parent():
path = "{}:{}".format(self.parent().pathToItem(), path)
2015-07-01 00:01:32 +12:00
return path
def findItemsByPOV(self, POV):
"Returns a list of IDs of all subitems whose POV is ``POV``."
lst = []
if self.POV() == POV:
lst.append(self.ID())
for c in self.children():
lst.extend(c.findItemsByPOV(POV))
return lst
2015-07-01 01:38:14 +12:00
def findItemsContaining(self, text, columns, mainWindow, caseSensitive=False):
2015-07-01 00:01:32 +12:00
"""Returns a list if IDs of all subitems
2015-07-01 01:38:14 +12:00
containing ``text`` in columns ``columns``
(being a list of int).
2015-07-01 00:01:32 +12:00
"""
lst = []
2015-07-01 01:38:14 +12:00
text = text.lower() if not caseSensitive else text
for c in columns:
if c == Outline.POV.value:
searchIn = mainWindow.mdlPersos.getPersoNameByID(self.POV())
elif c == Outline.status.value:
searchIn = mainWindow.mdlStatus.item(toInt(self.status()), 0).text()
elif c == Outline.label.value:
searchIn = mainWindow.mdlLabels.item(toInt(self.label()), 0).text()
else:
searchIn = self.data(c)
searchIn = searchIn.lower() if not caseSensitive else searchIn
if text in searchIn:
if not self.ID() in lst:
lst.append(self.ID())
2015-07-01 00:01:32 +12:00
for c in self.children():
2015-07-01 01:38:14 +12:00
lst.extend(c.findItemsContaining(text, columns, mainWindow, caseSensitive))
2015-07-01 00:01:32 +12:00
2015-07-04 04:41:18 +12:00
return lst
###############################################################################
# REVISIONS
###############################################################################
def revisions(self):
return self.data(Outline.revisions.value)
def appendRevision(self, ts, text):
if not Outline.revisions in self._data:
self._data[Outline.revisions] = []
self._data[Outline.revisions].append((
int(ts),
text))
def addRevision(self):
2015-07-04 09:00:54 +12:00
if not settings.revisions["keep"]:
return
2015-07-04 04:41:18 +12:00
if not Outline.text in self._data:
return
self.appendRevision(
time.time(),
self._data[Outline.text])
2015-07-04 09:00:54 +12:00
if settings.revisions["smartremove"]:
self.cleanRevisions()
self.emitDataChanged([Outline.revisions.value])
def deleteRevision(self, ts):
self._data[Outline.revisions] = [r for r in self._data[Outline.revisions] if r[0] != ts]
self.emitDataChanged([Outline.revisions.value])
def clearAllRevisions(self):
self._data[Outline.revisions] = []
self.emitDataChanged([Outline.revisions.value])
2015-07-04 04:41:18 +12:00
def cleanRevisions(self):
"Keep only one some the revisions."
rev = self.revisions()
rev2 = []
now = time.time()
2015-07-04 09:00:54 +12:00
rule = settings.revisions["rules"]
2015-07-04 04:41:18 +12:00
revs = {}
for i in rule:
revs[i] = []
for r in rev:
# Have to put the lambda key otherwise cannot order when one element is None
for span in sorted(rule, key=lambda x:x if x else 60*60*24*30*365):
2015-07-04 04:41:18 +12:00
if not span or now - r[0] < span:
revs[span].append(r)
break
2015-07-04 04:41:18 +12:00
for span in revs:
sortedRev = sorted(revs[span], key=lambda x:x[0])
last = None
for r in sortedRev:
if not last:
rev2.append(r)
last = r[0]
elif r[0] - last >= rule[span]:
rev2.append(r)
last = r[0]
if rev2 != rev:
self._data[Outline.revisions] = rev2
self.emitDataChanged([Outline.revisions.value])