Merge "Merge remote-tracking branch 'origin/qds/dev' into 12.0" into 12.0

This commit is contained in:
The Qt Project
2023-10-05 13:59:50 +00:00
12 changed files with 414 additions and 78 deletions

View File

@@ -792,6 +792,7 @@ extend_qtc_plugin(QmlDesigner
extend_qtc_plugin(QmlDesigner
SOURCES_PREFIX components/collectioneditor
SOURCES
collectiondetails.cpp collectiondetails.h
collectioneditorconstants.h
collectionlistmodel.cpp collectionlistmodel.h
collectionsourcemodel.cpp collectionsourcemodel.h

View File

@@ -0,0 +1,199 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "collectiondetails.h"
#include <QJsonObject>
#include <QVariant>
namespace QmlDesigner {
class CollectionDetails::Private
{
using SourceFormat = CollectionEditor::SourceFormat;
public:
QStringList headers;
QList<QJsonObject> elements;
SourceFormat sourceFormat = SourceFormat::Unknown;
CollectionReference reference;
bool isChanged = false;
bool isValidColumnId(int column) const { return column > -1 && column < headers.size(); }
bool isValidRowId(int row) const { return row > -1 && row < elements.size(); }
};
CollectionDetails::CollectionDetails()
: d(new Private())
{}
CollectionDetails::CollectionDetails(const CollectionReference &reference)
: CollectionDetails()
{
d->reference = reference;
}
CollectionDetails::CollectionDetails(const CollectionDetails &other) = default;
CollectionDetails::~CollectionDetails() = default;
void CollectionDetails::resetDetails(const QStringList &headers,
const QList<QJsonObject> &elements,
CollectionEditor::SourceFormat format)
{
if (!isValid())
return;
d->headers = headers;
d->elements = elements;
d->sourceFormat = format;
markSaved();
}
void CollectionDetails::insertHeader(const QString &header, int place, const QVariant &defaultValue)
{
if (!isValid())
return;
if (d->headers.contains(header))
return;
if (d->isValidColumnId(place))
d->headers.insert(place, header);
else
d->headers.append(header);
QJsonValue defaultJsonValue = QJsonValue::fromVariant(defaultValue);
for (QJsonObject &element : d->elements)
element.insert(header, defaultJsonValue);
markChanged();
}
void CollectionDetails::removeHeader(int place)
{
if (!isValid())
return;
if (!d->isValidColumnId(place))
return;
const QString header = d->headers.takeAt(place);
for (QJsonObject &element : d->elements)
element.remove(header);
markChanged();
}
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);
};
if (object.has_value()) {
insertJson(object.value());
} else {
QJsonObject defaultObject;
for (const QString &header : std::as_const(d->headers))
defaultObject.insert(header, {});
insertJson(defaultObject);
}
markChanged();
}
CollectionReference CollectionDetails::reference() const
{
return d->reference;
}
CollectionEditor::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->headers.at(column);
const QJsonObject &elementNode = d->elements.at(row);
if (elementNode.contains(propertyName))
return elementNode.value(propertyName).toVariant();
return {};
}
QString CollectionDetails::headerAt(int column) const
{
if (!d->isValidColumnId(column))
return {};
return d->headers.at(column);
}
bool CollectionDetails::isValid() const
{
return d->reference.node.isValid() && d->reference.name.size();
}
bool CollectionDetails::isChanged() const
{
return d->isChanged;
}
int CollectionDetails::columns() const
{
return d->headers.size();
}
int CollectionDetails::rows() const
{
return d->elements.size();
}
bool CollectionDetails::markSaved()
{
if (d->isChanged) {
d->isChanged = false;
return true;
}
return false;
}
void CollectionDetails::swap(CollectionDetails &other)
{
d.swap(other.d);
}
CollectionDetails &CollectionDetails::operator=(const CollectionDetails &other)
{
CollectionDetails value(other);
swap(value);
return *this;
}
void CollectionDetails::markChanged()
{
d->isChanged = true;
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,76 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "collectioneditorconstants.h"
#include "modelnode.h"
#include <QSharedPointer>
QT_BEGIN_NAMESPACE
class QJsonObject;
class QVariant;
QT_END_NAMESPACE
namespace QmlDesigner {
struct CollectionReference
{
ModelNode node;
QString name;
friend auto qHash(const CollectionReference &collection)
{
return qHash(collection.node) ^ ::qHash(collection.name);
}
bool operator==(const CollectionReference &other) const
{
return node == other.node && name == other.name;
}
bool operator!=(const CollectionReference &other) const { return !(*this == other); }
};
class CollectionDetails
{
public:
explicit CollectionDetails();
CollectionDetails(const CollectionReference &reference);
CollectionDetails(const CollectionDetails &other);
~CollectionDetails();
void resetDetails(const QStringList &headers,
const QList<QJsonObject> &elements,
CollectionEditor::SourceFormat format);
void insertHeader(const QString &header, int place = -1, const QVariant &defaultValue = {});
void removeHeader(int place);
void insertElementAt(std::optional<QJsonObject> object, int row = -1);
CollectionReference reference() const;
CollectionEditor::SourceFormat sourceFormat() const;
QVariant data(int row, int column) const;
QString headerAt(int column) const;
bool isValid() const;
bool isChanged() const;
int columns() const;
int rows() const;
bool markSaved();
void swap(CollectionDetails &other);
CollectionDetails &operator=(const CollectionDetails &other);
private:
void markChanged();
// The private data is supposed to be shared between the copies
class Private;
QSharedPointer<Private> d;
};
} // namespace QmlDesigner

View File

@@ -5,6 +5,8 @@
namespace QmlDesigner::CollectionEditor {
enum class SourceFormat { Unknown, Json, Csv };
inline constexpr char SOURCEFILE_PROPERTY[] = "sourceFile";
inline constexpr char COLLECTIONMODEL_IMPORT[] = "QtQuick.Studio.Models";

View File

@@ -11,6 +11,7 @@
#include <QFile>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonParseError>
namespace {
@@ -39,26 +40,19 @@ SingleCollectionModel::SingleCollectionModel(QObject *parent)
int SingleCollectionModel::rowCount([[maybe_unused]] const QModelIndex &parent) const
{
return m_elements.count();
return m_currentCollection.rows();
}
int SingleCollectionModel::columnCount([[maybe_unused]] const QModelIndex &parent) const
{
return m_headers.count();
return m_currentCollection.columns();
}
QVariant SingleCollectionModel::data(const QModelIndex &index, int) const
{
if (!index.isValid())
return {};
const QString &propertyName = m_headers.at(index.column());
const QJsonObject &elementNode = m_elements.at(index.row());
if (elementNode.contains(propertyName))
return elementNode.value(propertyName).toVariant();
return {};
return m_currentCollection.data(index.row(), index.column());
}
bool SingleCollectionModel::setData(const QModelIndex &, const QVariant &, int)
@@ -79,7 +73,7 @@ QVariant SingleCollectionModel::headerData(int section,
[[maybe_unused]] int role) const
{
if (orientation == Qt::Horizontal)
return m_headers.at(section);
return m_currentCollection.headerAt(section);
return {};
}
@@ -88,16 +82,62 @@ void SingleCollectionModel::loadCollection(const ModelNode &sourceNode, const QS
{
QString fileName = sourceNode.variantProperty(CollectionEditor::SOURCEFILE_PROPERTY).value().toString();
if (sourceNode.type() == CollectionEditor::JSONCOLLECTIONMODEL_TYPENAME)
loadJsonCollection(fileName, collection);
else if (sourceNode.type() == CollectionEditor::CSVCOLLECTIONMODEL_TYPENAME)
loadCsvCollection(fileName, collection);
CollectionReference newReference{sourceNode, collection};
bool alreadyOpen = m_openedCollections.contains(newReference);
if (alreadyOpen) {
if (m_currentCollection.reference() != newReference) {
beginResetModel();
switchToCollection(newReference);
endResetModel();
}
} else {
switchToCollection(newReference);
if (sourceNode.type() == CollectionEditor::JSONCOLLECTIONMODEL_TYPENAME)
loadJsonCollection(fileName, collection);
else if (sourceNode.type() == CollectionEditor::CSVCOLLECTIONMODEL_TYPENAME)
loadCsvCollection(fileName, collection);
}
}
void SingleCollectionModel::switchToCollection(const CollectionReference &collection)
{
if (m_currentCollection.reference() == collection)
return;
closeCurrentCollectionIfSaved();
if (!m_openedCollections.contains(collection))
m_openedCollections.insert(collection, CollectionDetails(collection));
m_currentCollection = m_openedCollections.value(collection);
setCollectionName(collection.name);
}
void SingleCollectionModel::closeCollectionIfSaved(const CollectionReference &collection)
{
if (!m_openedCollections.contains(collection))
return;
const CollectionDetails &collectionDetails = m_openedCollections.value(collection);
if (!collectionDetails.isChanged())
m_openedCollections.remove(collection);
m_currentCollection = CollectionDetails{};
}
void SingleCollectionModel::closeCurrentCollectionIfSaved()
{
if (m_currentCollection.isValid())
closeCollectionIfSaved(m_currentCollection.reference());
}
void SingleCollectionModel::loadJsonCollection(const QString &source, const QString &collection)
{
beginResetModel();
setCollectionName(collection);
using CollectionEditor::SourceFormat;
QFile sourceFile(source);
QJsonArray collectionNodes;
bool jsonFileIsOk = false;
@@ -119,62 +159,64 @@ void SingleCollectionModel::loadJsonCollection(const QString &source, const QStr
}
}
setCollectionSourceFormat(jsonFileIsOk ? SourceFormat::Json : SourceFormat::Unknown);
if (collectionNodes.isEmpty()) {
m_headers.clear();
m_elements.clear();
closeCurrentCollectionIfSaved();
endResetModel();
return;
}
};
m_headers = getJsonHeaders(collectionNodes);
m_elements.clear();
QList<QJsonObject> elements;
for (const QJsonValue &value : std::as_const(collectionNodes)) {
if (value.isObject()) {
QJsonObject object = value.toObject();
m_elements.append(object);
elements.append(object);
}
}
SourceFormat sourceFormat = jsonFileIsOk ? SourceFormat::Json : SourceFormat::Unknown;
beginResetModel();
m_currentCollection.resetDetails(getJsonHeaders(collectionNodes), elements, sourceFormat);
endResetModel();
}
void SingleCollectionModel::loadCsvCollection(const QString &source, const QString &collectionName)
void SingleCollectionModel::loadCsvCollection(const QString &source,
[[maybe_unused]] const QString &collectionName)
{
beginResetModel();
using CollectionEditor::SourceFormat;
setCollectionName(collectionName);
QFile sourceFile(source);
m_headers.clear();
m_elements.clear();
QStringList headers;
QList<QJsonObject> elements;
bool csvFileIsOk = false;
if (sourceFile.open(QFile::ReadOnly)) {
QTextStream stream(&sourceFile);
if (!stream.atEnd())
m_headers = stream.readLine().split(',');
headers = stream.readLine().split(',');
if (!m_headers.isEmpty()) {
if (!headers.isEmpty()) {
while (!stream.atEnd()) {
const QStringList recordDataList = stream.readLine().split(',');
int column = -1;
QJsonObject recordData;
for (const QString &cellData : recordDataList) {
if (++column == m_headers.size())
if (++column == headers.size())
break;
recordData.insert(m_headers.at(column), cellData);
recordData.insert(headers.at(column), cellData);
}
if (recordData.count())
m_elements.append(recordData);
elements.append(recordData);
}
csvFileIsOk = true;
}
}
setCollectionSourceFormat(csvFileIsOk ? SourceFormat::Csv : SourceFormat::Unknown);
SourceFormat sourceFormat = csvFileIsOk ? SourceFormat::Csv : SourceFormat::Unknown;
beginResetModel();
m_currentCollection.resetDetails(headers, elements, sourceFormat);
endResetModel();
}
@@ -186,8 +228,4 @@ void SingleCollectionModel::setCollectionName(const QString &newCollectionName)
}
}
void SingleCollectionModel::setCollectionSourceFormat(SourceFormat sourceFormat)
{
m_sourceFormat = sourceFormat;
}
} // namespace QmlDesigner

