449 lines
14 KiB
Python
449 lines
14 KiB
Python
# Simplified tree model (based on a QtAbstractItemModel)
|
|
|
|
from PySide2 import QtCore, QtWidgets
|
|
from Accessor import Getter
|
|
import contextlib
|
|
|
|
Qt = QtCore.Qt
|
|
|
|
class Column :
|
|
"""Column definition"""
|
|
|
|
Flag_Map = dict \
|
|
( no_flag = (Qt.NoItemFlags, False)
|
|
, selectable = (Qt.ItemIsSelectable, True)
|
|
, enabled = (Qt.ItemIsEnabled, True)
|
|
, editable = (Qt.ItemIsEditable, False)
|
|
, drag = (Qt.ItemIsDragEnabled, False)
|
|
, drop = (Qt.ItemIsDropEnabled, False)
|
|
, checkable = (Qt.ItemIsUserCheckable, False)
|
|
, tristate = (Qt.ItemIsTristate, False)
|
|
)
|
|
|
|
Clone_Attributes = ("name", "attr", "getter", "_flags", "model")
|
|
|
|
def __init__ ( self, name, attr = None, getter = None, model = None
|
|
, ** flags
|
|
) :
|
|
self.name = name
|
|
self.attr = attr or name.lower ()
|
|
self.getter = getter or getattr (Getter, self.attr)
|
|
self.model = model
|
|
self._flags = flags
|
|
# end def __init__
|
|
|
|
def background (self, row) :
|
|
return None
|
|
# end def background
|
|
|
|
def __call__ (self, row) :
|
|
return self.getter (row)
|
|
# end def __call__
|
|
|
|
def clone (self, ** overrides) :
|
|
d = dict \
|
|
( (a, overrides.get (a, getattr (self, a)))
|
|
for a in self.Clone_Attributes
|
|
)
|
|
d.update (d.pop ("_flags", {}))
|
|
return self.__class__ (** d)
|
|
# end def clone
|
|
|
|
def checkstate (self, row) :
|
|
return None
|
|
# end def checkstate
|
|
|
|
def decoration (self, row) :
|
|
return None
|
|
# end def decoration
|
|
|
|
def flags (self, row) :
|
|
result = 0
|
|
for attr, (flag, default) in self.Flag_Map.items () :
|
|
v = self._flags.get (attr, default)
|
|
if callable (v) :
|
|
v = v (row)
|
|
if v :
|
|
result |= flag
|
|
return result
|
|
# end def flags
|
|
|
|
def font (self, row) :
|
|
return None
|
|
# end def font
|
|
|
|
def foreground (self, row) :
|
|
return None
|
|
# end def foreground
|
|
|
|
def text_aligment (self, row) :
|
|
return None
|
|
# end def text_aligment
|
|
|
|
# end class Column
|
|
|
|
class _Root_Item_ :
|
|
"""Simple models which can be used as hidden root element of no real root
|
|
element exists in the model
|
|
"""
|
|
parent = None
|
|
|
|
def __init__ (self, * children) :
|
|
self.children = []
|
|
for c in children :
|
|
self.append (c)
|
|
# end def __init__
|
|
|
|
def append (self, child) :
|
|
self.children.append (child)
|
|
child.parent = self
|
|
# end def append
|
|
|
|
def index (self, child) :
|
|
return self.children.index (child)
|
|
# end def index
|
|
|
|
def __delitem__ (self, key) :
|
|
del self.children [key]
|
|
# end def __delitem__
|
|
|
|
def __getitem__ (self, key) :
|
|
return self.children [key]
|
|
# end def __getitem__
|
|
|
|
def __len__ (self) :
|
|
return len (self.children)
|
|
# end def __len__
|
|
|
|
def __setitem__ (self, key, value) :
|
|
self.children [key] = value
|
|
if not isinstance (value, (list, tuple)) :
|
|
value = (value, )
|
|
for v in value :
|
|
v.parent = self
|
|
# end def __setitem__
|
|
|
|
def __str__ (self) :
|
|
return "<Root-Item>"
|
|
# end def __str__
|
|
|
|
# end class _Root_Item_
|
|
|
|
class Model_Adapter (QtCore.QAbstractItemModel) :
|
|
"""The underlying QT class handling the interface to the wrapper class
|
|
"""
|
|
|
|
### QT Interface
|
|
def columnCount (self, parent) :
|
|
return len (self.Columns)
|
|
# end def columnCount
|
|
|
|
def data (self, index, role) :
|
|
#print ("data", role)
|
|
if not index.isValid () :
|
|
return None
|
|
column = index.column ()
|
|
item = index.internalPointer ()
|
|
col_desc = self.Columns [column]
|
|
if role == Qt.DisplayRole or role == Qt.EditRole :
|
|
return col_desc (item)
|
|
elif role == Qt.CheckStateRole :
|
|
return col_desc.checkstate (item)
|
|
elif role == Qt.BackgroundRole :
|
|
return col_desc.background (item)
|
|
elif role == Qt.ForegroundRole :
|
|
return col_desc.foreground (item)
|
|
elif role == Qt.FontRole :
|
|
return col_desc.font (item)
|
|
elif role == Qt.DecorationRole :
|
|
return col_desc.decoration (item)
|
|
elif role == Qt.TextAlignmentRole :
|
|
return col_desc.text_aligment (item)
|
|
return None
|
|
# end def data
|
|
|
|
def flags (self, index) :
|
|
if index.isValid () :
|
|
data = index.internalPointer ()
|
|
col = index.column ()
|
|
return self.Columns [col].flags (data)
|
|
return super ().flags (index)
|
|
# end def flags
|
|
|
|
def index (self, row, column, parent_index, parent = None) :
|
|
#debug = "ID: %d <%-7s>" % \
|
|
# (row, self.item_path (parent_index, ", ") or "R")
|
|
if parent is None :
|
|
if not parent_index.isValid () :
|
|
parent = self.root_item
|
|
else :
|
|
parent = parent_index.internalPointer ()
|
|
try :
|
|
child = parent [row]
|
|
#print ("%s, P<%s>, C<%s>" %(1, parent, child))
|
|
return self.createIndex (row, column, child)
|
|
except IndexError as exc:
|
|
#print (debug, exc)
|
|
return self.invalid_index
|
|
# end def index
|
|
|
|
def index_for_item (self, item, column = 0) :
|
|
if item.parent :
|
|
row = item.parent.index (item)
|
|
return self.createIndex (row, column, item)
|
|
return self.invalid_index
|
|
# end def index_for_item
|
|
|
|
def item_from_index (self, index) :
|
|
if index.isValid () :
|
|
return index.internalPointer ()
|
|
# end def item_from_index
|
|
|
|
@property
|
|
def invalid_index (self) :
|
|
return QtCore.QModelIndex ()
|
|
# end def invalid_index
|
|
|
|
def parent (self, index) :
|
|
if not index.isValid () :
|
|
return self.invalid_index
|
|
item = index.internalPointer ()
|
|
parent = item.parent
|
|
if parent is self.root_item :
|
|
return self.invalid_index
|
|
if parent.parent :
|
|
row = parent.parent.index (parent)
|
|
else :
|
|
row = 0
|
|
return self.createIndex (row, 0, parent)
|
|
# end def parent
|
|
|
|
def rowCount (self, parent_index) :
|
|
if not parent_index.isValid () :
|
|
parent = self.root_item
|
|
else:
|
|
parent = parent_index.internalPointer ()
|
|
#path = self.item_path (parent_index, ", ") or "R"
|
|
#print ("RC: %-7s [%d]" % (path, len (parent)))
|
|
return len (parent)
|
|
# end def rowCount
|
|
|
|
def setData (self, index, value, role) :
|
|
if index.isValid () :
|
|
if self.set_data (index, value, role) :
|
|
self.dataChanged.emit (index, index)
|
|
return True
|
|
return False
|
|
# end def setData
|
|
|
|
### QT Optional
|
|
def headerData (self, section, orientation, role) :
|
|
if ( (orientation == Qt.Horizontal)
|
|
and (role == Qt.DisplayRole)
|
|
) :
|
|
return self.Columns [section].name
|
|
return None
|
|
# end def headerData
|
|
|
|
def hasChildren (self, index) :
|
|
result = True
|
|
if index.isValid () :
|
|
result = bool (len (index.internalPointer ()))
|
|
#path = self.item_path (parent_index, ", ") or "R"
|
|
#print ("HC: %-7s [%s]" % (path, result))
|
|
return result
|
|
# end def hasChildren
|
|
|
|
### mime data handling
|
|
def supportedDropActions (self) :
|
|
default = super ().supportedDropActions ()
|
|
return getattr (self, "supported_drop_actions", default)
|
|
# end def supportedDropActions
|
|
|
|
def mimeTypes (self) :
|
|
model_fct = getattr (self, "mime_types", None)
|
|
result = super ().mimeTypes ()
|
|
if model_fct is not None :
|
|
result = model_fct (result)
|
|
return result
|
|
# end def mimeTypes
|
|
|
|
def mimeData (self, indexes) :
|
|
model_fct = getattr (self, "mime_data", None)
|
|
if model_fct :
|
|
items = [i.internalPointer () for i in indexes]
|
|
result = QtCore.QMimeData ()
|
|
model_fct (result, items, indexes)
|
|
return result
|
|
return super ().mimeData (indexes)
|
|
# end def mimeData
|
|
|
|
def dropMimeData (self, mime_data, action, row, column, parent) :
|
|
model_fct = getattr (self, "drop_mime_data", None)
|
|
if model_fct is None :
|
|
return super ().dropMimeData \
|
|
(mime_data, action, row, column, parent)
|
|
return model_fct \
|
|
(mime_data, action, row, column, parent)
|
|
# end def dropMimeData
|
|
|
|
### Python Interface
|
|
Columns = ()
|
|
Supported_Drop_Actions = ()
|
|
mimeTypes = ()
|
|
|
|
def __init__ (self, * items, ** kw) :
|
|
super ().__init__ ()
|
|
self.Columns = [c.clone (model = self) for c in self.Columns]
|
|
root_item = kw.pop ("root", None)
|
|
if root_item is None :
|
|
root_item = _Root_Item_ (* items)
|
|
self.root_item = root_item
|
|
# end def __init__
|
|
|
|
def append (self, item, parent = None) :
|
|
if parent is None :
|
|
parent = self.root_item
|
|
start = len (parent)
|
|
index = self.index_for_item (parent)
|
|
self.beginInsertRows (index, start, start)
|
|
parent.append (item)
|
|
self.endInsertRows ()
|
|
# end def append
|
|
|
|
def dump (self, root_index = None, indend = "") :
|
|
if root_index is None :
|
|
root_index = self.invalid_index
|
|
result = []
|
|
for i in range (self.rowCount (root_index)) :
|
|
index = self.index (i, 0, root_index)
|
|
item = self.item_from_index (index)
|
|
result.append ("%s%s" % (indend, item))
|
|
result.extend (self.dump (index, indend + "."))
|
|
if not indend :
|
|
print ("---- Dump by index functions")
|
|
print ("\n".join (result))
|
|
print ("---- Dump end")
|
|
return result
|
|
# end def dump
|
|
|
|
def _walk (self, path, root, root_index = None) :
|
|
for n in range (len (root)) :
|
|
c = root [n]
|
|
p = path + (n, )
|
|
if root_index is not None :
|
|
index = self.index (n, 0, root_index)
|
|
yield p, c, index
|
|
yield from self._walk (p, c, index)
|
|
else :
|
|
yield p, c
|
|
yield from self._walk (p, c)
|
|
# end def _walk
|
|
|
|
def walk (self, with_index = False) :
|
|
index = None
|
|
if with_index :
|
|
index = self.invalid_index
|
|
return self._walk ((), self.root_item, index)
|
|
# end def walk
|
|
|
|
def item_path (self, index, joiner = None) :
|
|
result = []
|
|
if index.isValid () :
|
|
result.extend (self.item_path (self.parent (index)))
|
|
result.append (index.row ())
|
|
if joiner :
|
|
result = joiner.join (result)
|
|
return result
|
|
# end def item_path
|
|
|
|
def qindex (self, item, col = 0) :
|
|
if item is self.root_item :
|
|
row = 0
|
|
else :
|
|
row = item.parent.index (item)
|
|
return self.createIndex (row, col, item.parent)
|
|
# end def qindex
|
|
|
|
def insert_row (self, item, parent_index = None, start = -1) :
|
|
count = 1
|
|
parent_index = parent_index or self.invalid_index
|
|
if start == -1 :
|
|
start = self.rowCount (parent_index)
|
|
if parent_index.isValid () :
|
|
parent = parent_index.internalPointer ()
|
|
else :
|
|
parent = self.root_item
|
|
self.beginInsertRows (parent_index, start, start + count - 1)
|
|
parent [start:start] = [item]
|
|
self.endInsertRows ()
|
|
return self.index (start, 0, parent_index)
|
|
# end def insert_row
|
|
|
|
def item_from_index (self, index) :
|
|
if index.isValid () :
|
|
return index.internalPointer ()
|
|
# end def item_from_index
|
|
|
|
def remove (self, index = None, item = None) :
|
|
if index is None :
|
|
parent = item.parent
|
|
row = parent.index (item)
|
|
parent_index = self.qindex (parent)
|
|
else :
|
|
row = index.row ()
|
|
parent_index = index.parent ()
|
|
if parent_index.isValid () :
|
|
parent = parent_index.internalPointer ()
|
|
else :
|
|
parent = self.root_item
|
|
item = parent [row]
|
|
self.beginRemoveRows (parent_index, row, row + 1)
|
|
del parent [row]
|
|
if hasattr (item, "delete") :
|
|
item.delete ()
|
|
self.endRemoveRows ()
|
|
# end def remove
|
|
|
|
@contextlib.contextmanager
|
|
def replace_model (self) :
|
|
self.beginResetModel ()
|
|
yield
|
|
self.endResetModel ()
|
|
# end def replace_model
|
|
|
|
def set_data (self, index, value, role) :
|
|
return False
|
|
# end def set_data
|
|
|
|
# end class Model_Adapter
|
|
|
|
class Tree_View (QtWidgets.QTreeView) :
|
|
|
|
def expanded_nodes (self) :
|
|
expanded_path = []
|
|
model = self.model ()
|
|
if model :
|
|
for p, _, index in model.walk (True) :
|
|
if self.isExpanded (index) :
|
|
expanded_path.append (p)
|
|
return expanded_path
|
|
# end def expanded_nodes
|
|
|
|
def restore_expanded_nodes (self, expanded) :
|
|
model = self.model ()
|
|
if model :
|
|
indixes = {}
|
|
iv_index = model.invalid_index
|
|
for path in expanded :
|
|
parent_index = indixes.get (path [:-1], iv_index)
|
|
index = model.index (path [-1], 0, parent_index)
|
|
indixes [path] = index
|
|
item = model.item_from_index (index)
|
|
if item and len (item) :
|
|
self.setExpanded (index, True)
|
|
# end def restore_expanded_nodes
|
|
|
|
# end class Tree_View
|
|
### __END__ QT.Model_Adapter
|