diff --git a/.github/workflows/build_cmake.yml b/.github/workflows/build_cmake.yml index 0784db70a70..a99d71736fa 100644 --- a/.github/workflows/build_cmake.yml +++ b/.github/workflows/build_cmake.yml @@ -7,7 +7,7 @@ on: - 'doc/**' env: - QT_VERSION: 6.3.1 + QT_VERSION: 6.3.2 CLANG_VERSION: 15.0.0 ELFUTILS_VERSION: 0.175 CMAKE_VERSION: 3.21.1 diff --git a/coin/instructions/common_environment.yaml b/coin/instructions/common_environment.yaml index 5ae9e8d63e4..8cbbcd352ac 100644 --- a/coin/instructions/common_environment.yaml +++ b/coin/instructions/common_environment.yaml @@ -13,7 +13,7 @@ instructions: instructions: - type: EnvironmentVariable variableName: QTC_QT_BASE_URL - variableValue: "http://ci-files02-hki.intra.qt.io/packages/jenkins/archive/qt/6.3/6.3.1-final-released/Qt6.3.1" + variableValue: "http://ci-files02-hki.intra.qt.io/packages/jenkins/archive/qt/6.3/6.3.2-final-released/Qt" - type: EnvironmentVariable variableName: QTC_QT_MODULES variableValue: "qt5compat qtbase qtdeclarative qtimageformats qtquick3d qtquickcontrols2 qtquicktimeline qtserialport qtshadertools qtsvg qttools qttranslations qtwebengine" diff --git a/doc/qtdesignstudio/images/icons/3d-background-color.png b/doc/qtdesignstudio/images/icons/3d-background-color.png new file mode 100644 index 00000000000..d896907b306 Binary files /dev/null and b/doc/qtdesignstudio/images/icons/3d-background-color.png differ diff --git a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc index 47e89c4aecd..e7487a7709a 100644 --- a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc +++ b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc @@ -264,6 +264,28 @@ selected in the \uicontrol{3D} view. \endtable + \section1 Changing Colors + + To change the \uicontrol 3D view background or grid color, select + \inlineimage icons/3d-background-color.png + in the toolbar. This opens a menu with the following options: + + \table + \row + \li Select Background Color + \li Select a color for the background. + \row + \li Select Grid Color + \li Select a color for the grid. + \row + \li Use Scene Environment Color + \li Sets the 3D view to use the scene environment color as background + color. + \row + \li Reset Colors + \li Resets the background and grid colors to the default colors. + \endtable + \section1 Particle Editor The particle editor tools help you preview your particle systems in @@ -359,6 +381,11 @@ \li Visibility Toggles \li \li \l{Toggling Visibility} + \row + \li \inlineimage icons/3d-background-color.png + \li Background Color Actions + \li + \li \l{Changing Colors} \row \li \inlineimage icons/particles-seek.png \li Seek Particle System Time diff --git a/share/qtcreator/qml/qmlpuppet/commands/puppettocreatorcommand.h b/share/qtcreator/qml/qmlpuppet/commands/puppettocreatorcommand.h index 356e7f7c84d..762054bb9b4 100644 --- a/share/qtcreator/qml/qmlpuppet/commands/puppettocreatorcommand.h +++ b/share/qtcreator/qml/qmlpuppet/commands/puppettocreatorcommand.h @@ -18,7 +18,7 @@ public: ActiveSceneChanged, RenderModelNodePreviewImage, Import3DSupport, - ModelAtPos, + NodeAtPos, None }; PuppetToCreatorCommand(Type type, const QVariant &data); diff --git a/share/qtcreator/qml/qmlpuppet/commands/view3dactioncommand.h b/share/qtcreator/qml/qmlpuppet/commands/view3dactioncommand.h index b54a86a0089..fca9777692f 100644 --- a/share/qtcreator/qml/qmlpuppet/commands/view3dactioncommand.h +++ b/share/qtcreator/qml/qmlpuppet/commands/view3dactioncommand.h @@ -38,7 +38,7 @@ public: SelectGridColor, ResetBackgroundColor, SyncBackgroundColor, - GetModelAtPos + GetNodeAtPos }; View3DActionCommand(Type type, const QVariant &value); diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditView3D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditView3D.qml index 833131b5e1d..672aca03c6a 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditView3D.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditView3D.qml @@ -525,6 +525,19 @@ Item { } } + function gizmoAt(x, y) + { + for (var i = 0; i < lightIconGizmos.length; ++i) { + if (lightIconGizmos[i].visible && lightIconGizmos[i].hasPoint(x, y)) + return lightIconGizmos[i].targetNode; + } + for (var i = 0; i < cameraGizmos.length; ++i) { + if (cameraGizmos[i].visible && cameraGizmos[i].hasPoint(x, y)) + return cameraGizmos[i].targetNode; + } + return null; + } + Component.onCompleted: { createEditView(); selectObjects([]); diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/IconGizmo.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/IconGizmo.qml index 4c3dd385988..e44cf74d080 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/IconGizmo.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/IconGizmo.qml @@ -30,6 +30,17 @@ Item { signal clicked(Node node, bool multi) + function hasPoint(x, y) + { + if (!view3D || !targetNode) + return false; + + var point = view3D.mapToItem(iconMouseArea, x, y); + + return point.x >= iconMouseArea.x && (point.x <= iconMouseArea.x + iconMouseArea.width) + && point.y >= iconMouseArea.y && (point.y <= iconMouseArea.y + iconMouseArea.height); + } + onSelectedChanged: { if (selected) hasMouse = false; diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml index ccb05244f3b..915feea4f2d 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml @@ -648,6 +648,23 @@ Item { } } + function gizmoAt(x, y) + { + for (var i = 0; i < lightIconGizmos.length; ++i) { + if (lightIconGizmos[i].visible && lightIconGizmos[i].hasPoint(x, y)) + return lightIconGizmos[i].targetNode; + } + for (var i = 0; i < cameraGizmos.length; ++i) { + if (cameraGizmos[i].visible && cameraGizmos[i].hasPoint(x, y)) + return cameraGizmos[i].targetNode; + } + for (var i = 0; i < particleSystemIconGizmos.length; ++i) { + if (particleSystemIconGizmos[i].visible && particleSystemIconGizmos[i].hasPoint(x, y)) + return particleSystemIconGizmos[i].targetNode; + } + return null; + } + Component.onCompleted: { createEditView(); selectObjects([]); diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/IconGizmo.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/IconGizmo.qml index cc0fdea2916..70ee9d51f1a 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/IconGizmo.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/IconGizmo.qml @@ -31,6 +31,17 @@ Item { signal clicked(Node node, bool multi) + function hasPoint(x, y) + { + if (!view3D || !targetNode) + return false; + + var point = view3D.mapToItem(iconMouseArea, x, y); + + return point.x >= iconMouseArea.x && (point.x <= iconMouseArea.x + iconMouseArea.width) + && point.y >= iconMouseArea.y && (point.y <= iconMouseArea.y + iconMouseArea.height); + } + onSelectedChanged: { if (selected) hasMouse = false; diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp index 1ac414185d3..b99f4c42827 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp @@ -272,7 +272,7 @@ void Qt5InformationNodeInstanceServer::handleInputEvents() // Context menu requested if (command.button() == Qt::RightButton && command.modifiers() == Qt::NoModifier) - getModelAtPos(command.pos()); + getNodeAtPos(command.pos()); } } @@ -386,7 +386,7 @@ void Qt5InformationNodeInstanceServer::removeRotationBlocks( #endif } -void Qt5InformationNodeInstanceServer::getModelAtPos(const QPointF &pos) +void Qt5InformationNodeInstanceServer::getNodeAtPos(const QPointF &pos) { #ifdef QUICK3D_MODULE // pick a Quick3DModel at view position @@ -398,13 +398,25 @@ void Qt5InformationNodeInstanceServer::getModelAtPos(const QPointF &pos) QObject *obj = qvariant_cast(editViewProp.read()); QQuick3DViewport *editView = qobject_cast(obj); - QQuick3DModel *hitModel = helper->pickViewAt(editView, pos.x(), pos.y()).objectHit(); + // Non-model nodes with icon gizmos are also valid results + QVariant gizmoVar; + QMetaObject::invokeMethod(m_editView3DData.rootItem, "gizmoAt", Qt::DirectConnection, + Q_RETURN_ARG(QVariant, gizmoVar), + Q_ARG(QVariant, pos.x()), + Q_ARG(QVariant, pos.y())); + QObject *gizmoObj = qvariant_cast(gizmoVar); + QVariant instance = -1; - // filter out picks of models created dynamically or inside components - QQuick3DModel *resolvedPick = qobject_cast(helper->resolvePick(hitModel)); + if (gizmoObj && hasInstanceForObject(gizmoObj)) { + instance = instanceForObject(gizmoObj).instanceId(); + } else { + QQuick3DModel *hitModel = helper->pickViewAt(editView, pos.x(), pos.y()).objectHit(); + QObject *resolvedPick = helper->resolvePick(hitModel); + if (hasInstanceForObject(resolvedPick)) + instance = instanceForObject(resolvedPick).instanceId(); + } - QVariant instance = resolvedPick ? instanceForObject(resolvedPick).instanceId() : -1; - nodeInstanceClient()->handlePuppetToCreatorCommand({PuppetToCreatorCommand::ModelAtPos, instance}); + nodeInstanceClient()->handlePuppetToCreatorCommand({PuppetToCreatorCommand::NodeAtPos, instance}); #else Q_UNUSED(pos) #endif @@ -2387,8 +2399,8 @@ void Qt5InformationNodeInstanceServer::view3DAction(const View3DActionCommand &c break; #endif #ifdef QUICK3D_MODULE - case View3DActionCommand::GetModelAtPos: { - getModelAtPos(command.value().toPointF()); + case View3DActionCommand::GetNodeAtPos: { + getNodeAtPos(command.value().toPointF()); return; } #endif diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h index a37fc42f9cb..2099c184d91 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h @@ -126,7 +126,7 @@ private: void updateMaterialPreviewData(const QVector &valueChanges); void updateRotationBlocks(const QVector &valueChanges); void removeRotationBlocks(const QVector &instanceIds); - void getModelAtPos(const QPointF &pos); + void getNodeAtPos(const QPointF &pos); void createAuxiliaryQuickView(const QUrl &url, RenderViewData &viewData); #ifdef QUICK3D_PARTICLES_MODULE diff --git a/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorPane.qml b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorPane.qml index dc3aedf381a..1ac7137a4f2 100644 --- a/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorPane.qml +++ b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorPane.qml @@ -41,6 +41,10 @@ PropertyEditorPane { Item { width: 1; height: 10 } + DynamicPropertiesSection { + propertiesModel: MaterialEditorDynamicPropertiesModel {} + } + Loader { id: specificsTwo @@ -57,7 +61,11 @@ PropertyEditorPane { } } - Item { width: 1; height: 10 } + Item { + width: 1 + height: 10 + visible: specificsTwo.visible + } Loader { id: specificsOne diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/3DItemFilterComboBoxEditorTemplate.template b/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/3DItemFilterComboBoxEditorTemplate.template new file mode 100644 index 00000000000..6870a3e74ce --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/3DItemFilterComboBoxEditorTemplate.template @@ -0,0 +1,17 @@ +PropertyLabel { + text: "%1" + tooltip: "%1" +} + +SecondColumnLayout { + ItemFilterComboBox { + typeFilter: "QtQuick3D.%3" + validator: RegExpValidator { regExp: /(^$|^[a-z_]\w*)/ } + backendValue: backendValues.%2 + implicitWidth: StudioTheme.Values.singleControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + width: implicitWidth + } + + ExpandingSpacer {} +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/TemplateTypes.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/TemplateTypes.qml index cc65afeb85c..ae9bebc1432 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/TemplateTypes.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/TemplateTypes.qml @@ -56,4 +56,25 @@ AutoTypes { sourceFile: "ImageEditorTemplate.template" separateSection: true } + + Type { + typeNames: ["TextureInput", "Texture"] + sourceFile: "3DItemFilterComboBoxEditorTemplate.template" + needsTypeArg: true + } + + Type { + typeNames: ["vector2d"] + sourceFile: "Vector2dEditorTemplate.template" + } + + Type { + typeNames: ["vector3d"] + sourceFile: "Vector3dEditorTemplate.template" + } + + Type { + typeNames: ["vector4d"] + sourceFile: "Vector4dEditorTemplate.template" + } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector2dEditorTemplate.template b/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector2dEditorTemplate.template new file mode 100644 index 00000000000..b339c254bac --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector2dEditorTemplate.template @@ -0,0 +1,42 @@ +PropertyLabel { + text: "%1" + tooltip: "%1" +} + +ColumnLayout { + SecondColumnLayout { + SpinBox { + minimumValue: -9999999 + maximumValue: 9999999 + decimals: 2 + backendValue: backendValues.%2_x + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } + + ControlLabel { + text: "X" + } + + Spacer { implicitWidth: StudioTheme.Values.controlGap } + + SpinBox { + minimumValue: -9999999 + maximumValue: 9999999 + decimals: 2 + backendValue: backendValues.%2_y + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } + + ControlLabel { + text: "Y" + } + + ExpandingSpacer {} + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector3dEditorTemplate.template b/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector3dEditorTemplate.template new file mode 100644 index 00000000000..5caff585d73 --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector3dEditorTemplate.template @@ -0,0 +1,61 @@ +PropertyLabel { + text: "%1" + tooltip: "%1" +} + +ColumnLayout { + SecondColumnLayout { + SpinBox { + minimumValue: -9999999 + maximumValue: 9999999 + decimals: 2 + backendValue: backendValues.%2_x + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } + + ControlLabel { + text: "X" + } + + Spacer { implicitWidth: StudioTheme.Values.controlGap } + + SpinBox { + minimumValue: -9999999 + maximumValue: 9999999 + decimals: 2 + backendValue: backendValues.%2_y + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } + + ControlLabel { + text: "Y" + } + + ExpandingSpacer {} + } + + SecondColumnLayout { + SpinBox { + minimumValue: -9999999 + maximumValue: 9999999 + decimals: 2 + backendValue: backendValues.%2_z + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } + + ControlLabel { + text: "Z" + } + + ExpandingSpacer {} + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector4dEditorTemplate.template b/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector4dEditorTemplate.template new file mode 100644 index 00000000000..619f51cebe3 --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector4dEditorTemplate.template @@ -0,0 +1,78 @@ +PropertyLabel { + text: "%1" + tooltip: "%1" +} + +ColumnLayout { + SecondColumnLayout { + SpinBox { + minimumValue: -9999999 + maximumValue: 9999999 + decimals: 2 + backendValue: backendValues.%2_x + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } + + ControlLabel { + text: "X" + } + + Spacer { implicitWidth: StudioTheme.Values.controlGap } + + SpinBox { + minimumValue: -9999999 + maximumValue: 9999999 + decimals: 2 + backendValue: backendValues.%2_y + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } + + ControlLabel { + text: "Y" + } + + ExpandingSpacer {} + } + + SecondColumnLayout { + SpinBox { + minimumValue: -9999999 + maximumValue: 9999999 + decimals: 2 + backendValue: backendValues.%2_z + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } + + ControlLabel { + text: "Z" + } + + Spacer { implicitWidth: StudioTheme.Values.controlGap } + + SpinBox { + minimumValue: -9999999 + maximumValue: 9999999 + decimals: 2 + backendValue: backendValues.%2_w + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } + + ControlLabel { + text: "W" + } + + ExpandingSpacer {} + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml index b400502c4b0..1805bc91095 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml @@ -15,6 +15,10 @@ PropertyEditorPane { showState: true } + DynamicPropertiesSection { + propertiesModel: SelectionDynamicPropertiesModel {} + } + GeometrySection {} Section { diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Object3DPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Object3DPane.qml index c8ebceca70d..8d9ac9b5104 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Object3DPane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Object3DPane.qml @@ -18,6 +18,10 @@ PropertyEditorPane { anchors.left: parent.left anchors.right: parent.right + DynamicPropertiesSection { + propertiesModel: SelectionDynamicPropertiesModel {} + } + Loader { id: specificsTwo anchors.left: parent.left diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditor.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditor.qml index fd0fdd823cd..f97db29715b 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditor.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditor.qml @@ -44,6 +44,10 @@ SecondColumnLayout { colorEditor.backendValue.resetValue() } + function initEditor() { + cePopup.initEditor() + } + Connections { id: backendConnection target: colorEditor diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditorPopup.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditorPopup.qml index 02b184c7ea7..fca7cbef216 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditorPopup.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditorPopup.qml @@ -22,6 +22,23 @@ T.Popup { property bool isInValidState: false + function initEditor() { + if (colorEditor.supportGradient && gradientModel.hasGradient) { + colorEditor.color = gradientLine.currentColor + gradientLine.currentColor = colorEditor.color + hexTextField.text = colorEditor.color + popupHexTextField.text = colorEditor.color + } + + cePopup.isInValidState = true + colorEditor.originalColor = colorEditor.color + colorPalette.selectedColor = colorEditor.color + colorPicker.color = colorEditor.color + + cePopup.createModel() + cePopup.determineActiveColorMode() + } + function commitGradientColor() { var hexColor = convertColorToString(colorEditor.color) cePopup.popupHexTextField.text = hexColor @@ -453,24 +470,10 @@ T.Popup { } } } - Connections { target: modelNodeBackend function onSelectionChanged() { - if (colorEditor.supportGradient && gradientModel.hasGradient) { - colorEditor.color = gradientLine.currentColor - gradientLine.currentColor = colorEditor.color - hexTextField.text = colorEditor.color - popupHexTextField.text = colorEditor.color - } - - cePopup.isInValidState = true - colorEditor.originalColor = colorEditor.color - colorPalette.selectedColor = colorEditor.color - colorPicker.color = colorEditor.color - - cePopup.createModel() - cePopup.determineActiveColorMode() + cePopup.initEditor() } } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml new file mode 100644 index 00000000000..df98ebe4107 --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml @@ -0,0 +1,772 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import HelperWidgets 2.0 +import QtQuick.Templates 2.15 as T +import StudioControls 1.0 as StudioControls +import StudioTheme 1.0 as StudioTheme + +Section { + id: root + anchors.left: parent.left + anchors.right: parent.right + caption: qsTr("User Added Properties") + + property DynamicPropertiesModel propertiesModel: null + + property Component colorEditor: Component { + id: colorEditor + ColorEditor { + id: colorEditorControl + property string propertyType + + signal remove + + supportGradient: false + spacer.visible: false + + Spacer { implicitWidth: StudioTheme.Values.twoControlColumnGap } + + IconIndicator { + Layout.alignment: Qt.AlignLeft + + icon: StudioTheme.Constants.closeCross + onClicked: colorEditorControl.remove() + } + ExpandingSpacer {} + } + } + + property Component intEditor: Component { + id: intEditor + SecondColumnLayout { + id: layoutInt + property var backendValue + property string propertyType + + signal remove + + SpinBox { + maximumValue: 9999999 + minimumValue: -9999999 + backendValue: layoutInt.backendValue + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + Spacer { + implicitWidth: StudioTheme.Values.twoControlColumnGap + } + + Item { + height: 10 + implicitWidth: { + return StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + } + + Spacer { + implicitWidth: StudioTheme.Values.twoControlColumnGap + } + + IconIndicator { + Layout.alignment: Qt.AlignLeft + icon: StudioTheme.Constants.closeCross + onClicked: layoutInt.remove() + } + + ExpandingSpacer {} + } + } + + property Component realEditor: Component { + id: realEditor + SecondColumnLayout { + id: layoutReal + property var backendValue + property string propertyType + + signal remove + + SpinBox { + backendValue: layoutReal.backendValue + minimumValue: -9999999 + maximumValue: 9999999 + decimals: 2 + stepSize: 0.1 + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + Spacer { + implicitWidth: StudioTheme.Values.twoControlColumnGap + } + + Item { + height: 10 + implicitWidth: { + return StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + } + + Spacer { + implicitWidth: StudioTheme.Values.twoControlColumnGap + } + + IconIndicator { + Layout.alignment: Qt.AlignLeft + icon: StudioTheme.Constants.closeCross + onClicked: layoutReal.remove() + } + + ExpandingSpacer {} + } + } + + property Component stringEditor: Component { + id: stringEditor + SecondColumnLayout { + id: layoutString + property var backendValue + property string propertyType + + signal remove + + LineEdit { + backendValue: layoutString.backendValue + implicitWidth: StudioTheme.Values.singleControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + Spacer { + implicitWidth: StudioTheme.Values.twoControlColumnGap + } + + IconIndicator { + Layout.alignment: Qt.AlignLeft + icon: StudioTheme.Constants.closeCross + onClicked: layoutString.remove() + } + + ExpandingSpacer {} + } + } + + property Component boolEditor: Component { + id: boolEditor + SecondColumnLayout { + id: layoutBool + property var backendValue + property string propertyType + + signal remove + + CheckBox { + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + text: layoutBool.backendValue.value + backendValue: layoutBool.backendValue + } + + Spacer { + implicitWidth: StudioTheme.Values.twoControlColumnGap + } + + Item { + height: 10 + implicitWidth: { + return StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + } + + Spacer { + implicitWidth: StudioTheme.Values.twoControlColumnGap + } + + IconIndicator { + Layout.alignment: Qt.AlignLeft + icon: StudioTheme.Constants.closeCross + onClicked: layoutBool.remove() + } + + ExpandingSpacer {} + } + } + + property Component urlEditor: Component { + id: urlEditor + SecondColumnLayout { + id: layoutUrl + property var backendValue + property string propertyType + + signal remove + + UrlChooser { + backendValue: layoutUrl.backendValue + comboBox.implicitWidth: StudioTheme.Values.singleControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + spacer.implicitWidth: StudioTheme.Values.controlLabelGap + } + + IconIndicator { + Layout.alignment: Qt.AlignLeft + icon: StudioTheme.Constants.closeCross + onClicked: layoutUrl.remove() + } + + ExpandingSpacer {} + } + } + + property Component aliasEditor: Component { + id: aliasEditor + SecondColumnLayout { + id: layoutAlias + property var backendValue + property string propertyType + property alias lineEdit: lineEdit + + signal remove + + function updateLineEditText() { + lineEdit.text = lineEdit.backendValue.expression + } + + LineEdit { + id: lineEdit + backendValue: layoutAlias.backendValue + implicitWidth: StudioTheme.Values.singleControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + writeAsExpression: true + showTranslateCheckBox: false + } + + Spacer { + implicitWidth: StudioTheme.Values.twoControlColumnGap + } + + IconIndicator { + Layout.alignment: Qt.AlignLeft + icon: StudioTheme.Constants.closeCross + onClicked: layoutAlias.remove() + } + + ExpandingSpacer {} + } + } + + property Component textureInputEditor: Component { + id: textureInputEditor + SecondColumnLayout { + id: layoutTextureInput + property var backendValue + property string propertyType + + signal remove + + ItemFilterComboBox { + typeFilter: "QtQuick3D.TextureInput" + validator: RegExpValidator { regExp: /(^$|^[a-z_]\w*)/ } + backendValue: layoutTextureInput.backendValue + implicitWidth: StudioTheme.Values.singleControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + Spacer { + implicitWidth: StudioTheme.Values.twoControlColumnGap + } + + IconIndicator { + Layout.alignment: Qt.AlignLeft + icon: StudioTheme.Constants.closeCross + onClicked: layoutTextureInput.remove() + } + + ExpandingSpacer {} + } + } + + property Component vectorEditor: Component { + id: vectorEditor + ColumnLayout { + id: layoutVector + property var backendValue + property string propertyType + property int vecSize: 0 + property var proxyValues: [] + property var spinBoxes: [boxX, boxY, boxZ, boxW] + + signal remove + + onVecSizeChanged: updateProxyValues() + + spacing: StudioTheme.Values.sectionRowSpacing + + function isValidValue(v) { + return !(v === undefined || isNaN(v)) + } + + function updateExpression() { + for (let i = 0; i < vecSize; ++i) { + if (!isValidValue(proxyValues[i].value)) + return + } + + let expStr = "Qt.vector" + vecSize + "d("+proxyValues[0].value + for (let j=1; j < vecSize; ++j) + expStr += ", " + proxyValues[j].value + expStr += ")" + + layoutVector.backendValue.expression = expStr + } + + function updateProxyValues() { + if (!backendValue) + return; + + const startIndex = backendValue.expression.indexOf('(') + const endIndex = backendValue.expression.indexOf(')') + if (startIndex === -1 || endIndex === -1 || endIndex < startIndex) + return + const numberStr = backendValue.expression.slice(startIndex + 1, endIndex) + const numbers = numberStr.split(",") + if (!Array.isArray(numbers) || numbers.length !== vecSize) + return + + let vals = [] + for (let i = 0; i < vecSize; ++i) { + vals[i] = parseFloat(numbers[i]) + if (!isValidValue(vals[i])) + return + } + + for (let j = 0; j < vecSize; ++j) + proxyValues[j].value = vals[j] + } + + SecondColumnLayout { + SpinBox { + id: boxX + minimumValue: -9999999 + maximumValue: 9999999 + decimals: 2 + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } + + ControlLabel { + text: "X" + tooltip: "X" + } + + Spacer { implicitWidth: StudioTheme.Values.controlGap } + + SpinBox { + id: boxY + minimumValue: -9999999 + maximumValue: 9999999 + decimals: 2 + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } + + ControlLabel { + text: "Y" + tooltip: "Y" + } + + Spacer { implicitWidth: StudioTheme.Values.controlGap } + + IconIndicator { + Layout.alignment: Qt.AlignLeft + icon: StudioTheme.Constants.closeCross + onClicked: layoutVector.remove() + } + + ExpandingSpacer {} + } + + SecondColumnLayout { + visible: vecSize > 2 + SpinBox { + id: boxZ + minimumValue: -9999999 + maximumValue: 9999999 + decimals: 2 + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } + + ControlLabel { + text: "Z" + tooltip: "Z" + visible: vecSize > 2 + } + + Spacer { implicitWidth: StudioTheme.Values.controlGap } + + SpinBox { + id: boxW + minimumValue: -9999999 + maximumValue: 9999999 + decimals: 2 + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + visible: vecSize > 3 + } + + Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } + + ControlLabel { + text: "W" + tooltip: "W" + visible: vecSize > 3 + } + + Spacer { implicitWidth: StudioTheme.Values.controlGap } + + Item { + height: 10 + implicitWidth: { + return StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + visible: vecSize === 2 // Placeholder for last spinbox + } + + Spacer { + implicitWidth: StudioTheme.Values.twoControlColumnGap + } + + ExpandingSpacer {} + } + } + } + + property Component readonlyEditor: Component { + id: readonlyEditor + SecondColumnLayout { + id: layoutReadonly + property var backendValue + property string propertyType + + signal remove + + PropertyLabel { + tooltip: layoutReadonly.propertyType + horizontalAlignment: Text.AlignLeft + leftPadding: StudioTheme.Values.actionIndicatorWidth + text: qsTr("No editor for type: ") + layoutReadonly.propertyType + + width: StudioTheme.Values.singleControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + Spacer { + implicitWidth: StudioTheme.Values.twoControlColumnGap + } + IconIndicator { + Layout.alignment: Qt.AlignLeft + icon: StudioTheme.Constants.closeCross + onClicked: layoutReadonly.remove() + } + + ExpandingSpacer {} + } + } + + Column { + width: parent.width + spacing: StudioTheme.Values.sectionRowSpacing + + Repeater { + id: repeater + model: root.propertiesModel + + property bool loadActive: true + onCountChanged: { + repeater.loadActive = false + repeater.loadActive = true + } + + SectionLayout { + DynamicPropertyRow { + id: propertyRow + model: root.propertiesModel + row: index + } + PropertyLabel { + text: propertyName + tooltip: propertyType + } + + Loader { + id: loader + asynchronous: true + active: repeater.loadActive + width: loader.item ? loader.item.width : 0 + height: loader.item ? loader.item.height : 0 + + sourceComponent: { + if (propertyType == "color") + return colorEditor + if (propertyType == "int") + return intEditor + if (propertyType == "real") + return realEditor + if (propertyType == "string") + return stringEditor + if (propertyType == "bool") + return boolEditor + if (propertyType == "url") + return urlEditor + if (propertyType == "alias") + return aliasEditor + if (propertyType == "variant") + return readonlyEditor + if (propertyType == "TextureInput") + return textureInputEditor + if (propertyType == "vector2d" || propertyType == "vector3d" || propertyType == "vector4d") + return vectorEditor + + return readonlyEditor + } + + onLoaded: { + loader.item.backendValue = propertyRow.backendValue + loader.item.propertyType = propertyType + if (sourceComponent == vectorEditor) { + let vecSize = 2 + if (propertyType == "vector3d") + vecSize = 3 + else if (propertyType == "vector4d") + vecSize = 4 + propertyRow.clearProxyBackendValues() + + for (let i = 0; i < vecSize; ++i) { + var newProxyValue = propertyRow.createProxyBackendValue() + loader.item.proxyValues.push(newProxyValue) + newProxyValue.valueChangedQml.connect(loader.item.updateExpression) + loader.item.spinBoxes[i].backendValue = newProxyValue + } + propertyRow.backendValue.expressionChanged.connect(loader.item.updateProxyValues) + loader.item.vecSize = vecSize + loader.item.updateProxyValues() + } else if (sourceComponent == aliasEditor) { + loader.item.lineEdit.text = propertyRow.backendValue.expression + loader.item.backendValue.expressionChanged.connect(loader.item.updateLineEditText) + } else if (sourceComponent == colorEditor) { + loader.item.initEditor() + } + } + + Connections { + target: loader.item + function onRemove() { + propertyRow.remove() + } + } + } + } + } + + SectionLayout { + PropertyLabel { + text: "" + tooltip: "" + } + + SecondColumnLayout { + Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth } + + StudioControls.AbstractButton { + + id: plusButton + buttonIcon: StudioTheme.Constants.plus + onClicked: { + cePopup.opened ? cePopup.close() : cePopup.open() + forceActiveFocus() + } + } + + ExpandingSpacer {} + } + } + } + + property T.Popup popup: T.Popup { + id: cePopup + + onOpened: { + cePopup.setPopupY() + cePopup.setMainScrollViewHeight() + } + + function setMainScrollViewHeight() { + if (Controller.mainScrollView == null) + return + + var mappedPos = plusButton.mapToItem(Controller.mainScrollView.contentItem, + cePopup.x, cePopup.y) + Controller.mainScrollView.temporaryHeight = mappedPos.y + cePopup.height + + StudioTheme.Values.colorEditorPopupMargin + } + + function setPopupY() { + if (Controller.mainScrollView == null) + return + + var mappedPos = plusButton.mapToItem(Controller.mainScrollView.contentItem, + plusButton.x, plusButton.y) + cePopup.y = Math.max(-mappedPos.y + StudioTheme.Values.colorEditorPopupMargin, + cePopup.__defaultY) + + textField.text = root.propertiesModel.newPropertyName() + } + + onClosed: Controller.mainScrollView.temporaryHeight = 0 + + property real __defaultX: (Controller.mainScrollView.contentItem.width + - StudioTheme.Values.colorEditorPopupWidth * 1.5) / 2 + + property real __defaultY: - StudioTheme.Values.colorEditorPopupPadding + - (StudioTheme.Values.colorEditorPopupSpacing * 2) + - StudioTheme.Values.defaultControlHeight + - StudioTheme.Values.colorEditorPopupLineHeight + + plusButton.width * 0.5 + + x: cePopup.__defaultX + y: cePopup.__defaultY + + width: 270 + height: 160 + + property int itemWidth: width / 2 + property int labelWidth: itemWidth - 32 + + padding: StudioTheme.Values.border + margins: 2 // If not defined margin will be -1 + + closePolicy: T.Popup.CloseOnPressOutside | T.Popup.CloseOnPressOutsideParent + + contentItem: Item { + id: content + Column { + spacing: StudioTheme.Values.sectionRowSpacing + RowLayout { + width: cePopup.width - 8 + PropertyLabel { + text: "Add New Property" + horizontalAlignment: Text.AlignLeft + leftPadding: 8 + width: cePopup.width - closeIndicator.width - 24 + } + IconIndicator { + id: closeIndicator + icon: StudioTheme.Constants.colorPopupClose + pixelSize: StudioTheme.Values.myIconFontSize * 1.4 + onClicked: cePopup.close() + Layout.alignment: Qt.AlignRight + } + } + RowLayout { + PropertyLabel { + id: textLabel + text: "Name" + width: cePopup.labelWidth + } + StudioControls.TextField { + id: textField + actionIndicator.visible: false + translationIndicatorVisible: false + width: cePopup.itemWidth + rightPadding: 8 + } + } + RowLayout { + PropertyLabel { + text: "Type" + width: cePopup.labelWidth + } + StudioControls.ComboBox { + id: comboBox + actionIndicator.visible: false + model: ["int", "real", "color", "string", "bool", "url", "alias", + "TextureInput", "vector2d", "vector3d", "vector4d"] + width: cePopup.itemWidth + } + } + Item { + width: 1 + height: StudioTheme.Values.sectionRowSpacing + } + + RowLayout { + width: cePopup.width + + StudioControls.AbstractButton { + id: acceptButton + + buttonIcon: qsTr("Add Property") + iconFont: StudioTheme.Constants.font + width: cePopup.width / 3 + + onClicked: { + root.propertiesModel.createProperty(textField.text, comboBox.currentText) + cePopup.close() + } + Layout.alignment: Qt.AlignHCenter + } + } + } + } + background: Rectangle { + color: StudioTheme.Values.themeControlBackground + border.color: StudioTheme.Values.themeInteraction + border.width: StudioTheme.Values.border + MouseArea { + // This area is to eat clicks so they do not go through the popup + anchors.fill: parent + acceptedButtons: Qt.AllButtons + } + } + + enter: Transition {} + exit: Transition {} + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ItemFilterComboBox.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ItemFilterComboBox.qml index c6219609862..ba0d8376f7a 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ItemFilterComboBox.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ItemFilterComboBox.qml @@ -57,7 +57,7 @@ HelperWidgets.ComboBox { comboBox.currentIndex = comboBox.find(text) - if (text === "") { + if (text === "" || text === "null") { comboBox.currentIndex = 0 comboBox.editText = comboBox.defaultItem } else { diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SpinBox.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SpinBox.qml index 425a8451199..ba059e9e4f1 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SpinBox.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SpinBox.qml @@ -40,6 +40,10 @@ Item { } } + onBackendValueChanged: { + spinBox.enabled = backendValue === undefined ? false : !isBlocked(backendValue.name) + } + StudioControls.RealSpinBox { id: spinBox diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir index 9c1c873ceb8..5a18d24846b 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir @@ -20,6 +20,7 @@ ComponentSection 2.0 ComponentSection.qml ControlLabel 2.0 ControlLabel.qml singleton Controller 2.0 Controller.qml DoubleSpinBox 2.0 DoubleSpinBox.qml +DynamicPropertiesSection 2.0 DynamicPropertiesSection.qml EditableListView 2.0 EditableListView.qml ExpandingSpacer 2.0 ExpandingSpacer.qml ExpressionTextField 2.0 ExpressionTextField.qml diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/common/app.qmlproject.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/common/app.qmlproject.tpl index 2dc38e566a2..206d8272da6 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/common/app.qmlproject.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/common/app.qmlproject.tpl @@ -20,13 +20,17 @@ Project { directory: "content" } - JavaScriptFiles { + JavaScriptFiles { directory: "imports" } ImageFiles { directory: "content" } + + ImageFiles { + directory: "asset_imports" + } Files { filter: "*.conf" @@ -95,7 +99,7 @@ Project { /* Required for deployment */ targetDirectory: "/opt/%{ProjectName}" - qdsVersion: "3.5" + qdsVersion: "3.7" quickVersion: "%{QtQuickVersion}" diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 748a96a4922..bd626092911 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -372,6 +372,7 @@ extend_qtc_plugin(QmlDesigner fileresourcesmodel.cpp fileresourcesmodel.h itemfiltermodel.cpp itemfiltermodel.h gradientmodel.cpp gradientmodel.h + dynamicpropertiesproxymodel.cpp dynamicpropertiesproxymodel.h gradientpresetcustomlistmodel.cpp gradientpresetcustomlistmodel.h gradientpresetdefaultlistmodel.cpp gradientpresetdefaultlistmodel.h gradientpresetitem.cpp gradientpresetitem.h @@ -393,6 +394,7 @@ extend_qtc_plugin(QmlDesigner SOURCES_PREFIX components/materialeditor SOURCES materialeditorcontextobject.cpp materialeditorcontextobject.h + materialeditordynamicpropertiesproxymodel.cpp materialeditordynamicpropertiesproxymodel.h materialeditorqmlbackend.cpp materialeditorqmlbackend.h materialeditortransaction.cpp materialeditortransaction.h materialeditorview.cpp materialeditorview.h @@ -405,6 +407,7 @@ extend_qtc_plugin(QmlDesigner materialbrowserview.cpp materialbrowserview.h materialbrowserwidget.cpp materialbrowserwidget.h materialbrowsermodel.cpp materialbrowsermodel.h + bundleimporter.cpp bundleimporter.h ) extend_qtc_plugin(QmlDesigner @@ -560,6 +563,7 @@ extend_qtc_plugin(QmlDesigner connectionviewwidget.cpp connectionviewwidget.h connectionviewwidget.ui delegates.cpp delegates.h dynamicpropertiesmodel.cpp dynamicpropertiesmodel.h + selectiondynamicpropertiesproxymodel.cpp selectiondynamicpropertiesproxymodel.h ) extend_qtc_plugin(QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp b/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp index d309fe69997..6250b6a25fc 100644 --- a/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp +++ b/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp @@ -60,41 +60,52 @@ bool Navigation2dFilter::gestureEvent(QGestureEvent *event) bool Navigation2dFilter::wheelEvent(QWheelEvent *event) { - if (event->source() == Qt::MouseEventSynthesizedBySystem) { - if (event->modifiers().testFlag(Qt::ControlModifier)) { - if (QPointF delta = event->pixelDelta(); !delta.isNull()) { - double dist = std::abs(delta.x()) > std::abs(delta.y()) ? -delta.x() : delta.y(); - emit zoomChanged(dist/200.0, event->position()); - event->accept(); - return true; - } - } else { + if (!event->modifiers().testFlag(Qt::ControlModifier)) { + if (event->source() == Qt::MouseEventSynthesizedBySystem) { emit panChanged(QPointF(event->pixelDelta())); event->accept(); return true; } - } else if (event->source() == Qt::MouseEventNotSynthesized) { + return false; + } - auto zoomInSignal = QMetaMethod::fromSignal(&Navigation2dFilter::zoomIn); - bool zoomInConnected = QObject::isSignalConnected(zoomInSignal); + auto zoomChangedSignal = QMetaMethod::fromSignal(&Navigation2dFilter::zoomChanged); + bool zoomChangedConnected = QObject::isSignalConnected(zoomChangedSignal); - auto zoomOutSignal = QMetaMethod::fromSignal(&Navigation2dFilter::zoomOut); - bool zoomOutConnected = QObject::isSignalConnected(zoomOutSignal); + if (zoomChangedConnected) { + if (QPointF delta = event->pixelDelta(); !delta.isNull()) { + double dist = std::abs(delta.x()) > std::abs(delta.y()) ? -delta.x() : delta.y(); + emit zoomChanged(dist/200.0, event->position()); + event->accept(); + return true; + } else if (QPointF delta = event->angleDelta(); !delta.isNull()) { + double dist = std::abs(delta.x()) > std::abs(delta.y()) ? -delta.x() : delta.y(); + dist = dist / (8*15); + emit zoomChanged(dist/200.0, event->position()); + event->accept(); + return true; + } + return false; + } - if (zoomInConnected && zoomOutConnected) { - if (event->modifiers().testFlag(Qt::ControlModifier)) { - if (QPointF angle = event->angleDelta(); !angle.isNull()) { - double delta = std::abs(angle.x()) > std::abs(angle.y()) ? angle.x() : angle.y(); - if (delta > 0) - emit zoomIn(); - else - emit zoomOut(); - event->accept(); - return true; - } - } + auto zoomInSignal = QMetaMethod::fromSignal(&Navigation2dFilter::zoomIn); + bool zoomInConnected = QObject::isSignalConnected(zoomInSignal); + + auto zoomOutSignal = QMetaMethod::fromSignal(&Navigation2dFilter::zoomOut); + bool zoomOutConnected = QObject::isSignalConnected(zoomOutSignal); + + if (zoomInConnected && zoomOutConnected) { + if (QPointF angle = event->angleDelta(); !angle.isNull()) { + double delta = std::abs(angle.x()) > std::abs(angle.y()) ? angle.x() : angle.y(); + if (delta > 0) + emit zoomIn(); + else + emit zoomOut(); + event->accept(); + return true; } } + return false; } diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp index 0d0f6e28457..b9eced9fbc1 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp @@ -28,7 +28,7 @@ ConnectionView::ConnectionView(QObject *parent) : AbstractView(parent), m_connectionViewWidget(new ConnectionViewWidget()), m_connectionModel(new ConnectionModel(this)), m_bindingModel(new BindingModel(this)), - m_dynamicPropertiesModel(new DynamicPropertiesModel(this)), + m_dynamicPropertiesModel(new DynamicPropertiesModel(false, this)), m_backendModel(new BackendModel(this)) { connectionViewWidget()->setBindingModel(m_bindingModel); @@ -43,7 +43,7 @@ void ConnectionView::modelAttached(Model *model) { AbstractView::modelAttached(model); bindingModel()->selectionChanged(QList()); - dynamicPropertiesModel()->selectionChanged(QList()); + dynamicPropertiesModel()->reset(); connectionModel()->resetModel(); connectionViewWidget()->resetItemViews(); backendModel()->resetModel(); @@ -53,7 +53,7 @@ void ConnectionView::modelAboutToBeDetached(Model *model) { AbstractView::modelAboutToBeDetached(model); bindingModel()->selectionChanged(QList()); - dynamicPropertiesModel()->selectionChanged(QList()); + dynamicPropertiesModel()->reset(); connectionModel()->resetModel(); connectionViewWidget()->resetItemViews(); } @@ -99,7 +99,7 @@ void ConnectionView::propertiesAboutToBeRemoved(const QList &p bindingModel()->bindingRemoved(property.toBindingProperty()); dynamicPropertiesModel()->bindingRemoved(property.toBindingProperty()); } else if (property.isVariantProperty()) { - //### dynamicPropertiesModel->bindingRemoved(property.toVariantProperty()); + dynamicPropertiesModel()->variantRemoved(property.toVariantProperty()); } else if (property.isSignalHandlerProperty()) { connectionModel()->removeRowFromTable(property.toSignalHandlerProperty()); } @@ -145,7 +145,7 @@ void ConnectionView::selectedNodesChanged(const QList & selectedNodeL const QList & /*lastSelectedNodeList*/) { bindingModel()->selectionChanged(selectedNodeList); - dynamicPropertiesModel()->selectionChanged(selectedNodeList); + dynamicPropertiesModel()->reset(); connectionViewWidget()->bindingTableViewSelectionChanged(QModelIndex(), QModelIndex()); connectionViewWidget()->dynamicPropertiesTableViewSelectionChanged(QModelIndex(), QModelIndex()); @@ -184,6 +184,11 @@ void ConnectionView::importsChanged(const QList & /*addedImports*/, cons backendModel()->resetModel(); } +void ConnectionView::currentStateChanged(const ModelNode &node) +{ + dynamicPropertiesModel()->reset(); +} + WidgetInfo ConnectionView::widgetInfo() { return createWidgetInfo(m_connectionViewWidget.data(), diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionview.h b/src/plugins/qmldesigner/components/connectioneditor/connectionview.h index 0039fb8aac0..336fdfdc45b 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionview.h +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionview.h @@ -54,6 +54,8 @@ public: void importsChanged(const QList &addedImports, const QList &removedImports) override; + void currentStateChanged(const ModelNode &node) override; + WidgetInfo widgetInfo() override; bool hasWidget() const override; bool isWidgetEnabled(); diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp index ec037deaf03..777f8e587f9 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp @@ -355,8 +355,8 @@ void ConnectionViewWidget::invalidateButtonStatus() } else if (currentTab() == DynamicPropertiesTab) { emit setEnabledRemoveButton(ui->dynamicPropertiesView->selectionModel()->hasSelection()); auto dynamicPropertiesModel = qobject_cast(ui->dynamicPropertiesView->model()); - emit setEnabledAddButton(dynamicPropertiesModel->connectionView()->model() && - dynamicPropertiesModel->connectionView()->selectedModelNodes().count() == 1); + emit setEnabledAddButton(dynamicPropertiesModel->view()->model() && + dynamicPropertiesModel->selectedNodes().count() == 1); } else if (currentTab() == BackendTab) { emit setEnabledAddButton(true); emit setEnabledRemoveButton(ui->backendView->selectionModel()->hasSelection()); @@ -526,9 +526,9 @@ void ConnectionViewWidget::editorForDynamic() QString newValue = m_dynamicEditor->bindingValue().trimmed(); if (m_dynamicIndex.isValid()) { - if (propertiesModel->connectionView()->isWidgetEnabled() + if (qobject_cast(propertiesModel->view())->isWidgetEnabled() && (propertiesModel->rowCount() > m_dynamicIndex.row())) { - propertiesModel->connectionView()->executeInTransaction( + propertiesModel->view()->executeInTransaction( "ConnectionView::setBinding", [this, propertiesModel, newValue]() { AbstractProperty abProp = propertiesModel->abstractPropertyForRow( m_dynamicIndex.row()); diff --git a/src/plugins/qmldesigner/components/connectioneditor/delegates.cpp b/src/plugins/qmldesigner/components/connectioneditor/delegates.cpp index a7e3290c8fd..ee3981c4965 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/delegates.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/delegates.cpp @@ -187,11 +187,11 @@ QWidget *DynamicPropertiesDelegate::createEditor(QWidget *parent, const QStyleOp return widget; } - if (!model->connectionView()) { + if (!model->view()) { qWarning() << "BindingDelegate::createEditor no connection view"; return widget; } - model->connectionView()->allModelNodes(); + model->view()->allModelNodes(); switch (index.column()) { case DynamicPropertiesModel::TargetModelNodeRow: { @@ -209,7 +209,7 @@ QWidget *DynamicPropertiesDelegate::createEditor(QWidget *parent, const QStyleOp }); dynamicPropertiesComboBox->addItem(QLatin1String("alias")); - //dynamicPropertiesComboBox->addItem(QLatin1String("Item")); + dynamicPropertiesComboBox->addItem(QLatin1String("Item")); dynamicPropertiesComboBox->addItem(QLatin1String("real")); dynamicPropertiesComboBox->addItem(QLatin1String("int")); dynamicPropertiesComboBox->addItem(QLatin1String("string")); @@ -217,6 +217,10 @@ QWidget *DynamicPropertiesDelegate::createEditor(QWidget *parent, const QStyleOp dynamicPropertiesComboBox->addItem(QLatin1String("url")); dynamicPropertiesComboBox->addItem(QLatin1String("color")); dynamicPropertiesComboBox->addItem(QLatin1String("variant")); + dynamicPropertiesComboBox->addItem(QLatin1String("TextureInput")); + dynamicPropertiesComboBox->addItem(QLatin1String("vector2d")); + dynamicPropertiesComboBox->addItem(QLatin1String("vector3d")); + dynamicPropertiesComboBox->addItem(QLatin1String("vector4d")); return dynamicPropertiesComboBox; }; case DynamicPropertiesModel::PropertyValueRow: { diff --git a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp index 0701fd58ff0..c5092d13e83 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -69,8 +70,18 @@ QVariant convertVariantForTypeName(const QVariant &variant, const QmlDesigner::T } else { returnValue = QColor(Qt::black); } + } else if (typeName == "vector2d") { + returnValue = "Qt.vector2d(0, 0)"; + } else if (typeName == "vector3d") { + returnValue = "Qt.vector3d(0, 0, 0)"; + } else if (typeName == "vector4d") { + returnValue = "Qt.vector4d(0, 0, 0 ,0)"; + } else if (typeName == "TextureInput") { + returnValue = "null"; + } else if (typeName == "alias") { + returnValue = "null"; } else if (typeName == "Item") { - returnValue = 0; + returnValue = "null"; } return returnValue; @@ -86,7 +97,7 @@ QmlDesigner::PropertyName DynamicPropertiesModel::unusedProperty(const QmlDesign { QmlDesigner::PropertyName propertyName = "property"; int i = 0; - if (modelNode.metaInfo().isValid()) { + if (modelNode.isValid() && modelNode.metaInfo().isValid()) { while (true) { const QmlDesigner::PropertyName currentPropertyName = propertyName + QString::number(i).toLatin1(); if (!modelNode.hasProperty(currentPropertyName) && !modelNode.metaInfo().hasProperty(currentPropertyName)) @@ -98,9 +109,18 @@ QmlDesigner::PropertyName DynamicPropertiesModel::unusedProperty(const QmlDesign return propertyName; } -DynamicPropertiesModel::DynamicPropertiesModel(ConnectionView *parent) +bool DynamicPropertiesModel::isValueType(const TypeName &type) +{ + // "variant" is considered value type as it is initialized as one. + // This may need to change if we provide any kind of proper editor for it. + static const QSet valueTypes {"int", "real", "color", "string", "bool", "url", "variant"}; + return valueTypes.contains(type); +} + +DynamicPropertiesModel::DynamicPropertiesModel(bool explicitSelection, AbstractView *parent) : QStandardItemModel(parent) - , m_connectionView(parent) + , m_view(parent) + , m_explicitSelection(explicitSelection) { connect(this, &QStandardItemModel::dataChanged, this, &DynamicPropertiesModel::handleDataChanged); } @@ -112,8 +132,9 @@ void DynamicPropertiesModel::resetModel() setHorizontalHeaderLabels( QStringList({tr("Item"), tr("Property"), tr("Property Type"), tr("Property Value")})); - if (connectionView()->isAttached()) { - for (const ModelNode &modelNode : connectionView()->selectedModelNodes()) + if (m_view->isAttached()) { + const auto nodes = selectedNodes(); + for (const ModelNode &modelNode : nodes) addModelNode(modelNode); } @@ -125,8 +146,8 @@ void DynamicPropertiesModel::resetModel() //Value copying is optional BindingProperty DynamicPropertiesModel::replaceVariantWithBinding(const PropertyName &name, bool copyValue) { - if (connectionView()->selectedModelNodes().count() == 1) { - const ModelNode modelNode = connectionView()->selectedModelNodes().constFirst(); + if (selectedNodes().count() == 1) { + const ModelNode modelNode = selectedNodes().constFirst(); if (modelNode.isValid()) { if (modelNode.hasVariantProperty(name)) { try { @@ -160,8 +181,8 @@ BindingProperty DynamicPropertiesModel::replaceVariantWithBinding(const Property //If it's a BindingProperty, then replaces it with empty VariantProperty void DynamicPropertiesModel::resetProperty(const PropertyName &name) { - if (connectionView()->selectedModelNodes().count() == 1) { - const ModelNode modelNode = connectionView()->selectedModelNodes().constFirst(); + if (selectedNodes().count() == 1) { + const ModelNode modelNode = selectedNodes().constFirst(); if (modelNode.isValid()) { if (modelNode.hasProperty(name)) { try { @@ -169,11 +190,10 @@ void DynamicPropertiesModel::resetProperty(const PropertyName &name) if (abProp.isVariantProperty()) { VariantProperty property = abProp.toVariantProperty(); - QVariant newValue = convertVariantForTypeName(QVariant("none.none"), property.dynamicTypeName()); + QVariant newValue = convertVariantForTypeName({}, property.dynamicTypeName()); property.setDynamicTypeNameAndValue(property.dynamicTypeName(), newValue); - } - else if (abProp.isBindingProperty()) { + } else if (abProp.isBindingProperty()) { BindingProperty property = abProp.toBindingProperty(); TypeName oldType = property.dynamicTypeName(); @@ -181,9 +201,8 @@ void DynamicPropertiesModel::resetProperty(const PropertyName &name) modelNode.removeProperty(name); VariantProperty newProperty = modelNode.variantProperty(name); - QVariant newValue = convertVariantForTypeName(QVariant("none.none"), oldType); - newProperty.setDynamicTypeNameAndValue(oldType, - newValue); + QVariant newValue = convertVariantForTypeName({}, oldType); + newProperty.setDynamicTypeNameAndValue(oldType, newValue); } } catch (RewritingException &e) { @@ -205,8 +224,8 @@ void DynamicPropertiesModel::bindingPropertyChanged(const BindingProperty &bindi m_handleDataChanged = false; - QList selectedNodes = connectionView()->selectedModelNodes(); - if (!selectedNodes.contains(bindingProperty.parentModelNode())) + const QList nodes = selectedNodes(); + if (!nodes.contains(bindingProperty.parentModelNode())) return; if (!m_lock) { int rowNumber = findRowForBindingProperty(bindingProperty); @@ -228,17 +247,16 @@ void DynamicPropertiesModel::variantPropertyChanged(const VariantProperty &varia m_handleDataChanged = false; - QList selectedNodes = connectionView()->selectedModelNodes(); - if (!selectedNodes.contains(variantProperty.parentModelNode())) + const QList nodes = selectedNodes(); + if (!nodes.contains(variantProperty.parentModelNode())) return; if (!m_lock) { int rowNumber = findRowForVariantProperty(variantProperty); - if (rowNumber == -1) { + if (rowNumber == -1) addVariantProperty(variantProperty); - } else { + else updateVariantProperty(rowNumber); - } } m_handleDataChanged = true; @@ -248,8 +266,8 @@ void DynamicPropertiesModel::bindingRemoved(const BindingProperty &bindingProper { m_handleDataChanged = false; - QList selectedNodes = connectionView()->selectedModelNodes(); - if (!selectedNodes.contains(bindingProperty.parentModelNode())) + const QList nodes = selectedNodes(); + if (!nodes.contains(bindingProperty.parentModelNode())) return; if (!m_lock) { int rowNumber = findRowForBindingProperty(bindingProperty); @@ -259,16 +277,36 @@ void DynamicPropertiesModel::bindingRemoved(const BindingProperty &bindingProper m_handleDataChanged = true; } -void DynamicPropertiesModel::selectionChanged([[maybe_unused]] const QList &selectedNodes) +void DynamicPropertiesModel::variantRemoved(const VariantProperty &variantProperty) +{ + m_handleDataChanged = false; + + const QList nodes = selectedNodes(); + if (!nodes.contains(variantProperty.parentModelNode())) + return; + if (!m_lock) { + int rowNumber = findRowForVariantProperty(variantProperty); + removeRow(rowNumber); + } + + m_handleDataChanged = true; +} + +void DynamicPropertiesModel::reset() { m_handleDataChanged = false; resetModel(); m_handleDataChanged = true; } -ConnectionView *DynamicPropertiesModel::connectionView() const +void DynamicPropertiesModel::setSelectedNode(const ModelNode &node) { - return m_connectionView; + QTC_ASSERT(m_explicitSelection, return); + QTC_ASSERT(node.isValid(), return); + + m_selectedNodes.clear(); + m_selectedNodes.append(node); + reset(); } AbstractProperty DynamicPropertiesModel::abstractPropertyForRow(int rowNumber) const @@ -276,10 +314,10 @@ AbstractProperty DynamicPropertiesModel::abstractPropertyForRow(int rowNumber) c const int internalId = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 1).toInt(); const QString targetPropertyName = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 2).toString(); - if (!connectionView()->isAttached()) + if (!m_view->isAttached()) return AbstractProperty(); - ModelNode modelNode = connectionView()->modelNodeForInternalId(internalId); + ModelNode modelNode = m_view->modelNodeForInternalId(internalId); if (modelNode.isValid()) return modelNode.property(targetPropertyName.toUtf8()); @@ -292,7 +330,7 @@ BindingProperty DynamicPropertiesModel::bindingPropertyForRow(int rowNumber) con const int internalId = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 1).toInt(); const QString targetPropertyName = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 2).toString(); - ModelNode modelNode = connectionView()->modelNodeForInternalId(internalId); + ModelNode modelNode = m_view->modelNodeForInternalId(internalId); if (modelNode.isValid()) return modelNode.bindingProperty(targetPropertyName.toUtf8()); @@ -305,7 +343,7 @@ VariantProperty DynamicPropertiesModel::variantPropertyForRow(int rowNumber) con const int internalId = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 1).toInt(); const QString targetPropertyName = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 2).toString(); - ModelNode modelNode = connectionView()->modelNodeForInternalId(internalId); + ModelNode modelNode = m_view->modelNodeForInternalId(internalId); if (modelNode.isValid()) return modelNode.variantProperty(targetPropertyName.toUtf8()); @@ -341,8 +379,8 @@ void DynamicPropertiesModel::addDynamicPropertyForCurrentNode() { QmlDesignerPlugin::emitUsageStatistics(Constants::EVENT_PROPERTY_ADDED); - if (connectionView()->selectedModelNodes().count() == 1) { - const ModelNode modelNode = connectionView()->selectedModelNodes().constFirst(); + if (selectedNodes().count() == 1) { + const ModelNode modelNode = selectedNodes().constFirst(); if (modelNode.isValid()) { try { modelNode.variantProperty(unusedProperty(modelNode)).setDynamicTypeNameAndValue("string", QLatin1String("none.none")); @@ -396,16 +434,14 @@ QStringList DynamicPropertiesModel::possibleSourceProperties(const BindingProper void DynamicPropertiesModel::deleteDynamicPropertyByRow(int rowNumber) { - connectionView()->executeInTransaction("DynamicPropertiesModel::deleteDynamicPropertyByRow", [this, rowNumber]() { + m_view->executeInTransaction("DynamicPropertiesModel::deleteDynamicPropertyByRow", [this, rowNumber]() { BindingProperty bindingProperty = bindingPropertyForRow(rowNumber); if (bindingProperty.isValid()) { bindingProperty.parentModelNode().removeProperty(bindingProperty.name()); - } - - VariantProperty variantProperty = variantPropertyForRow(rowNumber); - - if (variantProperty.isValid()) { - variantProperty.parentModelNode().removeProperty(variantProperty.name()); + } else { + VariantProperty variantProperty = variantPropertyForRow(rowNumber); + if (variantProperty.isValid()) + variantProperty.parentModelNode().removeProperty(variantProperty.name()); } }); @@ -431,7 +467,6 @@ void DynamicPropertiesModel::addProperty(const QVariant &propertyValue, items.append(idItem); items.append(propertyNameItem); - propertyTypeItem = new QStandardItem(propertyType); items.append(propertyTypeItem); @@ -487,6 +522,9 @@ void DynamicPropertiesModel::updateVariantProperty(int rowNumber) void DynamicPropertiesModel::addModelNode(const ModelNode &modelNode) { + if (!modelNode.isValid()) + return; + const QList bindingProperties = modelNode.bindingProperties(); for (const BindingProperty &bindingProperty : bindingProperties) { if (bindingProperty.isDynamic()) @@ -507,7 +545,7 @@ void DynamicPropertiesModel::updateValue(int row) if (bindingProperty.isBindingProperty()) { const QString expression = data(index(row, PropertyValueRow)).toString(); - RewriterTransaction transaction = connectionView()->beginRewriterTransaction(QByteArrayLiteral("DynamicPropertiesModel::updateValue")); + RewriterTransaction transaction = m_view->beginRewriterTransaction(QByteArrayLiteral("DynamicPropertiesModel::updateValue")); try { bindingProperty.setDynamicTypeNameAndExpression(bindingProperty.dynamicTypeName(), expression); transaction.commit(); //committing in the try block @@ -523,7 +561,7 @@ void DynamicPropertiesModel::updateValue(int row) if (variantProperty.isVariantProperty()) { const QVariant value = data(index(row, PropertyValueRow)); - RewriterTransaction transaction = connectionView()->beginRewriterTransaction(QByteArrayLiteral("DynamicPropertiesModel::updateValue")); + RewriterTransaction transaction = m_view->beginRewriterTransaction(QByteArrayLiteral("DynamicPropertiesModel::updateValue")); try { variantProperty.setDynamicTypeNameAndValue(variantProperty.dynamicTypeName(), value); transaction.commit(); //committing in the try block @@ -547,7 +585,7 @@ void DynamicPropertiesModel::updatePropertyName(int rowNumber) ModelNode targetNode = bindingProperty.parentModelNode(); if (bindingProperty.isBindingProperty()) { - connectionView()->executeInTransaction("DynamicPropertiesModel::updatePropertyName", [bindingProperty, newName, &targetNode](){ + m_view->executeInTransaction("DynamicPropertiesModel::updatePropertyName", [bindingProperty, newName, &targetNode](){ const QString expression = bindingProperty.expression(); const PropertyName dynamicPropertyType = bindingProperty.dynamicTypeName(); @@ -566,7 +604,7 @@ void DynamicPropertiesModel::updatePropertyName(int rowNumber) const PropertyName dynamicPropertyType = variantProperty.dynamicTypeName(); ModelNode targetNode = variantProperty.parentModelNode(); - connectionView()->executeInTransaction("DynamicPropertiesModel::updatePropertyName", [=](){ + m_view->executeInTransaction("DynamicPropertiesModel::updatePropertyName", [=](){ targetNode.variantProperty(newName).setDynamicTypeNameAndValue(dynamicPropertyType, value); targetNode.removeProperty(variantProperty.name()); }); @@ -592,7 +630,7 @@ void DynamicPropertiesModel::updatePropertyType(int rowNumber) const PropertyName propertyName = bindingProperty.name(); ModelNode targetNode = bindingProperty.parentModelNode(); - connectionView()->executeInTransaction("DynamicPropertiesModel::updatePropertyType", [=](){ + m_view->executeInTransaction("DynamicPropertiesModel::updatePropertyType", [=](){ targetNode.removeProperty(bindingProperty.name()); targetNode.bindingProperty(propertyName).setDynamicTypeNameAndExpression(newType, expression); }); @@ -608,12 +646,14 @@ void DynamicPropertiesModel::updatePropertyType(int rowNumber) ModelNode targetNode = variantProperty.parentModelNode(); const PropertyName propertyName = variantProperty.name(); - connectionView()->executeInTransaction("DynamicPropertiesModel::updatePropertyType", [=](){ + m_view->executeInTransaction("DynamicPropertiesModel::updatePropertyType", [=](){ targetNode.removeProperty(variantProperty.name()); - if (newType == "alias") { //alias properties have to be bindings - targetNode.bindingProperty(propertyName).setDynamicTypeNameAndExpression(newType, QLatin1String("none.none")); + if (!isValueType(newType)) { + targetNode.bindingProperty(propertyName).setDynamicTypeNameAndExpression( + newType, convertVariantForTypeName({}, newType).toString()); } else { - targetNode.variantProperty(propertyName).setDynamicTypeNameAndValue(newType, convertVariantForTypeName(value, newType)); + targetNode.variantProperty(propertyName).setDynamicTypeNameAndValue( + newType, convertVariantForTypeName(value, newType)); } }); @@ -632,7 +672,7 @@ ModelNode DynamicPropertiesModel::getNodeByIdOrParent(const QString &id, const M ModelNode modelNode; if (id != QLatin1String("parent")) { - modelNode = connectionView()->modelNodeForId(id); + modelNode = m_view->modelNodeForId(id); } else { if (targetNode.hasParentProperty()) { modelNode = targetNode.parentProperty().parentModelNode(); @@ -752,6 +792,24 @@ void DynamicPropertiesModel::handleException() resetModel(); } +const QList DynamicPropertiesModel::selectedNodes() const +{ + // If selected nodes are explicitly set, return those. + // Otherwise return actual selected nodes of the model. + if (m_explicitSelection) + return m_selectedNodes; + else + return m_view->selectedModelNodes(); +} + +const ModelNode DynamicPropertiesModel::singleSelectedNode() const +{ + if (m_explicitSelection) + return m_selectedNodes.first(); + else + return m_view->singleSelectedModelNode(); +} + } // namespace Internal } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h index f5c9d38a9bb..f86eb73357f 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h +++ b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h @@ -11,9 +11,9 @@ namespace QmlDesigner { -namespace Internal { +class AbstractView; -class ConnectionView; +namespace Internal { class DynamicPropertiesModel : public QStandardItemModel { @@ -26,13 +26,17 @@ public: PropertyTypeRow = 2, PropertyValueRow = 3 }; - DynamicPropertiesModel(ConnectionView *parent = nullptr); + DynamicPropertiesModel(bool explicitSelection, AbstractView *parent); void bindingPropertyChanged(const BindingProperty &bindingProperty); void variantPropertyChanged(const VariantProperty &variantProperty); void bindingRemoved(const BindingProperty &bindingProperty); - void selectionChanged(const QList &selectedNodes); + void variantRemoved(const VariantProperty &variantProperty); + void reset(); + void setSelectedNode(const ModelNode &node); + const QList selectedNodes() const; + const ModelNode singleSelectedNode() const; - ConnectionView *connectionView() const; + AbstractView *view() const { return m_view; } AbstractProperty abstractPropertyForRow(int rowNumber) const; BindingProperty bindingPropertyForRow(int rowNumber) const; VariantProperty variantPropertyForRow(int rowNumber) const; @@ -49,6 +53,8 @@ public: QmlDesigner::PropertyName unusedProperty(const QmlDesigner::ModelNode &modelNode); + static bool isValueType(const TypeName &type); + protected: void addProperty(const QVariant &propertyValue, const QString &propertyType, @@ -75,12 +81,12 @@ private: void handleDataChanged(const QModelIndex &topLeft, const QModelIndex& bottomRight); void handleException(); -private: - ConnectionView *m_connectionView; + AbstractView *m_view = nullptr; bool m_lock = false; bool m_handleDataChanged = false; QString m_exceptionError; - + QList m_selectedNodes; + bool m_explicitSelection = false; }; } // namespace Internal diff --git a/src/plugins/qmldesigner/components/connectioneditor/selectiondynamicpropertiesproxymodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/selectiondynamicpropertiesproxymodel.cpp new file mode 100644 index 00000000000..00732479e81 --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/selectiondynamicpropertiesproxymodel.cpp @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "selectiondynamicpropertiesproxymodel.h" + +#include +#include + +using namespace QmlDesigner::Internal; + +SelectionDynamicPropertiesProxyModel::SelectionDynamicPropertiesProxyModel(QObject *parent) + : DynamicPropertiesProxyModel(parent) +{ + initModel(ConnectionView::instance()->dynamicPropertiesModel()); +} + +void SelectionDynamicPropertiesProxyModel::registerDeclarativeType() +{ + DynamicPropertiesProxyModel::registerDeclarativeType(); + qmlRegisterType("HelperWidgets", 2, 0, "SelectionDynamicPropertiesModel"); +} diff --git a/src/plugins/qmldesigner/components/connectioneditor/selectiondynamicpropertiesproxymodel.h b/src/plugins/qmldesigner/components/connectioneditor/selectiondynamicpropertiesproxymodel.h new file mode 100644 index 00000000000..124846dc860 --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/selectiondynamicpropertiesproxymodel.h @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +class SelectionDynamicPropertiesProxyModel : public DynamicPropertiesProxyModel +{ + Q_OBJECT +public: + explicit SelectionDynamicPropertiesProxyModel(QObject *parent = nullptr); + + static void registerDeclarativeType(); +}; diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp index 35e7c6334e7..465ab875683 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp @@ -219,7 +219,7 @@ void Edit3DView::handleEntriesChanged() const QList itemLibEntries = model()->metaInfo().itemLibraryInfo()->entries(); for (const ItemLibraryEntry &entry : itemLibEntries) { - if (entry.typeName() == "QtQuick3D.Model") { + if (entry.typeName() == "QtQuick3D.Model" && entry.name() != "Empty") { entriesMap[primitives].append(entry); } else if (entry.typeName() == "QtQuick3D.DirectionalLight" || entry.typeName() == "QtQuick3D.PointLight" @@ -264,28 +264,31 @@ void Edit3DView::customNotification([[maybe_unused]] const AbstractView *view, } /** - * @brief get model at position from puppet process + * @brief Get node at position from puppet process * * Response from puppet process for the model at requested position * - * @param modelNode 3D model picked at the requested position, invalid node if no model exists + * @param modelNode Node picked at the requested position or invalid node if nothing could be picked */ -void Edit3DView::modelAtPosReady(const ModelNode &modelNode) +void Edit3DView::nodeAtPosReady(const ModelNode &modelNode) { - if (m_modelAtPosReqType == ModelAtPosReqType::ContextMenu) { + if (m_nodeAtPosReqType == NodeAtPosReqType::ContextMenu) { // Make sure right-clicked item is selected. Due to a bug in puppet side right-clicking an item // while the context-menu is shown doesn't select the item. if (modelNode.isValid() && !modelNode.isSelected()) setSelectedModelNode(modelNode); m_edit3DWidget->showContextMenu(m_contextMenuPos, modelNode); - } else if (m_modelAtPosReqType == ModelAtPosReqType::MaterialDrop) { - if (m_droppedMaterial.isValid() && modelNode.isValid()) { + } else if (m_nodeAtPosReqType == NodeAtPosReqType::MaterialDrop) { + // TODO: this is from 8.0 branch that doesn't apply anymore: + // const bool isModel = modelNode.isSubclassOf("QtQuick3D.Model"); + const bool isModel = false; + if (m_droppedMaterial.isValid() && modelNode.isValid() && isModel) { executeInTransaction(__FUNCTION__, [&] { assignMaterialTo3dModel(modelNode, m_droppedMaterial); }); } } - m_modelAtPosReqType = ModelAtPosReqType::None; + m_nodeAtPosReqType = NodeAtPosReqType::None; } void Edit3DView::sendInputEvent(QInputEvent *e) const @@ -671,18 +674,18 @@ void Edit3DView::addQuick3DImport() } // This method is called upon right-clicking the view to prepare for context-menu creation. The actual -// context menu is created when modelAtPosReady() is received from puppet +// context menu is created when nodeAtPosReady() is received from puppet void Edit3DView::startContextMenu(const QPoint &pos) { m_contextMenuPos = pos; - m_modelAtPosReqType = ModelAtPosReqType::ContextMenu; + m_nodeAtPosReqType = NodeAtPosReqType::ContextMenu; } void Edit3DView::dropMaterial(const ModelNode &matNode, const QPointF &pos) { - m_modelAtPosReqType = ModelAtPosReqType::MaterialDrop; + m_nodeAtPosReqType = NodeAtPosReqType::MaterialDrop; m_droppedMaterial = matNode; - QmlDesignerPlugin::instance()->viewManager().nodeInstanceView()->view3DAction({View3DActionCommand::GetModelAtPos, pos}); + QmlDesignerPlugin::instance()->viewManager().nodeInstanceView()->view3DAction({View3DActionCommand::GetNodeAtPos, pos}); } } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.h b/src/plugins/qmldesigner/components/edit3d/edit3dview.h index 6c78c13ff26..3df7b263afc 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.h @@ -41,7 +41,7 @@ public: void modelAboutToBeDetached(Model *model) override; void importsChanged(const QList &addedImports, const QList &removedImports) override; void customNotification(const AbstractView *view, const QString &identifier, const QList &nodeList, const QList &data) override; - void modelAtPosReady(const ModelNode &modelNode) override; + void nodeAtPosReady(const ModelNode &modelNode) override; void sendInputEvent(QInputEvent *e) const; void edit3DViewResized(const QSize &size) const; @@ -63,7 +63,7 @@ private slots: void onEntriesChanged(); private: - enum class ModelAtPosReqType { + enum class NodeAtPosReqType { MaterialDrop, ContextMenu, None @@ -108,7 +108,7 @@ private: int particlemode; ModelCache m_canvasCache; ModelNode m_droppedMaterial; - ModelAtPosReqType m_modelAtPosReqType; + NodeAtPosReqType m_nodeAtPosReqType; QPoint m_contextMenuPos; QTimer m_compressionTimer; }; diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index dc5f342d38e..9c1cc945f6f 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -278,8 +278,12 @@ void Edit3DWidget::showContextMenu(const QPoint &pos, const ModelNode &modelNode { m_contextMenuTarget = modelNode; - m_editMaterialAction->setEnabled(modelNode.isValid()); - m_deleteAction->setEnabled(modelNode.isValid()); + const bool isValid = modelNode.isValid(); + // TODO: this is from 8.0 branch that doesn't apply anymore: + // const bool isModel = isValid && modelNode.isSubclassOf("QtQuick3D.Model"); + const bool isModel = false; + m_editMaterialAction->setEnabled(isModel); + m_deleteAction->setEnabled(isValid && !modelNode.isRootNode()); m_contextMenu->popup(mapToGlobal(pos)); } diff --git a/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.cpp b/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.cpp new file mode 100644 index 00000000000..357be8db45a --- /dev/null +++ b/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.cpp @@ -0,0 +1,235 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "bundleimporter.h" + +#include "import.h" +#include "model.h" +#include "qmldesignerconstants.h" +#include "qmldesignerplugin.h" +#include "rewritingexception.h" + +#include + +#include +#include +#include +#include + +using namespace Utils; + +namespace QmlDesigner::Internal { + +BundleImporter::BundleImporter(const QString &bundleDir, + const QString &bundleId, + const QStringList &sharedFiles, + QObject *parent) + : QObject(parent) + , m_bundleDir(FilePath::fromString(bundleDir)) + , m_bundleId(bundleId) + , m_sharedFiles(sharedFiles) +{ + m_importTimer.setInterval(200); + connect(&m_importTimer, &QTimer::timeout, this, &BundleImporter::handleImportTimer); + m_moduleName = QStringLiteral("%1.%2").arg( + QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER), + m_bundleId).mid(1); // Chop leading slash +} + +// Returns empty string on success or an error message on failure. +// Note that there is also an asynchronous portion to the import, which will only +// be done if this method returns success. Once the asynchronous portion of the +// import is completed, importFinished signal will be emitted. +QString BundleImporter::importComponent(const QString &qmlFile, + const QStringList &files) +{ + FilePath bundleImportPath = QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath(); + if (bundleImportPath.isEmpty()) + return "Failed to resolve current project path"; + + const QString projectBundlePath = QStringLiteral("%1%2/%3").arg( + QLatin1String(Constants::DEFAULT_ASSET_IMPORT_FOLDER), + QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER), + m_bundleId).mid(1); // Chop leading slash + bundleImportPath = bundleImportPath.resolvePath(projectBundlePath); + + if (!bundleImportPath.exists()) { + if (!bundleImportPath.createDir()) + return QStringLiteral("Failed to create bundle import folder: '%1'").arg(bundleImportPath.toString()); + } + + for (const QString &file : qAsConst(m_sharedFiles)) { + FilePath target = bundleImportPath.resolvePath(file); + if (!target.exists()) { + FilePath parentDir = target.parentDir(); + if (!parentDir.exists() && !parentDir.createDir()) + return QStringLiteral("Failed to create folder for: '%1'").arg(target.toString()); + FilePath source = m_bundleDir.resolvePath(file); + if (!source.copyFile(target)) + return QStringLiteral("Failed to copy shared file: '%1'").arg(source.toString()); + } + } + + FilePath qmldirPath = bundleImportPath.resolvePath(QStringLiteral("qmldir")); + QFile qmldirFile(qmldirPath.toString()); + + QString qmldirContent; + if (qmldirPath.exists()) { + if (!qmldirFile.open(QIODeviceBase::ReadOnly)) + return QStringLiteral("Failed to open qmldir file for reading: '%1'").arg(qmldirPath.toString()); + qmldirContent = QString::fromUtf8(qmldirFile.readAll()); + qmldirFile.close(); + } else { + qmldirContent.append("module "); + qmldirContent.append(m_moduleName); + qmldirContent.append('\n'); + } + + FilePath qmlSourceFile = FilePath::fromString(qmlFile); + const bool qmlFileExists = qmlSourceFile.exists(); + const QString qmlType = qmlSourceFile.baseName(); + m_pendingTypes.append(QStringLiteral("%1.%2") + .arg(QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER).mid(1), qmlType)); + if (!qmldirContent.contains(qmlFile)) { + QSaveFile qmldirSaveFile(qmldirPath.toString()); + if (!qmldirSaveFile.open(QIODeviceBase::WriteOnly | QIODeviceBase::Truncate)) + return QStringLiteral("Failed to open qmldir file for writing: '%1'").arg(qmldirPath.toString()); + + qmldirContent.append(qmlType); + qmldirContent.append(" 1.0 "); + qmldirContent.append(qmlFile); + qmldirContent.append('\n'); + + qmldirSaveFile.write(qmldirContent.toUtf8()); + qmldirSaveFile.commit(); + } + + QStringList allFiles; + allFiles.append(files); + allFiles.append(qmlFile); + for (const QString &file : qAsConst(allFiles)) { + FilePath target = bundleImportPath.resolvePath(file); + FilePath parentDir = target.parentDir(); + if (!parentDir.exists() && !parentDir.createDir()) + return QStringLiteral("Failed to create folder for: '%1'").arg(target.toString()); + + FilePath source = m_bundleDir.resolvePath(file); + if (target.exists()) { + if (source.lastModified() == target.lastModified()) + continue; + target.removeFile(); // Remove existing file for update + } + if (!source.copyFile(target)) + return QStringLiteral("Failed to copy file: '%1'").arg(source.toString()); + } + + m_fullReset = !qmlFileExists; + auto doc = QmlDesignerPlugin::instance()->currentDesignDocument(); + Model *model = doc ? doc->currentModel() : nullptr; + if (!model) + return "Model not available, cannot add import statement or update code model"; + + Import import = Import::createLibraryImport(m_moduleName, "1.0"); + if (!model->hasImport(import)) { + if (model->possibleImports().contains(import)) { + m_importAddPending = false; + try { + model->changeImports({import}, {}); + } catch (const RewritingException &) { + // No point in trying to add import asynchronously either, so just fail out + return QStringLiteral("Failed to add import statement for: '%1'").arg(m_moduleName); + } + } else { + // If import is not yet possible, import statement needs to be added asynchronously to + // avoid errors, as code model update takes a while. Full reset is not necessary + // in this case, as new import directory appearing will trigger scanning of it. + m_importAddPending = true; + m_fullReset = false; + } + } + m_importTimerCount = 0; + m_importTimer.start(); + + return {}; +} + +void BundleImporter::handleImportTimer() +{ + auto handleFailure = [this]() { + m_importTimer.stop(); + m_fullReset = false; + m_importAddPending = false; + m_importTimerCount = 0; + m_pendingTypes.clear(); + emit importFinished({}); + }; + + auto doc = QmlDesignerPlugin::instance()->currentDesignDocument(); + Model *model = doc ? doc->currentModel() : nullptr; + if (!model || ++m_importTimerCount > 100) { + handleFailure(); + return; + } + + if (m_fullReset) { + // Force code model reset to notice changes to existing module + auto modelManager = QmlJS::ModelManagerInterface::instance(); + if (modelManager) + modelManager->resetCodeModel(); + m_fullReset = false; + return; + } + + if (m_importAddPending) { + try { + Import import = Import::createLibraryImport(m_moduleName, "1.0"); + if (model->possibleImports().contains(import)) { + model->changeImports({import}, {}); + m_importAddPending = false; + } + } catch (const RewritingException &) { + // Import adding is unlikely to succeed later, either, so just bail out + handleFailure(); + } + return; + } + + // Detect when the code model has the new material(s) fully available + const QStringList pendingTypes = m_pendingTypes; + for (const QString &pendingType : pendingTypes) { + NodeMetaInfo metaInfo = model->metaInfo(pendingType.toUtf8()); + if (metaInfo.isValid() && !metaInfo.superClasses().empty()) { + m_pendingTypes.removeAll(pendingType); + emit importFinished(metaInfo); + } + } + + if (m_pendingTypes.isEmpty()) { + m_importTimer.stop(); + m_importTimerCount = 0; + } +} + +} // namespace QmlDesigner::Internal diff --git a/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.h b/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.h new file mode 100644 index 00000000000..840c4c672f5 --- /dev/null +++ b/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +#include "nodemetainfo.h" + +#include + +QT_BEGIN_NAMESPACE +QT_END_NAMESPACE + +namespace QmlDesigner::Internal { + +class BundleImporter : public QObject +{ + Q_OBJECT + +public: + BundleImporter(const QString &bundleDir, + const QString &bundleId, + const QStringList &sharedFiles, + QObject *parent = nullptr); + ~BundleImporter() = default; + + QString importComponent(const QString &qmlFile, + const QStringList &files); +signals: + // The metaInfo parameter will be invalid if an error was encountered during + // asynchronous part of the import. In this case all remaining pending imports have been + // terminated, and will not receive separate importFinished notifications. + void importFinished(const QmlDesigner::NodeMetaInfo &metaInfo); + +private: + void handleImportTimer(); + + Utils::FilePath m_bundleDir; + QString m_bundleId; + QString m_moduleName; + QStringList m_sharedFiles; + QTimer m_importTimer; + int m_importTimerCount = 0; + bool m_importAddPending = false; + bool m_fullReset = false; + QStringList m_pendingTypes; +}; + +} // namespace QmlDesigner::Internal diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditordynamicpropertiesproxymodel.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditordynamicpropertiesproxymodel.cpp new file mode 100644 index 00000000000..2a56561001f --- /dev/null +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditordynamicpropertiesproxymodel.cpp @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "materialeditordynamicpropertiesproxymodel.h" + +#include + +#include + +using namespace QmlDesigner; + +MaterialEditorDynamicPropertiesProxyModel::MaterialEditorDynamicPropertiesProxyModel(QObject *parent) + : DynamicPropertiesProxyModel(parent) +{ + initModel(MaterialEditorView::instance()->dynamicPropertiesModel()); +} + +void MaterialEditorDynamicPropertiesProxyModel::registerDeclarativeType() +{ + DynamicPropertiesProxyModel::registerDeclarativeType(); + qmlRegisterType("HelperWidgets", 2, 0, "MaterialEditorDynamicPropertiesModel"); +} diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditordynamicpropertiesproxymodel.h b/src/plugins/qmldesigner/components/materialeditor/materialeditordynamicpropertiesproxymodel.h new file mode 100644 index 00000000000..9d4e6aa9a47 --- /dev/null +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditordynamicpropertiesproxymodel.h @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "dynamicpropertiesproxymodel.h" + +class MaterialEditorDynamicPropertiesProxyModel : public DynamicPropertiesProxyModel +{ + Q_OBJECT +public: + explicit MaterialEditorDynamicPropertiesProxyModel(QObject *parent = nullptr); + + static void registerDeclarativeType(); +}; diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp index fb9facf57cc..7b6f3a37409 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp @@ -5,12 +5,14 @@ #include "materialeditorqmlbackend.h" #include "materialeditorcontextobject.h" +#include "materialeditordynamicpropertiesproxymodel.h" #include "propertyeditorvalue.h" #include "materialeditortransaction.h" #include "assetslibrarywidget.h" #include #include +#include #include #include #include @@ -49,6 +51,7 @@ namespace QmlDesigner { MaterialEditorView::MaterialEditorView(QWidget *parent) : AbstractView(parent) , m_stackedWidget(new QStackedWidget(parent)) + , m_dynamicPropertiesModel(new Internal::DynamicPropertiesModel(true, this)) { m_updateShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F7), m_stackedWidget); connect(m_updateShortcut, &QShortcut::activated, this, &MaterialEditorView::reloadQml); @@ -71,6 +74,8 @@ MaterialEditorView::MaterialEditorView(QWidget *parent) QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css")))); m_stackedWidget->setMinimumWidth(250); QmlDesignerPlugin::trackWidgetFocusTime(m_stackedWidget, Constants::EVENT_MATERIALEDITOR_TIME); + + MaterialEditorDynamicPropertiesProxyModel::registerDeclarativeType(); } MaterialEditorView::~MaterialEditorView() @@ -294,6 +299,29 @@ void MaterialEditorView::currentTimelineChanged(const ModelNode &) m_qmlBackEnd->contextObject()->setHasActiveTimeline(QmlTimeline::hasActiveTimeline(this)); } +Internal::DynamicPropertiesModel *MaterialEditorView::dynamicPropertiesModel() const +{ + return m_dynamicPropertiesModel; +} + +MaterialEditorView *MaterialEditorView::instance() +{ + static MaterialEditorView *s_instance = nullptr; + + if (s_instance) + return s_instance; + + const auto views = QmlDesignerPlugin::instance()->viewManager().views(); + for (auto *view : views) { + MaterialEditorView *myView = qobject_cast(view); + if (myView) + s_instance = myView; + } + + QTC_ASSERT(s_instance, return nullptr); + return s_instance; +} + void MaterialEditorView::delayedResetView() { // TODO: it seems the delayed reset is not needed. Leaving it commented out for now just in case it @@ -575,6 +603,11 @@ void MaterialEditorView::setupQmlBackend() m_qmlBackEnd = currentQmlBackend; + if (m_hasMaterialRoot) + m_dynamicPropertiesModel->setSelectedNode(m_selectedMaterial); + else + m_dynamicPropertiesModel->reset(); + delayedTypeUpdate(); initPreviewData(); @@ -746,6 +779,7 @@ void MaterialEditorView::modelAttached(Model *model) void MaterialEditorView::modelAboutToBeDetached(Model *model) { AbstractView::modelAboutToBeDetached(model); + m_dynamicPropertiesModel->reset(); m_qmlBackEnd->materialEditorTransaction()->end(); } @@ -778,8 +812,9 @@ void MaterialEditorView::variantPropertiesChanged(const QList & bool changed = false; for (const VariantProperty &property : propertyList) { ModelNode node(property.parentModelNode()); - if (node == m_selectedMaterial || QmlObjectNode(m_selectedMaterial).propertyChangeForCurrentState() == node) { + if (property.isDynamic()) + m_dynamicPropertiesModel->variantPropertyChanged(property); if (m_selectedMaterial.property(property.name()).isBindingProperty()) setValue(m_selectedMaterial, property.name(), QmlObjectNode(m_selectedMaterial).instanceValue(property.name())); else @@ -805,6 +840,8 @@ void MaterialEditorView::bindingPropertiesChanged(const QList & m_qmlBackEnd->contextObject()->setHasAliasExport(QmlObjectNode(m_selectedMaterial).isAliasExported()); if (node == m_selectedMaterial || QmlObjectNode(m_selectedMaterial).propertyChangeForCurrentState() == node) { + if (property.isDynamic()) + m_dynamicPropertiesModel->bindingPropertyChanged(property); if (QmlObjectNode(m_selectedMaterial).modelNode().property(property.name()).isBindingProperty()) setValue(m_selectedMaterial, property.name(), QmlObjectNode(m_selectedMaterial).instanceValue(property.name())); else @@ -828,6 +865,16 @@ void MaterialEditorView::auxiliaryDataChanged(const ModelNode &node, m_qmlBackEnd->setValueforAuxiliaryProperties(m_selectedMaterial, key); } +void MaterialEditorView::propertiesAboutToBeRemoved(const QList &propertyList) +{ + for (const auto &property : propertyList) { + if (property.isBindingProperty()) + m_dynamicPropertiesModel->bindingRemoved(property.toBindingProperty()); + else if (property.isVariantProperty()) + m_dynamicPropertiesModel->variantRemoved(property.toVariantProperty()); + } +} + // request render image for the selected material node void MaterialEditorView::requestPreviewRender() { @@ -936,7 +983,7 @@ void MaterialEditorView::renameMaterial(ModelNode &material, const QString &newN QTC_ASSERT(material.isValid(), return); executeInTransaction("MaterialEditorView:renameMaterial", [&] { - material.setIdWithRefactoring(generateIdFromName(newName)); + material.setIdWithRefactoring(model()->generateIdFromName(newName, "material")); VariantProperty objNameProp = material.variantProperty("objectName"); objNameProp.setValue(newName); @@ -965,7 +1012,7 @@ void MaterialEditorView::duplicateMaterial(const ModelNode &material) // set name and id QString newName = sourceMat.modelNode().variantProperty("objectName").value().toString() + " copy"; duplicateMat.modelNode().variantProperty("objectName").setValue(newName); - duplicateMat.modelNode().setIdWithoutRefactoring(generateIdFromName(newName)); + duplicateMat.modelNode().setIdWithoutRefactoring(model()->generateIdFromName(newName, "material")); // sync properties const QList props = material.properties(); @@ -991,6 +1038,7 @@ void MaterialEditorView::customNotification([[maybe_unused]] const AbstractView if (identifier == "selected_material_changed") { if (!m_hasMaterialRoot) { m_selectedMaterial = nodeList.first(); + m_dynamicPropertiesModel->setSelectedNode(m_selectedMaterial); QTimer::singleShot(0, this, &MaterialEditorView::resetView); } } else if (identifier == "apply_to_selected_triggered") { @@ -1072,38 +1120,4 @@ void MaterialEditorView::reloadQml() resetView(); } -// generate a unique camelCase id from a name -QString MaterialEditorView::generateIdFromName(const QString &name) -{ - QString newId; - if (name.isEmpty()) { - newId = "material"; - } else { - // convert to camel case - QStringList nameWords = name.split(" "); - nameWords[0] = nameWords[0].at(0).toLower() + nameWords[0].mid(1); - for (int i = 1; i < nameWords.size(); ++i) - nameWords[i] = nameWords[i].at(0).toUpper() + nameWords[i].mid(1); - newId = nameWords.join(""); - - // if id starts with a number prepend an underscore - if (newId.at(0).isDigit()) - newId.prepend('_'); - } - - QRegularExpression rgx("\\d+$"); // matches a number at the end of a string - while (hasId(newId)) { // id exists - QRegularExpressionMatch match = rgx.match(newId); - if (match.hasMatch()) { // ends with a number, increment it - QString numStr = match.captured(); - int num = numStr.toInt() + 1; - newId = newId.mid(0, match.capturedStart()) + QString::number(num); - } else { - newId.append('1'); - } - } - - return newId; -} - } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h index bcae75f6dc1..249c78ea2c0 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h @@ -22,6 +22,10 @@ namespace QmlDesigner { class ModelNode; class MaterialEditorQmlBackend; +namespace Internal { +class DynamicPropertiesModel; +} + class MaterialEditorView : public AbstractView { Q_OBJECT @@ -46,6 +50,7 @@ public: void auxiliaryDataChanged(const ModelNode &node, AuxiliaryDataKeyView key, const QVariant &data) override; + void propertiesAboutToBeRemoved(const QList &propertyList) override; void resetView(); void currentStateChanged(const ModelNode &node) override; @@ -70,6 +75,10 @@ public: void currentTimelineChanged(const ModelNode &node) override; + Internal::DynamicPropertiesModel *dynamicPropertiesModel() const; + + static MaterialEditorView *instance(); + public slots: void handleToolBarAction(int action); void handlePreviewEnvChanged(const QString &envAndValue); @@ -85,7 +94,6 @@ private: void reloadQml(); void highlightSupportedProperties(bool highlight = true); - QString generateIdFromName(const QString &name); void requestPreviewRender(); void applyMaterialToSelectedModels(const ModelNode &material, bool add = false); @@ -122,6 +130,7 @@ private: QPointer m_colorDialog; QPointer m_itemLibraryInfo; + Internal::DynamicPropertiesModel *m_dynamicPropertiesModel = nullptr; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp b/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp new file mode 100644 index 00000000000..4fc1326c6e4 --- /dev/null +++ b/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp @@ -0,0 +1,392 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "dynamicpropertiesproxymodel.h" + +#include "propertyeditorvalue.h" + +#include + +#include +#include +#include +#include +#include + +#include + +#include + +using namespace QmlDesigner; + +static const int propertyNameRole = Qt::UserRole + 1; +static const int propertyTypeRole = Qt::UserRole + 2; +static const int propertyValueRole = Qt::UserRole + 3; +static const int propertyBindingRole = Qt::UserRole + 4; + +DynamicPropertiesProxyModel::DynamicPropertiesProxyModel(QObject *parent) + : QAbstractListModel(parent) +{ +} + +void DynamicPropertiesProxyModel::initModel(QmlDesigner::Internal::DynamicPropertiesModel *model) +{ + m_model = model; + + connect(m_model, &QAbstractItemModel::modelAboutToBeReset, + this, &QAbstractItemModel::modelAboutToBeReset); + connect(m_model, &QAbstractItemModel::modelReset, + this, &QAbstractItemModel::modelReset); + + connect(m_model, &QAbstractItemModel::rowsAboutToBeRemoved, + this, &QAbstractItemModel::rowsAboutToBeRemoved); + connect(m_model, &QAbstractItemModel::rowsRemoved, + this, &QAbstractItemModel::rowsRemoved); + connect(m_model, &QAbstractItemModel::rowsInserted, + this, &QAbstractItemModel::rowsInserted); + + connect(m_model, &QAbstractItemModel::dataChanged, + this, [this](const QModelIndex &topLeft, const QModelIndex &, const QList &) { + emit dataChanged(index(topLeft.row(), 0), + index(topLeft.row(), 0), + { propertyNameRole, propertyTypeRole, + propertyValueRole, propertyBindingRole }); + }); +} + +int DynamicPropertiesProxyModel::rowCount(const QModelIndex &) const +{ + return m_model->rowCount(); +} + +QHash DynamicPropertiesProxyModel::roleNames() const +{ + static QHash roleNames{{propertyNameRole, "propertyName"}, + {propertyTypeRole, "propertyType"}, + {propertyValueRole, "propertyValue"}, + {propertyBindingRole, "propertyBinding"}}; + + return roleNames; +} + +QVariant DynamicPropertiesProxyModel::data(const QModelIndex &index, int role) const +{ + if (index.isValid() && index.row() < rowCount()) { + AbstractProperty property = m_model->abstractPropertyForRow(index.row()); + + QTC_ASSERT(property.isValid(), return QVariant()); + + if (role == propertyNameRole) { + return property.name(); + } else if (propertyTypeRole) { + return property.dynamicTypeName(); + } else if (role == propertyValueRole) { + QmlObjectNode objectNode = property.parentQmlObjectNode(); + return objectNode.modelValue(property.name()); + } else if (role == propertyBindingRole) { + if (property.isBindingProperty()) + return property.toBindingProperty().expression(); + return QVariant(); + } + qWarning() << Q_FUNC_INFO << "invalid role"; + } else { + qWarning() << Q_FUNC_INFO << "invalid index"; + } + + return QVariant(); +} + +void DynamicPropertiesProxyModel::registerDeclarativeType() +{ + static bool registered = false; + if (!registered) + qmlRegisterType("HelperWidgets", 2, 0, "DynamicPropertiesModel"); +} + +QmlDesigner::Internal::DynamicPropertiesModel *DynamicPropertiesProxyModel::dynamicPropertiesModel() const +{ + return m_model; +} + +QString DynamicPropertiesProxyModel::newPropertyName() const +{ + auto propertiesModel = dynamicPropertiesModel(); + + return QString::fromUtf8(propertiesModel->unusedProperty( + propertiesModel->singleSelectedNode())); +} + +void DynamicPropertiesProxyModel::createProperty(const QString &name, const QString &type) +{ + QmlDesignerPlugin::emitUsageStatistics(Constants::EVENT_PROPERTY_ADDED); + + const auto selectedNodes = dynamicPropertiesModel()->selectedNodes(); + if (selectedNodes.count() == 1) { + const ModelNode modelNode = selectedNodes.constFirst(); + if (modelNode.isValid()) { + try { + if (Internal::DynamicPropertiesModel::isValueType(type.toUtf8())) { + QVariant value; + if (type == "int") + value = 0; + else if (type == "real") + value = 0.0; + else if (type == "color") + value = QColor(255, 255, 255); + else if (type == "string") + value = ""; + else if (type == "bool") + value = false; + else if (type == "url") + value = ""; + else if (type == "variant") + value = ""; + + modelNode.variantProperty(name.toUtf8()) + .setDynamicTypeNameAndValue(type.toUtf8(), value); + } else { + QString expression; + if (type == "alias") + expression = "null"; + else if (type == "TextureInput") + expression = "null"; + else if (type == "vector2d") + expression = "Qt.vector2d(0, 0)"; + else if (type == "vector3d") + expression = "Qt.vector3d(0, 0, 0)"; + else if (type == "vector4d") + expression = "Qt.vector4d(0, 0, 0 ,0)"; + + modelNode.bindingProperty(name.toUtf8()) + .setDynamicTypeNameAndExpression(type.toUtf8(), expression); + } + } catch (Exception &e) { + e.showException(); + } + } + } else { + qWarning() << " BindingModel::addBindingForCurrentNode not one node selected"; + } +} + +DynamicPropertyRow::DynamicPropertyRow(QObject *parent) +{ + m_backendValue = new PropertyEditorValue(this); + + QObject::connect(m_backendValue, + &PropertyEditorValue::valueChanged, + this, + [this](const QString &, const QVariant &value) { commitValue(value); }); + + QObject::connect(m_backendValue, + &PropertyEditorValue::expressionChanged, + this, + [this](const QString &) { commitExpression(m_backendValue->expression()); }); +} + +DynamicPropertyRow::~DynamicPropertyRow() +{ + clearProxyBackendValues(); +} + +void DynamicPropertyRow::registerDeclarativeType() +{ + qmlRegisterType("HelperWidgets", 2, 0, "DynamicPropertyRow"); +} + +void DynamicPropertyRow::setRow(int r) +{ + if (m_row == r) + return; + + m_row = r; + setupBackendValue(); + emit rowChanged(); +} + +int DynamicPropertyRow::row() const +{ + return m_row; +} + +void DynamicPropertyRow::setModel(DynamicPropertiesProxyModel *model) +{ + if (model == m_model) + return; + + if (m_model) { + disconnect(m_model, &QAbstractItemModel::dataChanged, + this, &DynamicPropertyRow::handleDataChanged); + } + + m_model = model; + + if (m_model) { + connect(m_model, &QAbstractItemModel::dataChanged, + this, &DynamicPropertyRow::handleDataChanged); + + if (m_row != -1) + setupBackendValue(); + } + + emit modelChanged(); +} + +DynamicPropertiesProxyModel *DynamicPropertyRow::model() const +{ + return m_model; +} + +PropertyEditorValue *DynamicPropertyRow::backendValue() const +{ + return m_backendValue; +} + +void DynamicPropertyRow::remove() +{ + m_model->dynamicPropertiesModel()->deleteDynamicPropertyByRow(m_row); +} + +PropertyEditorValue *DynamicPropertyRow::createProxyBackendValue() +{ + + PropertyEditorValue *newValue = new PropertyEditorValue(this); + m_proxyBackendValues.append(newValue); + + return newValue; +} + +void DynamicPropertyRow::clearProxyBackendValues() +{ + qDeleteAll(m_proxyBackendValues); + m_proxyBackendValues.clear(); +} + +void DynamicPropertyRow::setupBackendValue() +{ + if (!m_model) + return; + + QmlDesigner::AbstractProperty property = m_model->dynamicPropertiesModel()->abstractPropertyForRow(m_row); + if (!property.isValid()) + return; + + if (m_backendValue->name() != property.name()) + m_backendValue->setName(property.name()); + + ModelNode node = property.parentModelNode(); + if (node != m_backendValue->modelNode()) + m_backendValue->setModelNode(node); + + QVariant modelValue = property.parentQmlObjectNode().modelValue(property.name()); + if (modelValue != m_backendValue->value()) { + m_backendValue->setValue({}); + m_backendValue->setValue(modelValue); + } + + if (property.isBindingProperty()) { + QString expression = property.toBindingProperty().expression(); + if (m_backendValue->expression() != expression) + m_backendValue->setExpression(expression); + } + + emit m_backendValue->isBoundChanged(); +} + +void DynamicPropertyRow::commitValue(const QVariant &value) +{ + if (m_lock) + return; + + if (m_row < 0) + return; + + auto propertiesModel = m_model->dynamicPropertiesModel(); + VariantProperty variantProperty = propertiesModel->variantPropertyForRow(m_row); + + if (!Internal::DynamicPropertiesModel::isValueType(variantProperty.dynamicTypeName())) + return; + + m_lock = true; + auto unlock = qScopeGuard([this] { m_lock = false; }); + + auto view = propertiesModel->view(); + RewriterTransaction transaction = view->beginRewriterTransaction( + QByteArrayLiteral("DynamicPropertiesModel::commitValue")); + try { + if (view->currentState().isBaseState()) { + if (variantProperty.value() != value) + variantProperty.setDynamicTypeNameAndValue(variantProperty.dynamicTypeName(), value); + } else { + QmlObjectNode objectNode = variantProperty.parentQmlObjectNode(); + QTC_CHECK(objectNode.isValid()); + PropertyName name = variantProperty.name(); + if (objectNode.isValid() && objectNode.modelValue(name) != value) + objectNode.setVariantProperty(name, value); + } + transaction.commit(); //committing in the try block + } catch (Exception &e) { + e.showException(); + } +} + +void DynamicPropertyRow::commitExpression(const QString &expression) +{ + if (m_lock) + return; + + if (m_row < 0) + return; + + m_lock = true; + auto unlock = qScopeGuard([this] { m_lock = false; }); + + auto propertiesModel = m_model->dynamicPropertiesModel(); + BindingProperty bindingProperty = propertiesModel->bindingPropertyForRow(m_row); + + auto view = propertiesModel->view(); + RewriterTransaction transaction = view->beginRewriterTransaction( + QByteArrayLiteral("DynamicPropertiesModel::commitExpression")); + try { + QString theExpression = expression; + if (theExpression.isEmpty()) + theExpression = "null"; + + if (bindingProperty.expression() != theExpression) { + bindingProperty.setDynamicTypeNameAndExpression(bindingProperty.dynamicTypeName(), + theExpression); + } + transaction.commit(); //committing in the try block + } catch (Exception &e) { + e.showException(); + } + return; +} + +void DynamicPropertyRow::handleDataChanged(const QModelIndex &topLeft, const QModelIndex &, const QList &) +{ + if (topLeft.row() == m_row) + setupBackendValue(); +} diff --git a/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.h b/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.h new file mode 100644 index 00000000000..0d11fe8f3f2 --- /dev/null +++ b/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "propertyeditorvalue.h" + +#include +#include + +#include +#include +#include +#include + +namespace QmlDesigner { +namespace Internal { +class DynamicPropertiesModel; +} +} // namespace QmlDesigner + +class DynamicPropertiesProxyModel : public QAbstractListModel +{ + Q_OBJECT + +public: + explicit DynamicPropertiesProxyModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + QHash roleNames() const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + static void registerDeclarativeType(); + + QmlDesigner::Internal::DynamicPropertiesModel *dynamicPropertiesModel() const; + + Q_INVOKABLE QString newPropertyName() const; + Q_INVOKABLE void createProperty(const QString &name, const QString &type); + +protected: + void initModel(QmlDesigner::Internal::DynamicPropertiesModel *model); + +private: + QmlDesigner::Internal::DynamicPropertiesModel *m_model = nullptr; +}; + +class DynamicPropertyRow : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int row READ row WRITE setRow NOTIFY rowChanged FINAL) + Q_PROPERTY(PropertyEditorValue *backendValue READ backendValue NOTIFY rowChanged FINAL) + Q_PROPERTY(DynamicPropertiesProxyModel *model READ model WRITE setModel NOTIFY modelChanged FINAL) + +public: + explicit DynamicPropertyRow(QObject *parent = nullptr); + ~DynamicPropertyRow(); + + static void registerDeclarativeType(); + + void setRow(int r); + int row() const; + void setModel(DynamicPropertiesProxyModel *model); + DynamicPropertiesProxyModel *model() const; + PropertyEditorValue *backendValue() const; + + Q_INVOKABLE void remove(); + Q_INVOKABLE PropertyEditorValue *createProxyBackendValue(); + Q_INVOKABLE void clearProxyBackendValues(); + +signals: + void rowChanged(); + void modelChanged(); + +private: + void setupBackendValue(); + void commitValue(const QVariant &value); + void commitExpression(const QString &expression); + void handleDataChanged(const QModelIndex &topLeft, const QModelIndex &, const QList &); + + int m_row = -1; + PropertyEditorValue *m_backendValue = nullptr; + DynamicPropertiesProxyModel *m_model = nullptr; + QList m_proxyBackendValues; + bool m_lock = false; +}; + +QML_DECLARE_TYPE(DynamicPropertyRow) diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp index 9fb9a6f394d..188e4f7ed7d 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp @@ -567,6 +567,13 @@ inline bool dotPropertyHeuristic(const QmlObjectNode &node, const NodeMetaInfo & if (propertyType.isFont() || itemInfo.hasProperty(itemProperty) || propertyType.isBasedOn(textInfo, rectangleInfo, imageInfo)) return false; + // TODO In 8.0 this is now the following, which conflicts with master: + // if (typeName == "font" || typeName == "Texture" || typeName == "vector4d" + // || itemInfo.hasProperty(itemProperty) + // || textInfo.isSubclassOf(typeName) + // || rectangleInfo.isSubclassOf(typeName) + // || imageInfo.isSubclassOf(typeName)) + // return false; return true; } @@ -582,10 +589,13 @@ QString PropertyEditorQmlBackend::templateGeneration(const NodeMetaInfo &metaTyp QStringList allTypes; // all template types QStringList separateSectionTypes; // separate section types only + QStringList needsTypeArgTypes; // types that need type as third parameter for (const QmlJS::SimpleReaderNode::Ptr &node : nodes) { if (node->propertyNames().contains("separateSection")) separateSectionTypes.append(variantToStringList(node->property("typeNames").value)); + if (node->propertyNames().contains("needsTypeArg")) + needsTypeArgTypes.append(variantToStringList(node->property("typeNames").value)); allTypes.append(variantToStringList(node->property("typeNames").value)); } @@ -643,8 +653,8 @@ QString PropertyEditorQmlBackend::templateGeneration(const NodeMetaInfo &metaTyp Utils::sort(basicProperties, propertyMetaInfoCompare); - auto findAndFillTemplate = [&nodes, &node](const PropertyName &label, - const PropertyMetaInfo &property) { + auto findAndFillTemplate = [&nodes, &node, &needsTypeArgTypes](const PropertyName &label, + const PropertyMetaInfo &property) { const auto &propertyName = property.name(); PropertyName underscoreProperty = propertyName; underscoreProperty.replace('.', '_'); @@ -663,7 +673,14 @@ QString PropertyEditorQmlBackend::templateGeneration(const NodeMetaInfo &metaTyp if (file.open(QIODevice::ReadOnly)) { QString source = QString::fromUtf8(file.readAll()); file.close(); - filledTemplate = source.arg(QString::fromUtf8(label)).arg(QString::fromUtf8(underscoreProperty)); + if (needsTypeArgTypes.contains(QString::fromUtf8(typeName))) { + filledTemplate = source.arg(QString::fromUtf8(label), + QString::fromUtf8(underscoreProperty), + QString::fromUtf8(typeName)); + } else { + filledTemplate = source.arg(QString::fromUtf8(label), + QString::fromUtf8(underscoreProperty)); + } } else { qWarning().nospace() << "template definition source file not found:" << fileName; } diff --git a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp index 90496de9963..f28479050cf 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp @@ -8,6 +8,7 @@ #include "bindingeditor/actioneditor.h" #include "bindingeditor/bindingeditor.h" #include "colorpalettebackend.h" +#include "selectiondynamicpropertiesproxymodel.h" #include "fileresourcesmodel.h" #include "gradientmodel.h" #include "gradientpresetcustomlistmodel.h" @@ -53,6 +54,8 @@ void Quick2PropertyEditorView::registerQmlTypes() Tooltip::registerDeclarativeType(); EasingCurveEditor::registerDeclarativeType(); RichTextEditorProxy::registerDeclarativeType(); + SelectionDynamicPropertiesProxyModel::registerDeclarativeType(); + DynamicPropertyRow::registerDeclarativeType(); const QString resourcePath = PropertyEditorQmlBackend::propertyEditorResourcesPath(); diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineanimationform.ui b/src/plugins/qmldesigner/components/timelineeditor/timelineanimationform.ui index f24da6edf99..3329d30af66 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelineanimationform.ui +++ b/src/plugins/qmldesigner/components/timelineeditor/timelineanimationform.ui @@ -13,6 +13,9 @@ + + Number of times the animation runs before it stops. + Loops: @@ -49,6 +52,9 @@ + + Sets the animation to loop indefinitely. + Continuous @@ -113,6 +119,9 @@ 0 + + Name for the animation. + Animation ID: @@ -158,6 +167,9 @@ + + State to activate when the animation finishes. + Finished: @@ -165,6 +177,9 @@ + + Runs the animation backwards to the beginning when it reaches the end. + Ping pong @@ -218,6 +233,9 @@ true + + Runs the animation automatically when the base state is active. + Running in base state @@ -253,6 +271,9 @@ + + First frame of the animation. + Start frame: @@ -286,6 +307,9 @@ + + Length of the animation in milliseconds. If you set a shorter duration than the number of frames, frames are left out from the end of the animation. + Duration: @@ -293,6 +317,9 @@ + + Last frame of the animation. + End frame: diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineform.ui b/src/plugins/qmldesigner/components/timelineeditor/timelineform.ui index 370a6f8154c..53877b5695e 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelineform.ui +++ b/src/plugins/qmldesigner/components/timelineeditor/timelineform.ui @@ -42,6 +42,9 @@ + + Last frame of the timeline. + End frame: @@ -52,6 +55,9 @@ + + First frame of the timeline. Negative numbers are allowed. + Start frame: @@ -87,6 +93,9 @@ false + + To create an expression binding animation, delete all animations from this timeline. + Expression binding @@ -94,6 +103,9 @@ + + Name for the timeline. + Timeline ID: @@ -152,6 +164,9 @@ + + Sets the expression to bind the current keyframe to. + Expression binding: diff --git a/src/plugins/qmldesigner/designercore/include/abstractview.h b/src/plugins/qmldesigner/designercore/include/abstractview.h index 139f198fa5f..ad7a4b80dc2 100644 --- a/src/plugins/qmldesigner/designercore/include/abstractview.h +++ b/src/plugins/qmldesigner/designercore/include/abstractview.h @@ -145,7 +145,7 @@ public: void emitUpdateActiveScene3D(const QVariantMap &sceneState); void emitModelNodelPreviewPixmapChanged(const ModelNode &node, const QPixmap &pixmap); void emitImport3DSupportChanged(const QVariantMap &supportMap); - void emitModelAtPosResult(const ModelNode &modelNode); + void emitNodeAtPosResult(const ModelNode &modelNode); void sendTokenToInstances(const QString &token, int number, const QVector &nodeVector); @@ -213,7 +213,7 @@ public: virtual void renderImage3DChanged(const QImage &image); virtual void updateActiveScene3D(const QVariantMap &sceneState); virtual void updateImport3DSupport(const QVariantMap &supportMap); - virtual void modelAtPosReady(const ModelNode &modelNode); + virtual void nodeAtPosReady(const ModelNode &modelNode); virtual void modelNodePreviewPixmapChanged(const ModelNode &node, const QPixmap &pixmap); virtual void dragStarted(QMimeData *mimeData); diff --git a/src/plugins/qmldesigner/designercore/include/model.h b/src/plugins/qmldesigner/designercore/include/model.h index 0d4ebbfae89..e1a187e7d7c 100644 --- a/src/plugins/qmldesigner/designercore/include/model.h +++ b/src/plugins/qmldesigner/designercore/include/model.h @@ -138,8 +138,8 @@ public: bool hasId(const QString &id) const; bool hasImport(const QString &importUrl) const; - QString generateNewId(const QString &prefixName) const; - QString generateNewId(const QString &prefixName, const QString &fallbackPrefix) const; + QString generateNewId(const QString &prefixName, const QString &fallbackPrefix = "element") const; + QString generateIdFromName(const QString &name, const QString &fallbackId = "element") const; void startDrag(QMimeData *mimeData, const QPixmap &icon); void endDrag(); diff --git a/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h b/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h index 5ebfc84f4ee..11db282e39c 100644 --- a/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h +++ b/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h @@ -97,6 +97,11 @@ public: static QVariant instanceValue(const ModelNode &modelNode, const PropertyName &name); static QString generateTranslatableText(const QString& text); + + static QString stripedTranslatableTextFunction(const QString &text); + + static QString convertToCorrectTranslatableFunction(const QString &text); + QString simplifiedTypeName() const; QStringList allStateNames() const; diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp index 98e80a6ef74..70cc08c0b32 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp @@ -1736,9 +1736,9 @@ void NodeInstanceView::handlePuppetToCreatorCommand(const PuppetToCreatorCommand } else if (command.type() == PuppetToCreatorCommand::Import3DSupport) { const QVariantMap supportMap = qvariant_cast(command.data()); emitImport3DSupportChanged(supportMap); - } else if (command.type() == PuppetToCreatorCommand::ModelAtPos) { + } else if (command.type() == PuppetToCreatorCommand::NodeAtPos) { ModelNode modelNode = modelNodeForInternalId(command.data().toUInt()); - emitModelAtPosResult(modelNode); + emitNodeAtPosResult(modelNode); } } @@ -1955,6 +1955,9 @@ void NodeInstanceView::endNanotrace() QVariant NodeInstanceView::previewImageDataForGenericNode(const ModelNode &modelNode, const ModelNode &renderNode) { + if (!modelNode.isValid()) + return {}; + ModelNodePreviewImageData imageData; // We need puppet to generate the image, which needs to be asynchronous. diff --git a/src/plugins/qmldesigner/designercore/model/abstractview.cpp b/src/plugins/qmldesigner/designercore/model/abstractview.cpp index ce31ae02e7c..d1e1428972d 100644 --- a/src/plugins/qmldesigner/designercore/model/abstractview.cpp +++ b/src/plugins/qmldesigner/designercore/model/abstractview.cpp @@ -389,7 +389,7 @@ void AbstractView::updateImport3DSupport(const QVariantMap & /*supportMap*/) } // a Quick3DModel that is picked at the requested position in the 3D Editor -void AbstractView::modelAtPosReady(const ModelNode & /*modelNode*/) {} +void AbstractView::nodeAtPosReady(const ModelNode & /*modelNode*/) {} void AbstractView::modelNodePreviewPixmapChanged(const ModelNode & /*node*/, const QPixmap & /*pixmap*/) { @@ -781,10 +781,10 @@ void AbstractView::emitImport3DSupportChanged(const QVariantMap &supportMap) model()->d->notifyImport3DSupportChanged(supportMap); } -void AbstractView::emitModelAtPosResult(const ModelNode &modelNode) +void AbstractView::emitNodeAtPosResult(const ModelNode &modelNode) { if (model()) - model()->d->notifyModelAtPosResult(modelNode); + model()->d->notifyNodeAtPosResult(modelNode); } void AbstractView::emitRewriterEndTransaction() diff --git a/src/plugins/qmldesigner/designercore/model/bindingproperty.cpp b/src/plugins/qmldesigner/designercore/model/bindingproperty.cpp index 1eedd610faa..a19e4701fcf 100644 --- a/src/plugins/qmldesigner/designercore/model/bindingproperty.cpp +++ b/src/plugins/qmldesigner/designercore/model/bindingproperty.cpp @@ -299,9 +299,10 @@ void BindingProperty::setDynamicTypeNameAndExpression(const TypeName &typeName, Internal::InternalProperty::Pointer internalProperty = internalNode()->property(name()); if (internalProperty->isBindingProperty() && internalProperty->toBindingProperty()->expression() == expression - && internalProperty->toBindingProperty()->dynamicTypeName() == typeName) + && internalProperty->toBindingProperty()->dynamicTypeName() == typeName) { return; + } } if (internalNode()->hasProperty(name()) && !internalNode()->property(name())->isBindingProperty()) diff --git a/src/plugins/qmldesigner/designercore/model/model.cpp b/src/plugins/qmldesigner/designercore/model/model.cpp index f2f954cefe7..1430c5a0c03 100644 --- a/src/plugins/qmldesigner/designercore/model/model.cpp +++ b/src/plugins/qmldesigner/designercore/model/model.cpp @@ -579,9 +579,9 @@ void ModelPrivate::notifyImport3DSupportChanged(const QVariantMap &supportMap) notifyInstanceChanges([&](AbstractView *view) { view->updateImport3DSupport(supportMap); }); } -void ModelPrivate::notifyModelAtPosResult(const ModelNode &modelNode) +void ModelPrivate::notifyNodeAtPosResult(const ModelNode &modelNode) { - notifyInstanceChanges([&](AbstractView *view) { view->modelAtPosReady(modelNode); }); + notifyInstanceChanges([&](AbstractView *view) { view->nodeAtPosReady(modelNode); }); } void ModelPrivate::notifyDragStarted(QMimeData *mimeData) @@ -1539,6 +1539,41 @@ QString Model::generateNewId(const QString &prefixName, const QString &fallbackP return newId; } +// Generate a unique camelCase id from a name +// note: this methods does the same as generateNewId(). The 2 methods should be merged into one +QString Model::generateIdFromName(const QString &name, const QString &fallbackId) const +{ + QString newId; + if (name.isEmpty()) { + newId = fallbackId; + } else { + // convert to camel case + QStringList nameWords = name.split(" "); + nameWords[0] = nameWords[0].at(0).toLower() + nameWords[0].mid(1); + for (int i = 1; i < nameWords.size(); ++i) + nameWords[i] = nameWords[i].at(0).toUpper() + nameWords[i].mid(1); + newId = nameWords.join(""); + + // if id starts with a number prepend an underscore + if (newId.at(0).isDigit()) + newId.prepend('_'); + } + + QRegularExpression rgx("\\d+$"); // matches a number at the end of a string + while (hasId(newId)) { // id exists + QRegularExpressionMatch match = rgx.match(newId); + if (match.hasMatch()) { // ends with a number, increment it + QString numStr = match.captured(); + int num = numStr.toInt() + 1; + newId = newId.mid(0, match.capturedStart()) + QString::number(num); + } else { + newId.append('1'); + } + } + + return newId; +} + void Model::startDrag(QMimeData *mimeData, const QPixmap &icon) { d->notifyDragStarted(mimeData); @@ -1562,11 +1597,6 @@ NotNullPointer> Model::projectStorage() c return d->projectStorage; } -QString Model::generateNewId(const QString &prefixName) const -{ - return generateNewId(prefixName, QStringLiteral("element")); -} - bool Model::isImportPossible(const Import &import, bool ignoreAlias, bool allowHigherVersion) const { if (imports().contains(import)) diff --git a/src/plugins/qmldesigner/designercore/model/model_p.h b/src/plugins/qmldesigner/designercore/model/model_p.h index 0606663a25b..c346be45ca2 100644 --- a/src/plugins/qmldesigner/designercore/model/model_p.h +++ b/src/plugins/qmldesigner/designercore/model/model_p.h @@ -170,7 +170,7 @@ public: void notifyUpdateActiveScene3D(const QVariantMap &sceneState); void notifyModelNodePreviewPixmapChanged(const ModelNode &node, const QPixmap &pixmap); void notifyImport3DSupportChanged(const QVariantMap &supportMap); - void notifyModelAtPosResult(const ModelNode &modelNode); + void notifyNodeAtPosResult(const ModelNode &modelNode); void notifyDragStarted(QMimeData *mimeData); void notifyDragEnded(); diff --git a/src/plugins/qmldesigner/designercore/model/qmlobjectnode.cpp b/src/plugins/qmldesigner/designercore/model/qmlobjectnode.cpp index 448a12b8274..bfa6e694ff9 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlobjectnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlobjectnode.cpp @@ -637,7 +637,7 @@ QString QmlObjectNode::generateTranslatableText([[maybe_unused]] const QString & DesignerSettingsKey::TYPE_OF_QSTR_FUNCTION).toInt()) { case 0: return QString(QStringLiteral("qsTr(\"%1\")")).arg(text); case 1: return QString(QStringLiteral("qsTrId(\"%1\")")).arg(text); - case 2: return QString(QStringLiteral("qsTranslate(\"\"\"%1\")")).arg(text); + case 2: return QString(QStringLiteral("qsTranslate(\"%1\", \"context\")")).arg(text); default: break; @@ -648,6 +648,21 @@ QString QmlObjectNode::generateTranslatableText([[maybe_unused]] const QString & #endif } +QString QmlObjectNode::stripedTranslatableTextFunction(const QString &text) +{ + const QRegularExpression regularExpressionPattern( + QLatin1String("^qsTr(|Id|anslate)\\(\"(.*)\"\\)$")); + const QRegularExpressionMatch match = regularExpressionPattern.match(text); + if (match.hasMatch()) + return match.captured(2); + return text; +} + +QString QmlObjectNode::convertToCorrectTranslatableFunction(const QString &text) +{ + return generateTranslatableText(stripedTranslatableTextFunction(text)); +} + TypeName QmlObjectNode::instanceType(const PropertyName &name) const { return nodeInstance().instanceType(name); diff --git a/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp b/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp index 52abd8601c6..97289d67cbf 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp @@ -274,7 +274,8 @@ QmlObjectNode QmlVisualNode::createQmlObjectNode(AbstractView *view, for (const auto &property : itemLibraryEntry.properties()) { if (property.type() == "binding") { - propertyBindingList.append(PropertyBindingEntry(property.name(), property.value().toString())); + const QString value = QmlObjectNode::convertToCorrectTranslatableFunction(property.value().toString()); + propertyBindingList.append(PropertyBindingEntry(property.name(), value)); } else if (property.type() == "enum") { propertyEnumList.append(PropertyBindingEntry(property.name(), property.value().toString())); } else if (property.value().toString() == QString::fromLatin1(imagePlaceHolder)) { diff --git a/src/plugins/qmldesigner/designersettings.cpp b/src/plugins/qmldesigner/designersettings.cpp index 8e2787b8806..3cd86dfbcb2 100644 --- a/src/plugins/qmldesigner/designersettings.cpp +++ b/src/plugins/qmldesigner/designersettings.cpp @@ -62,6 +62,7 @@ void DesignerSettings::fromSettings(QSettings *settings) restoreValue(settings, DesignerSettingsKey::EDIT3DVIEW_BACKGROUND_COLOR, defaultValue); restoreValue(settings, DesignerSettingsKey::EDIT3DVIEW_GRID_COLOR, "#aaaaaa"); restoreValue(settings, DesignerSettingsKey::SMOOTH_RENDERING, false); + restoreValue(settings, DesignerSettingsKey::SHOW_DEBUG_SETTINGS, false); settings->endGroup(); settings->endGroup(); diff --git a/src/plugins/qmldesigner/qmldesignerconstants.h b/src/plugins/qmldesigner/qmldesignerconstants.h index 69deab667a2..ae113f1070e 100644 --- a/src/plugins/qmldesigner/qmldesignerconstants.h +++ b/src/plugins/qmldesigner/qmldesignerconstants.h @@ -61,6 +61,7 @@ const char EDIT3D_BACKGROUND_COLOR_ACTIONS[] = "QmlDesigner.Editor3D.BackgroundC const char QML_DESIGNER_SUBFOLDER[] = "/designer/"; +const char COMPONENT_BUNDLES_FOLDER[] = "/ComponentBundles"; const char QUICK_3D_ASSETS_FOLDER[] = "/Quick3DAssets"; const char QUICK_3D_ASSET_LIBRARY_ICON_SUFFIX[] = "_libicon"; const char QUICK_3D_ASSET_ICON_DIR[] = "_icons"; diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs index 560e0a24e85..c700ce5757d 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.qbs +++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs @@ -733,8 +733,12 @@ Project { "materialbrowser/materialbrowserview.h", "materialbrowser/materialbrowserwidget.cpp", "materialbrowser/materialbrowserwidget.h", + "materialbrowser/bundleimporter.cpp", + "materialbrowser/bundleimporter.h", "materialeditor/materialeditorcontextobject.cpp", "materialeditor/materialeditorcontextobject.h", + "materialeditor/materialeditordynamicpropertiesproxymodel.cpp", + "materialeditor/materialeditordynamicpropertiesproxymodel.h", "materialeditor/materialeditorqmlbackend.cpp", "materialeditor/materialeditorqmlbackend.h", "materialeditor/materialeditortransaction.cpp", @@ -769,6 +773,8 @@ Project { "propertyeditor/colorpalettebackend.h", "propertyeditor/designerpropertymap.cpp", "propertyeditor/designerpropertymap.h", + "propertyeditor/dynamicpropertiesproxymodel.cpp", + "propertyeditor/dynamicpropertiesproxymodel.h", "propertyeditor/fileresourcesmodel.cpp", "propertyeditor/fileresourcesmodel.h", "propertyeditor/itemfiltermodel.cpp", @@ -887,6 +893,8 @@ Project { "connectioneditor/connectionviewwidget.ui", "connectioneditor/dynamicpropertiesmodel.cpp", "connectioneditor/dynamicpropertiesmodel.h", + "connectioneditor/selectiondynamicpropertiesproxymodel.cpp", + "connectioneditor/selectiondynamicpropertiesproxymodel.h", "connectioneditor/stylesheet.css", "curveeditor/curveeditorview.cpp", "curveeditor/curveeditorview.h", diff --git a/src/plugins/qtsupport/exampleslistmodel.cpp b/src/plugins/qtsupport/exampleslistmodel.cpp index a0416e17ceb..1eba8d25986 100644 --- a/src/plugins/qtsupport/exampleslistmodel.cpp +++ b/src/plugins/qtsupport/exampleslistmodel.cpp @@ -666,6 +666,8 @@ void ExampleSetModel::selectExampleSet(int index) if (getType(m_selectedExampleSetIndex) == ExampleSetModel::QtExampleSet) { QtVersion *selectedQtVersion = QtVersionManager::version(getQtId(m_selectedExampleSetIndex)); m_selectedQtTypes = selectedQtVersion->targetDeviceTypes(); + } else { + m_selectedQtTypes.clear(); } emit selectedExampleSetChanged(m_selectedExampleSetIndex); } diff --git a/src/plugins/remotelinux/genericdirectuploadservice.cpp b/src/plugins/remotelinux/genericdirectuploadservice.cpp index 7a24a485675..c7230005860 100644 --- a/src/plugins/remotelinux/genericdirectuploadservice.cpp +++ b/src/plugins/remotelinux/genericdirectuploadservice.cpp @@ -63,7 +63,7 @@ GenericDirectUploadService::GenericDirectUploadService(QObject *parent) { connect(&d->uploader, &FileTransfer::done, this, [this](const ProcessResultData &result) { QTC_ASSERT(d->state == Uploading, return); - if (result.m_error != QProcess::UnknownError) { + if (result.m_error != QProcess::UnknownError || result.m_exitCode != 0) { emit errorMessage(result.m_errorString); setFinished(); handleDeploymentDone(); diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index deaa2bd7c51..a0da4d2f1c0 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -7851,7 +7851,7 @@ void TextEditorWidget::dropEvent(QDropEvent *e) MultiTextCursor cursor = multiTextCursor(); cursor.beginEditBlock(); const QTextCursor eventCursor = cursorForPosition(e->pos()); - if (e->dropAction() == Qt::MoveAction) + if (e->dropAction() == Qt::MoveAction && e->source() == viewport()) cursor.removeSelectedText(); cursor.setCursors({eventCursor}); setMultiTextCursor(cursor); @@ -7864,6 +7864,7 @@ void TextEditorWidget::dropEvent(QDropEvent *e) insertFromMimeData(mime); delete mimeOverwrite; cursor.endEditBlock(); + e->acceptProposedAction(); } QMimeData *TextEditorWidget::duplicateMimeData(const QMimeData *source) diff --git a/src/plugins/vcsbase/vcsoutputwindow.cpp b/src/plugins/vcsbase/vcsoutputwindow.cpp index 9a4852c9205..b6911cdcd35 100644 --- a/src/plugins/vcsbase/vcsoutputwindow.cpp +++ b/src/plugins/vcsbase/vcsoutputwindow.cpp @@ -406,7 +406,7 @@ void VcsOutputWindow::append(const QString &text, MessageStyle style, bool silen void VcsOutputWindow::appendError(const QString &text) { - append(text.endsWith('\n') ? text : text + '\n', Error, false); + append((text.endsWith('\n') || text.endsWith('\r')) ? text : text + '\n', Error, false); } void VcsOutputWindow::appendWarning(const QString &text) diff --git a/src/share/3rdparty/fonts/SourceCodePro-Bold.ttf b/src/share/3rdparty/fonts/SourceCodePro-Bold.ttf index dd00982d495..5a5be2fd456 100644 Binary files a/src/share/3rdparty/fonts/SourceCodePro-Bold.ttf and b/src/share/3rdparty/fonts/SourceCodePro-Bold.ttf differ diff --git a/src/share/3rdparty/fonts/SourceCodePro-BoldIt.ttf b/src/share/3rdparty/fonts/SourceCodePro-BoldIt.ttf index 0734c6af1a3..0b6d2122e24 100644 Binary files a/src/share/3rdparty/fonts/SourceCodePro-BoldIt.ttf and b/src/share/3rdparty/fonts/SourceCodePro-BoldIt.ttf differ diff --git a/src/share/3rdparty/fonts/SourceCodePro-It.ttf b/src/share/3rdparty/fonts/SourceCodePro-It.ttf index efc777e2a5e..437cbe16d60 100644 Binary files a/src/share/3rdparty/fonts/SourceCodePro-It.ttf and b/src/share/3rdparty/fonts/SourceCodePro-It.ttf differ diff --git a/src/share/3rdparty/fonts/SourceCodePro-Regular.ttf b/src/share/3rdparty/fonts/SourceCodePro-Regular.ttf index 1decfb95af6..c58300335a7 100644 Binary files a/src/share/3rdparty/fonts/SourceCodePro-Regular.ttf and b/src/share/3rdparty/fonts/SourceCodePro-Regular.ttf differ