aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAli Kianian <ali.kianian@qt.io>2023-10-25 17:29:48 +0300
committerAli Kianian <ali.kianian@qt.io>2023-10-26 18:12:05 +0300
commite8853656da4cbe33d5ca374227aef421924a160b (patch)
treeb9f06e135d0f1cc89f0b94d0c3553c116883b93b
parent127426d61cbac4b002e8fcb8ea9aefcd38547723 (diff)
QmlDesigner: Add CsvTableModel to QtQuick.Studio.Utils
Task-number: QDS-11025 Change-Id: Icefed55d576f14f34d3d25c5c88747b40726054b Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
-rw-r--r--src/imports/utils/CMakeLists.txt1
-rw-r--r--src/imports/utils/quickstudiocsvtablemodel.cpp280
-rw-r--r--src/imports/utils/quickstudiocsvtablemodel_p.h100
3 files changed, 381 insertions, 0 deletions
diff --git a/src/imports/utils/CMakeLists.txt b/src/imports/utils/CMakeLists.txt
index adcffa5..f4f0ae2 100644
--- a/src/imports/utils/CMakeLists.txt
+++ b/src/imports/utils/CMakeLists.txt
@@ -10,6 +10,7 @@ qt_internal_add_qml_module(QuickStudioUtils
INCLUDE_DIRECTORIES
${CMAKE_CURRENT_SOURCE_DIR}
SOURCES
+ quickstudiocsvtablemodel.cpp quickstudiocsvtablemodel_p.h
quickstudiofilereader.cpp quickstudiofilereader_p.h
QML_FILES
JSONListModel.qml JSONBackend.qml
diff --git a/src/imports/utils/quickstudiocsvtablemodel.cpp b/src/imports/utils/quickstudiocsvtablemodel.cpp
new file mode 100644
index 0000000..1bf831d
--- /dev/null
+++ b/src/imports/utils/quickstudiocsvtablemodel.cpp
@@ -0,0 +1,280 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Quick Dialogs module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "quickstudiocsvtablemodel_p.h"
+
+#include <QColor>
+#include <QFile>
+#include <QFileInfo>
+#include <QFileSystemWatcher>
+#include <QLoggingCategory>
+#include <QRegularExpression>
+#include <QTextStream>
+
+static QVariant stringToVariant(const QString &value)
+{
+ constexpr QStringView typesPattern{u"(?<boolean>^(?:true|false)$)|"
+ u"(?<number>^(?:-?(?:0|[1-9]\\d*)?(?:\\.\\d*)?(?<=\\d|\\.)"
+ u"(?:e-?(?:0|[1-9]\\d*))?|0x[0-9a-f]+)$)|"
+ u"(?<color>^(?:#(?:(?:[0-9a-fA-F]{2}){3,4}|"
+ u"(?:[0-9a-fA-F]){3,4}))$)"};
+
+ static QRegularExpression validator(typesPattern.toString());
+ const QString trimmedValue = value.trimmed();
+ QRegularExpressionMatch match = validator.match(trimmedValue);
+
+ if (!match.hasMatch())
+ return value;
+
+ if (match.hasCaptured(u"boolean"))
+ return QVariant::fromValue<bool>(trimmedValue.at(0).toLower() == u't');
+
+ if (match.hasCaptured(u"number"))
+ return trimmedValue.toDouble();
+
+ if (match.hasCaptured(u"color"))
+ return QColor::fromString(trimmedValue);
+
+ return value;
+}
+
+static QVariant stringToVariant(const QString &value, QMetaType::Type type, bool *ok = nullptr)
+{
+ if (type == QMetaType::Bool) {
+ const QString lowerValue = value.toLower().trimmed();
+ bool conversionOk = true;
+ bool booleanValue = false;
+
+ if (lowerValue == u"true")
+ booleanValue = true;
+ else if (lowerValue == u"false")
+ booleanValue = false;
+ else
+ conversionOk = false;
+
+ if (ok)
+ *ok = conversionOk;
+
+ if (conversionOk)
+ return booleanValue;
+ }
+
+ if (type == QMetaType::Double) {
+ bool conversionOk = false;
+ double numericValue = value.toDouble(&conversionOk);
+ if (ok)
+ *ok = conversionOk;
+
+ if (conversionOk)
+ return numericValue;
+ }
+
+ if (type == QMetaType::QColor) {
+ bool conversionOk = QColor::isValidColorName(value);
+ if (ok)
+ *ok = conversionOk;
+
+ if (conversionOk)
+ return QColor::fromString(value);
+ }
+
+ if (type == QMetaType::QString) {
+ if (ok)
+ *ok = !value.isEmpty();
+ }
+
+ return value;
+}
+
+static QString urlToLocalPath(const QUrl &url)
+{
+ QString localPath;
+
+ if (url.isLocalFile())
+ localPath = url.toLocalFile();
+
+ if (url.scheme() == QLatin1String("qrc")) {
+ const QString &path = url.path();
+ localPath = QStringLiteral(":") + path;
+ }
+
+ return localPath;
+}
+
+static Q_LOGGING_CATEGORY(texttomodelMergerDebug, "qt.StudioCsvTableModel.debug", QtDebugMsg)
+ QuickStudioCsvTableModel::QuickStudioCsvTableModel(QObject *parent)
+ : QAbstractTableModel(parent)
+ , m_fileWatcher(new QFileSystemWatcher(this))
+{
+ connect(m_fileWatcher,
+ &QFileSystemWatcher::fileChanged,
+ this,
+ &QuickStudioCsvTableModel::checkPathAndReload);
+}
+
+int QuickStudioCsvTableModel::rowCount([[maybe_unused]] const QModelIndex &parent) const
+{
+ return m_rows.size();
+}
+
+int QuickStudioCsvTableModel::columnCount([[maybe_unused]] const QModelIndex &parent) const
+{
+ return m_headers.size();
+}
+
+QVariant QuickStudioCsvTableModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid())
+ return {};
+
+ const QHash<int, QVariant> &recordData = m_rows.at(index.row());
+
+ if (role == Qt::DisplayRole)
+ return recordData.value(index.column()).toString();
+
+ return recordData.value(index.column());
+}
+
+QVariant QuickStudioCsvTableModel::headerData(int section,
+ Qt::Orientation orientation,
+ [[maybe_unused]] int role) const
+{
+ if (orientation == Qt::Horizontal) {
+ if (section > -1 && section < m_headers.size())
+ return m_headers.at(section);
+ } else if (orientation == Qt::Vertical) {
+ if (section > -1 && section < m_rows.size())
+ return section;
+ }
+
+ return {};
+}
+
+QUrl QuickStudioCsvTableModel::source() const
+{
+ return m_source;
+}
+
+void QuickStudioCsvTableModel::setSource(const QUrl &newSource)
+{
+ if (m_source == newSource)
+ return;
+
+ m_source = newSource;
+ emit this->sourceChanged(m_source);
+
+ startWatchingSource();
+ reloadModel();
+}
+
+void QuickStudioCsvTableModel::reloadModel()
+{
+ beginResetModel();
+ m_headers.clear();
+ m_rows.clear();
+ m_types.clear();
+ m_columnIsClean.clear();
+
+ QString filePath = ::urlToLocalPath(source());
+ QFile sourceFile(filePath);
+
+ if (!sourceFile.open(QFile::ReadOnly)) {
+ qWarning() << "File cannot be opened:" << sourceFile.errorString();
+ endResetModel();
+ return;
+ }
+
+ QTextStream stream(&sourceFile);
+
+ if (!stream.atEnd())
+ m_headers = stream.readLine().split(u',', Qt::KeepEmptyParts);
+
+ m_types.insert(0, m_headers.size(), QMetaType::UnknownType);
+ m_columnIsClean.insert(0, m_headers.size(), true);
+
+ if (!m_headers.isEmpty()) {
+ while (!stream.atEnd()) {
+ const QStringList recordDataList = stream.readLine().split(u',', Qt::KeepEmptyParts);
+ int column = -1;
+ QHash<int, QVariant> recordData;
+ for (const QString &cellString : recordDataList) {
+ if (++column == m_headers.size())
+ break;
+
+ if (!cellString.size())
+ continue;
+
+ const QMetaType::Type &type = m_types.at(column);
+
+ QVariant cellData;
+ if (type == QMetaType::UnknownType) {
+ cellData = stringToVariant(cellString);
+ m_types.replace(column, QMetaType::Type(cellData.typeId()));
+ } else {
+ bool columnIsClean = m_columnIsClean.at(column);
+ bool *ok = columnIsClean ? &columnIsClean : nullptr;
+ cellData = stringToVariant(cellString, type, ok);
+ if (ok)
+ m_columnIsClean.replace(column, *ok);
+ }
+ recordData.insert(column, cellData);
+ }
+ m_rows.append(recordData);
+ }
+ }
+
+ endResetModel();
+}
+
+void QuickStudioCsvTableModel::checkPathAndReload(const QString &path)
+{
+ QString sourceLocalPath = ::urlToLocalPath(source());
+ if (path == sourceLocalPath)
+ reloadModel();
+}
+
+void QuickStudioCsvTableModel::startWatchingSource()
+{
+ qCDebug(texttomodelMergerDebug) << Q_FUNC_INFO << "Load file: " << source();
+
+ const QStringList oldWatchingFiles = m_fileWatcher->files();
+ if (oldWatchingFiles.size())
+ m_fileWatcher->removePaths(oldWatchingFiles);
+
+ QString localPath = ::urlToLocalPath(source());
+ if (QFileInfo(localPath).isFile())
+ m_fileWatcher->addPath(localPath);
+}
diff --git a/src/imports/utils/quickstudiocsvtablemodel_p.h b/src/imports/utils/quickstudiocsvtablemodel_p.h
new file mode 100644
index 0000000..b0421a8
--- /dev/null
+++ b/src/imports/utils/quickstudiocsvtablemodel_p.h
@@ -0,0 +1,100 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Quick Dialogs module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QAbstractTableModel>
+#include <QtCore/qurl.h>
+#include <QtQml/qqml.h>
+
+QT_BEGIN_NAMESPACE
+
+class QFileSystemWatcher;
+class QuickStudioCsvTableModel : public QAbstractTableModel
+{
+ Q_OBJECT
+
+ QML_NAMED_ELEMENT(CsvTableModel)
+ QML_ADDED_IN_VERSION(6, 2)
+
+ Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
+
+public:
+ explicit QuickStudioCsvTableModel(QObject *parent = nullptr);
+
+ int rowCount(const QModelIndex &parent = {}) const override;
+ int columnCount(const QModelIndex &parent = {}) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ QVariant headerData(int section,
+ Qt::Orientation orientation,
+ int role = Qt::DisplayRole) const override;
+
+ QUrl source() const;
+ void setSource(const QUrl &newSource);
+
+signals:
+ void sourceChanged(const QUrl &url);
+
+private slots:
+ void reloadModel();
+ void checkPathAndReload(const QString &path);
+
+private:
+ void startWatchingSource();
+
+ QFileSystemWatcher *m_fileWatcher = nullptr;
+ QUrl m_source;
+
+ QStringList m_headers;
+ QList<QHash<int, QVariant>> m_rows;
+ QList<QMetaType::Type> m_types;
+ QList<bool> m_columnIsClean;
+};
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QuickStudioCsvTableModel)