Qt4: Read Default mimeData from QAbstractTableModel

前端 未结 2 2031
臣服心动
臣服心动 2021-01-15 16:14

By default, the QAbstractTableModel class has a mimeData() function that returns a QMimeData object which has it\'s data set as an enc

相关标签:
2条回答
  • 2021-01-15 16:33

    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;
    }
    
    0 讨论(0)
  • 2021-01-15 16:46

    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;
    }
    
    0 讨论(0)
提交回复
热议问题