Qt4: Read Default mimeData from QAbstractTableMode

2019-02-19 23:53发布

问题:

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.

回答1:

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;
}


回答2:

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;
}