mirror of
https://github.com/olivierkes/manuskript.git
synced 2024-06-11 07:24:35 +12:00
Checkpoint in refactoring outlineItem
This commit is contained in:
parent
cf4c1c83bf
commit
ad01de4cd4
|
@ -60,3 +60,8 @@ class Outline(IntEnum):
|
||||||
textFormat = 15
|
textFormat = 15
|
||||||
revisions = 16
|
revisions = 16
|
||||||
customIcon = 17
|
customIcon = 17
|
||||||
|
|
||||||
|
class Abstract(IntEnum):
|
||||||
|
title = 0
|
||||||
|
ID = 1
|
||||||
|
type = 2
|
||||||
|
|
|
@ -15,6 +15,7 @@ from manuskript import settings
|
||||||
from lxml import etree as ET
|
from lxml import etree as ET
|
||||||
|
|
||||||
from manuskript.enums import Outline
|
from manuskript.enums import Outline
|
||||||
|
from manuskript import enums
|
||||||
from manuskript.functions import mainWindow, toInt, wordCount
|
from manuskript.functions import mainWindow, toInt, wordCount
|
||||||
from manuskript.converters import HTML2PlainText
|
from manuskript.converters import HTML2PlainText
|
||||||
|
|
||||||
|
@ -24,26 +25,30 @@ except:
|
||||||
# Invalid locale, but not really a big deal because it's used only for
|
# Invalid locale, but not really a big deal because it's used only for
|
||||||
# number formating
|
# number formating
|
||||||
pass
|
pass
|
||||||
import time, os
|
import os
|
||||||
|
|
||||||
|
|
||||||
class abstractItem():
|
class abstractItem():
|
||||||
def __init__(self, model=None, title="", _type="folder", xml=None, parent=None, ID=None):
|
|
||||||
|
# Enum kept on the class for easier acces
|
||||||
|
enum = enums.Abstract
|
||||||
|
|
||||||
|
# Used for XML export
|
||||||
|
name = "abstractItem"
|
||||||
|
|
||||||
|
def __init__(self, model=None, title="", _type="abstract", xml=None, parent=None, ID=None):
|
||||||
|
|
||||||
self._data = {}
|
self._data = {}
|
||||||
self.childItems = []
|
self.childItems = []
|
||||||
self._parent = None
|
self._parent = None
|
||||||
self._model = model
|
self._model = model
|
||||||
self.defaultTextType = None
|
|
||||||
self.IDs = ["0"] # used by root item to store unique IDs
|
self.IDs = ["0"] # used by root item to store unique IDs
|
||||||
self._lastPath = "" # used by loadSave version_1 to remember which files the items comes from,
|
self._lastPath = "" # used by loadSave version_1 to remember which files the items comes from,
|
||||||
# in case it is renamed / removed
|
# in case it is renamed / removed
|
||||||
|
|
||||||
if title:
|
self._data[self.enum.title] = title
|
||||||
self._data[Outline.title] = title
|
self._data[self.enum.type] = _type
|
||||||
|
|
||||||
self._data[Outline.type] = _type
|
|
||||||
self._data[Outline.compile] = Qt.Checked
|
|
||||||
|
|
||||||
if xml is not None:
|
if xml is not None:
|
||||||
self.setFromXML(xml)
|
self.setFromXML(xml)
|
||||||
|
@ -52,7 +57,67 @@ class abstractItem():
|
||||||
parent.appendChild(self)
|
parent.appendChild(self)
|
||||||
|
|
||||||
if ID:
|
if ID:
|
||||||
self._data[Outline.ID] = ID
|
self._data[self.enum.ID] = ID
|
||||||
|
|
||||||
|
#######################################################################
|
||||||
|
# Model
|
||||||
|
#######################################################################
|
||||||
|
|
||||||
|
def setModel(self, model):
|
||||||
|
self._model = model
|
||||||
|
for c in self.children():
|
||||||
|
c.setModel(model)
|
||||||
|
|
||||||
|
def index(self, column=0):
|
||||||
|
if self._model:
|
||||||
|
return self._model.indexFromItem(self, column)
|
||||||
|
else:
|
||||||
|
return QModelIndex()
|
||||||
|
|
||||||
|
def emitDataChanged(self, cols=None, recursive=False):
|
||||||
|
"""
|
||||||
|
Emits the dataChanged signal of the model, to signal views that data
|
||||||
|
have changed.
|
||||||
|
|
||||||
|
@param cols: an array of int (or None). The columns of the index that
|
||||||
|
have been changed.
|
||||||
|
@param recursive: boolean. If true, all children will also emit the
|
||||||
|
dataChanged signal.
|
||||||
|
"""
|
||||||
|
idx = self.index()
|
||||||
|
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(self.enum)))
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Emit only for the specified columns
|
||||||
|
for c in cols:
|
||||||
|
self._model.dataChanged.emit(self.index(c), self.index(c))
|
||||||
|
|
||||||
|
if recursive:
|
||||||
|
for c in self.children():
|
||||||
|
c.emitDataChanged(cols, recursive=True)
|
||||||
|
|
||||||
|
#######################################################################
|
||||||
|
# Properties
|
||||||
|
#######################################################################
|
||||||
|
|
||||||
|
def title(self):
|
||||||
|
return self._data.get(self.enum.title, "")
|
||||||
|
|
||||||
|
def ID(self):
|
||||||
|
return self._data.get(self.enum.ID, 0)
|
||||||
|
|
||||||
|
def columnCount(self):
|
||||||
|
return len(self.enum)
|
||||||
|
|
||||||
|
def type(self):
|
||||||
|
return self._data[self.enum.type]
|
||||||
|
|
||||||
|
#######################################################################
|
||||||
|
# Parent / Children managment
|
||||||
|
#######################################################################
|
||||||
|
|
||||||
def child(self, row):
|
def child(self, row):
|
||||||
return self.childItems[row]
|
return self.childItems[row]
|
||||||
|
@ -69,8 +134,111 @@ class abstractItem():
|
||||||
def children(self):
|
def children(self):
|
||||||
return self.childItems
|
return self.childItems
|
||||||
|
|
||||||
def columnCount(self):
|
def row(self):
|
||||||
return len(Outline)
|
if self.parent():
|
||||||
|
return self.parent().childItems.index(self)
|
||||||
|
|
||||||
|
def appendChild(self, child):
|
||||||
|
self.insertChild(self.childCount(), child)
|
||||||
|
|
||||||
|
def insertChild(self, row, child):
|
||||||
|
self.childItems.insert(row, child)
|
||||||
|
child._parent = self
|
||||||
|
child.setModel(self._model)
|
||||||
|
if not child.ID():
|
||||||
|
child.getUniqueID()
|
||||||
|
|
||||||
|
def removeChild(self, row):
|
||||||
|
"""
|
||||||
|
Removes child at position `row` and returns it.
|
||||||
|
@param row: index (int) of the child to remove.
|
||||||
|
@return: the removed abstractItem
|
||||||
|
"""
|
||||||
|
r = self.childItems.pop(row)
|
||||||
|
return r
|
||||||
|
|
||||||
|
def parent(self):
|
||||||
|
return self._parent
|
||||||
|
|
||||||
|
def path(self, sep=" > "):
|
||||||
|
"Returns path to item as string."
|
||||||
|
if self.parent().parent():
|
||||||
|
return "{parent}{sep}{title}".format(
|
||||||
|
parent=self.parent().path(),
|
||||||
|
sep=sep,
|
||||||
|
title=self.title())
|
||||||
|
else:
|
||||||
|
return self.title()
|
||||||
|
|
||||||
|
def pathID(self):
|
||||||
|
"Returns path to item as list of (ID, title)."
|
||||||
|
if self.parent() and self.parent().parent():
|
||||||
|
return self.parent().pathID() + [(self.ID(), self.title())]
|
||||||
|
else:
|
||||||
|
return [(self.ID(), self.title())]
|
||||||
|
|
||||||
|
def level(self):
|
||||||
|
"""Returns the level of the current item. Root item returns -1."""
|
||||||
|
if self.parent():
|
||||||
|
return self.parent().level() + 1
|
||||||
|
else:
|
||||||
|
return -1
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
"""
|
||||||
|
Returns a copy of item, with no parent, and no ID.
|
||||||
|
"""
|
||||||
|
item = self.__class__(xml=self.toXML())
|
||||||
|
item.setData(self.enum.ID, None)
|
||||||
|
return item
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# IDS
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
def getUniqueID(self, recursive=False):
|
||||||
|
self.setData(Outline.ID, self._model.rootItem.findUniqueID())
|
||||||
|
|
||||||
|
if recursive:
|
||||||
|
for c in self.children():
|
||||||
|
c.getUniqueID(recursive)
|
||||||
|
|
||||||
|
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("WARNING ! There are some items with same IDs:", [i for i in self.IDs if i and self.IDs.count(i) != 1])
|
||||||
|
|
||||||
|
def checkChildren(item):
|
||||||
|
for c in item.children():
|
||||||
|
_id = c.ID()
|
||||||
|
if not _id or _id == "0":
|
||||||
|
c.getUniqueID()
|
||||||
|
checkChildren(c)
|
||||||
|
|
||||||
|
checkChildren(self)
|
||||||
|
|
||||||
|
def listAllIDs(self):
|
||||||
|
IDs = [self.ID()]
|
||||||
|
for c in self.children():
|
||||||
|
IDs.extend(c.listAllIDs())
|
||||||
|
return IDs
|
||||||
|
|
||||||
|
def findUniqueID(self):
|
||||||
|
IDs = [int(i) for i in self.IDs]
|
||||||
|
k = 1
|
||||||
|
while k in IDs:
|
||||||
|
k += 1
|
||||||
|
self.IDs.append(str(k))
|
||||||
|
return str(k)
|
||||||
|
|
||||||
|
#######################################################################
|
||||||
|
# Data
|
||||||
|
#######################################################################
|
||||||
|
|
||||||
def data(self, column, role=Qt.DisplayRole):
|
def data(self, column, role=Qt.DisplayRole):
|
||||||
|
|
||||||
|
@ -159,297 +327,48 @@ class abstractItem():
|
||||||
if updateWordCount:
|
if updateWordCount:
|
||||||
self.updateWordCount()
|
self.updateWordCount()
|
||||||
|
|
||||||
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(Outline.setGoal))
|
|
||||||
goal = toInt(self.data(Outline.goal))
|
|
||||||
|
|
||||||
if goal != setGoal:
|
|
||||||
self._data[Outline.goal] = setGoal
|
|
||||||
if setGoal:
|
|
||||||
wc = toInt(self.data(Outline.wordCount))
|
|
||||||
self.setData(Outline.goalPercentage, wc / float(setGoal))
|
|
||||||
|
|
||||||
else:
|
|
||||||
wc = 0
|
|
||||||
for c in self.children():
|
|
||||||
wc += toInt(c.data(Outline.wordCount))
|
|
||||||
self._data[Outline.wordCount] = wc
|
|
||||||
|
|
||||||
setGoal = toInt(self.data(Outline.setGoal))
|
|
||||||
goal = toInt(self.data(Outline.goal))
|
|
||||||
|
|
||||||
if setGoal:
|
|
||||||
if goal != setGoal:
|
|
||||||
self._data[Outline.goal] = setGoal
|
|
||||||
goal = setGoal
|
|
||||||
else:
|
|
||||||
goal = 0
|
|
||||||
for c in self.children():
|
|
||||||
goal += toInt(c.data(Outline.goal))
|
|
||||||
self._data[Outline.goal] = goal
|
|
||||||
|
|
||||||
if goal:
|
|
||||||
self.setData(Outline.goalPercentage, wc / float(goal))
|
|
||||||
else:
|
|
||||||
self.setData(Outline.goalPercentage, "")
|
|
||||||
|
|
||||||
if emit:
|
|
||||||
self.emitDataChanged([Outline.goal, Outline.setGoal,
|
|
||||||
Outline.wordCount, Outline.goalPercentage])
|
|
||||||
|
|
||||||
if self.parent():
|
|
||||||
self.parent().updateWordCount(emit)
|
|
||||||
|
|
||||||
def row(self):
|
|
||||||
if self.parent():
|
|
||||||
return self.parent().childItems.index(self)
|
|
||||||
|
|
||||||
def appendChild(self, child):
|
|
||||||
self.insertChild(self.childCount(), child)
|
|
||||||
|
|
||||||
def insertChild(self, row, child):
|
|
||||||
self.childItems.insert(row, child)
|
|
||||||
child._parent = self
|
|
||||||
child.setModel(self._model)
|
|
||||||
if not child.data(Outline.ID):
|
|
||||||
child.getUniqueID()
|
|
||||||
self.updateWordCount()
|
|
||||||
|
|
||||||
def setModel(self, model):
|
|
||||||
self._model = model
|
|
||||||
for c in self.children():
|
|
||||||
c.setModel(model)
|
|
||||||
|
|
||||||
def index(self, column=0):
|
|
||||||
if self._model:
|
|
||||||
return self._model.indexFromItem(self, column)
|
|
||||||
else:
|
|
||||||
return QModelIndex()
|
|
||||||
|
|
||||||
def emitDataChanged(self, cols=None, recursive=False):
|
|
||||||
idx = self.index()
|
|
||||||
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)))
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Emit only for the specified columns
|
|
||||||
for c in cols:
|
|
||||||
self._model.dataChanged.emit(self.index(c), self.index(c))
|
|
||||||
|
|
||||||
if recursive:
|
|
||||||
for c in self.children():
|
|
||||||
c.emitDataChanged(cols, recursive=True)
|
|
||||||
|
|
||||||
def removeChild(self, row):
|
|
||||||
"""
|
|
||||||
Removes child at position `row` and returns it.
|
|
||||||
@param row: index (int) of the child to remove.
|
|
||||||
@return: the removed outlineItem
|
|
||||||
"""
|
|
||||||
r = self.childItems.pop(row)
|
|
||||||
# Might be causing segfault when updateWordCount emits dataChanged
|
|
||||||
self.updateWordCount(emit=False)
|
|
||||||
return r
|
|
||||||
|
|
||||||
def parent(self):
|
|
||||||
return self._parent
|
|
||||||
|
|
||||||
def type(self):
|
|
||||||
return self._data[Outline.type]
|
|
||||||
|
|
||||||
def isFolder(self):
|
|
||||||
return self._data[Outline.type] == "folder"
|
|
||||||
|
|
||||||
def isText(self):
|
|
||||||
return self._data[Outline.type] == "md"
|
|
||||||
|
|
||||||
def isMD(self):
|
|
||||||
return self._data[Outline.type] == "md"
|
|
||||||
|
|
||||||
def isMMD(self):
|
|
||||||
return self._data[Outline.type] == "md"
|
|
||||||
|
|
||||||
def customIcon(self):
|
|
||||||
return self.data(Outline.customIcon)
|
|
||||||
|
|
||||||
def setCustomIcon(self, customIcon):
|
|
||||||
self.setData(Outline.customIcon, customIcon)
|
|
||||||
|
|
||||||
def text(self):
|
|
||||||
return self.data(Outline.text)
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
def title(self):
|
|
||||||
if Outline.title in self._data:
|
|
||||||
return self._data[Outline.title]
|
|
||||||
else:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def ID(self):
|
|
||||||
return self.data(Outline.ID)
|
|
||||||
|
|
||||||
def POV(self):
|
|
||||||
return self.data(Outline.POV)
|
|
||||||
|
|
||||||
def status(self):
|
|
||||||
return self.data(Outline.status)
|
|
||||||
|
|
||||||
def label(self):
|
|
||||||
return self.data(Outline.label)
|
|
||||||
|
|
||||||
def path(self):
|
|
||||||
"Returns path to item as string."
|
|
||||||
if self.parent().parent():
|
|
||||||
return "{} > {}".format(self.parent().path(), self.title())
|
|
||||||
else:
|
|
||||||
return self.title()
|
|
||||||
|
|
||||||
def pathID(self):
|
|
||||||
"Returns path to item as list of (ID, title)."
|
|
||||||
if self.parent() and self.parent().parent():
|
|
||||||
return self.parent().pathID() + [(self.ID(), self.title())]
|
|
||||||
else:
|
|
||||||
return [(self.ID(), self.title())]
|
|
||||||
|
|
||||||
def level(self):
|
|
||||||
"""Returns the level of the current item. Root item returns -1."""
|
|
||||||
if self.parent():
|
|
||||||
return self.parent().level() + 1
|
|
||||||
else:
|
|
||||||
return -1
|
|
||||||
|
|
||||||
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("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))
|
|
||||||
|
|
||||||
def copy(self):
|
|
||||||
"""
|
|
||||||
Returns a copy of item, with no parent, and no ID.
|
|
||||||
"""
|
|
||||||
item = outlineItem(xml=self.toXML())
|
|
||||||
item.setData(Outline.ID, None)
|
|
||||||
return item
|
|
||||||
|
|
||||||
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(Outline.text, txt[0])
|
|
||||||
|
|
||||||
k = 1
|
|
||||||
for subTxt in txt[1:]:
|
|
||||||
# Create a copy
|
|
||||||
item = self.copy()
|
|
||||||
|
|
||||||
# Change title adding _k
|
|
||||||
item.setData(Outline.title,
|
|
||||||
"{}_{}".format(item.title(), k+1))
|
|
||||||
|
|
||||||
# Set text
|
|
||||||
item.setData(Outline.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(Outline.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(Outline.title, title)
|
|
||||||
|
|
||||||
# Set text
|
|
||||||
item.setData(Outline.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(Outline.text, sep.join(text))
|
|
||||||
|
|
||||||
# Removes other items
|
|
||||||
self._model.removeIndexes([i.index() for i in items])
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# XML
|
# XML
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
def toXML(self):
|
# We don't want to write some datas (computed)
|
||||||
item = ET.Element("outlineItem")
|
XMLExclude = [Outline.wordCount, Outline.goal, Outline.goalPercentage, Outline.revisions]
|
||||||
|
# We want to force some data even if they're empty
|
||||||
|
XMLForce = [Outline.compile]
|
||||||
|
|
||||||
# We don't want to write some datas (computed)
|
def toXML(self):
|
||||||
exclude = [Outline.wordCount, Outline.goal, Outline.goalPercentage, Outline.revisions]
|
item = ET.Element(self.name)
|
||||||
# We want to force some data even if they're empty
|
|
||||||
force = [Outline.compile]
|
## We don't want to write some datas (computed)
|
||||||
|
#exclude = [Outline.wordCount, Outline.goal, Outline.goalPercentage, Outline.revisions]
|
||||||
|
## We want to force some data even if they're empty
|
||||||
|
#force = [Outline.compile]
|
||||||
|
|
||||||
|
for attrib in self.enum:
|
||||||
|
if attrib in self.XMLExclude:
|
||||||
|
continue
|
||||||
|
val = self.data(attrib)
|
||||||
|
if val or attrib in self.XMLForce:
|
||||||
|
item.set(attrib.name, str(val))
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
# Saving lastPath
|
||||||
|
item.set("lastPath", self._lastPath)
|
||||||
|
|
||||||
|
for i in self.childItems:
|
||||||
|
item.append(ET.XML(i.toXML()))
|
||||||
|
|
||||||
|
return ET.tostring(item)
|
||||||
|
|
||||||
|
def toXML_(self):
|
||||||
|
item = ET.Element("outlineItem")
|
||||||
|
|
||||||
for attrib in Outline:
|
for attrib in Outline:
|
||||||
if attrib in exclude: continue
|
if attrib in exclude: continue
|
||||||
|
@ -501,179 +420,3 @@ class abstractItem():
|
||||||
elif child.tag == "revision":
|
elif child.tag == "revision":
|
||||||
self.appendRevision(child.attrib["timestamp"], child.attrib["text"])
|
self.appendRevision(child.attrib["timestamp"], child.attrib["text"])
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# IDS
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
def getUniqueID(self, recursive=False):
|
|
||||||
self.setData(Outline.ID, self._model.rootItem.findUniqueID())
|
|
||||||
|
|
||||||
if recursive:
|
|
||||||
for c in self.children():
|
|
||||||
c.getUniqueID(recursive)
|
|
||||||
|
|
||||||
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("WARNING ! There are some items with same IDs:", [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)
|
|
||||||
if not _id or _id == "0":
|
|
||||||
c.getUniqueID()
|
|
||||||
checkChildren(c)
|
|
||||||
|
|
||||||
checkChildren(self)
|
|
||||||
|
|
||||||
def listAllIDs(self):
|
|
||||||
IDs = [self.data(Outline.ID)]
|
|
||||||
for c in self.children():
|
|
||||||
IDs.extend(c.listAllIDs())
|
|
||||||
return IDs
|
|
||||||
|
|
||||||
def findUniqueID(self):
|
|
||||||
IDs = [int(i) for i in self.IDs]
|
|
||||||
k = 1
|
|
||||||
while k in IDs:
|
|
||||||
k += 1
|
|
||||||
self.IDs.append(str(k))
|
|
||||||
return str(k)
|
|
||||||
|
|
||||||
def pathToItem(self):
|
|
||||||
path = self.data(Outline.ID)
|
|
||||||
if self.parent().parent():
|
|
||||||
path = "{}:{}".format(self.parent().pathToItem(), path)
|
|
||||||
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
|
|
||||||
|
|
||||||
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 == Outline.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 == Outline.status:
|
|
||||||
searchIn = mainWindow.mdlStatus.item(toInt(self.status()), 0).text()
|
|
||||||
|
|
||||||
elif c == Outline.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(Outline.revisions)
|
|
||||||
|
|
||||||
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):
|
|
||||||
if not settings.revisions["keep"]:
|
|
||||||
return
|
|
||||||
|
|
||||||
if not Outline.text in self._data:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.appendRevision(
|
|
||||||
time.time(),
|
|
||||||
self._data[Outline.text])
|
|
||||||
|
|
||||||
if settings.revisions["smartremove"]:
|
|
||||||
self.cleanRevisions()
|
|
||||||
|
|
||||||
self.emitDataChanged([Outline.revisions])
|
|
||||||
|
|
||||||
def deleteRevision(self, ts):
|
|
||||||
self._data[Outline.revisions] = [r for r in self._data[Outline.revisions] if r[0] != ts]
|
|
||||||
self.emitDataChanged([Outline.revisions])
|
|
||||||
|
|
||||||
def clearAllRevisions(self):
|
|
||||||
self._data[Outline.revisions] = []
|
|
||||||
self.emitDataChanged([Outline.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[Outline.revisions] = rev2
|
|
||||||
self.emitDataChanged([Outline.revisions])
|
|
||||||
|
|
|
@ -1,9 +1,364 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# --!-- coding: utf8 --!--
|
# --!-- coding: utf8 --!--
|
||||||
|
|
||||||
|
from PyQt5.QtCore import Qt
|
||||||
from manuskript.models.abstractItem import abstractItem
|
from manuskript.models.abstractItem import abstractItem
|
||||||
|
from manuskript import enums
|
||||||
|
from manuskript.functions import mainWindow, toInt
|
||||||
|
from manuskript import settings
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
class outlineItem(abstractItem):
|
class outlineItem(abstractItem):
|
||||||
|
|
||||||
|
enum = enums.Outline
|
||||||
|
|
||||||
|
# Used for XML export
|
||||||
|
name = "outlineItem"
|
||||||
|
|
||||||
def __init__(self, model=None, title="", _type="folder", xml=None, parent=None, ID=None):
|
def __init__(self, model=None, title="", _type="folder", xml=None, parent=None, ID=None):
|
||||||
abstractItem.__init__(self, model, title, _type, xml, parent, ID)
|
abstractItem.__init__(self, model, title, _type, xml, parent, ID)
|
||||||
|
|
||||||
|
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])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -22,9 +22,11 @@ class treeView(QTreeView, dndView, outlineBasics):
|
||||||
QTreeView.setModel(self, model)
|
QTreeView.setModel(self, model)
|
||||||
|
|
||||||
# Hiding columns
|
# Hiding columns
|
||||||
for c in range(1, self.model().columnCount()):
|
for c in range(self.model().columnCount()):
|
||||||
self.hideColumn(c)
|
self.hideColumn(c)
|
||||||
|
|
||||||
|
self.showColumn(Outline.title)
|
||||||
|
|
||||||
# Setting delegate
|
# Setting delegate
|
||||||
self.titleDelegate = treeTitleDelegate()
|
self.titleDelegate = treeTitleDelegate()
|
||||||
self.setItemDelegateForColumn(Outline.title, self.titleDelegate)
|
self.setItemDelegateForColumn(Outline.title, self.titleDelegate)
|
||||||
|
|
Loading…
Reference in a new issue