diff options
author | Ali Kianian <ali.kianian@qt.io> | 2024-02-02 14:33:52 +0200 |
---|---|---|
committer | Ali Kianian <ali.kianian@qt.io> | 2024-02-05 15:15:11 +0000 |
commit | c58efc4310d25bdbd62766f0efa087cbdafcd1f1 (patch) | |
tree | f69ccc2b82774afc709f6abd6038ccc1cf9e01bc | |
parent | ca9e72fe6cf9f8e0fe00d794bf7111abac372c54 (diff) |
QmlDesigner: Implement new data store structure for Model Editor
Task-number: QDS-11778
Change-Id: Ia98fee976e5d81acc608b6209da270cbee2f9c61
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
Reviewed-by: Qt CI Patch Build Bot <ci_patchbuild_bot@qt.io>
15 files changed, 779 insertions, 685 deletions
diff --git a/share/qtcreator/qmldesigner/collectionEditorQmlSource/ImportDialog.qml b/share/qtcreator/qmldesigner/collectionEditorQmlSource/ImportDialog.qml index 3f8108005c..cb77d567e8 100644 --- a/share/qtcreator/qmldesigner/collectionEditorQmlSource/ImportDialog.qml +++ b/share/qtcreator/qmldesigner/collectionEditorQmlSource/ImportDialog.qml @@ -192,7 +192,7 @@ StudioControls.Dialog { enabled: root.fileExists && collectionName.text !== "" onClicked: { - let collectionImported = root.backendValue.importCollectionToDataStore( + let collectionImported = root.backendValue.importFile( collectionName.text, fileName.text) diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/models.json.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/models.json.tpl index 8ebda6fb7e..ca9c173651 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/models.json.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/models.json.tpl @@ -1,30 +1,56 @@ { - "book": [ - { - "author": "Nigel Rees", - "category": "reference", - "price": 8.95, - "title": "Sayings of the Century" - }, - { - "author": "Evelyn Waugh", - "category": "fiction", - "price": 12.99, - "title": "Sword of Honor" - }, - { - "author": "Herman Melville", - "category": "fiction", - "isbn": "0-553-21311-3", - "price": 8.99, - "title": "Moby Dick" - }, - { - "author": "J. R. R. Tolkien", - "category": "fiction", - "isbn": "0-395-19395-8", - "price": 22.99, - "title": "The Lord of the Rings" - } - ] + "book": { + "columns": [ + { + "name": "author", + "type": "String" + }, + { + "name": "category", + "type": "String" + }, + { + "name": "isbn", + "type": "String" + }, + { + "name": "price", + "type": "Real" + }, + { + "name": "title", + "type": "String" + } + ], + "data": [ + [ + "Nigel Rees", + "reference", + "", + 8.95, + "Sayings of the Century" + ], + [ + "Evelyn Waugh", + "fiction", + "", + 12.99, + "Sword of Honor" + ], + [ + "Herman Melville", + "fiction", + "0-553-21311-3", + 8.99, + "Moby Dick" + ], + [ + "J. R. R. Tolkien", + "fiction", + "0-395-19395-8", + 22.99, + "The Lord of the Rings" + ] + ] + } } diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.cpp index f35e9f2ed2..f1827ee6be 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.cpp @@ -3,16 +3,25 @@ #include "collectiondetails.h" +#include "collectioneditorutils.h" + #include <utils/span.h> #include <qqml.h> #include <QJsonArray> #include <QJsonDocument> #include <QJsonObject> +#include <QTextStream> #include <QUrl> #include <QVariant> namespace QmlDesigner { +#define COLLERR_OK QT_TRANSLATE_NOOP("CollectioParseError", "no error occurred") +#define COLLERR_MAINOBJECT QT_TRANSLATE_NOOP("CollectioParseError", "Document object not found") +#define COLLERR_COLLECTIONNAME QT_TRANSLATE_NOOP("CollectioParseError", "Model name not found") +#define COLLERR_COLLECTIONOBJ QT_TRANSLATE_NOOP("CollectioParseError", "Model is not an object") +#define COLLERR_COLUMNARRAY QT_TRANSLATE_NOOP("CollectioParseError", "Column is not an array") +#define COLLERR_UNKNOWN QT_TRANSLATE_NOOP("CollectioParseError", "Unknown error") struct CollectionProperty { @@ -28,30 +37,23 @@ const QMap<DataTypeWarning::Warning, QString> DataTypeWarning::dataTypeWarnings class CollectionDetails::Private { - using SourceFormat = CollectionEditorConstants::SourceFormat; - public: QList<CollectionProperty> properties; - QList<QJsonObject> elements; - SourceFormat sourceFormat = SourceFormat::Unknown; + QList<QJsonArray> dataRecords; CollectionReference reference; bool isChanged = false; bool isValidColumnId(int column) const { return column > -1 && column < properties.size(); } - bool isValidRowId(int row) const { return row > -1 && row < elements.size(); } + bool isValidRowId(int row) const { return row > -1 && row < dataRecords.size(); } }; inline static bool isValidColorName(const QString &colorName) { -#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) - return QColor::isValidColorName(colorName); -#else constexpr QStringView colorPattern( u"(?<color>^(?:#(?:(?:[0-9a-fA-F]{2}){3,4}|(?:[0-9a-fA-F]){3,4}))$)"); static const QRegularExpression colorRegex(colorPattern.toString()); return colorRegex.match(colorName).hasMatch(); -#endif // >= Qt 6.4 } /** @@ -115,7 +117,7 @@ inline static bool getCustomUrl(const QString &value, return false; } -static CollectionProperty::DataType collectionDataTypeFromJsonValue(const QJsonValue &value) +static CollectionProperty::DataType dataTypeFromJsonValue(const QJsonValue &value) { using DataType = CollectionDetails::DataType; using JsonType = QJsonValue::Type; @@ -133,6 +135,10 @@ static CollectionProperty::DataType collectionDataTypeFromJsonValue(const QJsonV } case JsonType::String: { const QString stringValue = value.toString(); + + if (stringValue.isEmpty()) + return DataType::Unknown; + if (isValidColorName(stringValue)) return DataType::Color; @@ -147,6 +153,42 @@ static CollectionProperty::DataType collectionDataTypeFromJsonValue(const QJsonV } } +static QList<CollectionProperty> getColumnsFromImportedJsonArray(const QJsonArray &importedArray) +{ + using DataType = CollectionDetails::DataType; + + QHash<QString, int> resultSet; + QList<CollectionProperty> result; + + for (const QJsonValue &value : importedArray) { + if (value.isObject()) { + const QJsonObject object = value.toObject(); + QJsonObject::ConstIterator element = object.constBegin(); + const QJsonObject::ConstIterator stopItem = object.constEnd(); + + while (element != stopItem) { + const QString propertyName = element.key(); + if (resultSet.contains(propertyName)) { + CollectionProperty &property = result[resultSet.value(propertyName)]; + if (property.type == DataType::Unknown) { + property.type = dataTypeFromJsonValue(element.value()); + } else if (property.type == DataType::Integer) { + const DataType currentCellDataType = dataTypeFromJsonValue(element.value()); + if (currentCellDataType == DataType::Real) + property.type = currentCellDataType; + } + } else { + result.append({propertyName, dataTypeFromJsonValue(element.value())}); + resultSet.insert(propertyName, resultSet.size()); + } + ++element; + } + } + } + + return result; +} + static QVariant valueToVariant(const QJsonValue &value, CollectionDetails::DataType type) { using DataType = CollectionDetails::DataType; @@ -214,6 +256,30 @@ static QJsonValue variantToJsonValue( } } +inline static bool isEmptyJsonValue(const QJsonValue &value) +{ + return value.isNull() || value.isUndefined() || (value.isString() && value.toString().isEmpty()); +} + +QString CollectionParseError::errorString() const +{ + switch (errorNo) { + case NoError: + return COLLERR_OK; + case MainObjectMissing: + return COLLERR_MAINOBJECT; + case CollectionNameNotFound: + return COLLERR_COLLECTIONNAME; + case CollectionIsNotObject: + return COLLERR_COLLECTIONOBJ; + case ColumnsBlockIsNotArray: + return COLLERR_COLUMNARRAY; + case UnknownError: + default: + return COLLERR_UNKNOWN; + } +} + CollectionDetails::CollectionDetails() : d(new Private()) {} @@ -224,134 +290,102 @@ CollectionDetails::CollectionDetails(const CollectionReference &reference) d->reference = reference; } -CollectionDetails::CollectionDetails(const CollectionDetails &other) = default; - -CollectionDetails::~CollectionDetails() = default; - -void CollectionDetails::resetDetails(const QStringList &propertyNames, - const QList<QJsonObject> &elements, - CollectionEditorConstants::SourceFormat format) +void CollectionDetails::resetData(const QJsonDocument &localDocument, + const QString &collectionToImport, + CollectionParseError *error) { - if (!isValid()) - return; - - d->properties = Utils::transform(propertyNames, [](const QString &name) -> CollectionProperty { - return {name, DataType::Unknown}; - }); + CollectionDetails importedCollection = fromLocalJson(localDocument, collectionToImport, error); + d->properties.swap(importedCollection.d->properties); + d->dataRecords.swap(importedCollection.d->dataRecords); +} - d->elements = elements; - d->sourceFormat = format; +CollectionDetails::CollectionDetails(const CollectionDetails &other) = default; - resetPropertyTypes(); - markSaved(); -} +CollectionDetails::~CollectionDetails() = default; void CollectionDetails::insertColumn(const QString &propertyName, int colIdx, const QVariant &defaultValue, DataType type) { - if (!isValid()) - return; - if (containsPropertyName(propertyName)) return; CollectionProperty property = {propertyName, type}; - if (d->isValidColumnId(colIdx)) + if (d->isValidColumnId(colIdx)) { d->properties.insert(colIdx, property); - else + } else { + colIdx = d->properties.size(); d->properties.append(property); + } - QJsonValue defaultJsonValue = QJsonValue::fromVariant(defaultValue); - for (QJsonObject &element : d->elements) - element.insert(propertyName, defaultJsonValue); + const QJsonValue defaultJsonValue = QJsonValue::fromVariant(defaultValue); + for (QJsonArray &record : d->dataRecords) + record.insert(colIdx, defaultJsonValue); markChanged(); } bool CollectionDetails::removeColumns(int colIdx, int count) { - if (count < 1 || !isValid() || !d->isValidColumnId(colIdx)) + if (!d->isValidColumnId(colIdx)) return false; int maxCount = d->properties.count() - colIdx; count = std::min(maxCount, count); - const QList<CollectionProperty> removedProperties = d->properties.mid(colIdx, count); + if (count < 1) + return false; + d->properties.remove(colIdx, count); - for (const CollectionProperty &property : removedProperties) { - for (QJsonObject &element : d->elements) - element.remove(property.name); - } + for (QJsonArray &record : d->dataRecords) { + QJsonArray newElement; - markChanged(); + auto elementItr = record.constBegin(); + auto elementEnd = elementItr + colIdx; + while (elementItr != elementEnd) + newElement.append(*(elementItr++)); - return true; -} + elementItr += count; + elementEnd = record.constEnd(); -void CollectionDetails::insertElementAt(std::optional<QJsonObject> object, int row) -{ - if (!isValid()) - return; - - auto insertJson = [this, row](const QJsonObject &jsonObject) { - if (d->isValidRowId(row)) - d->elements.insert(row, jsonObject); - else - d->elements.append(jsonObject); - }; + while (elementItr != elementEnd) + newElement.append(*(elementItr++)); - if (object.has_value()) { - insertJson(object.value()); - } else { - QJsonObject defaultObject; - for (const CollectionProperty &property : std::as_const(d->properties)) - defaultObject.insert(property.name, {}); - insertJson(defaultObject); + record = newElement; } markChanged(); + + return true; } -void CollectionDetails::insertEmptyElements(int row, int count) +void CollectionDetails::insertEmptyRows(int row, int count) { - if (!isValid()) - return; - if (count < 1) return; row = qBound(0, row, rows()); - d->elements.insert(row, count, {}); + + insertRecords({}, row, count); markChanged(); } -bool CollectionDetails::removeElements(int row, int count) +bool CollectionDetails::removeRows(int row, int count) { - if (count < 1 || !isValid() || !d->isValidRowId(row)) + if (!d->isValidRowId(row)) return false; - int maxCount = d->elements.count() - row; + int maxCount = d->dataRecords.count() - row; count = std::min(maxCount, count); - QSet<QString> removedProperties; - Utils::span elementsSpan{std::as_const(d->elements)}; - for (const QJsonObject &element : elementsSpan.subspan(row, count)) { - const QStringList elementPropertyNames = element.keys(); - for (const QString &removedProperty : elementPropertyNames) - removedProperties.insert(removedProperty); - } - - d->elements.remove(row, count); - - for (const QString &removedProperty : removedProperties) - resetPropertyType(removedProperty); + if (count < 1) + return false; + d->dataRecords.remove(row, count); markChanged(); - return true; } @@ -360,13 +394,12 @@ bool CollectionDetails::setPropertyValue(int row, int column, const QVariant &va if (!d->isValidRowId(row) || !d->isValidColumnId(column)) return false; - QJsonObject &element = d->elements[row]; QVariant currentValue = data(row, column); - if (value == currentValue) return false; - element.insert(d->properties.at(column).name, variantToJsonValue(value, typeAt(column))); + QJsonArray &record = d->dataRecords[row]; + record.replace(column, variantToJsonValue(value, typeAt(column))); markChanged(); return true; } @@ -377,17 +410,10 @@ bool CollectionDetails::setPropertyName(int column, const QString &value) return false; const CollectionProperty &oldProperty = d->properties.at(column); - const QString oldColumnName = oldProperty.name; - if (oldColumnName == value) + if (oldProperty.name == value) return false; d->properties.replace(column, {value, oldProperty.type}); - for (QJsonObject &element : d->elements) { - if (element.contains(oldColumnName)) { - element.insert(value, element.value(oldColumnName)); - element.remove(oldColumnName); - } - } markChanged(); return true; @@ -395,7 +421,7 @@ bool CollectionDetails::setPropertyName(int column, const QString &value) bool CollectionDetails::setPropertyType(int column, DataType type) { - if (!isValid() || !d->isValidColumnId(column)) + if (!d->isValidColumnId(column)) return false; bool changed = false; @@ -406,12 +432,12 @@ bool CollectionDetails::setPropertyType(int column, DataType type) const DataType formerType = property.type; property.type = type; - for (QJsonObject &element : d->elements) { - if (element.contains(property.name)) { - const QJsonValue value = element.value(property.name); + for (QJsonArray &rowData : d->dataRecords) { + if (column < rowData.size()) { + const QJsonValue value = rowData.at(column); const QVariant properTypedValue = valueToVariant(value, formerType); const QJsonValue properTypedJsonValue = variantToJsonValue(properTypedValue, type); - element.insert(property.name, properTypedJsonValue); + rowData.replace(column, properTypedJsonValue); changed = true; } } @@ -427,29 +453,15 @@ CollectionReference CollectionDetails::reference() const return d->reference; } -CollectionEditorConstants::SourceFormat CollectionDetails::sourceFormat() const -{ - return d->sourceFormat; -} - QVariant CollectionDetails::data(int row, int column) const { - if (!isValid()) - return {}; - if (!d->isValidRowId(row)) return {}; if (!d->isValidColumnId(column)) return {}; - const QString &propertyName = d->properties.at(column).name; - const QJsonObject &elementNode = d->elements.at(row); - - if (!elementNode.contains(propertyName)) - return {}; - - const QJsonValue cellValue = elementNode.value(propertyName); + const QJsonValue cellValue = d->dataRecords.at(row).at(column); if (typeAt(column) == DataType::Image) { const QUrl imageUrl = valueToVariant(cellValue, DataType::Image).toUrl(); @@ -482,48 +494,37 @@ CollectionDetails::DataType CollectionDetails::typeAt(int row, int column) const if (!d->isValidRowId(row) || !d->isValidColumnId(column)) return {}; - const QString &propertyName = d->properties.at(column).name; - const QJsonObject &element = d->elements.at(row); - - if (element.contains(propertyName)) - return collectionDataTypeFromJsonValue(element.value(propertyName)); - - return {}; + const QJsonValue cellData = d->dataRecords.at(row).at(column); + return dataTypeFromJsonValue(cellData); } DataTypeWarning::Warning CollectionDetails::cellWarningCheck(int row, int column) const { - const QString &propertyName = d->properties.at(column).name; - const QJsonObject &element = d->elements.at(row); + const QJsonValue cellValue = d->dataRecords.at(row).at(column); const DataType columnType = typeAt(column); const DataType cellType = typeAt(row, column); - if (columnType == DataType::Unknown || element.isEmpty() - || data(row, column) == QVariant::fromValue(nullptr)) { + + if (columnType == DataType::Unknown || isEmptyJsonValue(cellValue)) return DataTypeWarning::Warning::None; - } - if (element.contains(propertyName)) { - if (columnType == DataType::Real && cellType == DataType::Integer) - return DataTypeWarning::Warning::None; - else if (columnType != cellType) - return DataTypeWarning::Warning::CellDataTypeMismatch; - } + if (columnType == DataType::Real && cellType == DataType::Integer) + return DataTypeWarning::Warning::None; + + if (columnType != cellType) + return DataTypeWarning::Warning::CellDataTypeMismatch; return DataTypeWarning::Warning::None; } -bool CollectionDetails::containsPropertyName(const QString &propertyName) +bool CollectionDetails::containsPropertyName(const QString &propertyName) const { - if (!isValid()) - return false; - return Utils::anyOf(d->properties, [&propertyName](const CollectionProperty &property) { return property.name == propertyName; }); } -bool CollectionDetails::isValid() const +bool CollectionDetails::hasValidReference() const { return d->reference.node.isValid() && d->reference.name.size(); } @@ -540,7 +541,7 @@ int CollectionDetails::columns() const int CollectionDetails::rows() const { - return d->elements.size(); + return d->dataRecords.size(); } bool CollectionDetails::markSaved() @@ -565,6 +566,90 @@ void CollectionDetails::resetReference(const CollectionReference &reference) } } +QString CollectionDetails::toJson() const +{ + QJsonArray exportedArray; + const int propertyCount = d->properties.count(); + + for (const QJsonArray &record : std::as_const(d->dataRecords)) { + const int valueCount = std::min(int(record.count()), propertyCount); + + QJsonObject exportedElement; + for (int i = 0; i < valueCount; ++i) { + const QJsonValue &value = record.at(i); + if (!isEmptyJsonValue(value)) + exportedElement.insert(d->properties.at(i).name, value); + } + + exportedArray.append(exportedElement); + } + + return QString::fromUtf8(QJsonDocument(exportedArray).toJson()); +} + +QString CollectionDetails::toCsv() const +{ + QString content; + + auto gotoNextLine = [&content]() { + if (content.size() && content.back() == ',') + content.back() = '\n'; + else + content += "\n"; + }; + + const int propertyCount = d->properties.count(); + if (propertyCount <= 0) + return ""; + + for (const CollectionProperty &property : std::as_const(d->properties)) + content += property.name + ','; + + gotoNextLine(); + + for (const QJsonArray &record : std::as_const(d->dataRecords)) { + const int valueCount = std::min(int(record.count()), propertyCount); + int i = 0; + for (; i < valueCount; ++i) { + const QJsonValue &value = record.at(i); + + if (value.isDouble()) + content += QString::number(value.toDouble()) + ','; + else + content += value.toString() + ','; + } + + for (; i < propertyCount; ++i) + content += ','; + + gotoNextLine(); + } + + return content; +} + +QJsonObject CollectionDetails::toLocalJson() const +{ + QJsonObject collectionObject; + QJsonArray columnsArray; + QJsonArray dataArray; + + for (const CollectionProperty &property : std::as_const(d->properties)) { + QJsonObject columnObject; + columnObject.insert("name", property.name); + columnObject.insert("type", CollectionEditorUtils::dataTypeToString(property.type)); + columnsArray.append(columnObject); + } + + for (const QJsonArray &record : std::as_const(d->dataRecords)) + dataArray.append(record); + + collectionObject.insert("columns", columnsArray); + collectionObject.insert("data", dataArray); + + return collectionObject; +} + void CollectionDetails::registerDeclarativeType() { typedef CollectionDetails::DataType DataType; @@ -575,93 +660,209 @@ void CollectionDetails::registerDeclarativeType() qmlRegisterUncreatableType<DataTypeWarning>("CollectionDetails", 1, 0, "Warning", "Enum type"); } -CollectionDetails &CollectionDetails::operator=(const CollectionDetails &other) +CollectionDetails CollectionDetails::fromImportedCsv(const QByteArray &document) { - CollectionDetails value(other); - swap(value); - return *this; -} + QStringList headers; + QJsonArray importedArray; -void CollectionDetails::markChanged() -{ - d->isChanged = true; -} + QTextStream stream(document); -void CollectionDetails::resetPropertyType(const QString &propertyName) -{ - for (CollectionProperty &property : d->properties) { - if (property.name == propertyName) - resetPropertyType(property); + if (!stream.atEnd()) + headers = stream.readLine().split(','); + + for (QString &header : headers) + header = header.trimmed(); + + if (!headers.isEmpty()) { + while (!stream.atEnd()) { + const QStringList recordDataList = stream.readLine().split(','); + int column = -1; + QJsonObject recordData; + for (const QString &cellData : recordDataList) { + if (++column == headers.size()) + break; + recordData.insert(headers.at(column), cellData); + } + importedArray.append(recordData); + } } + + return fromImportedJson(importedArray); } -void CollectionDetails::resetPropertyType(CollectionProperty &property) +CollectionDetails CollectionDetails::fromImportedJson(const QJsonDocument &document) { - const QString &propertyName = property.name; - DataType columnType = DataType::Unknown; - for (const QJsonObject &element : std::as_const(d->elements)) { - if (element.contains(propertyName)) { - const DataType cellType = collectionDataTypeFromJsonValue(element.value(propertyName)); - if (cellType != DataType::Unknown) { - if (columnType == DataType::Integer && cellType != DataType::Real) - continue; - - columnType = cellType; - if (columnType == DataType::Integer) - continue; + QJsonArray importedCollection; + auto refineJsonArray = [](const QJsonArray &array) -> QJsonArray { + QJsonArray resultArray; + for (const QJsonValue &collectionData : array) { + if (collectionData.isObject()) { + QJsonObject rowObject = collectionData.toObject(); + const QStringList rowKeys = rowObject.keys(); + for (const QString &key : rowKeys) { + const QJsonValue cellValue = rowObject.value(key); + if (cellValue.isArray()) + rowObject.remove(key); + } + resultArray.push_back(rowObject); + } + } + return resultArray; + }; + if (document.isArray()) { + importedCollection = refineJsonArray(document.array()); + } else if (document.isObject()) { + QJsonObject documentObject = document.object(); + const QStringList mainKeys = documentObject.keys(); + + bool arrayFound = false; + for (const QString &key : mainKeys) { + const QJsonValue value = documentObject.value(key); + if (value.isArray()) { + arrayFound = true; + importedCollection = refineJsonArray(value.toArray()); break; } } + + if (!arrayFound) { + QJsonObject singleObject; + for (const QString &key : mainKeys) { + const QJsonValue value = documentObject.value(key); + + if (!value.isObject()) + singleObject.insert(key, value); + } + importedCollection.push_back(singleObject); + } } - property.type = columnType; + + return fromImportedJson(importedCollection); } -void CollectionDetails::resetPropertyTypes() +CollectionDetails CollectionDetails::fromLocalJson(const QJsonDocument &document, + const QString &collectionName, + CollectionParseError *error) { - for (CollectionProperty &property : d->properties) - resetPropertyType(property); + auto setError = [&error](CollectionParseError::ParseError parseError) { + if (error) + error->errorNo = parseError; + }; + + setError(CollectionParseError::NoError); + + if (document.isObject()) { + QJsonObject collectionMap = document.object(); + if (collectionMap.contains(collectionName)) { + QJsonValue collectionValue = collectionMap.value(collectionName); + if (collectionValue.isObject()) + return fromLocalCollection(collectionValue.toObject()); + else + setError(CollectionParseError::CollectionIsNotObject); + } else { + setError(CollectionParseError::CollectionNameNotFound); + } + } else { + setError(CollectionParseError::MainObjectMissing); + } + + return CollectionDetails{}; } -QJsonArray CollectionDetails::getCollectionAsJsonArray() const +CollectionDetails &CollectionDetails::operator=(const CollectionDetails &other) { - QJsonArray collectionArray; - - for (const QJsonObject &element : std::as_const(d->elements)) - collectionArray.push_back(element); + CollectionDetails value(other); + swap(value); + return *this; +} - return collectionArray; +void CollectionDetails::markChanged() +{ + d->isChanged = true; } -QString CollectionDetails::getCollectionAsJsonString() const +void CollectionDetails::insertRecords(const QJsonArray &record, int idx, int count) { - return QString::fromUtf8(QJsonDocument(getCollectionAsJsonArray()).toJson()); + if (count < 1) + return; + + QJsonArray localRecord; + const int columnsCount = columns(); + for (int i = 0; i < columnsCount; i++) { + const QJsonValue originalCellData = record.at(i); + if (originalCellData.isArray()) + localRecord.append({}); + else + localRecord.append(originalCellData); + } + + if (idx > d->dataRecords.size() || idx < 0) + idx = d->dataRecords.size(); + + d->dataRecords.insert(idx, count, localRecord); } -QString CollectionDetails::getCollectionAsCsvString() const +CollectionDetails CollectionDetails::fromImportedJson(const QJsonArray &importedArray) { - QString content; - if (d->properties.count() <= 0) - return ""; + const QList<CollectionProperty> columnData = getColumnsFromImportedJsonArray(importedArray); + QList<QJsonArray> localJsonArray; + for (const QJsonValue &importedRowValue : importedArray) { + QJsonObject importedRowObject = importedRowValue.toObject(); + QJsonArray localRow; + for (const CollectionProperty &property : columnData) + localRow.append(importedRowObject.value(property.name)); + localJsonArray.append(localRow); + } + CollectionDetails result; + result.d->properties = columnData; + result.d->dataRecords = localJsonArray; + result.markSaved(); - for (const CollectionProperty &property : std::as_const(d->properties)) - content += property.name + ','; + return result; +} - content.back() = '\n'; +CollectionDetails CollectionDetails::fromLocalCollection(const QJsonObject &localCollection, + CollectionParseError *error) +{ + auto setError = [&error](CollectionParseError::ParseError parseError) { + if (error) + error->errorNo = parseError; + }; - for (const QJsonObject &elementsRow : std::as_const(d->elements)) { - for (const CollectionProperty &property : std::as_const(d->properties)) { - const QJsonValue &value = elementsRow.value(property.name); + CollectionDetails result; + setError(CollectionParseError::NoError); + + if (localCollection.contains("columns")) { + const QJsonValue columnsValue = localCollection.value("columns"); + if (columnsValue.isArray()) { + const QJsonArray columns = columnsValue.toArray(); + for (const QJsonValue &columnValue : columns) { + if (columnValue.isObject()) { + const QJsonObject column = columnValue.toObject(); + const QString columnName = column.value("name").toString(); + if (!columnName.isEmpty()) { + result.insertColumn(columnName, + -1, + {}, + CollectionEditorUtils::dataTypeFromString( + column.value("type").toString())); + } + } + } - if (value.isDouble()) - content += QString::number(value.toDouble()) + ','; - else - content += value.toString() + ','; + if (int columnsCount = result.columns()) { + const QJsonArray dataRecords = localCollection.value("data").toArray(); + for (const QJsonValue &dataRecordValue : dataRecords) + result.insertRecords(dataRecordValue.toArray()); + } + } else { + setError(CollectionParseError::ColumnsBlockIsNotArray); + return result; } - content.back() = '\n'; } - return content; + return result; } } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.h b/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.h index a18c557c52..2ccc69e447 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.h @@ -3,7 +3,6 @@ #pragma once -#include "collectioneditorconstants.h" #include "modelnode.h" #include <QSharedPointer> @@ -36,8 +35,6 @@ struct CollectionReference struct CollectionProperty; struct DataTypeWarning { - Q_GADGET - public: enum Warning { None, CellDataTypeMismatch }; Q_ENUM(Warning) @@ -47,14 +44,31 @@ public: : warning(warning) {} - static QString getDataTypeWarningString(Warning warning) { + static QString getDataTypeWarningString(Warning warning) + { return dataTypeWarnings.value(warning); } private: + Q_GADGET static const QMap<Warning, QString> dataTypeWarnings; }; +struct CollectionParseError +{ + enum ParseError { + NoError, + MainObjectMissing, + CollectionNameNotFound, + CollectionIsNotObject, + ColumnsBlockIsNotArray, + UnknownError + }; + + ParseError errorNo = ParseError::NoError; + QString errorString() const; +}; + class CollectionDetails { Q_GADGET @@ -68,33 +82,32 @@ public: CollectionDetails(const CollectionDetails &other); ~CollectionDetails(); - void resetDetails(const QStringList &propertyNames, - const QList<QJsonObject> &elements, - CollectionEditorConstants::SourceFormat format); + void resetData(const QJsonDocument &localDocument, + const QString &collectionToImport, + CollectionParseError *error = nullptr); + void insertColumn(const QString &propertyName, int colIdx = -1, const QVariant &defaultValue = {}, DataType type = DataType::Unknown); bool removeColumns(int colIdx, int count = 1); - void insertElementAt(std::optional<QJsonObject> object, int row = -1); - void insertEmptyElements(int row = 0, int count = 1); - bool removeElements(int row, int count = 1); + void insertEmptyRows(int row = 0, int count = 1); + bool removeRows(int row, int count = 1); bool setPropertyValue(int row, int column, const QVariant &value); bool setPropertyName(int column, const QString &value); bool setPropertyType(int column, DataType type); CollectionReference reference() const; - CollectionEditorConstants::SourceFormat sourceFormat() const; QVariant data(int row, int column) const; QString propertyAt(int column) const; DataType typeAt(int column) const; DataType typeAt(int row, int column) const; DataTypeWarning::Warning cellWarningCheck(int row, int column) const; - bool containsPropertyName(const QString &propertyName); + bool containsPropertyName(const QString &propertyName) const; - bool isValid() const; + bool hasValidReference() const; bool isChanged() const; int columns() const; @@ -104,23 +117,32 @@ public: void swap(CollectionDetails &other); void resetReference(const CollectionReference &reference); - QString getCollectionAsJsonString() const; - QString getCollectionAsCsvString() const; - QJsonArray getCollectionAsJsonArray() const; + QString toJson() const; + QString toCsv() const; + QJsonObject toLocalJson() const; static void registerDeclarativeType(); + static CollectionDetails fromImportedCsv(const QByteArray &document); + static CollectionDetails fromImportedJson(const QJsonDocument &document); + static CollectionDetails fromLocalJson(const QJsonDocument &document, + const QString &collectionName, + CollectionParseError *error = nullptr); + CollectionDetails &operator=(const CollectionDetails &other); private: void markChanged(); - void resetPropertyType(const QString &propertyName); - void resetPropertyType(CollectionProperty &property); - void resetPropertyTypes(); + void insertRecords(const QJsonArray &record, int idx = -1, int count = 1); + + static CollectionDetails fromImportedJson(const QJsonArray &importedArray); + static CollectionDetails fromLocalCollection(const QJsonObject &localCollection, + CollectionParseError *error = nullptr); // The private data is supposed to be shared between the copies class Private; QSharedPointer<Private> d; }; + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.cpp index 51e3be9ad6..17a26c58c5 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.cpp @@ -3,7 +3,6 @@ #include "collectiondetailsmodel.h" -#include "collectioneditorconstants.h" #include "collectioneditorutils.h" #include "modelnode.h" @@ -13,116 +12,11 @@ #include <utils/qtcassert.h> #include <utils/textfileformat.h> -#include <QFile> -#include <QFileInfo> #include <QJsonArray> #include <QJsonDocument> #include <QJsonObject> #include <QJsonParseError> -namespace { - -QStringList getJsonHeaders(const QJsonArray &collectionArray) -{ - QSet<QString> resultSet; - QList<QString> result; - - for (const QJsonValue &value : collectionArray) { - if (value.isObject()) { - const QJsonObject object = value.toObject(); - QJsonObject::ConstIterator element = object.constBegin(); - const QJsonObject::ConstIterator stopItem = object.constEnd(); - - while (element != stopItem) { - const QString property = element.key(); - if (!resultSet.contains(property)) { - result.append(property); - resultSet.insert(property); - } - ++element; - } - } - } - - return result; -} - -class CollectionDataTypeHelper -{ -public: - using DataType = QmlDesigner::CollectionDetails::DataType; - - static QString typeToString(DataType dataType) - { - static const QHash<DataType, QString> typeStringHash = typeToStringHash(); - return typeStringHash.value(dataType); - } - - static DataType typeFromString(const QString &dataType) - { - static const QHash<QString, DataType> stringTypeHash = stringToTypeHash(); - return stringTypeHash.value(dataType, DataType::Unknown); - } - - static QStringList typesStringList() - { - static const QStringList typesList = orderedTypeNames(); - return typesList; - } - -private: - CollectionDataTypeHelper() = delete; - - static QHash<DataType, QString> typeToStringHash() - { - return { - {DataType::Unknown, "Unknown"}, - {DataType::String, "String"}, - {DataType::Url, "Url"}, - {DataType::Real, "Real"}, - {DataType::Integer, "Integer"}, - {DataType::Boolean, "Boolean"}, - {DataType::Image, "Image"}, - {DataType::Color, "Color"}, - }; - } - - static QHash<QString, DataType> stringToTypeHash() - { - QHash<QString, DataType> stringTypeHash; - const QHash<DataType, QString> typeStringHash = typeToStringHash(); - for (const auto &transferItem : typeStringHash.asKeyValueRange()) - stringTypeHash.insert(transferItem.second, transferItem.first); - - return stringTypeHash; - } - - static QStringList orderedTypeNames() - { - const QList<DataType> orderedtypes{ - DataType::String, - DataType::Integer, - DataType::Real, - DataType::Image, - DataType::Color, - DataType::Url, - DataType::Boolean, - DataType::Unknown, - }; - - QStringList orderedNames; - QHash<DataType, QString> typeStringHash = typeToStringHash(); - - for (const DataType &type : orderedtypes) - orderedNames.append(typeStringHash.take(type)); - - Q_ASSERT(typeStringHash.isEmpty()); - return orderedNames; - } -}; - -} // namespace - namespace QmlDesigner { CollectionDetailsModel::CollectionDetailsModel(QObject *parent) @@ -161,6 +55,8 @@ QVariant CollectionDetailsModel::data(const QModelIndex &index, int role) const if (!index.isValid()) return {}; + QTC_ASSERT(m_currentCollection.hasValidReference(), return {}); + if (role == SelectedRole) return (index.column() == m_selectedColumn || index.row() == m_selectedRow); @@ -181,6 +77,8 @@ QVariant CollectionDetailsModel::data(const QModelIndex &index, int role) const bool CollectionDetailsModel::setData(const QModelIndex &index, const QVariant &value, int role) { + QTC_ASSERT(m_currentCollection.hasValidReference(), return false); + if (!index.isValid()) return {}; @@ -208,6 +106,8 @@ bool CollectionDetailsModel::setHeaderData(int section, const QVariant &value, [[maybe_unused]] int role) { + QTC_ASSERT(m_currentCollection.hasValidReference(), return false); + if (orientation == Qt::Vertical) return false; @@ -220,13 +120,15 @@ bool CollectionDetailsModel::setHeaderData(int section, bool CollectionDetailsModel::insertRows(int row, int count, [[maybe_unused]] const QModelIndex &parent) { + QTC_ASSERT(m_currentCollection.hasValidReference(), return false); + if (count < 1) return false; row = qBound(0, row, rowCount()); beginResetModel(); - m_currentCollection.insertEmptyElements(row, count); + m_currentCollection.insertEmptyRows(row, count); endResetModel(); selectRow(row); @@ -235,6 +137,8 @@ bool CollectionDetailsModel::insertRows(int row, int count, [[maybe_unused]] con bool CollectionDetailsModel::removeColumns(int column, int count, const QModelIndex &parent) { + QTC_ASSERT(m_currentCollection.hasValidReference(), return false); + if (column < 0 || column >= columnCount(parent) || count < 1) return false; @@ -258,12 +162,14 @@ bool CollectionDetailsModel::removeColumns(int column, int count, const QModelIn bool CollectionDetailsModel::removeRows(int row, int count, const QModelIndex &parent) { + QTC_ASSERT(m_currentCollection.hasValidReference(), return false); + if (row < 0 || row >= rowCount(parent) || count < 1) return false; count = std::min(count, rowCount(parent) - row); beginRemoveRows(parent, row, row + count - 1); - bool rowsRemoved = m_currentCollection.removeElements(row, count); + bool rowsRemoved = m_currentCollection.removeRows(row, count); endRemoveRows(); ensureSingleCell(); @@ -282,7 +188,7 @@ QVariant CollectionDetailsModel::headerData(int section, Qt::Orientation orienta { if (orientation == Qt::Horizontal) { if (role == DataTypeRole) - return CollectionDataTypeHelper::typeToString(m_currentCollection.typeAt(section)); + return CollectionEditorUtils::dataTypeToString(m_currentCollection.typeAt(section)); else return m_currentCollection.propertyAt(section); } @@ -295,6 +201,8 @@ QVariant CollectionDetailsModel::headerData(int section, Qt::Orientation orienta CollectionDetails::DataType CollectionDetailsModel::propertyDataType(int column) const { + QTC_ASSERT(m_currentCollection.hasValidReference(), return CollectionDetails::DataType::Unknown); + return m_currentCollection.typeAt(column); } @@ -310,21 +218,29 @@ int CollectionDetailsModel::selectedRow() const QString CollectionDetailsModel::propertyName(int column) const { + QTC_ASSERT(m_currentCollection.hasValidReference(), return {}); + return m_currentCollection.propertyAt(column); } QString CollectionDetailsModel::propertyType(int column) const { - return CollectionDataTypeHelper::typeToString(m_currentCollection.typeAt(column)); + QTC_ASSERT(m_currentCollection.hasValidReference(), return {}); + + return CollectionEditorUtils::dataTypeToString(m_currentCollection.typeAt(column)); } bool CollectionDetailsModel::isPropertyAvailable(const QString &name) { + QTC_ASSERT(m_currentCollection.hasValidReference(), return false); + return m_currentCollection.containsPropertyName(name); } bool CollectionDetailsModel::addColumn(int column, const QString &name, const QString &propertyType) { + QTC_ASSERT(m_currentCollection.hasValidReference(), return false); + if (m_currentCollection.containsPropertyName(name)) return false; @@ -335,7 +251,7 @@ bool CollectionDetailsModel::addColumn(int column, const QString &name, const QS m_currentCollection.insertColumn(name, column, {}, - CollectionDataTypeHelper::typeFromString(propertyType)); + CollectionEditorUtils::dataTypeFromString(propertyType)); endInsertColumns(); return m_currentCollection.containsPropertyName(name); } @@ -379,8 +295,10 @@ bool CollectionDetailsModel::renameColumn(int section, const QString &newValue) bool CollectionDetailsModel::setPropertyType(int column, const QString &newValue) { + QTC_ASSERT(m_currentCollection.hasValidReference(), return false); + bool changed = m_currentCollection.setPropertyType(column, - CollectionDataTypeHelper::typeFromString( + CollectionEditorUtils::dataTypeFromString( newValue)); if (changed) { emit headerDataChanged(Qt::Horizontal, column, column); @@ -430,7 +348,7 @@ void CollectionDetailsModel::deselectAll() QStringList CollectionDetailsModel::typesList() { - return CollectionDataTypeHelper::typesStringList(); + return CollectionEditorUtils::dataTypesStringList(); } void CollectionDetailsModel::loadCollection(const ModelNode &sourceNode, const QString &collection) @@ -451,10 +369,7 @@ void CollectionDetailsModel::loadCollection(const ModelNode &sourceNode, const Q } else { deselectAll(); switchToCollection(newReference); - if (sourceNode.type() == CollectionEditorConstants::JSONCOLLECTIONMODEL_TYPENAME) - loadJsonCollection(fileName, collection); - else if (sourceNode.type() == CollectionEditorConstants::CSVCOLLECTIONMODEL_TYPENAME) - loadCsvCollection(fileName, collection); + loadJsonCollection(fileName, collection); } } @@ -516,7 +431,7 @@ bool CollectionDetailsModel::saveDataStoreCollections() for (CollectionDetails &openedCollection : m_openedCollections) { const CollectionReference reference = openedCollection.reference(); if (reference.node == node) { - obj.insert(reference.name, openedCollection.getCollectionAsJsonArray()); + obj.insert(reference.name, openedCollection.toLocalJson()); collectionsToBeSaved << openedCollection; } } @@ -545,14 +460,14 @@ bool CollectionDetailsModel::exportCollection(const QUrl &url) using Utils::FilePath; using Utils::TextFileFormat; - QTC_ASSERT(m_currentCollection.isValid(), return false); + QTC_ASSERT(m_currentCollection.hasValidReference(), return false); bool saved = false; const FilePath filePath = FilePath::fromUserInput(url.isLocalFile() ? url.toLocalFile() : url.toString()); const QString saveFormat = filePath.toFileInfo().suffix().toLower(); - const QString content = saveFormat == "csv" ? m_currentCollection.getCollectionAsCsvString() - : m_currentCollection.getCollectionAsJsonString(); + const QString content = saveFormat == "csv" ? m_currentCollection.toCsv() + : m_currentCollection.toJson(); TextFileFormat textFileFormat; textFileFormat.codec = EditorManager::defaultTextCodec(); @@ -566,6 +481,49 @@ bool CollectionDetailsModel::exportCollection(const QUrl &url) return saved; } +const CollectionDetails CollectionDetailsModel::upToDateConstCollection( + const CollectionReference &reference) const +{ + using Utils::FilePath; + using Utils::FileReader; + CollectionDetails collection; + + if (m_openedCollections.contains(reference)) { + collection = m_openedCollections.value(reference); + } else { + QUrl url = CollectionEditorUtils::getSourceCollectionPath(reference.node); + FilePath path = FilePath::fromUserInput(url.isLocalFile() ? url.toLocalFile() + : url.toString()); + FileReader file; + + if (!file.fetch(path)) + return collection; + + QJsonParseError jpe; + QJsonDocument document = QJsonDocument::fromJson(file.data(), &jpe); + + if (jpe.error != QJsonParseError::NoError) + return collection; + + collection = CollectionDetails::fromLocalJson(document, reference.name); + collection.resetReference(reference); + } + return collection; +} + +bool CollectionDetailsModel::collectionHasColumn(const CollectionReference &reference, + const QString &columnName) const +{ + const CollectionDetails collection = upToDateConstCollection(reference); + return collection.containsPropertyName(columnName); +} + +QString CollectionDetailsModel::getFirstColumnName(const CollectionReference &reference) const +{ + const CollectionDetails collection = upToDateConstCollection(reference); + return collection.propertyAt(0); +} + void CollectionDetailsModel::updateEmpty() { bool isEmptyNow = rowCount() == 0; @@ -603,113 +561,25 @@ void CollectionDetailsModel::closeCollectionIfSaved(const CollectionReference &c void CollectionDetailsModel::closeCurrentCollectionIfSaved() { - if (m_currentCollection.isValid()) { + if (m_currentCollection.hasValidReference()) { closeCollectionIfSaved(m_currentCollection.reference()); m_currentCollection = CollectionDetails{}; } } -void CollectionDetailsModel::loadJsonCollection(const QString &source, const QString &collection) +void CollectionDetailsModel::loadJsonCollection(const QString &filePath, const QString &collection) { - using CollectionEditorConstants::SourceFormat; + QJsonDocument document = readJsonFile(filePath); - QFile sourceFile(source); - QJsonArray collectionNodes; - bool jsonFileIsOk = false; - if (sourceFile.open(QFile::ReadOnly)) { - QJsonParseError jpe; - QJsonDocument document = QJsonDocument::fromJson(sourceFile.readAll(), &jpe); - if (jpe.error == QJsonParseError::NoError) { - jsonFileIsOk = true; - if (document.isObject()) { - QJsonObject collectionMap = document.object(); - if (collectionMap.contains(collection)) { - QJsonValue collectionVal = collectionMap.value(collection); - if (collectionVal.isArray()) - collectionNodes = collectionVal.toArray(); - else - collectionNodes.append(collectionVal); - } - } - } - } - - if (collectionNodes.isEmpty()) { - endResetModel(); - return; - }; - - QList<QJsonObject> elements; - for (const QJsonValue &value : std::as_const(collectionNodes)) { - if (value.isObject()) { - QJsonObject object = value.toObject(); - elements.append(object); - } - } - - SourceFormat sourceFormat = jsonFileIsOk ? SourceFormat::Json : SourceFormat::Unknown; beginResetModel(); - m_currentCollection.resetDetails(getJsonHeaders(collectionNodes), elements, sourceFormat); - ensureSingleCell(); - endResetModel(); -} - -void CollectionDetailsModel::loadCsvCollection(const QString &source, - [[maybe_unused]] const QString &collectionName) -{ - using CollectionEditorConstants::SourceFormat; - - QFile sourceFile(source); - QStringList headers; - QList<QJsonObject> elements; - bool csvFileIsOk = false; - - if (sourceFile.open(QFile::ReadOnly)) { - QTextStream stream(&sourceFile); - - if (!stream.atEnd()) - headers = stream.readLine().split(','); - - if (!headers.isEmpty()) { - while (!stream.atEnd()) { - const QStringList recordDataList = stream.readLine().split(','); - int column = -1; - QJsonObject recordData; - for (const QString &cellData : recordDataList) { - if (++column == headers.size()) - break; - recordData.insert(headers.at(column), cellData); - } - if (recordData.count()) - elements.append(recordData); - } - csvFileIsOk = true; - } - } - - for (const QString &header : std::as_const(headers)) { - for (QJsonObject &element: elements) { - QVariant variantValue; - if (element.contains(header)) { - variantValue = variantFromString(element.value(header).toString()); - element[header] = variantValue.toJsonValue(); - - if (variantValue.isValid()) - break; - } - } - } - - SourceFormat sourceFormat = csvFileIsOk ? SourceFormat::Csv : SourceFormat::Unknown; - beginResetModel(); - m_currentCollection.resetDetails(headers, elements, sourceFormat); + m_currentCollection.resetData(document, collection); ensureSingleCell(); endResetModel(); } void CollectionDetailsModel::ensureSingleCell() { - if (!m_currentCollection.isValid()) + if (!m_currentCollection.hasValidReference()) return; if (!columnCount()) @@ -748,6 +618,27 @@ QVariant CollectionDetailsModel::variantFromString(const QString &value) return QVariant::fromValue(value); } +QJsonDocument CollectionDetailsModel::readJsonFile(const QUrl &url) +{ + using Utils::FilePath; + using Utils::FileReader; + FilePath path = FilePath::fromUserInput(url.isLocalFile() ? url.toLocalFile() : url.toString()); + FileReader file; + + if (!file.fetch(path)) { + emit warning(tr("File reading problem"), file.errorString()); + return {}; + } + + QJsonParseError jpe; + QJsonDocument document = QJsonDocument::fromJson(file.data(), &jpe); + + if (jpe.error != QJsonParseError::NoError) + emit warning(tr("Json parse error"), jpe.errorString()); + + return document; +} + void CollectionDetailsModel::setCollectionName(const QString &newCollectionName) { if (m_collectionName != newCollectionName) { @@ -761,5 +652,4 @@ QString CollectionDetailsModel::warningToString(DataTypeWarning::Warning warning return DataTypeWarning::getDataTypeWarningString(warning); } - } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.h b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.h index cef942b044..1fe152eaa2 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.h @@ -68,11 +68,16 @@ public: Q_INVOKABLE bool saveDataStoreCollections(); Q_INVOKABLE bool exportCollection(const QUrl &url); + const CollectionDetails upToDateConstCollection(const CollectionReference &reference) const; + bool collectionHasColumn(const CollectionReference &reference, const QString &columnName) const; + QString getFirstColumnName(const CollectionReference &reference) const; + signals: void collectionNameChanged(const QString &collectionName); void selectedColumnChanged(int); void selectedRowChanged(int); void isEmptyChanged(bool); + void warning(const QString &title, const QString &body); private slots: void updateEmpty(); @@ -82,10 +87,10 @@ private: void closeCollectionIfSaved(const CollectionReference &collection); void closeCurrentCollectionIfSaved(); void setCollectionName(const QString &newCollectionName); - void loadJsonCollection(const QString &source, const QString &collection); - void loadCsvCollection(const QString &source, const QString &collectionName); + void loadJsonCollection(const QString &filePath, const QString &collection); void ensureSingleCell(); QVariant variantFromString(const QString &value); + QJsonDocument readJsonFile(const QUrl &url); QHash<CollectionReference, CollectionDetails> m_openedCollections; CollectionDetails m_currentCollection; diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.cpp index 4b779c52fa..9c77724932 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.cpp @@ -24,19 +24,77 @@ #include <QJsonValue> #include <QUrl> +using DataType = QmlDesigner::CollectionDetails::DataType; + namespace { using CollectionDataVariant = std::variant<QString, bool, double, int, QUrl, QColor>; +class CollectionDataTypeHelper +{ +private: + using DataType = QmlDesigner::CollectionDetails::DataType; + friend QString QmlDesigner::CollectionEditorUtils::dataTypeToString(DataType); + friend DataType QmlDesigner::CollectionEditorUtils::dataTypeFromString(const QString &); + friend QStringList QmlDesigner::CollectionEditorUtils::dataTypesStringList(); + + CollectionDataTypeHelper() = delete; + + static QHash<DataType, QString> typeToStringHash() + { + return { + {DataType::Unknown, "Unknown"}, + {DataType::String, "String"}, + {DataType::Url, "Url"}, + {DataType::Real, "Real"}, + {DataType::Integer, "Integer"}, + {DataType::Boolean, "Boolean"}, + {DataType::Image, "Image"}, + {DataType::Color, "Color"}, + }; + } + + static QHash<QString, DataType> stringToTypeHash() + { + QHash<QString, DataType> stringTypeHash; + const QHash<DataType, QString> typeStringHash = typeToStringHash(); + for (const auto &transferItem : typeStringHash.asKeyValueRange()) + stringTypeHash.insert(transferItem.second, transferItem.first); + + return stringTypeHash; + } + + static QStringList orderedTypeNames() + { + const QList<DataType> orderedtypes{ + DataType::String, + DataType::Integer, + DataType::Real, + DataType::Image, + DataType::Color, + DataType::Url, + DataType::Boolean, + DataType::Unknown, + }; + + QStringList orderedNames; + QHash<DataType, QString> typeStringHash = typeToStringHash(); + + for (const DataType &type : orderedtypes) + orderedNames.append(typeStringHash.take(type)); + + Q_ASSERT(typeStringHash.isEmpty()); + return orderedNames; + } +}; + inline bool operator<(const QColor &a, const QColor &b) { return a.name(QColor::HexArgb) < b.name(QColor::HexArgb); } -inline CollectionDataVariant valueToVariant(const QVariant &value, - QmlDesigner::CollectionDetails::DataType type) +inline CollectionDataVariant valueToVariant(const QVariant &value, DataType type) { - using DataType = QmlDesigner::CollectionDetails::DataType; switch (type) { case DataType::String: return value.toString(); @@ -112,7 +170,7 @@ inline Utils::FilePath qmlDirFilePath() namespace QmlDesigner::CollectionEditorUtils { -bool variantIslessThan(const QVariant &a, const QVariant &b, CollectionDetails::DataType type) +bool variantIslessThan(const QVariant &a, const QVariant &b, DataType type) { return std::visit(LessThanVisitor{}, valueToVariant(a, type), valueToVariant(b, type)); } @@ -210,16 +268,6 @@ bool isDataStoreNode(const ModelNode &dataStoreNode) return modelPath.isSameFile(expectedFile); } -QJsonArray defaultCollectionArray() -{ - QJsonObject initialObject; - QJsonArray initialCollection; - - initialObject.insert("Column1", ""); - initialCollection.append(initialObject); - return initialCollection; -} - bool ensureDataStoreExists(bool &justCreated) { using Utils::FilePath; @@ -306,178 +354,43 @@ bool ensureDataStoreExists(bool &justCreated) return false; } -QJsonArray loadAsSingleJsonCollection(const QUrl &url) +QJsonObject defaultCollection() { - QFile file(url.isLocalFile() ? url.toLocalFile() : url.toString()); - QJsonArray collection; - QByteArray jsonData; - if (file.open(QFile::ReadOnly)) - jsonData = file.readAll(); - - file.close(); - if (jsonData.isEmpty()) - return {}; - - QJsonParseError parseError; - QJsonDocument document = QJsonDocument::fromJson(jsonData, &parseError); - if (parseError.error != QJsonParseError::NoError) - return {}; + QJsonObject collectionObject; - auto refineJsonArray = [](const QJsonArray &array) -> QJsonArray { - QJsonArray resultArray; - for (const QJsonValue &collectionData : array) { - if (collectionData.isObject()) { - QJsonObject rowObject = collectionData.toObject(); - const QStringList rowKeys = rowObject.keys(); - for (const QString &key : rowKeys) { - QJsonValue cellValue = rowObject.value(key); - if (cellValue.isArray()) - rowObject.remove(key); - } - resultArray.push_back(rowObject); - } - } - return resultArray; - }; + QJsonArray columns; + QJsonObject defaultColumn; + defaultColumn.insert("name", "Column 1"); + defaultColumn.insert("type", "string"); + columns.append(defaultColumn); - if (document.isArray()) { - collection = refineJsonArray(document.array()); - } else if (document.isObject()) { - QJsonObject documentObject = document.object(); - const QStringList mainKeys = documentObject.keys(); - - bool arrayFound = false; - for (const QString &key : mainKeys) { - const QJsonValue &value = documentObject.value(key); - if (value.isArray()) { - arrayFound = true; - collection = refineJsonArray(value.toArray()); - break; - } - } + QJsonArray collectionData; + QJsonArray cellData; + cellData.append(QString{}); + collectionData.append(cellData); - if (!arrayFound) { - QJsonObject singleObject; - for (const QString &key : mainKeys) { - const QJsonValue value = documentObject.value(key); + collectionObject.insert("columns", columns); + collectionObject.insert("data", collectionData); - if (!value.isObject()) - singleObject.insert(key, value); - } - collection.push_back(singleObject); - } - } - return collection; + return collectionObject; } -QJsonArray loadAsCsvCollection(const QUrl &url) +QString dataTypeToString(CollectionDetails::DataType dataType) { - QFile sourceFile(url.isLocalFile() ? url.toLocalFile() : url.toString()); - QStringList headers; - QJsonArray elements; - - if (sourceFile.open(QFile::ReadOnly)) { - QTextStream stream(&sourceFile); - - if (!stream.atEnd()) - headers = stream.readLine().split(','); - - for (QString &header : headers) - header = header.trimmed(); - - if (!headers.isEmpty()) { - while (!stream.atEnd()) { - const QStringList recordDataList = stream.readLine().split(','); - int column = -1; - QJsonObject recordData; - for (const QString &cellData : recordDataList) { - if (++column == headers.size()) - break; - recordData.insert(headers.at(column), cellData); - } - elements.append(recordData); - } - } - } - - return elements; + static const QHash<DataType, QString> typeStringHash = CollectionDataTypeHelper::typeToStringHash(); + return typeStringHash.value(dataType); } -QString getFirstColumnName(const QString &collectionName) +CollectionDetails::DataType dataTypeFromString(const QString &dataType) { - Utils::FilePath dataStorePath = CollectionEditorUtils::dataStoreJsonFilePath(); - - if (!dataStorePath.exists()) - return {}; - - Utils::FileReader dataStoreFile; - if (!dataStoreFile.fetch(dataStorePath)) - return {}; - - QJsonParseError jsonError; - QJsonDocument dataStoreDocument = QJsonDocument::fromJson(dataStoreFile.data(), &jsonError); - if (jsonError.error == QJsonParseError::NoError) { - QJsonObject rootObject = dataStoreDocument.object(); - if (rootObject.contains(collectionName)) { - QJsonArray collectionArray = rootObject.value(collectionName).toArray(); - for (const QJsonValue &elementValue : std::as_const(collectionArray)) { - const QJsonObject elementObject = elementValue.toObject(); - QJsonObject::ConstIterator element = elementObject.constBegin(); - if (element != elementObject.constEnd()) - return element.key(); - } - } else { - qWarning() << Q_FUNC_INFO << __LINE__ - << QString("Collection \"%1\" not found.").arg(collectionName); - } - } else { - qWarning() << Q_FUNC_INFO << __LINE__ << "Problem in reading json file." - << jsonError.errorString(); - } - - return {}; + static const QHash<QString, DataType> stringTypeHash = CollectionDataTypeHelper::stringToTypeHash(); + return stringTypeHash.value(dataType, DataType::Unknown); } -bool collectionHasColumn(const QString &collectionName, const QString &columnName) +QStringList dataTypesStringList() { - Utils::FilePath dataStorePath = CollectionEditorUtils::dataStoreJsonFilePath(); - - if (!dataStorePath.exists()) - return false; - - Utils::FileReader dataStoreFile; - if (!dataStoreFile.fetch(dataStorePath)) - return false; - - QJsonParseError jsonError; - QJsonDocument dataStoreDocument = QJsonDocument::fromJson(dataStoreFile.data(), &jsonError); - if (jsonError.error == QJsonParseError::NoError) { - QJsonObject rootObject = dataStoreDocument.object(); - if (rootObject.contains(collectionName)) { - QJsonArray collectionArray = rootObject.value(collectionName).toArray(); - for (const QJsonValue &elementValue : std::as_const(collectionArray)) { - const QJsonObject elementObject = elementValue.toObject(); - QJsonObject::ConstIterator element = elementObject.constBegin(); - const QJsonObject::ConstIterator stopItem = elementObject.constEnd(); - - while (element != stopItem) { - const QString keyName = element.key(); - ++element; - - if (columnName == keyName) - return true; - } - } - } else { - qWarning() << Q_FUNC_INFO << __LINE__ - << QString("Collection \"%1\" not found.").arg(collectionName); - } - } else { - qWarning() << Q_FUNC_INFO << __LINE__ << "Problem in reading json file." - << jsonError.errorString(); - } - - return false; + static const QStringList typesList = CollectionDataTypeHelper::orderedTypeNames(); + return typesList; } } // namespace QmlDesigner::CollectionEditorUtils diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.h b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.h index 46429f04b6..5f59329fc6 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.h @@ -8,6 +8,7 @@ QT_BEGIN_NAMESPACE class QJsonArray; +class QJsonObject; QT_END_NAMESPACE namespace Utils { @@ -36,14 +37,12 @@ bool canAcceptCollectionAsModel(const ModelNode &node); bool hasTextRoleProperty(const ModelNode &node); -QJsonArray defaultCollectionArray(); +QJsonObject defaultCollection(); -QJsonArray loadAsSingleJsonCollection(const QUrl &url); +QString dataTypeToString(CollectionDetails::DataType dataType); -QJsonArray loadAsCsvCollection(const QUrl &url); +CollectionDetails::DataType dataTypeFromString(const QString &dataType); -QString getFirstColumnName(const QString &collectionName); - -bool collectionHasColumn(const QString &collectionName, const QString &columnName); +QStringList dataTypesStringList(); } // namespace QmlDesigner::CollectionEditorUtils diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.cpp index 1d27f20548..9378d6871c 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.cpp @@ -270,7 +270,7 @@ bool CollectionSourceModel::collectionExists(const ModelNode &node, const QStrin bool CollectionSourceModel::addCollectionToSource(const ModelNode &node, const QString &collectionName, - const QJsonArray &newCollectionData, + const QJsonObject &newCollection, QString *errorString) { auto returnError = [errorString](const QString &msg) -> bool { @@ -308,7 +308,7 @@ bool CollectionSourceModel::addCollectionToSource(const ModelNode &node, if (document.isObject()) { QJsonObject sourceObject = document.object(); - sourceObject.insert(collectionName, newCollectionData); + sourceObject.insert(collectionName, newCollection); document.setObject(sourceObject); if (!jsonFile.resize(0)) return returnError(tr("Can't clean \"%1\".").arg(sourceFileInfo.absoluteFilePath())); diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.h b/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.h index 5ab77f2a98..ac01c0de22 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.h @@ -54,7 +54,7 @@ public: bool collectionExists(const ModelNode &node, const QString &collectionName) const; bool addCollectionToSource(const ModelNode &node, const QString &collectionName, - const QJsonArray &newCollectionData, + const QJsonObject &newCollection, QString *errorString = nullptr); ModelNode sourceNodeAt(int idx); diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp index 1b1d1a7d9f..f2075b4abb 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp @@ -226,7 +226,18 @@ void CollectionView::addResource(const QUrl &url, const QString &name, const QSt void CollectionView::assignCollectionToSelectedNode(const QString &collectionName) { QTC_ASSERT(dataStoreNode() && hasSingleSelectedModelNode(), return); - m_dataStore->assignCollectionToNode(this, singleSelectedModelNode(), collectionName); + m_dataStore->assignCollectionToNode( + this, + singleSelectedModelNode(), + collectionName, + [&](const QString &collectionName, const QString &columnName) -> bool { + const CollectionReference reference{dataStoreNode(), collectionName}; + return m_widget->collectionDetailsModel()->collectionHasColumn(reference, columnName); + }, + [&](const QString &collectionName) -> QString { + const CollectionReference reference{dataStoreNode(), collectionName}; + return m_widget->collectionDetailsModel()->getFirstColumnName(reference); + }); } void CollectionView::registerDeclarativeType() diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp index a123cf3361..efffa2c2e9 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp @@ -3,6 +3,7 @@ #include "collectionwidget.h" +#include "collectiondetails.h" #include "collectiondetailsmodel.h" #include "collectiondetailssortfiltermodel.h" #include "collectioneditorutils.h" @@ -215,7 +216,7 @@ bool CollectionWidget::addCollection(const QString &collectionName, if (collectionType == "json") { QJsonObject jsonObject; - jsonObject.insert(collectionName, CollectionEditorUtils::defaultCollectionArray()); + jsonObject.insert(collectionName, CollectionEditorUtils::defaultCollection()); QFile sourceFile(sourcePath); if (!sourceFile.open(QFile::WriteOnly)) { @@ -257,8 +258,10 @@ bool CollectionWidget::addCollection(const QString &collectionName, } } else if (collectionType == "json") { QString errorMsg; - bool added = m_sourceModel->addCollectionToSource( - node, collectionName, CollectionEditorUtils::defaultCollectionArray(), &errorMsg); + bool added = m_sourceModel->addCollectionToSource(node, + collectionName, + CollectionEditorUtils::defaultCollection(), + &errorMsg); if (!added) warn(tr("Can not add a model to the JSON file"), errorMsg); return added; @@ -267,48 +270,69 @@ bool CollectionWidget::addCollection(const QString &collectionName, return false; } -bool CollectionWidget::importToJson(const QVariant &sourceNode, - const QString &collectionName, - const QUrl &url) +bool CollectionWidget::importFile(const QString &collectionName, const QUrl &url) { - using CollectionEditorConstants::SourceFormat; using Utils::FilePath; - const ModelNode node = sourceNode.value<ModelNode>(); - const SourceFormat nodeFormat = CollectionEditorUtils::getSourceCollectionFormat(node); - QTC_ASSERT(node.isValid() && nodeFormat == SourceFormat::Json, return false); + ensureDataStoreExists(); + + const ModelNode node = dataStoreNode(); + if (!node.isValid()) { + warn(tr("Can not import to the main model"), tr("The data store is not available.")); + return false; + } FilePath fileInfo = FilePath::fromUserInput(url.isLocalFile() ? url.toLocalFile() : url.toString()); bool added = false; QString errorMsg; - QJsonArray loadedCollection; + CollectionDetails loadedCollection; + QByteArray fileContent; - if (fileInfo.suffix() == "json") - loadedCollection = CollectionEditorUtils::loadAsSingleJsonCollection(url); - else if (fileInfo.suffix() == "csv") - loadedCollection = CollectionEditorUtils::loadAsCsvCollection(url); + auto loadUrlContent = [&]() -> bool { + QFile file(url.isLocalFile() ? url.toLocalFile() : url.toString()); + + if (file.open(QFile::ReadOnly)) { + fileContent = file.readAll(); + file.close(); + return true; + } + + warn(tr("Import from file"), tr("Cannot import from file \"%1\"").arg(file.fileName())); + return false; + }; + + if (fileInfo.suffix() == "json") { + if (!loadUrlContent()) + return false; - if (!loadedCollection.isEmpty()) { + QJsonParseError parseError; + QJsonDocument document = QJsonDocument::fromJson(fileContent, &parseError); + if (parseError.error != QJsonParseError::NoError) { + warn(tr("Json file Import error"), + tr("Cannot parse json content\n%1").arg(parseError.errorString())); + } + + loadedCollection = CollectionDetails::fromImportedJson(document); + } else if (fileInfo.suffix() == "csv") { + if (!loadUrlContent()) + return false; + loadedCollection = CollectionDetails::fromImportedCsv(fileContent); + } + + if (loadedCollection.columns()) { const QString newCollectionName = generateUniqueCollectionName(node, collectionName); - added = m_sourceModel->addCollectionToSource(node, newCollectionName, loadedCollection, &errorMsg); + added = m_sourceModel->addCollectionToSource(node, + newCollectionName, + loadedCollection.toLocalJson(), + &errorMsg); } else { errorMsg = tr("The imported model is empty or is not supported."); } if (!added) warn(tr("Can not add a model to the JSON file"), errorMsg); - return added; -} -bool CollectionWidget::importCollectionToDataStore(const QString &collectionName, const QUrl &url) -{ - using Utils::FilePath; - const ModelNode node = dataStoreNode(); - if (node.isValid()) - return importToJson(QVariant::fromValue(node), collectionName, url); - - warn(tr("Can not import to the main model"), tr("The data store is not available.")); - return false; + return added; } bool CollectionWidget::addCollectionToDataStore(const QString &collectionName) @@ -324,7 +348,7 @@ bool CollectionWidget::addCollectionToDataStore(const QString &collectionName) bool added = m_sourceModel->addCollectionToSource(node, generateUniqueCollectionName(node, collectionName), - CollectionEditorUtils::defaultCollectionArray(), + CollectionEditorUtils::defaultCollection(), &errorMsg); if (!added) warn(tr("Failed to add a model to the default model group"), errorMsg); diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h index 2be98df190..5a9c45d591 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h @@ -44,18 +44,10 @@ public: const QUrl &sourceUrl, const QVariant &sourceNode); - Q_INVOKABLE bool importToJson(const QVariant &sourceNode, - const QString &collectionName, - const QUrl &url); - - Q_INVOKABLE bool importCollectionToDataStore(const QString &collectionName, const QUrl &url); - + Q_INVOKABLE bool importFile(const QString &collectionName, const QUrl &url); Q_INVOKABLE bool addCollectionToDataStore(const QString &collectionName); - Q_INVOKABLE void assignCollectionToSelectedNode(const QString collectionName); - Q_INVOKABLE void ensureDataStoreExists(); - Q_INVOKABLE ModelNode dataStoreNode() const; void warn(const QString &title, const QString &body); diff --git a/src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.cpp b/src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.cpp index a1f82bbc65..b85c651785 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.cpp @@ -159,7 +159,9 @@ void DataStoreModelNode::reloadModel() reset(); } - QTC_ASSERT(m_model.get(), return); + if (!m_model.get()) + return; + m_model->setFileUrl(dataStoreQmlUrl); m_dataRelativePath = dataStoreJsonPath.relativePathFrom(dataStoreQmlPath).toFSPathString(); @@ -182,7 +184,8 @@ Model *DataStoreModelNode::model() const ModelNode DataStoreModelNode::modelNode() const { - QTC_ASSERT(m_model.get(), return {}); + if (!m_model.get()) + return {}; return m_model->rootModelNode(); } @@ -427,7 +430,9 @@ void DataStoreModelNode::removeCollection(const QString &collectionName) void DataStoreModelNode::assignCollectionToNode(AbstractView *view, const ModelNode &targetNode, - const QString &collectionName) + const QString &collectionName, + CollectionColumnFinder collectionHasColumn, + FirstColumnProvider firstColumnProvider) { QTC_ASSERT(targetNode.isValid(), return); @@ -461,14 +466,14 @@ void DataStoreModelNode::assignCollectionToNode(AbstractView *view, if (currentTextRoleValue.isValid() && !currentTextRoleValue.isNull()) { if (currentTextRoleValue.type() == QVariant::String) { const QString currentTextRole = currentTextRoleValue.toString(); - if (CollectionEditorUtils::collectionHasColumn(collectionName, currentTextRole)) + if (collectionHasColumn(collectionName, currentTextRole)) return; } else { return; } } - QString textRoleValue = CollectionEditorUtils::getFirstColumnName(collectionName); + QString textRoleValue = firstColumnProvider(collectionName); textRoleProperty.setValue(textRoleValue); } }); diff --git a/src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.h b/src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.h index 1c855bca7a..d23908bc0c 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.h +++ b/src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.h @@ -18,6 +18,10 @@ class Model; class DataStoreModelNode { public: + using CollectionColumnFinder = std::function<bool(const QString &collectionName, + const QString &columnName)>; + using FirstColumnProvider = std::function<QString(const QString &collectionName)>; + DataStoreModelNode(); void reloadModel(); @@ -32,7 +36,9 @@ public: void assignCollectionToNode(AbstractView *view, const ModelNode &targetNode, - const QString &collectionName); + const QString &collectionName, + CollectionColumnFinder collectionHasColumn, + FirstColumnProvider firstColumnProvider); private: QString getModelQmlText(); |