Qt4: Read Default mimeData from QAbstractTableModel

前端 未结 2 2030
臣服心动
臣服心动 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 
    #include 
    
    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>
    *
    * 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
        bool canConvert(int role = Qt::EditRole) const {
            return canConvert(qMetaTypeId(), role);
        }
    
        QVariant data(int role, int row = -1, int column = -1) const;
    
        int count();
    
        QPair firstElement() const;
    
        quint32 typeId(int role = Qt::EditRole, int row = -1, int column = -1) const;
    
    private:
        QModelDataListDecoderPrivate* d_ptr;
    };
    
    #include 
    #include 
    #include 
    
    // 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, QMap > 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(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");
        qRegisterMetaTypeStreamOperators("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 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 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(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(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(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;
    }
    

提交回复
热议问题