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:

However after dragging into empty spaces it looks like this:

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;
}
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:
You're leaking the textures all over the place. You should use std::shared_ptr or QSharedPointer instead of a raw pointer.
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.
dropMimeData must react to row == -1: this indicates that the drop happened directly over the item indicated by parent.
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();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With