order-processing/QT/Model_Adapter.py

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