Qt AbstractItemModel reorders images

I want to change a set of images in qlistview. I've looked at examples and I just can't seem to get this to work. When I drag an image on top of another the dropomimedata () function is called, however its "data-> hasImage ()" is always false. When I drop the image to an empty space, for some reason dropmimedata () doesn't fire 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 on top of 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;
}

      

+3


source to share


1 answer


A QDataStream

will not encode your image data as png. Why not encode it as BMP or GIF, for example? The default mimetype you would encode is application/x-qabstractitemmodeldatalist

- because you didn't override mimeTypes()

.

Since you presumably want to support moving multiple elements at the same time, you should stick with x-qabstractitemmodeldatalist

and encode / decode accordingly. See this question for details .

Note that this mimetype will not return true for hasImage

as the data is a list of role value maps.

Other problems:



  • You are losing textures all over the place. You should use std::shared_ptr

    or QSharedPointer

    instead of a raw pointer.

  • insertrows

    places the same texture instance across multiple records. If you try to delete textures, you cannot avoid multiple deletions unless you use a shared pointer.

  • dropMimeData

    must react to row == -1

    : this means that the fall happened directly over the element indicated parent

    .

  • setContents

    no elements are added, they completely reset the model.

The example below illustrates all 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();
}

      

+1


source







All Articles