manuskript/manuskript/models/outlineItem.py

365 lines
11 KiB
Python
Raw Normal View History

2017-11-16 08:33:27 +13:00
#!/usr/bin/env python
# --!-- coding: utf8 --!--
2017-11-18 00:16:39 +13:00
from PyQt5.QtCore import Qt
2017-11-16 08:33:27 +13:00
from manuskript.models.abstractItem import abstractItem
2017-11-18 00:16:39 +13:00
from manuskript import enums
from manuskript.functions import mainWindow, toInt
from manuskript import settings
import time
2017-11-16 08:33:27 +13:00
class outlineItem(abstractItem):
2017-11-18 00:16:39 +13:00
enum = enums.Outline
# Used for XML export
name = "outlineItem"
2017-11-16 08:33:27 +13:00
def __init__(self, model=None, title="", _type="folder", xml=None, parent=None, ID=None):
abstractItem.__init__(self, model, title, _type, xml, parent, ID)
2017-11-18 00:16:39 +13:00
self.defaultTextType = None
self._data[self.enum.compile] = Qt.Checked
#######################################################################
# Properties
#######################################################################
def isFolder(self):
return self._data[self.enum.type] == "folder"
def isText(self):
return self._data[self.enum.type] == "md"
def isMD(self):
return self._data[self.enum.type] == "md"
def isMMD(self):
return self._data[self.enum.type] == "md"
def text(self):
return self.data(self.enum.text)
def compile(self):
if self._data[self.enum.compile] in ["0", 0]:
return False
elif self.parent():
return self.parent().compile()
else:
return True # rootItem always compile
def POV(self):
return self.data(self.enum.POV)
def status(self):
return self.data(self.enum.status)
def label(self):
return self.data(self.enum.label)
def customIcon(self):
return self.data(self.enum.customIcon)
def setCustomIcon(self, customIcon):
self.setData(self.enum.customIcon, customIcon)
#######################################################################
# Wordcount
#######################################################################
def insertChild(self, row, child):
abstractItem.insertChild(self, row, child)
self.updateWordCount()
def removeChild(self, row):
r = abstractItem.removeChild(self, row)
# Might be causing segfault when updateWordCount emits dataChanged
self.updateWordCount(emit=False)
return r
def updateWordCount(self, emit=True):
"""Update word count for item and parents.
If emit is False, no signal is emitted (sometimes cause segfault)"""
if not self.isFolder():
setGoal = toInt(self.data(self.enum.setGoal))
goal = toInt(self.data(self.enum.goal))
if goal != setGoal:
self._data[self.enum.goal] = setGoal
if setGoal:
wc = toInt(self.data(self.enum.wordCount))
self.setData(self.enum.goalPercentage, wc / float(setGoal))
else:
wc = 0
for c in self.children():
wc += toInt(c.data(self.enum.wordCount))
self._data[self.enum.wordCount] = wc
setGoal = toInt(self.data(self.enum.setGoal))
goal = toInt(self.data(self.enum.goal))
if setGoal:
if goal != setGoal:
self._data[self.enum.goal] = setGoal
goal = setGoal
else:
goal = 0
for c in self.children():
goal += toInt(c.data(self.enum.goal))
self._data[self.enum.goal] = goal
if goal:
self.setData(self.enum.goalPercentage, wc / float(goal))
else:
self.setData(self.enum.goalPercentage, "")
if emit:
self.emitDataChanged([self.enum.goal, self.enum.setGoal,
self.enum.wordCount, self.enum.goalPercentage])
if self.parent():
self.parent().updateWordCount(emit)
def stats(self):
wc = self.data(Outline.wordCount)
goal = self.data(Outline.goal)
progress = self.data(Outline.goalPercentage)
if not wc:
wc = 0
if goal:
return qApp.translate("outlineItem", "{} words / {} ({})").format(
locale.format("%d", wc, grouping=True),
locale.format("%d", goal, grouping=True),
"{}%".format(str(int(progress * 100))))
else:
return qApp.translate("outlineItem", "{} words").format(
locale.format("%d", wc, grouping=True))
#######################################################################
# Tools: split and merge
#######################################################################
def split(self, splitMark, recursive=True):
"""
Split scene at splitMark. If multiple splitMark, multiple splits.
If called on a folder and recursive is True, then it is recursively
applied to every children.
"""
if self.isFolder() and recursive:
for c in self.children():
c.split(splitMark)
else:
txt = self.text().split(splitMark)
if len(txt) == 1:
# Mark not found
return False
else:
# Stores the new text
self.setData(self.enum.text, txt[0])
k = 1
for subTxt in txt[1:]:
# Create a copy
item = self.copy()
# Change title adding _k
item.setData(self.enum.title,
"{}_{}".format(item.title(), k+1))
# Set text
item.setData(self.enum.text, subTxt)
# Inserting item
#self.parent().insertChild(self.row()+k, item)
self._model.insertItem(item, self.row()+k, self.parent().index())
k += 1
def splitAt(self, position, length=0):
"""
Splits note at position p.
If length is bigger than 0, it describes the length of the title, made
from the character following position.
"""
txt = self.text()
# Stores the new text
self.setData(self.enum.text, txt[:position])
# Create a copy
item = self.copy()
# Update title
if length > 0:
title = txt[position:position+length].replace("\n", "")
else:
title = "{}_{}".format(item.title(), 2)
item.setData(self.enum.title, title)
# Set text
item.setData(self.enum.text, txt[position+length:])
# Inserting item using the model to signal views
self._model.insertItem(item, self.row()+1, self.parent().index())
def mergeWith(self, items, sep="\n\n"):
"""
Merges item with several other items. Merge is basic, it merges only
the text.
@param items: list of `outlineItem`s.
@param sep: a text added between each item's text.
"""
# Merges the texts
text = [self.text()]
text.extend([i.text() for i in items])
self.setData(self.enum.text, sep.join(text))
# Removes other items
self._model.removeIndexes([i.index() for i in items])
#######################################################################
# Search
#######################################################################
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
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:
if c == self.enum.POV and self.POV():
c = mainWindow.mdlCharacter.getCharacterByID(self.POV())
if c:
searchIn = c.name()
else:
searchIn = ""
print("Character POV not found:", self.POV())
elif c == self.enum.status:
searchIn = mainWindow.mdlStatus.item(toInt(self.status()), 0).text()
elif c == self.enum.label:
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())
return lst
###############################################################################
# REVISIONS
###############################################################################
def revisions(self):
return self.data(self.enum.revisions)
def appendRevision(self, ts, text):
if not self.enum.revisions in self._data:
self._data[self.enum.revisions] = []
self._data[self.enum.revisions].append((
int(ts),
text))
def addRevision(self):
if not settings.revisions["keep"]:
return
if not self.enum.text in self._data:
return
self.appendRevision(
time.time(),
self._data[self.enum.text])
if settings.revisions["smartremove"]:
self.cleanRevisions()
self.emitDataChanged([self.enum.revisions])
def deleteRevision(self, ts):
self._data[self.enum.revisions] = [r for r in self._data[self.enum.revisions] if r[0] != ts]
self.emitDataChanged([self.enum.revisions])
def clearAllRevisions(self):
self._data[self.enum.revisions] = []
self.emitDataChanged([self.enum.revisions])
def cleanRevisions(self):
"Keep only one some the revisions."
rev = self.revisions()
rev2 = []
now = time.time()
rule = settings.revisions["rules"]
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):
if not span or now - r[0] < span:
revs[span].append(r)
break
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[self.enum.revisions] = rev2
self.emitDataChanged([self.enum.revisions])