Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Qt AbstractItemModel rearranging images

I want to rearrange a set of images in a qlistview. Ive looked at the examples and I just can't get this to work. When I drag an image over another Image dropomimedata() is executed however its "data->hasImage()" is always false. When I drop an Image in empty space for some reason dropmimedata() isn't triggered at all.

My model should look like this:

enter image description here

However after dragging into empty spaces it looks like this:

enter image description here

And when I drag an image over another nothing changes because hasImage is always false. What am I doing wrong? What am I missing?

#include "spritemodel.h"

#include <QDebug>
#include <QMimeData>

SpriteModel::SpriteModel() : QAbstractListModel()
{
}

void SpriteModel::setContents(QList<QPair<QImage, QOpenGLTexture*>> &newList)
{
    beginInsertRows(QModelIndex(), 0, newList.size());
    imageList = newList;
    endInsertRows();
}

int SpriteModel::rowCount(const QModelIndex & parent) const
{
    Q_UNUSED(parent);
    return imageList.size();
}

QVariant SpriteModel::data(const QModelIndex & index, int role) const
{
    if (role == Qt::DecorationRole)
        return imageList[index.row()].first;
    else if (role == Qt::DisplayRole)
        return "";
    else
        return QVariant();
}

Qt::DropActions SpriteModel::supportedDropActions() const
{
    return Qt::CopyAction | Qt::MoveAction;
}

Qt::ItemFlags SpriteModel::flags(const QModelIndex &index) const
{
    Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);

    if (index.isValid())
        return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
    else
        return Qt::ItemIsDropEnabled | defaultFlags;
}

bool SpriteModel::removeRows(int position, int rows, const QModelIndex &parent)
{
    beginRemoveRows(QModelIndex(), position, position+rows-1);

    for (int row = 0; row < rows; ++row) {
        imageList.removeAt(position);
    }

    endRemoveRows();
    return true;
}

bool SpriteModel::insertRows(int position, int rows, const QModelIndex &parent)
{
    beginInsertRows(QModelIndex(), position, position+rows-1);

    QImage img(imageList[0].first.width(), imageList[0].first.height(), imageList[0].first.format());
    QOpenGLTexture *texture = new QOpenGLTexture(img);

    for (int row = 0; row < rows; ++row) {
        imageList.insert(position, qMakePair(img, texture));
    }

    endInsertRows();
    return true;
}

bool SpriteModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    QImage img = value.value<QImage>();
    QOpenGLTexture *texture = new QOpenGLTexture(img);

    if (index.isValid() && role == Qt::EditRole) {

        imageList.replace(index.row(), qMakePair(img, texture));
        emit dataChanged(index, index);
        return true;
    }
    return false;
}

QMimeData *SpriteModel::mimeData(const QModelIndexList &indexes) const
{
    QMimeData *mimeData = new QMimeData();
    QByteArray encodedData;

    QDataStream stream(&encodedData, QIODevice::WriteOnly);

    foreach (const QModelIndex &index, indexes) {
        if (index.isValid()) {
            QVariant img = data(index, Qt::DecorationRole);
            stream << img;
        }
    }

    mimeData->setData("image/png", encodedData);
    return mimeData;
}

bool SpriteModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
    if (data->hasImage()) {
        qDebug() << "wut";
        QImage img = qvariant_cast<QImage>(data->imageData());
        QOpenGLTexture *texture = new QOpenGLTexture(img);

        beginInsertRows(parent, 0, 1); // test
        imageList.insert(row, qMakePair(img, texture));
        endInsertRows();

        emit dataChanged(QModelIndex(),QModelIndex());
        return true;
    }
    return false;
}
like image 646
Fundies Avatar asked Dec 05 '25 07:12

Fundies


1 Answers

A QDataStream will not encode your image data as a png. Why would it not encode it as a bmp or a gif, for example? The default mimetype that you would be encoding as is application/x-qabstractitemmodeldatalist - that's because you didn't override mimeTypes().

Since you presumably want to support moving multiple items at once, you should stick to x-qabstractitemmodeldatalist, and encode/decode it appropriately. See this question for details.

Note that this mimetype will not return true for hasImage, since the data is a list of role-value maps.

Other problems:

  1. You're leaking the textures all over the place. You should use std::shared_ptr or QSharedPointer instead of a raw pointer.

  2. insertrows is putting the same texture instance into multiple entries. If you ever attempt to delete the textures, you will be unable to avoid multiple deletions unless you use a shared pointer.

  3. dropMimeData must react to row == -1: this indicates that the drop happened directly over the item indicated by parent.

  4. setContents are not adding any items, they completely reset the model.

The example below illustrates all of the points.

#include <QtWidgets>

const auto mimeType = QStringLiteral("application/x-qabstractitemmodeldatalist");

class SpriteModel : public QAbstractListModel {
public:
   typedef QSharedPointer<QOpenGLTexture> TexturePtr;
   typedef QPair<QImage, TexturePtr> Item;
private:
   QList<Item> m_imageList;
public:
   SpriteModel(QObject * parent = 0) : QAbstractListModel(parent) {}