View File

@@ -3,8 +3,10 @@
#pragma once
#include "collectiondetails.h"
#include <QAbstractTableModel>
#include <QJsonObject>
#include <QHash>
namespace QmlDesigner {
@@ -17,7 +19,6 @@ class SingleCollectionModel : public QAbstractTableModel
Q_PROPERTY(QString collectionName MEMBER m_collectionName NOTIFY collectionNameChanged)
public:
enum class SourceFormat { Unknown, Json, Csv };
explicit SingleCollectionModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent) const override;
@@ -35,15 +36,17 @@ signals:
void collectionNameChanged(const QString &collectionName);
private:
void switchToCollection(const CollectionReference &collection);
void closeCollectionIfSaved(const CollectionReference &collection);
void closeCurrentCollectionIfSaved();
void setCollectionName(const QString &newCollectionName);
void setCollectionSourceFormat(SourceFormat sourceFormat);
void loadJsonCollection(const QString &source, const QString &collection);
void loadCsvCollection(const QString &source, const QString &collectionName);
QStringList m_headers;
QList<QJsonObject> m_elements;
QHash<CollectionReference, CollectionDetails> m_openedCollections;
CollectionDetails m_currentCollection;
QString m_collectionName;
SourceFormat m_sourceFormat = SourceFormat::Unknown;
};
} // namespace QmlDesigner

