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:
However, after dragging into empty spaces, it looks like this:
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;
}
source to share
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
orQSharedPointer
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 torow == -1
: this means that the fall happened directly over the element indicatedparent
. -
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();
}
source to share