mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-04-28 09:47:20 +02:00
391 lines
13 KiB
Python
391 lines
13 KiB
Python
from PySide6 import QtCore, QtQml
|
|
import shiboken6
|
|
|
|
|
|
class QObjectListModel(QtCore.QAbstractListModel):
|
|
"""
|
|
QObjectListModel provides a more powerful, but still easy to use, alternative to using
|
|
QObjectList lists as models for QML views. As a QAbstractListModel, it has the ability to
|
|
automatically notify the view of specific changes to the list, such as adding or removing
|
|
items. At the same time it provides QList-like convenience functions such as append, at,
|
|
and removeAt for easily working with the model from Python.
|
|
"""
|
|
ObjectRole = QtCore.Qt.UserRole
|
|
|
|
def __init__(self, keyAttrName='', parent=None):
|
|
""" Constructs an object list model with the given parent. """
|
|
super(QObjectListModel, self).__init__(parent)
|
|
|
|
self._objects = list() # Internal list of objects
|
|
self._keyAttrName = keyAttrName
|
|
self._objectByKey = {}
|
|
self.roles = QtCore.QAbstractListModel.roleNames(self)
|
|
self.roles[self.ObjectRole] = b"object"
|
|
|
|
self.requestDeletion.connect(self.onRequestDeletion, QtCore.Qt.QueuedConnection)
|
|
|
|
def roleNames(self):
|
|
return self.roles
|
|
|
|
def __iter__(self):
|
|
""" Enables iteration over the list of objects. """
|
|
return iter(self._objects)
|
|
|
|
def keys(self):
|
|
return self._objectByKey.keys()
|
|
|
|
def items(self):
|
|
return self._objectByKey.items()
|
|
|
|
def __len__(self):
|
|
return self.size()
|
|
|
|
def __bool__(self):
|
|
return self.size() > 0
|
|
|
|
def __getitem__(self, index):
|
|
""" Enables the [] operator.
|
|
Only accepts index (integer).
|
|
"""
|
|
return self._objects[index]
|
|
|
|
def data(self, index, role):
|
|
""" Returns data for the specified role, from the item with the
|
|
given index. The only valid role is ObjectRole.
|
|
|
|
If the view requests an invalid index or role, an invalid variant
|
|
is returned.
|
|
"""
|
|
if index.row() < 0 or index.row() >= len(self._objects):
|
|
return None
|
|
return self._objects[index.row()]
|
|
|
|
def rowCount(self, parent):
|
|
""" Returns the number of rows in the model. This value corresponds to the
|
|
number of items in the model's internal object list.
|
|
"""
|
|
return self.size()
|
|
|
|
def objectList(self):
|
|
""" Returns the object list used by the model to store data. """
|
|
return self._objects
|
|
|
|
def values(self):
|
|
return self._objects
|
|
|
|
def setObjectList(self, objects):
|
|
""" Sets the model's internal objects list to objects. The model will
|
|
notify any attached views that its underlying data has changed.
|
|
"""
|
|
oldSize = self.size()
|
|
self.beginResetModel()
|
|
for obj in self._objects:
|
|
self._dereferenceItem(obj)
|
|
self._objects = objects
|
|
for obj in self._objects:
|
|
self._referenceItem(obj)
|
|
self.endResetModel()
|
|
self.dataChanged.emit(self.index(0), self.index(self.size() - 1), [])
|
|
if self.size() != oldSize:
|
|
self.countChanged.emit()
|
|
|
|
# ######
|
|
# BaseModel API
|
|
# ######
|
|
@property
|
|
def objects(self):
|
|
return self._objectByKey
|
|
|
|
@QtCore.Slot(str, result=QtCore.QObject)
|
|
def get(self, key):
|
|
"""
|
|
:param key:
|
|
:return: the value or None if not found
|
|
"""
|
|
return self._objectByKey.get(key)
|
|
|
|
@QtCore.Slot(str, result=QtCore.QObject)
|
|
def getr(self, key):
|
|
"""
|
|
Get or raise an error if the key does not exists.
|
|
:param key:
|
|
:return: the value
|
|
"""
|
|
return self._objectByKey[key]
|
|
|
|
def add(self, obj):
|
|
self.append(obj)
|
|
|
|
def pop(self, key):
|
|
obj = self.get(key)
|
|
self.remove(obj)
|
|
return obj
|
|
|
|
############
|
|
# List API #
|
|
############
|
|
@QtCore.Slot(QtCore.QObject)
|
|
def append(self, obj):
|
|
""" Insert object at the end of the model. """
|
|
self.extend([obj])
|
|
|
|
def extend(self, iterable):
|
|
""" Insert objects at the end of the model. """
|
|
self.beginInsertRows(QtCore.QModelIndex(), self.size(), self.size() + len(iterable) - 1)
|
|
[self._referenceItem(obj) for obj in iterable]
|
|
self._objects.extend(iterable)
|
|
self.endInsertRows()
|
|
self.countChanged.emit()
|
|
|
|
def insert(self, i, toInsert):
|
|
""" Inserts object(s) at index position i in the model and notifies
|
|
any views. If i is 0, the object is prepended to the model. If i
|
|
is size(), the object is appended to the list.
|
|
Accepts both QObject and list of QObjects.
|
|
"""
|
|
if not isinstance(toInsert, list):
|
|
toInsert = [toInsert]
|
|
self.beginInsertRows(QtCore.QModelIndex(), i, i + len(toInsert) - 1)
|
|
for obj in reversed(toInsert):
|
|
self._referenceItem(obj)
|
|
self._objects.insert(i, obj)
|
|
self.endInsertRows()
|
|
self.countChanged.emit()
|
|
|
|
@QtCore.Slot(int, result=QtCore.QObject)
|
|
def at(self, i):
|
|
""" Return the object at index i. """
|
|
return self._objects[i]
|
|
|
|
def replace(self, i, obj):
|
|
""" Replaces the item at index position i with object and
|
|
notifies any views. i must be a valid index position in the list
|
|
(i.e., 0 <= i < size()).
|
|
"""
|
|
self._dereferenceItem(self._objects[i])
|
|
self._referenceItem(obj)
|
|
self._objects[i] = obj
|
|
self.dataChanged.emit(self.index(i), self.index(i), [])
|
|
|
|
def move(self, fromIndex, toIndex):
|
|
""" Moves the item at index position from to index position to
|
|
and notifies any views.
|
|
This function assumes that both from and to are at least 0 but less than
|
|
size(). To avoid failure, test that both from and to are at
|
|
least 0 and less than size().
|
|
"""
|
|
value = toIndex
|
|
if toIndex > fromIndex:
|
|
value += 1
|
|
if not self.beginMoveRows(QtCore.QModelIndex(), fromIndex, fromIndex, QtCore.QModelIndex(), value):
|
|
return
|
|
self._objects.insert(toIndex, self._objects.pop(fromIndex))
|
|
self.endMoveRows()
|
|
|
|
def removeAt(self, i, count=1):
|
|
""" Removes count number of items from index position i and notifies any views.
|
|
i must be a valid index position in the model (i.e., 0 <= i < size()), as
|
|
must as i + count - 1.
|
|
"""
|
|
self.beginRemoveRows(QtCore.QModelIndex(), i, i + count - 1)
|
|
for cpt in range(count):
|
|
obj = self._objects.pop(i)
|
|
self._dereferenceItem(obj)
|
|
self.endRemoveRows()
|
|
self.countChanged.emit()
|
|
|
|
@QtCore.Slot(QtCore.QObject)
|
|
def remove(self, obj):
|
|
""" Removes the first occurrence of the given object. Raises a ValueError if not in list. """
|
|
if not self.contains(obj):
|
|
raise ValueError("QObjectListModel.remove(obj) : obj not in list")
|
|
self.removeAt(self.indexOf(obj))
|
|
|
|
def takeAt(self, i):
|
|
""" Removes the item at index position i (notifying any views) and returns it.
|
|
i must be a valid index position in the model (i.e., 0 <= i < size()).
|
|
"""
|
|
self.beginRemoveRows(QtCore.QModelIndex(), i, i)
|
|
obj = self._objects.pop(i)
|
|
self._dereferenceItem(obj)
|
|
self.endRemoveRows()
|
|
self.countChanged.emit()
|
|
return obj
|
|
|
|
def clear(self):
|
|
""" Removes all items from the model and notifies any views. """
|
|
if not self._objects:
|
|
return
|
|
self.beginResetModel()
|
|
for obj in self._objects:
|
|
self._dereferenceItem(obj)
|
|
self._objects = []
|
|
self.endResetModel()
|
|
self.countChanged.emit()
|
|
|
|
def update(self, objects):
|
|
self.extend(objects)
|
|
|
|
def reset(self, objects):
|
|
self.setObjectList(objects)
|
|
|
|
@QtCore.Slot(QtCore.QObject, result=bool)
|
|
def contains(self, obj):
|
|
""" Returns true if the list contains an occurrence of object;
|
|
otherwise returns false.
|
|
"""
|
|
return obj in self._objects
|
|
|
|
@QtCore.Slot(QtCore.QObject, result=int)
|
|
def indexOf(self, matchObj, fromIndex=0, positive=True):
|
|
""" Returns the index position of the first occurrence of object in
|
|
the model, searching forward from index position from.
|
|
If positive is True, will always return a positive index.
|
|
"""
|
|
index = self._objects[fromIndex:].index(matchObj) + fromIndex
|
|
if positive and index < 0:
|
|
index += self.size()
|
|
return index
|
|
|
|
def lastIndexOf(self, matchObj, fromIndex=-1, positive=True):
|
|
""" Returns the index position of the last occurrence of object in
|
|
the list, searching backward from index position from. If
|
|
from is -1 (the default), the search starts at the last item.
|
|
If positive is True, will always return a positive index.
|
|
"""
|
|
r = list(self._objects)
|
|
r.reverse()
|
|
index = - r[-fromIndex - 1:].index(matchObj) + fromIndex
|
|
if positive and index < 0:
|
|
index += self.size()
|
|
return index
|
|
|
|
def size(self):
|
|
""" Returns the number of items in the model. """
|
|
return len(self._objects)
|
|
|
|
@QtCore.Slot(result=bool)
|
|
def isEmpty(self):
|
|
""" Returns true if the model contains no items; otherwise returns false. """
|
|
return len(self._objects) == 0
|
|
|
|
def _referenceItem(self, item):
|
|
if not item.parent():
|
|
# Take ownership of the object if not already parented
|
|
item.setParent(self)
|
|
if not self._keyAttrName:
|
|
return
|
|
key = getattr(item, self._keyAttrName, None)
|
|
if key is None:
|
|
return
|
|
if key in self._objectByKey:
|
|
raise ValueError("Object key {}:{} is not unique".format(self._keyAttrName, key))
|
|
|
|
self._objectByKey[key] = item
|
|
|
|
def _dereferenceItem(self, item):
|
|
# Ask for object deletion if parented to the model
|
|
if shiboken6.isValid(item) and item.parent() == self:
|
|
# delay deletion until the next event loop
|
|
# This avoids warnings when the QML engine tries to evaluate (but should not)
|
|
# an object that has already been deleted
|
|
self.requestDeletion.emit(item)
|
|
|
|
if not self._keyAttrName:
|
|
return
|
|
key = getattr(item, self._keyAttrName, None)
|
|
if key is None:
|
|
return
|
|
assert key in self._objectByKey
|
|
del self._objectByKey[key]
|
|
|
|
def onRequestDeletion(self, item):
|
|
item.deleteLater()
|
|
|
|
countChanged = QtCore.Signal()
|
|
count = QtCore.Property(int, size, notify=countChanged)
|
|
|
|
requestDeletion = QtCore.Signal(QtCore.QObject)
|
|
|
|
|
|
class QTypedObjectListModel(QObjectListModel):
|
|
""" Typed QObjectListModel that exposes T properties as roles """
|
|
# TODO: handle notify signal to emit dataChanged signal
|
|
|
|
def __init__(self, keyAttrName="name", T=QtCore.QObject, parent=None):
|
|
super(QTypedObjectListModel, self).__init__(keyAttrName, parent)
|
|
|
|
self._T = T
|
|
blacklist = ["id", "index", "class", "model", "modelData"]
|
|
|
|
self._metaObject = T.staticMetaObject
|
|
propCount = self._metaObject.propertyCount()
|
|
|
|
role = self.ObjectRole + 1
|
|
for i in range(0, propCount):
|
|
prop = self._metaObject.property(i)
|
|
if not prop.name() in blacklist:
|
|
self.roles[role] = prop.name()
|
|
role += 1
|
|
else:
|
|
print("Reserved role name: " + prop.name())
|
|
|
|
def data(self, index, role):
|
|
obj = super(QTypedObjectListModel, self).data(index, self.ObjectRole)
|
|
if role == self.ObjectRole:
|
|
return obj
|
|
if obj:
|
|
return obj.property(self.roles[role])
|
|
return None
|
|
|
|
def roleForName(self, name):
|
|
roles = [role for role, value in self.roles.items() if value == name]
|
|
return roles[0] if roles else -1
|
|
|
|
def _referenceItem(self, item):
|
|
if item.staticMetaObject != self._metaObject:
|
|
raise TypeError("Invalid object type: expected {}, got {}".format(
|
|
self._metaObject.className(), item.staticMetaObject.className()))
|
|
super(QTypedObjectListModel, self)._referenceItem(item)
|
|
|
|
|
|
class SortedModelByReference(QtCore.QSortFilterProxyModel):
|
|
""" Sort a source model based on the ordered list (reference) of the same elements.
|
|
This proxy is useful if the model needs to be sorted a certain way for a specific use.
|
|
"""
|
|
def __init__(self, parent):
|
|
super(SortedModelByReference, self).__init__(parent)
|
|
self._reference = []
|
|
|
|
def setReference(self, iterable):
|
|
""" Set the reference ordered list """
|
|
self._reference = iterable
|
|
self.sort()
|
|
|
|
def reference(self):
|
|
return self._reference
|
|
|
|
def lessThan(self, left, right):
|
|
l = self.sourceModel().data(left, QObjectListModel.ObjectRole)
|
|
r = self.sourceModel().data(right, QObjectListModel.ObjectRole)
|
|
if l not in self._reference:
|
|
return False
|
|
if r not in self._reference:
|
|
return True
|
|
return self._reference.index(l) < self._reference.index(r)
|
|
|
|
def sort(self):
|
|
""" Sort the proxy and call invalidate() """
|
|
super(SortedModelByReference, self).sort(0, QtCore.Qt.AscendingOrder)
|
|
self.invalidate()
|
|
|
|
|
|
DictModel = QObjectListModel
|
|
ListModel = QObjectListModel
|
|
Slot = QtCore.Slot
|
|
Signal = QtCore.Signal
|
|
Property = QtCore.Property
|
|
BaseObject = QtCore.QObject
|
|
Variant = "QVariant"
|
|
VariantList = "QVariantList"
|
|
JSValue = QtQml.QJSValue
|