View File

@@ -744,8 +744,9 @@ std::shared_ptr<NodeMetaInfoPrivate> NodeMetaInfoPrivate::create(Model *model,
newData->minorVersion());
if (auto found = cache.find(stringfiedQualifiedType); found != cache.end()) {
cache.insert(stringfiedType, *found);
return *found;
newData = *found;
cache.insert(stringfiedType, newData);
return newData;
}
if (stringfiedQualifiedType != stringfiedType)
@@ -1709,10 +1710,12 @@ std::vector<NodeMetaInfo> NodeMetaInfo::selfAndPrototypes() const
NodeMetaInfos hierarchy = {*this};
Model *model = m_privateData->model();
for (const TypeDescription &type : m_privateData->prototypes()) {
hierarchy.emplace_back(model,
type.className.toUtf8(),
type.majorVersion,
type.minorVersion);
auto &last = hierarchy.emplace_back(model,
type.className.toUtf8(),
type.majorVersion,
type.minorVersion);
if (!last.isValid())
hierarchy.pop_back();
}
return hierarchy;
@@ -1735,11 +1738,14 @@ NodeMetaInfos NodeMetaInfo::prototypes() const
if (isValid()) {
NodeMetaInfos hierarchy;
Model *model = m_privateData->model();
for (const TypeDescription &type : m_privateData->prototypes())
hierarchy.emplace_back(model,
type.className.toUtf8(),
type.majorVersion,
type.minorVersion);
for (const TypeDescription &type : m_privateData->prototypes()) {
auto &last = hierarchy.emplace_back(model,
type.className.toUtf8(),
type.majorVersion,
type.minorVersion);
if (!last.isValid())
hierarchy.pop_back();
}
return hierarchy;
}

