Search code examples
pythonpyqtpyqt5qstandarditemmodelqstandarditem

Implementation of clone in QStandardItem subclass


I am using a QStandardItemModel with a QTreeView to display custom Items. The items come in three different types FILTER_TYPE, MODIFIER_TYPE and GROUP_TYPE.

I would like to be able to reorder items within the model using drag and drop in the view (InternalMove). If I understood it correctly, I have to use setItemPrototype(MyItem()) on my model in order for it to use the custom MyItem and not the general QStandardItem when moving items.

My understanding was that a new instance of the custom MyItem gets created and then all data and flags from the old item are copied over to the new item. However, it seems the model only initialises a new MyItem and never copies the data.

Therefore: How do I reimplement QStandardItem.clone() in the MyItem subclass to copy all data and flags into the new item? Do I have to manually go through all the custom data roles and assign their value to the new item?

The Item class looks like this:

class MyItem(QtGui.QStandardItem):
    FILTER_TYPE = QtGui.QStandardItem.UserType + 1
    MODIFIER_TYPE = QtGui.QStandardItem.UserType + 2
    GROUP_TYPE = QtGui.QStandardItem.UserType + 3

    TYPE = QtCore.Qt.UserRole + 0
    NAME = QtCore.Qt.UserRole + 1 
  
    IS_PROCESSED = QtCore.Qt.UserRole + 5
    OUTPUT = QtCore.Qt.UserRole + 6

    FN = QtCore.Qt.UserRole + 7
    DEFAULT_PARAMS = QtCore.Qt.UserRole + 8
    PARAMETER_SET = QtCore.Qt.UserRole + 9

    def __init__(self):
        super().__init__()

        self.name = ""
        self.full_name = ""
        self.description = ""

        self.fn = None
        self.default_params = None
        self.parameter_set = None

        self.is_active = True
        self.is_processed = False
        self.output = None

        self.icon = QtGui.QIcon()

    def clone(self):
        item = Item() 
        
        ??? WHAT GOES HERE TO COPY ALL DATA AND FLAGS ???
            
        return item

    def __setattr__(self, name, value):
        if name == 'name':
            self.setData(value, self.NAME)
        elif name == 'full_name':
            self.setData(value, QtCore.Qt.DisplayRole)
            self.setData(value, QtCore.Qt.EditRole)
        elif name == 'description':
            self.setData(value, QtCore.Qt.ToolTipRole)
        
        ...

        else:
            super().__setattr__(name, value)

    def __getattribute__(self, name):
        if name == 'name':
            return self.data(self.NAME)
        elif name == 'full_name':
            return self.data(QtCore.Qt.DisplayRole)
        elif name == 'description':
            return self.data(QtCore.Qt.ToolTipRole)

        ...

        else:
            return super().__getattribute__(name)

    def initializeItem(self, type_, name, full_name, description="", fn=None, default_params=None):
        
        self.name = name
        self.full_name = full_name
        self.description = description
        
        self.fn = fn
        self.default_params = default_params
        self.parameter_set = ParameterSet(params_list=default_params)

        self.setData(type_, self.TYPE)
        
        flags = QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsDragEnabled|QtCore.Qt.ItemIsUserCheckable|QtCore.Qt.ItemIsEnabled
        if type_ == self.FILTER_TYPE:
            self.icon = QtGui.QIcon('resources/filter.png')
            flags = flags|QtCore.Qt.ItemNeverHasChildren
        elif type_ == self.MODIFIER_TYPE:
            self.icon = QtGui.QIcon('resources/modifier.png')
            flags = flags|QtCore.Qt.ItemIsDropEnabled
        elif type_ == self.GROUP_TYPE:
            self.icon = QtGui.QIcon('resources/folder.png')
            flags = flags|QtCore.Qt.ItemIsDropEnabled|QtCore.Qt.ItemIsEditable
        self.setFlags(flags)

    def type(self):
        return self.data(self.TYPE)

      

The Model implementation looks like this:

from tree.items import MyItem


class TreeModel(QtGui.QStandardItemModel):
    
    def __init__(self):
        super().__init__()

        self.setItemPrototype(MyItem())

Solution

  • The logic of "clone" is to create an object with the same information of the item, so in this case you are using the roles to store that information so you must copy all that information in the new item, in this case you can use QDataStream:

    def clone(self):
        item = MyItem()
        ba = QtCore.QByteArray()
        ds = QtCore.QDataStream(ba, QtCore.QIODevice.WriteOnly)
        ds << self
        ds = QtCore.QDataStream(ba)
        ds >> item
        return item