By default, the QAbstractTableModel
class has a mimeData()
function that returns a QMimeData
object which has it's data set as an encoded QModelIndexList
(see here). I would like to unpack this data in an overloaded dropMimeData()
function, but can't figure out how to convert this QMimeData
back into a QModelIndexList.
I tried the obvious:
bool myTableModel::dropMimeData(const QMimeData * mimeData, Qt::DropAction action, int row, int column, const QModelIndex & parent)
{
QStringList formats = mimeData->formats();
QByteArray encodedData = mimeData->data(formats[0]);
QDataStream stream(&encodedData, QIODevice::ReadOnly);
QModelIndexList list;
stream >> index;
}
but get the error:
no match for ‘operator>>’ in ‘stream >> ((myTableModel*)this)->QAbstractTableModel::index’
because there is no >> operator for QModelIndex.
Note: this question is a much more focused version of this one. Sorry if this breaks SO ettiquete, I'm a bit new here.
Got it, thanks to Kaleb Peterson at the old question's link:
bool ObjectAnimation::dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent)
{
QStringList formats = data->formats();
QByteArray encodedData = data->data(formats[0]);
QDataStream stream(&encodedData, QIODevice::ReadOnly);
int row, column;
stream >> row >> column;
qDebug() << "row: " << row << " column:" << column;
return false;
}
6 years late, but in case someone hit the issue in the future, here's a more complete solution. It also handle the corner case where there is typed, but invalid QVariants (because they are not serializable). In my (related, but not identical) use case, I mostly cared about the source and destination QVariants to be type compatible, I didn't cared as much about the value, but the code should work.
#include <QtCore/QVariant>
#include <QtCore/QPair>
class QMimeData;
class QModelDataListDecoderPrivate;
/*
* The default model implementation adds the `application/x-qabstractitemmodeldatalist`
* MIME Type. This things is totally undocumented, but some reverse engineering
* of it's encoder indicate it is (as of Qt 5.6) an array of:
*
* Tuple<int, int, QMap<int, QVaritant>>
*
* pushed in a `QDataStream`. This format suck, as it's not really able to
* express the source `QModelIndex`. However, it does contain the QVariant
* of some of its role. From them, even the invalid ones, the QMetaType id
* should be pushed into the stream. It should have been easy, but there
* is another problem. QVariant::load exits early when decoding a QMetaType
* that cannot be encoder. While it prints the QMetaType on stderr, it doesn't
* export it. This, in turn, causes another problem where the QMap will be empty
* if a single element fail to be deserialized. This little class implements a
* serializable Qt type that mimics the QVariant decoder to be able to extract
* the correct type.
*
* The QVariant data is encoded as (it is stable and documented):
*
* * The type of the data (quint32)
* * The null flag (qint8)
* * The data of the specified type
*
* Reference:
*
* * http://doc.qt.io/qt-5/datastreamformat.html
* * qvariant.cpp
* * qabstractitemmodel.cpp
*/
class QModelDataListDecoder
{
public:
explicit QModelDataListDecoder(const QMimeData* data);
virtual ~QModelDataListDecoder();
bool canConvert(quint32 typeId, int role = Qt::EditRole, int row = -1, int column = -1) const;
template<typename T>
bool canConvert(int role = Qt::EditRole) const {
return canConvert(qMetaTypeId<T>(), role);
}
QVariant data(int role, int row = -1, int column = -1) const;
int count();
QPair<int, int> firstElement() const;
quint32 typeId(int role = Qt::EditRole, int row = -1, int column = -1) const;
private:
QModelDataListDecoderPrivate* d_ptr;
};
#include <QtCore/QMimeData>
#include <QtCore/QDataStream>
#include <QtCore/QDebug>
// Be less strict about unserializable values that are part of the stream.
class QLousyVariantDecoder
{
public:
class QVariantExt : public QVariant {
public:
inline void createProxy(int typeId) { create(typeId, Q_NULLPTR); }
};
quint32 metaType;
qint8 isNull;
QVariantExt variant ;
QByteArray userType;
bool isLoaded;
};
class QModelDataListDecoderPrivate
{
public:
QHash<QPair<int, int>, QMap<int, QLousyVariantDecoder> > m_Data;
};
QDebug operator<<(QDebug debug, const QLousyVariantDecoder &v)
{
return debug << QStringLiteral("<<<")
<< QStringLiteral("Type:" ) << v.metaType << v.userType
<< QStringLiteral(", Is NULL:" ) << v.isNull
<< QStringLiteral(", Is valid:") << v.isLoaded
<< QStringLiteral(", Value:" ) << v.variant
<< QStringLiteral(">>>");
}
QDataStream &operator<<(QDataStream &s, const QLousyVariantDecoder &self)
{
return s << self.variant;
}
QDataStream &operator>>(QDataStream &s, QLousyVariantDecoder &self)
{
// There is no Qt ways to doing this before 5.7 beside creating it by hand
// Qt5.7 support transactions, but replicating the exact behavior of the
// following code is longer than not using transactions.
s >> self.metaType;
s >> self.isNull;
if (self.metaType == QVariant::UserType) {
s >> self.userType;
self.metaType = QMetaType::type(self.userType.constData());
if (self.metaType == QMetaType::UnknownType) {
s.setStatus(QDataStream::ReadCorruptData);
return s;
}
}
else
self.userType = QMetaType::typeName(self.metaType);
if (!self.isNull) {
self.variant.createProxy(self.metaType);
void* data = const_cast<void *>(self.variant.constData());
// Ignore errors, as the way it is implemented, the field is empty,
// so the streams remains valid. However dropping the data wont work.
self.isLoaded = QMetaType::load(s, self.variant.type(), data);
}
return s;
}
// hack to execute code at a RANDOM moment during initialization.
static auto _DUMMY = ([]()->bool {
qRegisterMetaType <QLousyVariantDecoder>("QLousyVariantDecoder");
qRegisterMetaTypeStreamOperators<QLousyVariantDecoder>("QLousyVariantDecoder");
return true;
})();
QModelDataListDecoder::QModelDataListDecoder(const QMimeData* data)
: d_ptr(new QModelDataListDecoderPrivate)
{
if (!data)
return;
// Check all payloads if one can be converted to the right QMetaType
auto buf = data->data("application/x-qabstractitemmodeldatalist");
if (buf.isEmpty())
return;
QDataStream s(buf);
while (!s.atEnd()) {
int r, c;
QMap<int, QLousyVariantDecoder> v;
s >> r >> c >> v;
// only add valid items
if (r+1 && c+1)
d_ptr->m_Data[{r, c}] = std::move(v);
}
}
QModelDataListDecoder::~QModelDataListDecoder()
{
delete d_ptr;
}
QPair<int, int> QModelDataListDecoder::firstElement() const
{
if (d_ptr->m_Data.isEmpty())
return {-1,-1};
return d_ptr->m_Data.begin().key();
}
bool QModelDataListDecoder::canConvert(quint32 typeId, int role, int row, int col) const
{
auto v = data(role, row, col);
if (v.isValid())
return v.canConvert(typeId);
const auto pair = (row+1&&col+1) ? QPair<int,int>(row, col) : firstElement();
if (!d_ptr->m_Data.contains(pair))
return false;
auto info = d_ptr->m_Data[pair][role];
if (info.metaType == typeId)
return true;
QLousyVariantDecoder::QVariantExt var;
var.createProxy(info.metaType);
return var.canConvert(typeId);;
}
QVariant QModelDataListDecoder::data(int role, int row, int col) const
{
const auto pair = (row+1&&col+1) ? QPair<int,int>(row, col) : firstElement();
if (!d_ptr->m_Data.contains(pair))
return {};
return d_ptr->m_Data[pair][role].variant;
}
quint32 QModelDataListDecoder::typeId(int role, int row, int col) const
{
const auto pair = (row+1&&col+1) ? QPair<int,int>(row, col) : firstElement();
if (!d_ptr->m_Data.contains(pair))
return QMetaType::UnknownType;
const auto data = d_ptr->m_Data[pair];
if (!data.contains(role))
return QMetaType::UnknownType;
return data[role].metaType;
}