View File

@@ -17,6 +17,5 @@ CustomMaterial {
sourceBlend: CustomMaterial.NoBlend
destinationBlend: CustomMaterial.NoBlend
shadingMode: CustomMaterial.Unshaded
depthDrawMode: Material.AlwaysDepthDraw
cullMode: Material.NoCulling
}

View File

@@ -14,7 +14,18 @@ Node {
property double distance: 500
readonly property int maxGridStep: 32 * _generalHelper.minGridStep
readonly property int gridArea: _generalHelper.minGridStep * 512
readonly property int gridArea: {
let newArea = _generalHelper.minGridStep * 512
// Let's limit the grid size to something sensible
while (newArea > 30000)
newArea -= gridStep
return newArea
}
readonly property double gridOpacity: 0.99
// Step of the main lines of the grid, between those is always one subdiv line
property int gridStep: 100
@@ -71,7 +82,7 @@ Node {
orthoMode: grid.orthoMode
}
]
opacity: 0.99
opacity: grid.gridOpacity
}
Model { // Subdivision lines
@@ -91,7 +102,7 @@ Node {
orthoMode: grid.orthoMode
}
]
opacity: 0.99
opacity: grid.gridOpacity
}
Model { // Z Axis
@@ -110,7 +121,7 @@ Node {
orthoMode: grid.orthoMode
}
]
opacity: 0.99
opacity: grid.gridOpacity
}
Model { // X Axis
readonly property bool _edit3dLocked: true // Make this non-pickable
@@ -129,6 +140,6 @@ Node {
orthoMode: grid.orthoMode
}
]
opacity: 0.99
opacity: grid.gridOpacity
}
}

