how to sort QTreeWidgetItem manually and dynamically by user?
-
how to sort QTreeWidgetItem manually and dynamically by user?
i want to allow user drag on QTreeWidgetItem-A from below QTreeWidgetItem-B, to above QTreeWidgetItem-B...
is it possible? and which is the best way to do this..and i want the manually-ordered result can be saved, when the user open the application next time, it'll be the order he changed and saved last time.
Which is more complicated, when the user manually create a new QTreeWidgtItem, i want this new QTreeWidgetItem is sorted!!thank you very much!
-
Here goes the most generic I could do.
Requires Qt version >=5.6 (if you use something else to replace QVersionNumber you can make it compatible with earlier versions) and C++11 (but again, not many changes needed to make it compatible)
you need to fill
loadVariant
andsaveVariant
to save and load other type of data (in particular Icons, Brushes and other GUI types) as mine are particularly trivial.modelserialisation.h
#ifndef modelserialisation_h__ #define modelserialisation_h__ class QAbstractItemModel; class QString; namespace ModelSerialisation{ bool saveModel(const QAbstractItemModel* const model, const QString& destination, const QList<int>& rolesToSave); bool saveModel(const QAbstractItemModel* const model, const QString& destination); bool loadModel(QAbstractItemModel* const model, const QString& source); } #endif // modelserialisation_h__
modelserialisation.cpp
#include <QAbstractItemModel> #include <QXmlStreamReader> #include <QXmlStreamWriter> #include <QSaveFile> #include <QFile> #include <QVersionNumber> #include "modelserialisation.h" namespace ModelSerialisation { QVariant loadVariant(int type, const QString& val){ switch (type) { case QMetaType::QString: return val; case QMetaType::Double: return val.toDouble(); case QMetaType::Int: return val.toInt(); // Add other types here default: return QVariant(); } } QString saveVariant(const QVariant& val){ switch(val.type()){ case QMetaType::QString: case QMetaType::Bool: case QMetaType::QByteArray: case QMetaType::QChar: case QMetaType::QDate: case QMetaType::QDateTime: case QMetaType::Double: case QMetaType::Int: case QMetaType::LongLong: case QMetaType::QStringList: case QMetaType::QTime: case QMetaType::UInt: case QMetaType::ULongLong: return val.toString(); break; // Add other types here default: return QString(); } } void writeElement(QXmlStreamWriter& destination, const QAbstractItemModel* const model, const QList<int>& rolesToSave, const QModelIndex& parent = QModelIndex()) { if (model->rowCount(parent) + model->columnCount(parent) == 0) return; destination.writeStartElement(QStringLiteral("Element")); destination.writeAttribute(QStringLiteral("RowCount"), QString::number(model->rowCount(parent))); destination.writeAttribute(QStringLiteral("ColumnCount"), QString::number(model->columnCount(parent))); for (int i = 0; i < model->rowCount(parent); ++i) { for (int j = 0; j < model->columnCount(parent); ++j) { destination.writeStartElement(QStringLiteral("Cell")); destination.writeStartElement(QStringLiteral("Row")); destination.writeCharacters(QString::number(i)); destination.writeEndElement(); // Row destination.writeStartElement(QStringLiteral("Column")); destination.writeCharacters(QString::number(j)); destination.writeEndElement(); // Column for (int singleRole : rolesToSave) { const QVariant roleData = model->index(i, j, parent).data(singleRole); if (roleData.isNull()) continue; // Skip empty roles destination.writeStartElement(QStringLiteral("DataPoint")); destination.writeAttribute(QStringLiteral("Role"), QString::number(singleRole)); destination.writeAttribute(QStringLiteral("Type"), QString::number(roleData.type())); destination.writeCharacters(saveVariant(roleData)); destination.writeEndElement(); // DataPoint } if (model->hasChildren(model->index(i, j, parent))) { writeElement(destination, model, rolesToSave, model->index(i, j, parent)); } destination.writeEndElement(); // Cell } } destination.writeEndElement(); // Element } bool readElement(QXmlStreamReader& source, QAbstractItemModel* const model, const QModelIndex& parent = QModelIndex()){ if (source.name() != QStringLiteral("Element")) return false; int rowCount, colCount; const auto tableSizeAttribute = source.attributes(); if (!( tableSizeAttribute.hasAttribute(QStringLiteral("RowCount")) && tableSizeAttribute.hasAttribute(QStringLiteral("ColumnCount")) )) return false; rowCount = tableSizeAttribute.value(QStringLiteral("RowCount")).toInt(); colCount = tableSizeAttribute.value(QStringLiteral("ColumnCount")).toInt(); if (rowCount <= 0 || colCount <= 0) return false; if (model->rowCount(parent) < rowCount) model->insertRows(model->rowCount(parent), rowCount - model->rowCount(parent), parent); if (model->columnCount(parent) < colCount) model->insertColumns(model->columnCount(parent), colCount - model->columnCount(parent), parent); int rowIndex = -1; int colIndex = -1; bool cellStarted =false; while (!source.atEnd() && !source.hasError()) { source.readNext(); if (source.isStartElement()) { if (source.name() == QStringLiteral("Cell")) { cellStarted = true; } else if (source.name() == QStringLiteral("Row") && cellStarted) { rowIndex = source.readElementText().toInt(); } else if (source.name() == QStringLiteral("Column") && cellStarted) { colIndex = source.readElementText().toInt(); } else if (source.name() == QStringLiteral("DataPoint") && cellStarted) { if (rowIndex < 0 || colIndex < 0) return false; const auto dataPointTattributes = source.attributes(); if (!( dataPointTattributes.hasAttribute(QStringLiteral("Role")) && dataPointTattributes.hasAttribute(QStringLiteral("Type")) )) return false; int dataRole = dataPointTattributes.value(QStringLiteral("Role")).toInt(); int dataType = dataPointTattributes.value(QStringLiteral("Type")).toInt(); model->setData(model->index(rowIndex, colIndex, parent), loadVariant(dataType, source.readElementText()), dataRole); } else if (source.name() == QStringLiteral("Element") && cellStarted) { if (rowIndex < 0 || colIndex < 0) return false; readElement(source, model, model->index(rowIndex, colIndex, parent)); } } else if (source.isEndElement()){ if (source.name() == QStringLiteral("Cell")) { cellStarted = false; rowIndex = -1; colIndex = -1; } else if (source.name() == QStringLiteral("Element")) { if(!cellStarted) return true; } } } return false; } bool saveModel(const QAbstractItemModel* const model, const QString& destination) { return saveModel(model, destination, { Qt::DecorationRole , Qt::EditRole , Qt::ToolTipRole , Qt::StatusTipRole , Qt::WhatsThisRole , Qt::SizeHintRole , Qt::FontRole , Qt::TextAlignmentRole , Qt::BackgroundRole , Qt::ForegroundRole , Qt::CheckStateRole , Qt::InitialSortOrderRole , Qt::AccessibleTextRole , Qt::AccessibleDescriptionRole , Qt::UserRole }); } bool saveModel(const QAbstractItemModel* const model, const QString& destination, const QList<int>& rolesToSave) { const QVersionNumber modelSerialisationVersion(1, 0, 0); // Use this to implement versioning of the serialised values QSaveFile destinationFile(destination); if (!destinationFile.open(QIODevice::WriteOnly)) return false; QXmlStreamWriter writer(&destinationFile); writer.writeStartDocument(); writer.writeStartElement(QStringLiteral("ItemModel")); writer.writeEmptyElement(QStringLiteral("Version")); writer.writeAttribute(QStringLiteral("VersionNumber"), modelSerialisationVersion.toString()); writeElement(writer, model, rolesToSave); writer.writeStartElement(QStringLiteral("HeaderData")); // Header data is saved only for the number of rows and columns in the root table writer.writeStartElement(QStringLiteral("Horizontal")); for (int i = 0; i < model->columnCount(); ++i) { for (int singleRole : rolesToSave) { const QVariant roleData = model->headerData(i, Qt::Horizontal, singleRole); if (roleData.isNull()) continue; writer.writeStartElement(QStringLiteral("HeaderDataPoint")); writer.writeAttribute(QStringLiteral("Section"), QString::number(i)); writer.writeAttribute(QStringLiteral("Role"), QString::number(singleRole)); writer.writeAttribute(QStringLiteral("Type"), QString::number(roleData.type())); writer.writeCharacters(saveVariant(roleData)); writer.writeEndElement(); // HeaderDataPoint } } writer.writeEndElement(); // Horizontal writer.writeStartElement(QStringLiteral("Vertical")); for (int i = 0; i < model->rowCount(); ++i) { for (int singleRole : rolesToSave) { const QVariant roleData = model->headerData(i, Qt::Vertical, singleRole); if (roleData.isNull()) continue; writer.writeStartElement(QStringLiteral("HeaderDataPoint")); writer.writeAttribute(QStringLiteral("Section"), QString::number(i)); writer.writeAttribute(QStringLiteral("Role"), QString::number(singleRole)); writer.writeAttribute(QStringLiteral("Type"), QString::number(roleData.type())); writer.writeCharacters(saveVariant(roleData)); writer.writeEndElement(); // HeaderDataPoint } } writer.writeEndElement(); // Vertical writer.writeEndElement(); // HeaderData writer.writeEndElement(); // ItemModel writer.writeEndDocument(); return destinationFile.commit(); } bool loadModel(QAbstractItemModel* const model, const QString& source){ QFile sourceFile(source); if (!sourceFile.open(QIODevice::ReadOnly)) return false; model->removeColumns(0, model->columnCount()); model->removeRows(0, model->rowCount()); QVersionNumber modelSerialisationVersion; // Use this to implement versioning of the serialised values QXmlStreamReader reader(&sourceFile); bool headerDataStarted = false; bool hHeaderDataStarted = false; bool vHeaderDataStarted = false; while (!reader.atEnd() && !reader.hasError()) { reader.readNext(); if (reader.isStartElement()) { if (reader.name() == QStringLiteral("Version")) { const auto versionAttributes = reader.attributes(); if (!versionAttributes.hasAttribute(QStringLiteral("VersionNumber"))) return false; modelSerialisationVersion = QVersionNumber::fromString(versionAttributes.value(QStringLiteral("VersionNumber")).toString()); } else if (reader.name() == QStringLiteral("Element")){ if(!readElement(reader, model)){ model->removeColumns(0, model->columnCount()); model->removeRows(0, model->rowCount()); return false; } } else if (reader.name() == QStringLiteral("HeaderData")) { headerDataStarted = true; } else if (reader.name() == QStringLiteral("Vertical") && headerDataStarted) { if (hHeaderDataStarted) return false; vHeaderDataStarted = true; } else if (reader.name() == QStringLiteral("Horizontal") && headerDataStarted) { if (vHeaderDataStarted) return false; hHeaderDataStarted = true; } else if (reader.name() == QStringLiteral("HeaderDataPoint") && headerDataStarted) { if (!(vHeaderDataStarted || hHeaderDataStarted)) return false; const auto headDataAttribute = reader.attributes(); if (!( headDataAttribute.hasAttribute(QStringLiteral("Section")) && headDataAttribute.hasAttribute(QStringLiteral("Role")) && headDataAttribute.hasAttribute(QStringLiteral("Type")) )) return false; int headerSection = headDataAttribute.value(QStringLiteral("Section")).toInt(); int headerRole = headDataAttribute.value(QStringLiteral("Role")).toInt(); int headerType = headDataAttribute.value(QStringLiteral("Type")).toInt(); model->setHeaderData(headerSection, (vHeaderDataStarted ? Qt::Vertical : Qt::Horizontal), loadVariant(headerType, reader.readElementText()), headerRole); } } else if (reader.isEndElement()){ if (reader.name() == QStringLiteral("HeaderData")) { headerDataStarted = false; } else if (reader.name() == QStringLiteral("Vertical") && headerDataStarted) { vHeaderDataStarted = false; } else if (reader.name() == QStringLiteral("Horizontal") && headerDataStarted) { hHeaderDataStarted = false; } } } if (reader.hasError()) { model->removeColumns(0, model->columnCount()); model->removeRows(0, model->rowCount()); return false; } return true; } }
Example Usage:
if (!ModelSerialisation::saveModel(treeWidget->model(), "TestSave.xml")) Q_ASSERT(false); if (!ModelSerialisation::loadModel(treeWidget->model(), "TestSave.xml")) Q_ASSERT(false);
Suggestions, corrections and comments on the code are MORE than welcome
-
Slightly more general versions, still missing a lot
QVariant loadVariant(int type, const QString& val){ switch (type) { case QMetaType::QString: return val; case QMetaType::Double: return val.toDouble(); case QMetaType::Int: return val.toInt(); case QMetaType::LongLong: return val.toLongLong(); case QMetaType::Bool: return val.toInt() == 1; case QMetaType::QChar: return val[0]; case QMetaType::QDate: return QDate::fromString(val, Qt::ISODate); case QMetaType::QDateTime: return QDateTime::fromString(val, Qt::ISODate); case QMetaType::QTime: return QTime::fromString(val, Qt::ISODate); case QMetaType::UInt: return val.toUInt(); case QMetaType::ULongLong: return val.toULongLong(); // Add other types here default: return QVariant(); } } QString saveVariant(const QVariant& val){ switch(val.type()){ case QMetaType::QString: return val.toString(); case QMetaType::Bool: if (val.toBool()) return QString::number(1); return QString::number(0); case QMetaType::QChar: return QString(val.toChar()); case QMetaType::QDate: return val.toDate().toString(Qt::ISODate); case QMetaType::QDateTime: return val.toDateTime().toString(Qt::ISODate); case QMetaType::Double: return QString::number(val.toDouble(), 'f'); case QMetaType::Int: return QString::number(val.toInt()); case QMetaType::LongLong: return QString::number(val.toLongLong()); case QMetaType::QTime: return val.toTime().toString(Qt::ISODate); case QMetaType::UInt: return QString::number(val.toUInt()); case QMetaType::ULongLong: return QString::number(val.toULongLong()); // Add other types here default: return QString(); } }
-
ok, this is the most general I could come out with.
You still have to add any custom class but that should be straightforward using the template methods.I'm not saying it's efficient but it works
modelserialisation.cpp
#include <QAbstractItemModel> #include <QXmlStreamReader> #include <QXmlStreamWriter> #include <QSaveFile> #include <QFile> #include <QVersionNumber> #include <QDataStream> #include <QDateTime> #include <QBitArray> #include <QUrl> #include <QLocale> #include <QRect> #include <QRectF> #include <QSize> #include <QSizeF> #include <QLine> #include <QLineF> #include <QEasingCurve> #include <QUuid> #include <QRegularExpression> #include <QJsonValue> #include <QJsonObject> #include <QJsonArray> #include <QJsonDocument> #include <QFont> #include <QPixmap> #include <QBrush> #include <QColor> #include <QPalette> #include <QIcon> #include <QImage> #include <QPolygon> #include <QRegion> #include <QBitmap> #include <QCursor> #include <QKeySequence> #include <QPen> #include <QTextLength> #include <QTextFormat> #include <QMatrix> #include <QTransform> #include <QMatrix4x4> #include <QVector2D> #include <QVector3D> #include <QVector4D> #include <QQuaternion> #include <QPolygonF> #include <QSizePolicy> #include "modelserialisation.h" namespace ModelSerialisation { template <class T> QString variantToString(const QVariant& val){ QString result; QByteArray data; QDataStream outStream(&data, QIODevice::WriteOnly); outStream << val.value<T>(); for (const char* i = data.constBegin(); i != data.constEnd();++i){ const QString tempString = QString::number(*reinterpret_cast<const unsigned char*>(i), 16); Q_ASSERT(tempString.size() == 2 || tempString.size() == 1); if (tempString.size() == 1) result += '0'; result += tempString; } Q_ASSERT(result.size() % 2 == 0); return result; } template <class T> QVariant stringToVariant(const QString& val) { QByteArray data; for (int i = 0; i < val.size(); i += 2) data.append(static_cast<char>(val.midRef(i, 2).toInt(nullptr, 16))); QDataStream inStream(data); T result; inStream >> result; return QVariant::fromValue(result); } QVariant loadVariant(int type, const QString& val){ switch (type) { case QMetaType::Bool: return stringToVariant<bool>(val); case QMetaType::Int: return stringToVariant<qint32>(val); case QMetaType::UInt: return stringToVariant<quint32>(val); case QMetaType::LongLong: return stringToVariant<qint64>(val); case QMetaType::ULongLong: return stringToVariant<quint64>(val); case QMetaType::Double: return stringToVariant<double>(val); case QMetaType::Long: return stringToVariant<qint32>(val); case QMetaType::Short: return stringToVariant<qint16>(val); case QMetaType::Char: return stringToVariant<qint8>(val); case QMetaType::ULong: return stringToVariant<quint32>(val); case QMetaType::UShort: return stringToVariant<quint16>(val); case QMetaType::UChar: return stringToVariant<quint8>(val); case QMetaType::Float: return stringToVariant<float>(val); case QMetaType::SChar: return stringToVariant<qint8>(val); case QMetaType::QChar: return stringToVariant<QChar>(val); case QMetaType::QString: return stringToVariant<QString>(val); case QMetaType::QStringList: return stringToVariant<QStringList>(val); case QMetaType::QByteArray: return stringToVariant<QByteArray>(val); case QMetaType::QBitArray: return stringToVariant<QBitArray>(val); case QMetaType::QDate: return stringToVariant<QDate>(val); case QMetaType::QTime: return stringToVariant<QTime>(val); case QMetaType::QDateTime: return stringToVariant<QDateTime>(val); case QMetaType::QUrl: return stringToVariant<QUrl>(val); case QMetaType::QLocale: return stringToVariant<QLocale>(val); case QMetaType::QRect: return stringToVariant<QRect>(val); case QMetaType::QRectF: return stringToVariant<QRectF>(val); case QMetaType::QSize: return stringToVariant<QSize>(val); case QMetaType::QSizeF: return stringToVariant<QSizeF>(val); case QMetaType::QLine: return stringToVariant<QLine>(val); case QMetaType::QLineF: return stringToVariant<QLineF>(val); case QMetaType::QPoint: return stringToVariant<QPoint>(val); case QMetaType::QPointF: return stringToVariant<QPointF>(val); case QMetaType::QRegExp: return stringToVariant<QRegExp>(val); case QMetaType::QEasingCurve: return stringToVariant<QEasingCurve>(val); case QMetaType::QUuid: return stringToVariant<QUuid>(val); case QMetaType::QVariant: return stringToVariant<QVariant>(val); case QMetaType::QRegularExpression: return stringToVariant<QRegularExpression>(val); case QMetaType::QVariantMap: return stringToVariant<QVariantMap>(val); case QMetaType::QVariantList: return stringToVariant<QVariantList>(val); case QMetaType::QVariantHash: return stringToVariant<QVariantHash>(val); case QMetaType::QByteArrayList: return stringToVariant<QByteArrayList>(val); case QMetaType::QFont: return stringToVariant<QFont>(val); case QMetaType::QPixmap: return stringToVariant<QPixmap>(val); case QMetaType::QBrush: return stringToVariant<QBrush>(val); case QMetaType::QColor: return stringToVariant<QColor>(val); case QMetaType::QPalette: return stringToVariant<QPalette>(val); case QMetaType::QIcon: return stringToVariant<QIcon>(val); case QMetaType::QImage: return stringToVariant<QImage>(val); case QMetaType::QPolygon: return stringToVariant<QPolygon>(val); case QMetaType::QRegion: return stringToVariant<QRegion>(val); case QMetaType::QBitmap: return stringToVariant<QBitmap>(val); case QMetaType::QCursor: return stringToVariant<QCursor>(val); case QMetaType::QKeySequence: return stringToVariant<QKeySequence>(val); case QMetaType::QPen: return stringToVariant<QPen>(val); case QMetaType::QTextLength: return stringToVariant<QTextLength>(val); case QMetaType::QTextFormat: return stringToVariant<QTextFormat>(val); case QMetaType::QMatrix: return stringToVariant<QMatrix>(val); case QMetaType::QTransform: return stringToVariant<QTransform>(val); case QMetaType::QMatrix4x4: return stringToVariant<QMatrix4x4>(val); case QMetaType::QVector2D: return stringToVariant<QVector2D>(val); case QMetaType::QVector3D: return stringToVariant<QVector3D>(val); case QMetaType::QVector4D: return stringToVariant<QVector4D>(val); case QMetaType::QQuaternion: return stringToVariant<QQuaternion>(val); case QMetaType::QPolygonF: return stringToVariant<QPolygonF>(val); case QMetaType::QSizePolicy: return stringToVariant<QSizePolicy>(val); default: return QVariant(); } } QString saveVariant(const QVariant& val){ switch(val.type()){ case QMetaType::Bool: return variantToString<bool>(val); case QMetaType::Int: return variantToString<qint32>(val); case QMetaType::UInt: return variantToString<quint32>(val); case QMetaType::LongLong: return variantToString<qint64>(val); case QMetaType::ULongLong: return variantToString<quint64>(val); case QMetaType::Double: return variantToString<double>(val); case QMetaType::Long: return variantToString<qint32>(val); case QMetaType::Short: return variantToString<qint16>(val); case QMetaType::Char: return variantToString<qint8>(val); case QMetaType::ULong: return variantToString<quint32>(val); case QMetaType::UShort: return variantToString<quint16>(val); case QMetaType::UChar: return variantToString<quint8>(val); case QMetaType::Float: return variantToString<float>(val); case QMetaType::SChar: return variantToString<qint8>(val); case QMetaType::QChar: return variantToString<QChar>(val); case QMetaType::QString: return variantToString<QString>(val); case QMetaType::QStringList: return variantToString<QStringList>(val); case QMetaType::QByteArray: return variantToString<QByteArray>(val); case QMetaType::QBitArray: return variantToString<QBitArray>(val); case QMetaType::QDate: return variantToString<QDate>(val); case QMetaType::QTime: return variantToString<QTime>(val); case QMetaType::QDateTime: return variantToString<QDateTime>(val); case QMetaType::QUrl: return variantToString<QUrl>(val); case QMetaType::QLocale: return variantToString<QLocale>(val); case QMetaType::QRect: return variantToString<QRect>(val); case QMetaType::QRectF: return variantToString<QRectF>(val); case QMetaType::QSize: return variantToString<QSize>(val); case QMetaType::QSizeF: return variantToString<QSizeF>(val); case QMetaType::QLine: return variantToString<QLine>(val); case QMetaType::QLineF: return variantToString<QLineF>(val); case QMetaType::QPoint: return variantToString<QPoint>(val); case QMetaType::QPointF: return variantToString<QPointF>(val); case QMetaType::QRegExp: return variantToString<QRegExp>(val); case QMetaType::QEasingCurve: return variantToString<QEasingCurve>(val); case QMetaType::QUuid: return variantToString<QUuid>(val); case QMetaType::QVariant: return variantToString<QVariant>(val); case QMetaType::QRegularExpression: return variantToString<QRegularExpression>(val); case QMetaType::QVariantMap: return variantToString<QVariantMap>(val); case QMetaType::QVariantList: return variantToString<QVariantList>(val); case QMetaType::QVariantHash: return variantToString<QVariantHash>(val); case QMetaType::QByteArrayList: return variantToString<QByteArrayList>(val); case QMetaType::QFont: return variantToString<QFont>(val); case QMetaType::QPixmap: return variantToString<QPixmap>(val); case QMetaType::QBrush: return variantToString<QBrush>(val); case QMetaType::QColor: return variantToString<QColor>(val); case QMetaType::QPalette: return variantToString<QPalette>(val); case QMetaType::QIcon: return variantToString<QIcon>(val); case QMetaType::QImage: return variantToString<QImage>(val); case QMetaType::QPolygon: return variantToString<QPolygon>(val); case QMetaType::QRegion: return variantToString<QRegion>(val); case QMetaType::QBitmap: return variantToString<QBitmap>(val); case QMetaType::QCursor: return variantToString<QCursor>(val); case QMetaType::QKeySequence: return variantToString<QKeySequence>(val); case QMetaType::QPen: return variantToString<QPen>(val); case QMetaType::QTextLength: return variantToString<QTextLength>(val); case QMetaType::QTextFormat: return variantToString<QTextFormat>(val); case QMetaType::QMatrix: return variantToString<QMatrix>(val); case QMetaType::QTransform: return variantToString<QTransform>(val); case QMetaType::QMatrix4x4: return variantToString<QMatrix4x4>(val); case QMetaType::QVector2D: return variantToString<QVector2D>(val); case QMetaType::QVector3D: return variantToString<QVector3D>(val); case QMetaType::QVector4D: return variantToString<QVector4D>(val); case QMetaType::QQuaternion: return variantToString<QQuaternion>(val); case QMetaType::QPolygonF: return variantToString<QPolygonF>(val); case QMetaType::QSizePolicy: return variantToString<QSizePolicy>(val); default: return QString(); } } void writeElement(QXmlStreamWriter& destination, const QAbstractItemModel* const model, const QList<int>& rolesToSave, const QModelIndex& parent = QModelIndex()) { if (model->rowCount(parent) + model->columnCount(parent) == 0) return; destination.writeStartElement(QStringLiteral("Element")); destination.writeAttribute(QStringLiteral("RowCount"), QString::number(model->rowCount(parent))); destination.writeAttribute(QStringLiteral("ColumnCount"), QString::number(model->columnCount(parent))); for (int i = 0; i < model->rowCount(parent); ++i) { for (int j = 0; j < model->columnCount(parent); ++j) { destination.writeStartElement(QStringLiteral("Cell")); destination.writeStartElement(QStringLiteral("Row")); destination.writeCharacters(QString::number(i)); destination.writeEndElement(); // Row destination.writeStartElement(QStringLiteral("Column")); destination.writeCharacters(QString::number(j)); destination.writeEndElement(); // Column for (int singleRole : rolesToSave) { const QVariant roleData = model->index(i, j, parent).data(singleRole); if (roleData.isNull()) continue; // Skip empty roles destination.writeStartElement(QStringLiteral("DataPoint")); destination.writeAttribute(QStringLiteral("Role"), QString::number(singleRole)); destination.writeAttribute(QStringLiteral("Type"), QString::number(roleData.type())); destination.writeCharacters(saveVariant(roleData)); destination.writeEndElement(); // DataPoint } if (model->hasChildren(model->index(i, j, parent))) { writeElement(destination, model, rolesToSave, model->index(i, j, parent)); } destination.writeEndElement(); // Cell } } destination.writeEndElement(); // Element } bool readElement(QXmlStreamReader& source, QAbstractItemModel* const model, const QModelIndex& parent = QModelIndex()){ if (source.name() != QStringLiteral("Element")) return false; int rowCount, colCount; const auto tableSizeAttribute = source.attributes(); if (!( tableSizeAttribute.hasAttribute(QStringLiteral("RowCount")) && tableSizeAttribute.hasAttribute(QStringLiteral("ColumnCount")) )) return false; rowCount = tableSizeAttribute.value(QStringLiteral("RowCount")).toInt(); colCount = tableSizeAttribute.value(QStringLiteral("ColumnCount")).toInt(); if (rowCount <= 0 || colCount <= 0) return false; if (model->rowCount(parent) < rowCount) model->insertRows(model->rowCount(parent), rowCount - model->rowCount(parent), parent); if (model->columnCount(parent) < colCount) model->insertColumns(model->columnCount(parent), colCount - model->columnCount(parent), parent); int rowIndex = -1; int colIndex = -1; bool cellStarted =false; while (!source.atEnd() && !source.hasError()) { source.readNext(); if (source.isStartElement()) { if (source.name() == QStringLiteral("Cell")) { cellStarted = true; } else if (source.name() == QStringLiteral("Row") && cellStarted) { rowIndex = source.readElementText().toInt(); } else if (source.name() == QStringLiteral("Column") && cellStarted) { colIndex = source.readElementText().toInt(); } else if (source.name() == QStringLiteral("DataPoint") && cellStarted) { if (rowIndex < 0 || colIndex < 0) return false; const auto dataPointTattributes = source.attributes(); if (!( dataPointTattributes.hasAttribute(QStringLiteral("Role")) && dataPointTattributes.hasAttribute(QStringLiteral("Type")) )) return false; int dataRole = dataPointTattributes.value(QStringLiteral("Role")).toInt(); int dataType = dataPointTattributes.value(QStringLiteral("Type")).toInt(); model->setData(model->index(rowIndex, colIndex, parent), loadVariant(dataType, source.readElementText()), dataRole); } else if (source.name() == QStringLiteral("Element") && cellStarted) { if (rowIndex < 0 || colIndex < 0) return false; readElement(source, model, model->index(rowIndex, colIndex, parent)); } } else if (source.isEndElement()){ if (source.name() == QStringLiteral("Cell")) { cellStarted = false; rowIndex = -1; colIndex = -1; } else if (source.name() == QStringLiteral("Element")) { if(!cellStarted) return true; } } } return false; } bool saveModel(const QAbstractItemModel* const model, const QString& destination) { return saveModel(model, destination, { Qt::DecorationRole , Qt::EditRole , Qt::ToolTipRole , Qt::StatusTipRole , Qt::WhatsThisRole , Qt::SizeHintRole , Qt::FontRole , Qt::TextAlignmentRole , Qt::BackgroundRole , Qt::ForegroundRole , Qt::CheckStateRole , Qt::InitialSortOrderRole , Qt::AccessibleTextRole , Qt::AccessibleDescriptionRole , Qt::UserRole }); } bool saveModel(const QAbstractItemModel* const model, const QString& destination, const QList<int>& rolesToSave) { const QVersionNumber modelSerialisationVersion(1, 0, 0); // Use this to implement versioning of the serialised values QSaveFile destinationFile(destination); if (!destinationFile.open(QIODevice::WriteOnly)) return false; QXmlStreamWriter writer(&destinationFile); writer.writeStartDocument(); writer.writeStartElement(QStringLiteral("ItemModel")); writer.writeEmptyElement(QStringLiteral("Version")); writer.writeAttribute(QStringLiteral("VersionNumber"), modelSerialisationVersion.toString()); writeElement(writer, model, rolesToSave); writer.writeStartElement(QStringLiteral("HeaderData")); // Header data is saved only for the number of rows and columns in the root table writer.writeStartElement(QStringLiteral("Horizontal")); for (int i = 0; i < model->columnCount(); ++i) { for (int singleRole : rolesToSave) { const QVariant roleData = model->headerData(i, Qt::Horizontal, singleRole); if (roleData.isNull()) continue; writer.writeStartElement(QStringLiteral("HeaderDataPoint")); writer.writeAttribute(QStringLiteral("Section"), QString::number(i)); writer.writeAttribute(QStringLiteral("Role"), QString::number(singleRole)); writer.writeAttribute(QStringLiteral("Type"), QString::number(roleData.type())); writer.writeCharacters(saveVariant(roleData)); writer.writeEndElement(); // HeaderDataPoint } } writer.writeEndElement(); // Horizontal writer.writeStartElement(QStringLiteral("Vertical")); for (int i = 0; i < model->rowCount(); ++i) { for (int singleRole : rolesToSave) { const QVariant roleData = model->headerData(i, Qt::Vertical, singleRole); if (roleData.isNull()) continue; writer.writeStartElement(QStringLiteral("HeaderDataPoint")); writer.writeAttribute(QStringLiteral("Section"), QString::number(i)); writer.writeAttribute(QStringLiteral("Role"), QString::number(singleRole)); writer.writeAttribute(QStringLiteral("Type"), QString::number(roleData.type())); writer.writeCharacters(saveVariant(roleData)); writer.writeEndElement(); // HeaderDataPoint } } writer.writeEndElement(); // Vertical writer.writeEndElement(); // HeaderData writer.writeEndElement(); // ItemModel writer.writeEndDocument(); return destinationFile.commit(); } bool loadModel(QAbstractItemModel* const model, const QString& source){ QFile sourceFile(source); if (!sourceFile.open(QIODevice::ReadOnly)) return false; model->removeColumns(0, model->columnCount()); model->removeRows(0, model->rowCount()); QVersionNumber modelSerialisationVersion; // Use this to implement versioning of the serialised values QXmlStreamReader reader(&sourceFile); bool headerDataStarted = false; bool hHeaderDataStarted = false; bool vHeaderDataStarted = false; while (!reader.atEnd() && !reader.hasError()) { reader.readNext(); if (reader.isStartElement()) { if (reader.name() == QStringLiteral("Version")) { const auto versionAttributes = reader.attributes(); if (!versionAttributes.hasAttribute(QStringLiteral("VersionNumber"))) return false; modelSerialisationVersion = QVersionNumber::fromString(versionAttributes.value(QStringLiteral("VersionNumber")).toString()); } else if (reader.name() == QStringLiteral("Element")){ if(!readElement(reader, model)){ model->removeColumns(0, model->columnCount()); model->removeRows(0, model->rowCount()); return false; } } else if (reader.name() == QStringLiteral("HeaderData")) { headerDataStarted = true; } else if (reader.name() == QStringLiteral("Vertical") && headerDataStarted) { if (hHeaderDataStarted) return false; vHeaderDataStarted = true; } else if (reader.name() == QStringLiteral("Horizontal") && headerDataStarted) { if (vHeaderDataStarted) return false; hHeaderDataStarted = true; } else if (reader.name() == QStringLiteral("HeaderDataPoint") && headerDataStarted) { if (!(vHeaderDataStarted || hHeaderDataStarted)) return false; const auto headDataAttribute = reader.attributes(); if (!( headDataAttribute.hasAttribute(QStringLiteral("Section")) && headDataAttribute.hasAttribute(QStringLiteral("Role")) && headDataAttribute.hasAttribute(QStringLiteral("Type")) )) return false; int headerSection = headDataAttribute.value(QStringLiteral("Section")).toInt(); int headerRole = headDataAttribute.value(QStringLiteral("Role")).toInt(); int headerType = headDataAttribute.value(QStringLiteral("Type")).toInt(); model->setHeaderData(headerSection, (vHeaderDataStarted ? Qt::Vertical : Qt::Horizontal), loadVariant(headerType, reader.readElementText()), headerRole); } } else if (reader.isEndElement()){ if (reader.name() == QStringLiteral("HeaderData")) { headerDataStarted = false; } else if (reader.name() == QStringLiteral("Vertical") && headerDataStarted) { vHeaderDataStarted = false; } else if (reader.name() == QStringLiteral("Horizontal") && headerDataStarted) { hHeaderDataStarted = false; } } } if (reader.hasError()) { model->removeColumns(0, model->columnCount()); model->removeRows(0, model->rowCount()); return false; } return true; } }
-
@kshegunov is it that bad?
I fixed a few thing:
- major bugs causing undefined behaviour and preventing the usage with user types
- generalised to QIODevice instead of files only
- removed the C++11 syntax (I hope I caught it all)
- added MIT license
- made the most common types (numbers, strings, dates and time) human readable in the xml (this should also improve efficiency)
code is now stored here: https://github.com/VSRonin/Qt-Model-Serialisation
Pull requests are always welcome