AaronMK
AaronMK

Reputation: 1488

QStandardItem::clone() not being called for Drag and Drop

I have a Qt application where I am using a QStandardItemModel derived class, and a QTreeView to interact with it. I would like to enable drag and drop to copy and move items around the model. To enable this, I have done the following:

Drag and Drop works to the extent that it honors which items can be dragged and which can accept drops. However, whether moving or copying it does not create new items using my clone() functions. It only copies the settings and data available to the QStandardItem base class, losing subclass overrides and such.

How do I get the model and views to make use of my clone() functions, or work around this?

Thank you for any assistance.

Upvotes: 1

Views: 1847

Answers (2)

AaronMK
AaronMK

Reputation: 1488

I think I have found a work around that more or less does what I was expecting the Framework to do.

The header file:

class QextDragDropModel : public QStandardItemModel
{
public:
    /**
     * Uses the passed indexes, and encodes a list of QStandardItem pointers into
     * the mime data.
     */
    virtual QMimeData* mimeData(const QModelIndexList &indexes) const;

    /**
     * Decodes the mimedata, and uses the each QStandardItem::clone() implmentation
     * to place a copy at the requested position of the model.  If it is a move
     * operation Qt will remove the previous item.
     */
    virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action,
                              int row, int column, const QModelIndex &parent);

};

The implmentation:

QMimeData* QextDragDropModel::mimeData(const QModelIndexList &indexes) const
{
    // Need to have the base function create the initial mimeData.
    // It apparently puts something in there that makes Qt call dropMimeData().
    QMimeData* mimeData = QStandardItemModel::mimeData(indexes);

    // The raw data that will be placed in the mimeData.
    QByteArray mimeBytes;

    // Scope the data stream.
    {
         QDataStream ds(&mimeBytes, QIODevice::WriteOnly);

         // The first item encoded will be the number of pointers to expect.
         ds << quint32(indexes.size());

         // Now for each index get a pointer to the standardItem, and write
         // itto the datastream.
         for (int i = 0; i < indexes.size(); i++)
         {
              QStandardItem* ptrItem = itemFromIndex(indexes[i]);
              ds.writeRawData((const char*)&ptrItem, sizeof(QStandardItem*));
         }
    }

    // Add the encoded standard item pointers into the mimeData.
    mimeData->setData("Qt/QStandardItemArray", mimeBytes);

    return mimeData;
}

bool QextDragDropModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
                                     int row, int column, const QModelIndex &parent)
{
    // Get the QStandardItem target of the drop.
    QStandardItem* target = itemFromIndex(parent);

    // If the target is valid, accepts drops and the mimedata has QStandardItem pointers
    // go ahead with decode and insertion.  (Checking drop enabled pobably already
    // done by the framework before calling this function.)
    if ( NULL != target && target->isDropEnabled() && data->hasFormat("Qt/QStandardItemArray") )
    {
         // Fetch the encoded bytes, create a data stream for decoding,
         // and variables to store the output.
         QByteArray indexListBytes = data->data("Qt/QStandardItemArray");
         QDataStream ds(&indexListBytes, QIODevice::ReadOnly);
         quint32 numItems = 0;

         // Get the number of items, allocate memory to store pointers to
         // them and read the pointer data into that memory.
         ds >> numItems;
         int byteLen = numItems*sizeof(QStandardItem*);
         QStandardItem** stdItems = (QStandardItem**)malloc(byteLen);
         ds.readRawData((char*)stdItems, byteLen);

         // Add items to the target at a specific child index if requested,
         // using thier clone() function to create the items.
         for (int i = 0; i < numItems; i++)
         {
             if ( 0 <= row )
                  target->insertRow(row, stdItems[i]->clone());
              else
                  target->appendRow(stdItems[i]->clone());
         }

         // Free memory allocated to store item pointers.
         free(stdItems);

         return true;
    }

    return false;
}

For my application, I'll probably add a custom item class with functionality to accept or reject specific items, with the model querying that instead of simply dumping into anything that accepts drops, but for the main question, this is good.

Upvotes: 2

arne
arne

Reputation: 4674

Have you checked that your clone() prototype matches the one from the base class? I had a similar problem with sizeHint, which was not called during layout. The problem was a missing const modifier.

Upvotes: 1

Related Questions