View File

@@ -22,8 +22,12 @@ View3D {
// gives a reasonable grid spacing in most cases while keeping spacing constant when
// orbiting the camera.
readonly property double cameraDistance: {
if (usePerspective)
return cameraLookAt.minus(camera.position).length() + Math.abs(cameraLookAt.y)
if (usePerspective) {
// Round to five decimals to avoid rounding errors causing constant property updates
// on the material when simply orbiting.
let dist = cameraLookAt.minus(camera.position).length() + Math.abs(cameraLookAt.y)
return Number(dist.toPrecision(5));
}
// Orthocamera should only care about camera magnification,
// as grid will be same size regardless of distance, so setting steps based on distance

View File

@@ -19,15 +19,9 @@ void MAIN()
if (depth > 90000.0)
alpha *= clamp((100000.0 - depth) / 10000.0, 0, 1);
if (alpha > 0.01) {
vec2 uv = FRAGCOORD.xy / vec2(textureSize(SCREEN_TEXTURE, 0));
vec4 sc = texture(SCREEN_TEXTURE, uv);
if (sc.a == 0.0)
FRAGCOLOR = vec4(color.xyz * alpha, alpha);
else
FRAGCOLOR = vec4((color.xyz * alpha + sc.xyz * (1.0 - alpha)) * alpha, alpha);
} else {
if (alpha > 0.01)
FRAGCOLOR = vec4(color.xyz * alpha, alpha);
else
discard;
}
}
}

View File

@@ -81,9 +81,12 @@ void GridGeometry::doUpdateGeometry()
setVertexData(vertexData);
int lastIndex = (vertexData.size() - 1) / int(sizeof(QVector3D));
auto vertexPtr = reinterpret_cast<QVector3D *>(vertexData.data());
setBounds(QVector3D(vertexPtr[0][0], vertexPtr[0][1], 0.0),
QVector3D(vertexPtr[lastIndex][0], vertexPtr[lastIndex][1], 0.0));
// Set bounds based on main grid size instead of actual mesh size to make all parts of the
// grid have consistent bounds.
const float extent = float(m_lines) * m_step;
setBounds(QVector3D(-extent, -extent, 0.0),
QVector3D(extent, extent, 0.0));
}
#if QT_VERSION_MAJOR == 6 && QT_VERSION_MINOR == 4