   static Item makeItem(const QImage & image) {
      return qMakePair(image, TexturePtr(new QOpenGLTexture(image)));
   }

   void setContents(QList<Item> &newList) {
      beginResetModel();
      m_imageList = newList;
      endResetModel();
   }

   int rowCount(const QModelIndex &) const Q_DECL_OVERRIDE {
      return m_imageList.size();
   }

   QVariant data(const QModelIndex & index, int role) const Q_DECL_OVERRIDE {
      if (role == Qt::DecorationRole)
         return m_imageList[index.row()].first;
      else if (role == Qt::DisplayRole)
         return "";
      else
         return QVariant();
   }

   Qt::DropActions supportedDropActions() const Q_DECL_OVERRIDE {
      return Qt::CopyAction | Qt::MoveAction;
   }

   Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE {
      auto defaultFlags = QAbstractListModel::flags(index);
      if (index.isValid())
         return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
      else
         return Qt::ItemIsDropEnabled | defaultFlags;
   }

   bool removeRows(int position, int rows, const QModelIndex &) Q_DECL_OVERRIDE {
      beginRemoveRows(QModelIndex(), position, position+rows-1);
      for (int row = 0; row < rows; ++row) {
         m_imageList.removeAt(position);
      }
      endRemoveRows();
      return true;
   }

   bool insertRows(int position, int rows, const QModelIndex &) Q_DECL_OVERRIDE {
      beginInsertRows(QModelIndex(), position, position+rows-1);
      auto size = m_imageList.isEmpty() ? QSize(10, 10) : m_imageList.at(0).first.size();
      QImage img(size, m_imageList[0].first.format());
      for (int row = 0; row < rows; ++row) {
         m_imageList.insert(position, makeItem(img));
      }
      endInsertRows();
      return true;
   }

   bool setData(const QModelIndex &index, const QVariant &value, int role) Q_DECL_OVERRIDE {
      if (index.isValid() && role == Qt::EditRole) {
         m_imageList.replace(index.row(), makeItem(value.value<QImage>()));
         emit dataChanged(index, index);
         return true;
      }
      return false;
   }

   QMimeData *mimeData(const QModelIndexList &indexes) const Q_DECL_OVERRIDE {
      auto mimeData = new QMimeData();
      QByteArray encodedData;
      QDataStream stream(&encodedData, QIODevice::WriteOnly);

      qDebug() << "mimeData" << indexes;

      for (const auto & index : indexes) {
         if (! index.isValid()) continue;
         QMap<int, QVariant> roleDataMap;
         roleDataMap[Qt::DecorationRole] = data(index, Qt::DecorationRole);
         stream << index.row() << index.column() << roleDataMap;
      }
      mimeData->setData(mimeType, encodedData);
      return mimeData;
   }

   bool canDropMimeData(const QMimeData *data,
                        Qt::DropAction, int, int column, const QModelIndex & parent) const Q_DECL_OVERRIDE
   {
      return data->hasFormat(mimeType) && (column == 0 || (column == -1 && parent.column() == 0));
   }

   bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) Q_DECL_OVERRIDE {
      Q_UNUSED(column);
      qDebug() << "drop" << action << row << column << parent;
      if (! data->hasFormat(mimeType)) return false;

      auto encoded = data->data(mimeType);
      QDataStream stream(&encoded, QIODevice::ReadOnly);
      QList<QImage> images;
      while (! stream.atEnd()) {
         int row, col;
         QMap<int, QVariant> roleDataMap;
         stream >> row >> col >> roleDataMap;
         auto it = roleDataMap.find(Qt::DecorationRole);
         if (it != roleDataMap.end()) {
            images << it.value().value<QImage>();
         }
      }
      if (row == -1) row = parent.row();
      if (! images.isEmpty()) {
         beginInsertRows(parent, row, row+images.size() - 1);
         qDebug() << "inserting" << images.count();
         for (auto & image : images)
            m_imageList.insert(row ++, makeItem(image));
         endInsertRows();
         return true;
      }
      return false;
   }
};

QImage makeImage(int n) {
   QImage img(64, 128, QImage::Format_RGBA8888);
   img.fill(Qt::transparent);
   QPainter p(&img);
   p.setFont(QFont("Helvetica", 32));
   p.drawText(img.rect(), Qt::AlignCenter, QString::number(n));
   return img;
}

int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
   QList<SpriteModel::Item> items;
   for (int i = 0; i < 5; ++i) items << SpriteModel::makeItem(makeImage(i));
   SpriteModel model;
   model.setContents(items);
   QListView view;
   view.setModel(&model);
   view.setViewMode(QListView::IconMode);
   view.setSelectionMode(QAbstractItemView::ExtendedSelection);
   view.setDragEnabled(true);
   view.setAcceptDrops(true);
   view.setDropIndicatorShown(true);
   view.show();
   qDebug() << model.mimeTypes();
   return a.exec();
}
like image 90
Kuba hasn't forgotten Monica Avatar answered Dec 08 '25 02:12

Kuba hasn't forgotten Monica



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!