diff --git a/cmake/QtCreatorAPIInternal.cmake b/cmake/QtCreatorAPIInternal.cmake index fd736545b44..fb48d033249 100644 --- a/cmake/QtCreatorAPIInternal.cmake +++ b/cmake/QtCreatorAPIInternal.cmake @@ -196,9 +196,7 @@ function(qtc_enable_sanitize _target _sanitize_flags) endfunction() function(qtc_deeper_concept_diagnostic_depth _target) - if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - target_compile_options("${_target}" PUBLIC -fconcepts-diagnostics-depth=8) - endif() + target_compile_options("${_target}" PRIVATE $<$:-fconcepts-diagnostics-depth=8>) endfunction() function(qtc_add_link_flags_no_undefined target) diff --git a/dist/branding/qtdesignstudio/QtCreatorIDEBranding.cmake b/dist/branding/qtdesignstudio/QtCreatorIDEBranding.cmake index 8356743d8ac..92c2b049857 100644 --- a/dist/branding/qtdesignstudio/QtCreatorIDEBranding.cmake +++ b/dist/branding/qtdesignstudio/QtCreatorIDEBranding.cmake @@ -55,7 +55,6 @@ set(DESIGNSTUDIO_PLUGINS Texteditor UpdateInfo VcsBase - assetexporterplugin componentsplugin qmlpreviewplugin qtquickplugin) diff --git a/doc/qtcreator/src/external-resources/external-resources-qds.qdoc b/doc/qtcreator/src/external-resources/external-resources-qds.qdoc index f730b626677..91c2c8c9433 100644 --- a/doc/qtcreator/src/external-resources/external-resources-qds.qdoc +++ b/doc/qtcreator/src/external-resources/external-resources-qds.qdoc @@ -141,6 +141,10 @@ \externalpage https://doc.qt.io/qtdesignstudio/qtquick-positioning.html#using-layouts \title Using layouts */ +/*! + \externalpage https://www.qt.io/blog/qt-design-studio-4.7-released + \title Qt Design Studio 4.7 released +*/ /*! \externalpage https://www.qt.io/blog/qt-design-studio-4.6-released \title Qt Design Studio 4.6 released diff --git a/doc/qtdesignstudio/examples/doc/webinardemo.qdoc b/doc/qtdesignstudio/examples/doc/webinardemo.qdoc index 8ab2d061eeb..2ecd0e9c3da 100644 --- a/doc/qtdesignstudio/examples/doc/webinardemo.qdoc +++ b/doc/qtdesignstudio/examples/doc/webinardemo.qdoc @@ -14,8 +14,6 @@ to \QDS and to edit them to create a UI. The following sections describe some of the main points of the webinar. - Select the \uicontrol Tutorials tab to watch the webinar video for the - full details. \section1 Exporting from Adobe Photoshop diff --git a/doc/qtdesignstudio/src/qtdesignstudio-sharing-assets.qdoc b/doc/qtdesignstudio/src/qtdesignstudio-sharing-assets.qdoc index 52ab0eb0015..0f2f4bf732e 100644 --- a/doc/qtdesignstudio/src/qtdesignstudio-sharing-assets.qdoc +++ b/doc/qtdesignstudio/src/qtdesignstudio-sharing-assets.qdoc @@ -16,7 +16,7 @@ \list \li In the \uicontrol 3D or \uicontrol Navigator view, right-click a 3D component and select - \uicontrol {Export Component}. + \uicontrol {Export Bundle}. \li In the \uicontrol {Material Browser} view, right-click a material and select \uicontrol {Export Material}. \endlist @@ -25,7 +25,7 @@ To import a 3D component or material bundle, do one of the following: \list \li In the \uicontrol {3D}, \uicontrol {2D}, or \uicontrol {Navigator} view, right-click - and select \uicontrol {Import Component}. If you use this method to import a bundle of 3D + and select \uicontrol {Import Bundle}. If you use this method to import a bundle of 3D components, the 3D components are added to the 3D scene. \li In \uicontrol {Content Library} > \uicontrol {User Assets}, right-click and select \uicontrol {Import Bundle}. If you use this method to import a bundle of 3D diff --git a/doc/qtdesignstudio/src/whats new/qds-releases.qdoc b/doc/qtdesignstudio/src/whats new/qds-releases.qdoc index 4923d1b916d..cd203501b56 100644 --- a/doc/qtdesignstudio/src/whats new/qds-releases.qdoc +++ b/doc/qtdesignstudio/src/whats new/qds-releases.qdoc @@ -9,11 +9,12 @@ Read the \QDS release blog posts to see what's new in every version. - \section1 \QDS Release blog posts + \section1 \QDS release blog posts \section2 \QDS 4 \list + \li \l{Qt Design Studio 4.7 released} \li \l{Qt Design Studio 4.6 released} \li \l{Qt Design Studio 4.5.1 released} \li \l{Qt Design Studio 4.5 released} diff --git a/share/qtcreator/qmldesigner/assetsLibraryQmlSources/AssetsContextMenu.qml b/share/qtcreator/qmldesigner/assetsLibraryQmlSources/AssetsContextMenu.qml index cc4b0c17fce..3fe07f47a89 100644 --- a/share/qtcreator/qmldesigner/assetsLibraryQmlSources/AssetsContextMenu.qml +++ b/share/qtcreator/qmldesigner/assetsLibraryQmlSources/AssetsContextMenu.qml @@ -141,6 +141,15 @@ StudioControls.Menu { onTriggered: root.rootView.editAssetComponent(root.__selectedAssetPathsList[0]) } + StudioControls.MenuItem { + id: updateComponent + text: qsTr("Reimport 3D Asset") + visible: root.__fileIndex && root.__selectedAssetPathsList.length === 1 + && root.rootView.assetIsImported3d(root.__selectedAssetPathsList[0]) + height: editComponent.visible ? editComponent.implicitHeight : 0 + onTriggered: root.rootView.updateAssetComponent(root.__selectedAssetPathsList[0]) + } + StudioControls.MenuItem { id: addTexturesItem text: qsTr("Add Texture") diff --git a/share/qtcreator/qmldesigner/connectionseditor/BindingsDialogForm.qml b/share/qtcreator/qmldesigner/connectionseditor/BindingsDialogForm.qml index 9d1d74901e9..14398b7299a 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/BindingsDialogForm.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/BindingsDialogForm.qml @@ -5,6 +5,7 @@ import QtQuick import QtQuick.Controls import StudioControls as StudioControls import StudioTheme as StudioTheme +import HelperWidgets as HelperWidgets Column { id: root @@ -21,13 +22,13 @@ Column { Row { spacing: root.horizontalSpacing - PopupLabel { + HelperWidgets.PopupLabel { width: root.columnWidth text: qsTr("From") tooltip: qsTr("Sets the component and its property from which the value is copied.") } - PopupLabel { + HelperWidgets.PopupLabel { width: root.columnWidth text: qsTr("To") tooltip: qsTr("Sets the property of the selected component to which the copied value is assigned.") @@ -49,7 +50,7 @@ Column { onCurrentTypeIndexChanged: sourceNode.currentIndex = sourceNode.currentTypeIndex } - PopupLabel { + HelperWidgets.PopupLabel { width: root.columnWidth text: backend.targetNode anchors.verticalCenter: parent.verticalCenter diff --git a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml index 81f26f6a090..5e1aacee6ee 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml @@ -49,8 +49,6 @@ StudioControls.PopupDialog { ConnectionsDialogForm { id: form - parentWindow: root.window - Connections { target: root.backend function onPopupShouldClose() { diff --git a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml index 5ca4ed28d25..7e1330cb2f5 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml @@ -6,6 +6,8 @@ import QtQuick.Controls import HelperWidgets as HelperWidgets import StudioControls as StudioControls import StudioTheme as StudioTheme +import ScriptsEditor as ScriptsEditor +import ScriptEditorBackend Column { id: root @@ -16,8 +18,7 @@ Column { property var backend - property bool keepOpen: expressionDialogLoader.visible - property Window parentWindow: null + property bool keepOpen: scriptEditor.keepOpen width: parent.width spacing: root.verticalSpacing @@ -29,13 +30,13 @@ Column { Row { spacing: root.horizontalSpacing - PopupLabel { + HelperWidgets.PopupLabel { width: root.columnWidth text: qsTr("Signal") tooltip: qsTr("Sets an interaction method that connects to the Target component.") } - PopupLabel { + HelperWidgets.PopupLabel { width: root.columnWidth text: qsTr("Action") tooltip: qsTr("Sets an action that is associated with the selected Target component's Signal.") @@ -58,259 +59,40 @@ Column { onCurrentTypeIndexChanged: signal.currentIndex = signal.currentTypeIndex } - StudioControls.TopLevelComboBox { + ScriptsEditor.ActionsComboBox { id: action - style: StudioTheme.Values.connectionPopupControlStyle + width: root.columnWidth - textRole: "text" - valueRole: "value" - ///model.getData(currentIndex, "role") - property int indexFromBackend: indexOfValue(backend.actionType) - onIndexFromBackendChanged: action.currentIndex = action.indexFromBackend - onActivated: backend.changeActionType(action.currentValue) - - model: ListModel { - ListElement { - value: ConnectionModelStatementDelegate.CallFunction - text: qsTr("Call Function") - enabled: true - } - ListElement { - value: ConnectionModelStatementDelegate.Assign - text: qsTr("Assign") - enabled: true - } - ListElement { - value: ConnectionModelStatementDelegate.ChangeState - text: qsTr("Change State") - enabled: true - } - ListElement { - value: ConnectionModelStatementDelegate.SetProperty - text: qsTr("Set Property") - enabled: true - } - ListElement { - value: ConnectionModelStatementDelegate.PrintMessage - text: qsTr("Print Message") - enabled: true - } - ListElement { - value: ConnectionModelStatementDelegate.Custom - text: qsTr("Custom") - enabled: false - } - } + backend: root.backend } } - StatementEditor { - width: root.width - actionType: action.currentValue ?? ConnectionModelStatementDelegate.Custom + ScriptsEditor.ScriptEditorForm { + id: scriptEditor + + anchors.left: parent.left + anchors.right: parent.right + horizontalSpacing: root.horizontalSpacing + verticalSpacing: root.verticalSpacing columnWidth: root.columnWidth - statement: backend.okStatement + spacing: root.spacing + backend: root.backend - spacing: root.verticalSpacing - } - HelperWidgets.AbstractButton { - style: StudioTheme.Values.connectionPopupButtonStyle - width: 160 - buttonIcon: qsTr("Add Condition") - tooltip: qsTr("Sets a logical condition for the selected Signal. It works with the properties of the Target component.") - iconSize: StudioTheme.Values.baseFontSize - iconFontFamily: StudioTheme.Constants.font.family - anchors.horizontalCenter: parent.horizontalCenter - visible: action.currentValue !== ConnectionModelStatementDelegate.Custom && !backend.hasCondition + currentAction: action.currentValue ?? StatementDelegate.Custom - onClicked: backend.addCondition() - } - - HelperWidgets.AbstractButton { - style: StudioTheme.Values.connectionPopupButtonStyle - width: 160 - buttonIcon: qsTr("Remove Condition") - tooltip: qsTr("Removes the logical condition for the Target component.") - iconSize: StudioTheme.Values.baseFontSize - iconFontFamily: StudioTheme.Constants.font.family - anchors.horizontalCenter: parent.horizontalCenter - visible: action.currentValue !== ConnectionModelStatementDelegate.Custom && backend.hasCondition - - onClicked: backend.removeCondition() - } - - ExpressionBuilder { - style: StudioTheme.Values.connectionPopupControlStyle - width: root.width - - visible: backend.hasCondition - model: backend.conditionListModel - - onRemove: function(index) { - //console.log("remove", index) - backend.conditionListModel.removeToken(index) - } - - onUpdate: function(index, value) { - //console.log("update", index, value) - backend.conditionListModel.updateToken(index, value) - } - - onAdd: function(value) { - //console.log("add", value) - backend.conditionListModel.appendToken(value) - } - - onInsert: function(index, value, type) { - //console.log("insert", index, value, type) - if (type === ConditionListModel.Intermediate) - backend.conditionListModel.insertIntermediateToken(index, value) - else if (type === ConditionListModel.Shadow) - backend.conditionListModel.insertShadowToken(index, value) - else - backend.conditionListModel.insertToken(index, value) - } - - onSetValue: function(index, value) { - //console.log("setValue", index, value) - backend.conditionListModel.setShadowToken(index, value) - } - } - - HelperWidgets.AbstractButton { - style: StudioTheme.Values.connectionPopupButtonStyle - width: 160 - buttonIcon: qsTr("Add Else Statement") - tooltip: qsTr("Sets an alternate condition for the previously defined logical condition.") - iconSize: StudioTheme.Values.baseFontSize - iconFontFamily: StudioTheme.Constants.font.family - anchors.horizontalCenter: parent.horizontalCenter - visible: action.currentValue !== ConnectionModelStatementDelegate.Custom - && backend.hasCondition && !backend.hasElse - - onClicked: backend.addElse() - } - - HelperWidgets.AbstractButton { - style: StudioTheme.Values.connectionPopupButtonStyle - width: 160 - buttonIcon: qsTr("Remove Else Statement") - tooltip: qsTr("Removes the alternate logical condition for the previously defined logical condition.") - iconSize: StudioTheme.Values.baseFontSize - iconFontFamily: StudioTheme.Constants.font.family - anchors.horizontalCenter: parent.horizontalCenter - visible: action.currentValue !== ConnectionModelStatementDelegate.Custom - && backend.hasCondition && backend.hasElse - - onClicked: backend.removeElse() - } - - //Else Statement - StatementEditor { - width: root.width - actionType: action.currentValue ?? ConnectionModelStatementDelegate.Custom - horizontalSpacing: root.horizontalSpacing - columnWidth: root.columnWidth - statement: backend.koStatement - backend: root.backend - spacing: root.verticalSpacing - visible: action.currentValue !== ConnectionModelStatementDelegate.Custom - && backend.hasCondition && backend.hasElse - } - - // code preview toolbar - Column { - id: miniToolbarEditor - width: parent.width - spacing: -2 - - Rectangle { - id: miniToolbar - width: parent.width - height: editorButton.height + 2 - radius: 4 - z: -1 - color: StudioTheme.Values.themeConnectionEditorMicroToolbar - - Row { - spacing: 2 - HelperWidgets.AbstractButton { - id: editorButton - style: StudioTheme.Values.microToolbarButtonStyle - buttonIcon: StudioTheme.Constants.codeEditor_medium - tooltip: qsTr("Write the conditions for the components and the signals manually.") - onClicked: expressionDialogLoader.show() - } - HelperWidgets.AbstractButton { - id: jumpToCodeButton - style: StudioTheme.Values.microToolbarButtonStyle - buttonIcon: StudioTheme.Constants.jumpToCode_medium - tooltip: qsTr("Jump to the code.") - onClicked: backend.jumpToCode() - } - } - } - - // Editor - Rectangle { - id: editor - width: parent.width - height: 150 - color: StudioTheme.Values.themeConnectionCodeEditor - - Text { - id: code - anchors.fill: parent - anchors.margins: 4 - text: backend.indentedSource - color: StudioTheme.Values.themeTextColor - font.pixelSize: StudioTheme.Values.myFontSize - wrapMode: Text.Wrap - horizontalAlignment: code.lineCount === 1 ? Text.AlignHCenter : Text.AlignLeft - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - } - - Loader { - id: expressionDialogLoader - parent: editor - anchors.fill: parent - visible: false - active: visible - - function show() { - expressionDialogLoader.visible = true - } - - sourceComponent: Item { - id: bindingEditorParent - - Component.onCompleted: { - bindingEditor.showWidget() - bindingEditor.text = backend.source - bindingEditor.showControls(false) - bindingEditor.setMultilne(true) - bindingEditor.updateWindowName() - } - - ActionEditor { - id: bindingEditor - - onRejected: { - bindingEditor.hideWidget() - expressionDialogLoader.visible = false - } - - onAccepted: { - backend.setNewSource(bindingEditor.text) - bindingEditor.hideWidget() - expressionDialogLoader.visible = false - } - } - } - } - } + itemTooltip: qsTr("Sets the component that is affected by the action of the Target component's Signal.") + methodTooltip: qsTr("Sets the item component's method that is affected by the Target component's Signal.") + fromTooltip: qsTr("Sets the component and its property from which the value is copied when the Target component initiates the Signal.") + toTooltip: qsTr("Sets the component and its property to which the copied value is assigned when the Target component initiates the Signal.") + addConditionTooltip: qsTr("Sets a logical condition for the selected Signal. It works with the properties of the Target component.") + removeConditionTooltip: qsTr("Removes the logical condition for the Target component.") + stateGroupTooltip: qsTr("Sets a State Group that is accessed when the Target component initiates the Signal.") + stateTooltip: qsTr("Sets a State within the assigned State Group that is accessed when the Target component initiates the Signal.") + propertyTooltip: qsTr("Sets the property of the component that is affected by the action of the Target component's Signal.") + valueTooltip: qsTr("Sets the value of the property of the component that is affected by the action of the Target component's Signal.") + messageTooltip: qsTr("Sets a text that is printed when the Signal of the Target component initiates.") } } diff --git a/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialogForm.qml b/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialogForm.qml index eee45df8605..7111fbb40e9 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialogForm.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialogForm.qml @@ -5,6 +5,7 @@ import QtQuick import QtQuick.Controls import StudioControls as StudioControls import StudioTheme as StudioTheme +import HelperWidgets as HelperWidgets Column { id: root @@ -18,7 +19,7 @@ Column { width: parent.width spacing: root.verticalSpacing - PopupLabel { + HelperWidgets.PopupLabel { text: qsTr("Type") tooltip: qsTr("Sets the category of the Local Custom Property.") } @@ -37,13 +38,13 @@ Column { Row { spacing: root.horizontalSpacing - PopupLabel { + HelperWidgets.PopupLabel { width: root.columnWidth text: qsTr("Name") tooltip: qsTr("Sets a name for the Local Custom Property.") } - PopupLabel { + HelperWidgets.PopupLabel { width: root.columnWidth text: qsTr("Value") tooltip: qsTr("Sets a valid Local Custom Property value.") diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryEffectsView.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryEffectsView.qml index ca8650d116a..87777c04463 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryEffectsView.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryEffectsView.qml @@ -109,29 +109,35 @@ HelperWidgets.ScrollView { id: infoText text: { - if (!ContentLibraryBackend.rootView.isQt6Project) + if (!ContentLibraryBackend.rootView.isQt6Project) { qsTr("Content Library effects are not supported in Qt5 projects.") - else if (!ContentLibraryBackend.rootView.hasQuick3DImport) - qsTr('To use Content Library, first - add the QtQuick3D module in the Components view.') - .arg(StudioTheme.Values.themeInteraction) - else if (!ContentLibraryBackend.effectsModel.hasRequiredQuick3DImport) + } else if (!ContentLibraryBackend.rootView.hasQuick3DImport) { + qsTr('To use Content Library effects, add the QtQuick3D module and the View3D + component in the Components view, or click + + here.').arg(StudioTheme.Values.themeInteraction) + } else if (!ContentLibraryBackend.effectsModel.hasRequiredQuick3DImport) { qsTr("To use Content Library, version 6.4 or later of the QtQuick3D module is required.") - else if (!ContentLibraryBackend.rootView.hasMaterialLibrary) + } else if (!ContentLibraryBackend.rootView.hasMaterialLibrary) { qsTr("Content Library is disabled inside a non-visual component.") - else if (!ContentLibraryBackend.effectsModel.bundleExists) + } else if (!ContentLibraryBackend.effectsModel.bundleExists) { qsTr("No effects available.") - else if (!searchBox.isEmpty()) + } else if (!searchBox.isEmpty()) { qsTr("No match found.") - else + } else { "" + } } textFormat: Text.RichText color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.baseFontSize topPadding: 10 leftPadding: 10 + rightPadding: 10 visible: ContentLibraryBackend.effectsModel.isEmpty + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + width: root.width onLinkActivated: ContentLibraryBackend.rootView.addQtQuick3D() } diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml index 0ace2e7efd0..acdcdb21c9d 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml @@ -114,30 +114,34 @@ HelperWidgets.ScrollView { id: infoText text: { - if (!ContentLibraryBackend.rootView.isQt6Project) + if (!ContentLibraryBackend.rootView.isQt6Project) { qsTr("Content Library materials are not supported in Qt5 projects.") - else if (!ContentLibraryBackend.rootView.hasQuick3DImport) - qsTr('To use Content Library, first - add the QtQuick3D module in the Components view.') - .arg(StudioTheme.Values.themeInteraction) - else if (!root.materialsModel.hasRequiredQuick3DImport) + } else if (!ContentLibraryBackend.rootView.hasQuick3DImport) { + qsTr('To use Content Library materials, add the QtQuick3D module and the View3D + component in the Components view, or click + + here.').arg(StudioTheme.Values.themeInteraction) + } else if (!root.materialsModel.hasRequiredQuick3DImport) { qsTr("To use Content Library, version 6.3 or later of the QtQuick3D module is required.") - else if (!ContentLibraryBackend.rootView.hasMaterialLibrary) + } else if (!ContentLibraryBackend.rootView.hasMaterialLibrary) { qsTr("Content Library is disabled inside a non-visual component.") - else if (!root.materialsModel.bundleExists) + } else if (!root.materialsModel.bundleExists) { qsTr("No materials available. Make sure you have an internet connection.") - else if (!searchBox.isEmpty()) + } else if (!searchBox.isEmpty()) { qsTr("No match found.") - else + } else { "" + } } textFormat: Text.RichText color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.baseFontSize topPadding: 10 leftPadding: 10 + rightPadding: 10 wrapMode: Text.WordWrap width: root.width - x + horizontalAlignment: Text.AlignHCenter onLinkActivated: ContentLibraryBackend.rootView.addQtQuick3D() } diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryUserView.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryUserView.qml index 29d662615c5..2c371385dd1 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryUserView.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryUserView.qml @@ -213,26 +213,33 @@ Item { text: { let categoryName = (categoryTitle === "3D") ? categoryTitle + " assets" : categoryTitle.toLowerCase() - if (!ContentLibraryBackend.rootView.isQt6Project) + if (!ContentLibraryBackend.rootView.isQt6Project) { qsTr("Content Library is not supported in Qt5 projects.") - else if (!ContentLibraryBackend.rootView.hasQuick3DImport && categoryTitle !== "Textures") - qsTr(`To use %1, first - add the QtQuick3D module in the Components view.`) - .arg(categoryName) - .arg(StudioTheme.Values.themeInteraction) - else if (!ContentLibraryBackend.rootView.hasMaterialLibrary && categoryTitle !== "Textures") + } else if (!ContentLibraryBackend.rootView.hasQuick3DImport && categoryTitle !== "Textures") { + qsTr('To use %1, add the QtQuick3D module and the View3D + component in the Components view, or click + + here.') + .arg(categoryName) + .arg(StudioTheme.Values.themeInteraction) + } else if (!ContentLibraryBackend.rootView.hasMaterialLibrary && categoryTitle !== "Textures") { qsTr("Content Library is disabled inside a non-visual component.") - else if (categoryEmpty) + } else if (categoryEmpty) { qsTr("There are no "+ categoryName + " in the User Assets.") - else + } else { "" + } } textFormat: Text.RichText color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.baseFontSize topPadding: 10 leftPadding: 10 + rightPadding: 10 visible: infoText.text !== "" + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + width: root.width onLinkActivated: ContentLibraryBackend.rootView.addQtQuick3D() } diff --git a/share/qtcreator/qmldesigner/designsystem/Main.qml b/share/qtcreator/qmldesigner/designsystem/Main.qml index 7c444c44e4f..9695f95270a 100644 --- a/share/qtcreator/qmldesigner/designsystem/Main.qml +++ b/share/qtcreator/qmldesigner/designsystem/Main.qml @@ -13,6 +13,7 @@ import DesignSystemControls as DSC import StudioControls as StudioControls import StudioTheme as StudioTheme +import StudioQuickUtils Rectangle { id: root @@ -33,6 +34,11 @@ Rectangle { readonly property int iconSize: 16 readonly property int leftPadding: 14 + readonly property int rightPadding: 14 + + property var customStyle: StudioTheme.ControlStyle { + border.idle: root.borderColor + } width: 400 height: 400 @@ -41,6 +47,10 @@ Rectangle { function loadModel(name) { root.currentCollectionName = name tableView.model = DesignSystemBackend.dsInterface.model(name) + + topLeftCell.visible = tableView.model.columnCount() + createModeButton.enabled = tableView.model.rowCount() + modelConnections.target = tableView.model } function groupString(gt) { @@ -56,6 +66,178 @@ Rectangle { return "unknow_group" } + function setValue(value: var, row: int, column: int, isBinding: bool): bool { + console.log("setValue(", value, row, column, isBinding, ")") + return tableView.model.setData(tableView.index(row, column), + DesignSystemBackend.dsInterface.createThemeProperty("", value, isBinding), + Qt.EditRole) + } + + Connections { + id: modelConnections + ignoreUnknownSignals: true // model might initially be null + + function onModelReset() { + topLeftCell.visible = tableView.model.columnCount() + createModeButton.enabled = tableView.model.rowCount() + } + } + + StudioControls.Dialog { + id: renameCollectionDialog + title: qsTr("Rename collection") + width: Math.min(300, root.width) + closePolicy: Popup.CloseOnEscape + anchors.centerIn: parent + modal: true + + onOpened: renameCollectionTextField.forceActiveFocus() + + contentItem: Column { + spacing: 20 + width: parent.width + + StudioControls.TextField { + id: renameCollectionTextField + actionIndicatorVisible: false + translationIndicatorVisible: false + width: parent.width + + onAccepted: renameCollectionDialog.accept() + onRejected: renameCollectionDialog.reject() + } + + Row { + spacing: 10 + anchors.right: parent.right + + StudioControls.DialogButton { + text: qsTr("Rename") + enabled: (renameCollectionDialog.previousString !== renameCollectionTextField.text) + onClicked: renameCollectionDialog.accept() + } + + StudioControls.DialogButton { + text: qsTr("Cancel") + onClicked: renameCollectionDialog.reject() + } + } + } + + onAccepted: { + DesignSystemBackend.dsInterface.renameCollection(collectionsComboBox.currentText, + renameCollectionTextField.text) + renameCollectionDialog.close() + } + + property string previousString + + onAboutToShow: { + renameCollectionTextField.text = collectionsComboBox.currentText + renameCollectionDialog.previousString = collectionsComboBox.currentText + } + } + + StudioControls.Dialog { + id: createCollectionDialog + title: qsTr("Create collection") + width: Math.min(300, root.width) + closePolicy: Popup.CloseOnEscape + anchors.centerIn: parent + modal: true + + onOpened: createCollectionTextField.forceActiveFocus() + + contentItem: Column { + spacing: 20 + width: parent.width + + StudioControls.TextField { + id: createCollectionTextField + actionIndicatorVisible: false + translationIndicatorVisible: false + width: parent.width + + text: qsTr("NewCollection") + + onAccepted: createCollectionDialog.accept() + onRejected: createCollectionDialog.reject() + } + + Row { + spacing: 10 + anchors.right: parent.right + + StudioControls.DialogButton { + text: qsTr("Create") + onClicked: createCollectionDialog.accept() + } + + StudioControls.DialogButton { + text: qsTr("Cancel") + onClicked: createCollectionDialog.reject() + } + } + } + + onAccepted: { + DesignSystemBackend.dsInterface.addCollection(createCollectionTextField.text) + root.loadModel(createCollectionTextField.text) + collectionsComboBox.currentIndex = collectionsComboBox.indexOfValue(createCollectionTextField.text) + createCollectionDialog.close() + } + } + + StudioControls.Dialog { + id: removeCollectionDialog + title: qsTr("Remove collection") + width: Math.min(300, root.width) + closePolicy: Popup.CloseOnEscape + anchors.centerIn: parent + modal: true + + onOpened: removeCollectionDialog.forceActiveFocus() + + contentItem: Column { + spacing: 20 + width: parent.width + + Text { + id: warningText + + text: qsTr("Are you sure? The action cannot be undone.") + color: StudioTheme.Values.themeTextColor + wrapMode: Text.WordWrap + width: parent.width + } + + Row { + spacing: 10 + anchors.right: parent.right + + StudioControls.DialogButton { + text: qsTr("Remove") + onClicked: removeCollectionDialog.accept() + } + + StudioControls.DialogButton { + text: qsTr("Cancel") + onClicked: removeCollectionDialog.reject() + } + } + } + + onAccepted: { + let currentCollectionName = collectionsComboBox.currentText + let currentCollectionIndex = collectionsComboBox.currentIndex + let previousCollectionIndex = (currentCollectionIndex === 0) ? 1 : currentCollectionIndex - 1 + + root.loadModel(collectionsComboBox.textAt(previousCollectionIndex)) + DesignSystemBackend.dsInterface.removeCollection(currentCollectionName) + removeCollectionDialog.close() + } + } + Rectangle { id: toolBar @@ -77,6 +259,15 @@ Rectangle { onActivated: root.loadModel(collectionsComboBox.currentText) } + StudioControls.ComboBox { + id: themesComboBox + style: StudioTheme.Values.viewBarControlStyle + anchors.verticalCenter: parent.verticalCenter + actionIndicatorVisible: false + model: tableView.model.themeNames + onActivated: tableView.model.setActiveTheme(themesComboBox.currentText) + } + StudioControls.IconTextButton { id: moreButton anchors.verticalCenter: parent.verticalCenter @@ -84,26 +275,33 @@ Rectangle { checkable: true checked: moreMenu.visible - onClicked: moreMenu.popup(0, moreButton.height) + onToggled: { + if (moreMenu.visible) + moreMenu.close() + else + moreMenu.popup(0, moreButton.height) + } StudioControls.Menu { id: moreMenu + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent + StudioControls.MenuItem { text: qsTr("Rename") - onTriggered: console.log(">>> Rename collection") + onTriggered: renameCollectionDialog.open() } StudioControls.MenuItem { text: qsTr("Delete") - onTriggered: console.log(">>> Delete collection") + onTriggered: removeCollectionDialog.open() } StudioControls.MenuSeparator {} StudioControls.MenuItem { text: qsTr("Create collection") - onTriggered: console.log(">>> Create collection") + onTriggered: createCollectionDialog.open() } } } @@ -127,9 +325,15 @@ Rectangle { required property bool editing + required property var resolvedValue + required property bool isActive required property bool isBinding required property var propertyValue + property bool creatingBinding: false + + readonly property bool bindingEditor: isBinding || creatingBinding + color: root.backgroundColor implicitWidth: root.cellWidth implicitHeight: root.cellHeight @@ -140,18 +344,24 @@ Rectangle { } component DataCell: Cell { + id: dataCell + HoverHandler { id: cellHoverHandler } - StudioControls.IconIndicator { - icon: isBinding ? StudioTheme.Constants.actionIconBinding - : StudioTheme.Constants.actionIcon + DSC.BindingIndicator { + icon.text: dataCell.isBinding ? StudioTheme.Constants.actionIconBinding + : StudioTheme.Constants.actionIcon + icon.color: dataCell.isBinding ? StudioTheme.Values.themeInteraction + : StudioTheme.Values.themeTextColor + anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right anchors.rightMargin: 10 - visible: cellHoverHandler.hovered + visible: !dataCell.editing && (cellHoverHandler.hovered || dataCell.isBinding) onClicked: { tableView.closeEditor() + menu.show(dataCell.row, dataCell.column) } } } @@ -171,37 +381,39 @@ Rectangle { leftPadding: root.leftPadding horizontalAlignment: TextInput.AlignLeft verticalAlignment: TextInput.AlignVCenter - color: StudioTheme.Values.themeTextColor - text: stringDelegate.display + color: stringDelegate.isBinding ? StudioTheme.Values.themeInteraction + : StudioTheme.Values.themeTextColor + text: stringDelegate.resolvedValue visible: !stringDelegate.editing } + // This edit delegate combines the binding editor and string value editor TableView.editDelegate: DSC.TextField { id: stringEditDelegate + style: root.customStyle + anchors.fill: parent leftPadding: root.leftPadding - // Only apply more to right padding when hovered - //rightPadding - horizontalAlignment: TextInput.AlignLeft verticalAlignment: TextInput.AlignVCenter - text: stringDelegate.display + text: stringDelegate.bindingEditor ? stringDelegate.propertyValue + : stringDelegate.resolvedValue + Component.onCompleted: stringEditDelegate.selectAll() + Component.onDestruction: stringDelegate.creatingBinding = false TableView.onCommit: { - console.log("onCommit", stringEditDelegate.text) - let index = TableView.view.index(stringDelegate.row, stringDelegate.column) - var prop = DesignSystemBackend.dsInterface.createThemeProperty("", - stringEditDelegate.text, - stringDelegate.isBinding) - TableView.view.model.setData(index, prop, Qt.EditRole) + root.setValue(stringEditDelegate.text, + stringDelegate.row, + stringDelegate.column, + stringDelegate.bindingEditor) } } - Component.onCompleted: console.log("DelegateChoice - string", stringDelegate.display) + //Component.onCompleted: console.log("DelegateChoice - string", stringDelegate.resolvedValue) } } @@ -214,38 +426,107 @@ Rectangle { Text { anchors.fill: parent leftPadding: root.leftPadding + rightPadding: root.rightPadding + horizontalAlignment: TextInput.AlignLeft verticalAlignment: TextInput.AlignVCenter - color: StudioTheme.Values.themeTextColor - text: numberDelegate.display - visible: !numberDelegate.editing + color: numberDelegate.isBinding ? StudioTheme.Values.themeInteraction + : StudioTheme.Values.themeTextColor + // -128 is the value of QLocale::FloatingPointShortest + text: Number(numberDelegate.resolvedValue).toLocaleString(Utils.locale, 'f', -128) } - TableView.editDelegate: SpinBox { - id: numberEditDelegate - + // This edit delegate has two different controls, one for number editing and one for + // binding editing. Depending on the mode one is hidden and the other is shown. + TableView.editDelegate: FocusScope { + id: numberEditDelegateFocusScope anchors.fill: parent - leftPadding: root.leftPadding - value: numberDelegate.display - from: -1000 // TODO define min/max - to: 1000 - editable: true + property bool alreadyCommited: false + + DSC.SpinBox { + id: numberEditDelegate + + property real previousValue: 0 + + style: root.customStyle + + anchors.fill: parent + + realValue: numberDelegate.resolvedValue + realFrom: -1000 // TODO define min/max + realTo: 1000 + editable: true + decimals: 2 + + focus: !numberDelegate.bindingEditor + visible: !numberDelegate.bindingEditor + + Component.onCompleted: { + numberEditDelegate.previousValue = numberDelegate.resolvedValue + numberEditDelegate.contentItem.selectAll() + } + Component.onDestruction: { + if (numberEditDelegateFocusScope.alreadyCommited || numberDelegate.bindingEditor) + return + + let val = numberEditDelegate.realValue + + if (numberEditDelegate.previousValue === val) + return + + root.setValue(val, + numberDelegate.row, + numberDelegate.column, + numberDelegate.bindingEditor) + } + } + + DSC.TextField { + id: numberBindingEditDelegate + + style: root.customStyle + + anchors.fill: parent + leftPadding: root.leftPadding + rightPadding: root.rightPadding + + horizontalAlignment: TextInput.AlignLeft + verticalAlignment: TextInput.AlignVCenter + + text: numberDelegate.propertyValue + + focus: numberDelegate.bindingEditor + visible: numberDelegate.bindingEditor + + Component.onCompleted: numberBindingEditDelegate.selectAll() + Component.onDestruction: numberDelegate.creatingBinding = false + } TableView.onCommit: { - let val = numberEditDelegate.valueFromText(numberEditDelegate.contentItem.text, - numberEditDelegate.locale) - console.log("onCommit", val) - let index = TableView.view.index(numberDelegate.row, numberDelegate.column) - var prop = DesignSystemBackend.dsInterface.createThemeProperty("", - val, - numberDelegate.isBinding) - TableView.view.model.setData(index, prop, Qt.EditRole) + // By default assume binding edit delegate is used + let val = numberBindingEditDelegate.text + + // If binding editor isn't used then the SpinBox value needs to be written + if (!numberDelegate.bindingEditor) { + numberEditDelegate.valueFromText(numberEditDelegate.contentItem.text, + numberEditDelegate.locale) + // Don't use return value of valueFromText as it is of type int. + // Internally the float value is set on realValue property. + val = numberEditDelegate.realValue + } + + root.setValue(val, + numberDelegate.row, + numberDelegate.column, + numberDelegate.bindingEditor) + + numberEditDelegateFocusScope.alreadyCommited = true } } - Component.onCompleted: console.log("DelegateChoice - number", display) + //Component.onCompleted: console.log("DelegateChoice - number", numberDelegate.resolvedValue) } } @@ -262,20 +543,62 @@ Rectangle { anchors.verticalCenter: parent.verticalCenter anchors.leftMargin: root.leftPadding - checked: flagDelegate.display - text: flagDelegate.display + checked: flagDelegate.resolvedValue + text: flagDelegate.resolvedValue + + labelColor: flagDelegate.isBinding ? StudioTheme.Values.themeInteraction + : StudioTheme.Values.themeTextColor + + readonly: flagDelegate.isBinding onToggled: { - console.log("onCommit", flagEditDelegate.checked) - let index = flagDelegate.TableView.view.index(flagDelegate.row, flagDelegate.column) - var prop = DesignSystemBackend.dsInterface.createThemeProperty("", - flagEditDelegate.checked, - flagEditDelegate.isBinding) - flagDelegate.TableView.view.model.setData(index, prop, Qt.EditRole) + root.setValue(flagEditDelegate.checked, + flagDelegate.row, + flagDelegate.column, + false) } } - Component.onCompleted: console.log("DelegateChoice - bool", flagDelegate.display) + // Dummy item to show forbidden cursor when hovering over bound switch control + Item { + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: root.leftPadding + + width: flagEditDelegate.indicator.width + height: flagEditDelegate.indicator.height + + visible: !flagEditDelegate.enabled + + HoverHandler { + enabled: !flagEditDelegate.enabled + cursorShape: Qt.ForbiddenCursor + } + } + + TableView.editDelegate: DSC.TextField { + id: flagBindingEditDelegate + + anchors.fill: parent + leftPadding: root.leftPadding + + horizontalAlignment: TextInput.AlignLeft + verticalAlignment: TextInput.AlignVCenter + + text: flagDelegate.bindingEditor ? flagDelegate.propertyValue + : flagDelegate.resolvedValue + Component.onCompleted: flagBindingEditDelegate.selectAll() + Component.onDestruction: flagDelegate.creatingBinding = false + + TableView.onCommit: { + root.setValue(flagBindingEditDelegate.text, + flagDelegate.row, + flagDelegate.column, + true) + } + } + + //Component.onCompleted: console.log("DelegateChoice - bool", flagDelegate.resolvedValue) } } @@ -286,18 +609,20 @@ Rectangle { id: colorDelegate Row { + id: colorDelegateRow anchors.fill: parent leftPadding: root.leftPadding spacing: 8 Rectangle { + id: colorDelegatePreview anchors.verticalCenter: parent.verticalCenter width: 20 height: 20 - color: colorDelegate.display + color: colorDelegate.resolvedValue border.color: "black" - border.width: 1 + border.width: StudioTheme.Values.border Image { anchors.fill: parent @@ -309,38 +634,338 @@ Rectangle { MouseArea { id: colorMouseArea anchors.fill: parent + enabled: !colorDelegate.isBinding - onClicked: { - if (popupDialog.visibility) { - popupDialog.close() + cursorShape: colorDelegate.isBinding ? Qt.ForbiddenCursor + : Qt.PointingHandCursor + + function togglePopup() { + if (colorPopup.visibility) { + colorPopup.close() } else { - popupDialog.ensureLoader() - popupDialog.show(colorDelegate) + colorPopup.modelIndex = tableView.index(colorDelegate.row, colorDelegate.column) - if (loader.status === Loader.Ready) - loader.item.originalColor = root.color + colorPopup.ensureLoader() + colorPopup.show(colorDelegate) + + if (loader.status === Loader.Ready) { + loader.item.color = colorDelegate.resolvedValue + loader.item.originalColor = colorDelegate.resolvedValue + } } + tableView.closeEditor() colorMouseArea.forceActiveFocus() } + + onClicked: colorMouseArea.togglePopup() } } Text { height: parent.height verticalAlignment: Qt.AlignVCenter - color: StudioTheme.Values.themeTextColor - text: colorDelegate.propertyValue + color: colorDelegate.isBinding ? StudioTheme.Values.themeInteraction + : StudioTheme.Values.themeTextColor + text: colorDelegate.resolvedValue } } - Component.onCompleted: console.log("DelegateChoice - color", colorDelegate.display) + // This edit delegate combines the binding editor and hex value editor + TableView.editDelegate: DSC.TextField { + id: colorEditDelegate + + anchors.fill: parent + leftPadding: root.leftPadding + + (colorDelegate.bindingEditor ? 0 + : (colorDelegatePreview.width + + colorDelegateRow.spacing)) + + horizontalAlignment: TextInput.AlignLeft + verticalAlignment: TextInput.AlignVCenter + + text: colorDelegate.bindingEditor ? colorDelegate.propertyValue + : colorDelegate.resolvedValue + + RegularExpressionValidator { + id: hexValidator + regularExpression: /#[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?/g + } + + validator: colorDelegate.bindingEditor ? null : hexValidator + + Component.onCompleted: colorEditDelegate.selectAll() + Component.onDestruction: colorDelegate.creatingBinding = false + + TableView.onCommit: { + root.setValue(colorEditDelegate.text, + colorDelegate.row, + colorDelegate.column, + colorDelegate.bindingEditor) + } + + // Extra color Rectangle to be shown on top of edit delegate + Rectangle { + anchors.left: parent.left + anchors.leftMargin: root.leftPadding + anchors.verticalCenter: parent.verticalCenter + + width: colorDelegatePreview.width + height: colorDelegatePreview.height + color: colorDelegate.resolvedValue + border.color: "black" + border.width: StudioTheme.Values.border + + visible: !colorDelegate.bindingEditor + + Image { + anchors.fill: parent + source: "qrc:/navigator/icon/checkers.png" + fillMode: Image.Tile + z: -1 + } + + MouseArea { + anchors.fill: parent + enabled: !colorDelegate.isBinding + + onClicked: colorMouseArea.togglePopup() + } + } + } + + //Component.onCompleted: console.log("DelegateChoice - color", colorDelegate.resolvedValue) } } } - StudioControls.PopupDialog { - id: popupDialog + StudioControls.Menu { + id: menu + property var modelIndex + + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + + function show(row, column) { + menu.modelIndex = tableView.index(row, column) + menu.popup() + } + + StudioControls.MenuItem { + enabled: { + if (menu.modelIndex) + return tableView.itemAtIndex(menu.modelIndex).isBinding + + return false + } + text: qsTr("Reset") + onTriggered: { + let data = tableView.model.data(menu.modelIndex, CollectionModel.ResolvedValueRole) + var prop = DesignSystemBackend.dsInterface.createThemeProperty("", data, false) + let result = tableView.model.setData(menu.modelIndex, prop, Qt.EditRole) + } + } + + StudioControls.MenuItem { + text: qsTr("Set Binding") + onTriggered: { + let cell = tableView.itemAtIndex(menu.modelIndex) + cell.creatingBinding = true + tableView.edit(menu.modelIndex) + } + } + } + + StudioControls.Menu { + id: modeMenu + + property int column + + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + + function show(column) { // Qt.Horizontal + tableView.closeEditor() // Close all currently visible edit delegates + modeMenu.column = column + modeMenu.popup() + } + + StudioControls.MenuItem { + enabled: false + text: qsTr("Duplicate mode") + onTriggered: { console.log("Duplicate mode") } + } + + StudioControls.MenuItem { + text: qsTr("Rename mode") + onTriggered: overlay.show(modeMenu.column, Qt.Horizontal) + } + + StudioControls.MenuItem { + text: qsTr("Delete mode") + onTriggered: tableView.model.removeColumns(modeMenu.column, 1) + } + } + + StudioControls.Menu { + id: variableMenu + + property int row + + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + + function show(row) { // Qt.Vertical + tableView.closeEditor() // Close all currently visible edit delegates + variableMenu.row = row + variableMenu.popup() + } + + StudioControls.MenuItem { + enabled: false + text: qsTr("Duplicate variable") + onTriggered: { console.log("Duplicate variable") } + } + + StudioControls.MenuItem { + text: qsTr("Rename variable") + onTriggered: overlay.show(variableMenu.row, Qt.Vertical) + } + + StudioControls.MenuItem { + text: qsTr("Delete variable") + onTriggered: tableView.model.removeRows(variableMenu.row, 1) + } + } + + Rectangle { + id: overlay + + property int section + property var orientation + property var group + + width: 200 + height: 200 + color: "red" + visible: false + + z: 111 + parent: tableView.contentItem + + DSC.TextField { + id: overlayTextField + anchors.fill: parent + leftPadding: root.leftPadding + (overlayIcon.visible ? overlayIcon.width + 8 : 0) + + horizontalAlignment: TextInput.AlignLeft + verticalAlignment: TextInput.AlignVCenter + + onActiveFocusChanged: { + if (!overlayTextField.activeFocus) + overlay.hide() + } + + onAccepted: { + let result = tableView.model.setHeaderData(overlay.section, + overlay.orientation, + overlayTextField.text, + Qt.EditRole) + + // Revoke active focus from text field by forcing active focus on another item + tableView.forceActiveFocus() + } + + Text { + id: overlayIcon + anchors.left: parent.left + anchors.leftMargin: root.leftPadding + + height: parent.height + verticalAlignment: Qt.AlignVCenter + font.family: StudioTheme.Constants.iconFont.family + font.pixelSize: root.iconSize + color: StudioTheme.Values.themeTextColor + + visible: overlay.group !== undefined + + text: { + if (overlay.group === GroupType.Strings) + return StudioTheme.Constants.string_medium + + if (overlay.group === GroupType.Numbers) + return StudioTheme.Constants.number_medium + + if (overlay.group === GroupType.Flags) + return StudioTheme.Constants.flag_medium + + if (overlay.group === GroupType.Colors) + return StudioTheme.Constants.colorSelection_medium + + return StudioTheme.Constants.error_medium + } + } + } + + function show(section, orientation) { + // Close all currently visible edit delegates + tableView.closeEditor() + + if (orientation === Qt.Horizontal) + overlay.parent = horizontalHeaderView.contentItem + else + overlay.parent = verticalHeaderView.contentItem + + overlay.visible = true + overlay.section = section + overlay.orientation = orientation + overlay.group = tableView.model.headerData(section, + orientation, + CollectionModel.GroupRole) + + overlay.layout() + + overlayTextField.text = tableView.model.headerData(section, + orientation, + CollectionModel.EditRole) + overlayTextField.forceActiveFocus() + overlayTextField.selectAll() + } + + function hide() { + overlay.visible = false + } + + function layout() { + if (!overlay.visible) + return + + let item = null + + if (overlay.orientation === Qt.Horizontal) + item = horizontalHeaderView.itemAtCell(Qt.point(overlay.section, 0)) + else + item = verticalHeaderView.itemAtCell(Qt.point(0, overlay.section)) + + let insideViewport = item !== null + + //overlay.visible = insideViewport + if (insideViewport) { + overlay.x = item.x + overlay.y = item.y + overlay.width = item.width + overlay.height = item.height + } + } + + Connections { + target: tableView + + function onLayoutChanged() { overlay.layout() } + } + } + + StudioControls.PopupDialog { + id: colorPopup + + property var modelIndex property QtObject loaderItem: loader.item keepOpen: loader.item?.eyeDropperActive ?? false @@ -357,27 +982,18 @@ Rectangle { sourceComponent: StudioControls.ColorEditorPopup { id: popup - width: popupDialog.contentWidth - visible: popupDialog.visible - parentWindow: popupDialog.window + width: colorPopup.contentWidth + visible: colorPopup.visible + parentWindow: colorPopup.window onActivateColor: function(color) { - console.log("set color", color) - //colorBackend.activateColor(color) + popup.color = color + var prop = DesignSystemBackend.dsInterface.createThemeProperty("", color, false) + let result = tableView.model.setData(colorPopup.modelIndex, prop, Qt.EditRole) } } - Binding { - target: loader.item - property: "color" - value: root.color - when: loader.status === Loader.Ready - } - - onLoaded: { - loader.item.originalColor = root.color - popupDialog.titleBar = loader.item.titleBarContent - } + onLoaded: colorPopup.titleBar = loader.item.titleBarContent } } @@ -403,21 +1019,22 @@ Rectangle { // top left cell // TODO Can't use Cell as it contains required properties Rectangle { - anchors.right: horizontalHeader.left - anchors.bottom: verticalHeader.top + id: topLeftCell + anchors.right: horizontalHeaderView.left + anchors.bottom: verticalHeaderView.top anchors.rightMargin: -root.borderWidth anchors.bottomMargin: -root.borderWidth color: root.backgroundColor - implicitWidth: verticalHeader.width - implicitHeight: horizontalHeader.height + implicitWidth: verticalHeaderView.width + implicitHeight: horizontalHeaderView.height border { width: root.borderWidth color: root.borderColor } - visible: tableView.model // TODO good enough? - z: 101 + visible: tableView.model + z: 99 Row { // TODO might not be necessary anchors.fill: parent @@ -428,13 +1045,13 @@ Rectangle { height: parent.height verticalAlignment: Qt.AlignVCenter color: StudioTheme.Values.themeTextColor - text: "Name" + text: qsTr("Name") } } } HorizontalHeaderView { - id: horizontalHeader + id: horizontalHeaderView anchors.left: scrollView.left anchors.top: parent.top @@ -442,62 +1059,52 @@ Rectangle { syncView: tableView clip: true - z: 100 + z: overlay.visible ? 102 : 100 delegate: Cell { id: horizontalHeaderDelegate - property bool customEditing: false - TapHandler { - onTapped: { - console.log("onTapped") - tableView.closeEditor() - horizontalHeaderDelegate.customEditing = true - horizontalHeaderTextField.forceActiveFocus() + acceptedButtons: Qt.LeftButton | Qt.RightButton + onTapped: function(eventPoint, button) { + if (button === Qt.LeftButton) + overlay.show(horizontalHeaderDelegate.column, Qt.Horizontal) + + if (button === Qt.RightButton) + modeMenu.show(horizontalHeaderDelegate.column) } } - Text { + Row { anchors.fill: parent - leftPadding: root.leftPadding - verticalAlignment: Qt.AlignVCenter - color: StudioTheme.Values.themeTextColor - text: horizontalHeaderDelegate.display - visible: !horizontalHeaderDelegate.customEditing - } + spacing: 8 - DSC.TextField { - id: horizontalHeaderTextField - - anchors.fill: parent - leftPadding: root.leftPadding - horizontalAlignment: TextInput.AlignLeft - verticalAlignment: TextInput.AlignVCenter - text: horizontalHeaderDelegate.display - - visible: horizontalHeaderDelegate.customEditing - - //Component.onCompleted: stringEditDelegate.selectAll() - - onActiveFocusChanged: { - if (!horizontalHeaderTextField.activeFocus) - horizontalHeaderDelegate.customEditing = false + Text { + id: activeIcon + height: parent.height + leftPadding: root.leftPadding + verticalAlignment: Qt.AlignVCenter + font.family: StudioTheme.Constants.iconFont.family + font.pixelSize: StudioTheme.Values.baseIconFontSize + color: StudioTheme.Values.themeTextColor + text: StudioTheme.Constants.apply_small + visible: horizontalHeaderDelegate.isActive } - onEditingFinished: { - console.log("onEditingFinished", horizontalHeaderTextField.text) - horizontalHeaderDelegate.TableView.view.model.setHeaderData(horizontalHeaderDelegate.column, - Qt.Horizontal, - horizontalHeaderTextField.text, - Qt.EditRole) + Text { + height: parent.height + + leftPadding: horizontalHeaderDelegate.isActive ? 0 : root.leftPadding + verticalAlignment: Qt.AlignVCenter + color: StudioTheme.Values.themeTextColor + text: horizontalHeaderDelegate.display } } } } VerticalHeaderView { - id: verticalHeader + id: verticalHeaderView anchors.top: scrollView.top anchors.left: parent.left @@ -505,13 +1112,24 @@ Rectangle { syncView: tableView clip: true - z: 100 + z: overlay.visible ? 102 : 100 delegate: Cell { id: verticalHeaderDelegate required property int group + TapHandler { + acceptedButtons: Qt.LeftButton | Qt.RightButton + onTapped: function(eventPoint, button) { + if (button === Qt.LeftButton) + overlay.show(verticalHeaderDelegate.row, Qt.Vertical) + + if (button === Qt.RightButton) + variableMenu.show(verticalHeaderDelegate.row) + } + } + Row { anchors.fill: parent leftPadding: root.leftPadding @@ -555,8 +1173,8 @@ Rectangle { ScrollView { id: scrollView - anchors.left: verticalHeader.right - anchors.top: horizontalHeader.bottom + anchors.left: verticalHeaderView.right + anchors.top: horizontalHeaderView.bottom anchors.right: parent.right anchors.bottom: parent.bottom @@ -564,6 +1182,8 @@ Rectangle { anchors.topMargin: -root.borderWidth anchors.rightMargin: -root.borderWidth + z: 101 + contentItem: TableView { id: tableView @@ -577,7 +1197,6 @@ Rectangle { } onModelChanged: { - console.log("onModelChanged") tableView.clearColumnWidths() tableView.contentX = 0 tableView.contentY = 0 @@ -588,8 +1207,14 @@ Rectangle { columnSpacing: -root.borderWidth rowSpacing: -root.borderWidth clip: true + boundsBehavior: Flickable.StopAtBounds delegate: chooser + + onActiveFocusChanged: { + if (!tableView.activeFocus) + tableView.closeEditor() + } } ScrollBar.horizontal: StudioControls.TransientScrollBar { @@ -634,14 +1259,14 @@ Rectangle { Layout.fillHeight: true StudioControls.IconTextButton { + id: createModeButton anchors.centerIn: parent buttonIcon: StudioTheme.Constants.add_medium text: qsTr("Create mode") rotation: -90 + enabled: false - onClicked: { - tableView.model.insertColumn(0) - } + onClicked: tableView.model.insertColumns(0, 1) } } @@ -671,15 +1296,22 @@ Rectangle { width: createVariableButton.width + function insertInitalMode() { + if (tableView.model.columnCount()) + return + + tableView.model.insertColumn(0) + } + DSC.MenuItem { text: qsTr("Color") buttonIcon: StudioTheme.Constants.colorSelection_medium onTriggered: { - console.log(">>> Add Color Property") + createVariableMenu.insertInitalMode() tableView.model.addProperty(GroupType.Colors, - "color_new", - "#800080", - false) + "color_new", + "#800080", + false) } } @@ -687,11 +1319,11 @@ Rectangle { text: qsTr("Number") buttonIcon: StudioTheme.Constants.number_medium onTriggered: { - console.log(">>> Add Number Property") + createVariableMenu.insertInitalMode() tableView.model.addProperty(GroupType.Numbers, - "number_new", - 0, - false) + "number_new", + 0, + false) } } @@ -699,11 +1331,11 @@ Rectangle { text: qsTr("String") buttonIcon: StudioTheme.Constants.string_medium onTriggered: { - console.log(">>> Add String Property") - tableView.model.addProperty(GroupType.Flags, - "string_new", - "String value", - false) + createVariableMenu.insertInitalMode() + tableView.model.addProperty(GroupType.Strings, + "string_new", + "String value", + false) } } @@ -711,11 +1343,11 @@ Rectangle { text: qsTr("Boolean") buttonIcon: StudioTheme.Constants.flag_medium onTriggered: { - console.log(">>> Add Boolean Property") + createVariableMenu.insertInitalMode() tableView.model.addProperty(GroupType.Flags, - "boolean_new", - true, - false) + "boolean_new", + true, + false) } } } diff --git a/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/BindingIndicator.qml b/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/BindingIndicator.qml new file mode 100644 index 00000000000..096b639e246 --- /dev/null +++ b/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/BindingIndicator.qml @@ -0,0 +1,62 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Templates as T +import StudioTheme as StudioTheme + +Item { + id: control + + property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle + property alias icon: icon + + property bool hover: mouseArea.containsMouse//false + property bool pressed: false + property bool forceVisible: false + + implicitWidth: control.style.actionIndicatorSize.width + implicitHeight: control.style.actionIndicatorSize.height + + signal clicked + z: 10 + + T.Label { + id: icon + anchors.fill: parent + text: StudioTheme.Constants.actionIcon + color: control.style.icon.idle + font.family: StudioTheme.Constants.iconFont.family + font.pixelSize: control.style.baseIconFontSize + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + + states: [ + State { + name: "hover" + when: control.hover && !control.pressed && control.enabled + PropertyChanges { + target: icon + scale: 1.2 + visible: true + } + }, + State { + name: "disable" + when: !control.enabled + PropertyChanges { + target: icon + color: control.style.icon.disabled + } + } + ] + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + //onContainsMouseChanged: control.hover = mouseArea.containsMouse + onClicked: control.clicked() + } +} diff --git a/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/SpinBox.qml b/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/SpinBox.qml new file mode 100644 index 00000000000..165ca1f21de --- /dev/null +++ b/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/SpinBox.qml @@ -0,0 +1,365 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Templates as T +import StudioTheme as StudioTheme +import StudioQuickUtils + +T.SpinBox { + id: control + + property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle + + property real realFrom: 0.0 + property real realTo: 99.0 + property real realValue: 1.0 + property real realStepSize: 1.0 + + property alias labelColor: spinBoxInput.color + + property int decimals: 0 + + property real minStepSize: { + var tmpMinStepSize = Number((control.realStepSize * 0.1).toFixed(control.decimals)) + return (tmpMinStepSize) ? tmpMinStepSize : control.realStepSize + } + property real maxStepSize: { + var tmpMaxStepSize = Number((control.realStepSize * 10.0).toFixed(control.decimals)) + return (tmpMaxStepSize < control.realTo) ? tmpMaxStepSize : control.realStepSize + } + + property bool edit: spinBoxInput.activeFocus + // This property is used to indicate the global hover state + property bool hover: (spinBoxInput.hover || spinBoxIndicatorUp.hover || spinBoxIndicatorDown.hover) + && control.enabled + + property bool dirty: false // user modification flag + + property bool spinBoxIndicatorVisible: true + property real __spinBoxIndicatorWidth: control.style.spinBoxIndicatorSize.width + property real __spinBoxIndicatorHeight: control.height / 2 - control.style.borderWidth + + property alias compressedValueTimer: myTimer + + property string preFocusText: "" + + signal realValueModified + signal compressedRealValueModified + signal indicatorPressed + + locale: Utils.locale + + // Use custom wheel handling due to bugs + property bool __wheelEnabled: false + wheelEnabled: false + hoverEnabled: true + + width: control.style.controlSize.width + height: control.style.controlSize.height + + leftPadding: spinBoxIndicatorDown.x + spinBoxIndicatorDown.width + rightPadding: control.style.borderWidth + + font.pixelSize: control.style.baseFontSize + editable: true + + // Leave this in for now + from: -99 + value: 0 + to: 99 + + function checkAndClearFocus() { + if (!spinBoxIndicatorUp.activeFocus && !spinBoxIndicatorDown.activeFocus && !spinBoxInput.activeFocus) + control.focus = false + } + + DoubleValidator { + id: doubleValidator + locale: control.locale + notation: DoubleValidator.StandardNotation + decimals: control.decimals + bottom: Math.min(control.realFrom, control.realTo) + top: Math.max(control.realFrom, control.realTo) + } + + IntValidator { + id: intValidator + locale: control.locale + bottom: Math.round(Math.min(control.realFrom, control.realTo)) + top: Math.round(Math.max(control.realFrom, control.realTo)) + } + + validator: control.decimals === 0 ? intValidator : doubleValidator + + up.indicator: SpinBoxIndicator { + id: spinBoxIndicatorUp + style: control.style + parentHover: control.hover + parentEdit: control.edit + iconFlip: -1 + visible: control.spinBoxIndicatorVisible + onRealPressed: control.indicatorPressed() + onRealReleased: control.realIncrease() + onRealPressAndHold: control.realIncrease() + x: control.style.borderWidth + y: control.style.borderWidth + width: control.spinBoxIndicatorVisible ? control.__spinBoxIndicatorWidth : 0 + height: control.spinBoxIndicatorVisible ? control.__spinBoxIndicatorHeight : 0 + + realEnabled: (control.realFrom < control.realTo) ? (control.realValue < control.realTo) + : (control.realValue > control.realTo) + } + + down.indicator: SpinBoxIndicator { + id: spinBoxIndicatorDown + style: control.style + parentHover: control.hover + parentEdit: control.edit + visible: control.spinBoxIndicatorVisible + onRealPressed: control.indicatorPressed() + onRealReleased: control.realDecrease() + onRealPressAndHold: control.realDecrease() + x: control.style.borderWidth + y: spinBoxIndicatorUp.y + spinBoxIndicatorUp.height + width: control.spinBoxIndicatorVisible ? control.__spinBoxIndicatorWidth : 0 + height: control.spinBoxIndicatorVisible ? control.__spinBoxIndicatorHeight : 0 + + realEnabled: (control.realFrom < control.realTo) ? (control.realValue > control.realFrom) + : (control.realValue < control.realFrom) + } + + contentItem: SpinBoxInput { + id: spinBoxInput + style: control.style + validator: control.validator + font: control.font + readOnly: !control.editable + inputMethodHints: control.inputMethodHints + + function handleEditingFinished() { + control.checkAndClearFocus() + + // Keep the dirty state before calling setValueFromInput(), + // it will be set to false (cleared) internally + var valueModified = control.dirty + + control.setValueFromInput() + myTimer.stop() + + // Only trigger the signal, if the value was modified + if (valueModified) + control.compressedRealValueModified() + } + + onEditingFinished: { + spinBoxInput.focus = false + spinBoxInput.handleEditingFinished() + } + + onTextEdited: control.dirty = true + } + + background: Rectangle { + id: spinBoxBackground + color: control.style.background.idle + border.color: control.style.border.idle + border.width: control.style.borderWidth + x: 0 + width: control.width + height: control.height + } + + textFromValue: function (value, locale) { + // -128 is the value of QLocale::FloatingPointShortest + return Number(control.realValue).toLocaleString(locale, 'f', -128) + } + + valueFromText: function (text, locale) { + control.setRealValue(Number.fromLocaleString(locale, spinBoxInput.text)) + + return 0 + } + + states: [ + State { + name: "default" + when: control.enabled && !control.hover && !control.hovered + && !control.edit + PropertyChanges { + target: control + __wheelEnabled: false + } + PropertyChanges { + target: spinBoxInput + selectByMouse: false + } + PropertyChanges { + target: spinBoxBackground + color: control.style.background.idle + border.color: control.style.border.idle + } + }, + State { + name: "hover" + when: control.enabled && control.hover && control.hovered + && !control.edit + PropertyChanges { + target: spinBoxBackground + border.color: control.style.border.hover + } + }, + State { + name: "edit" + when: control.edit + PropertyChanges { + target: control + __wheelEnabled: true + } + PropertyChanges { + target: spinBoxInput + selectByMouse: true + } + PropertyChanges { + target: spinBoxBackground + border.color: control.style.border.interaction + } + }, + State { + name: "disable" + when: !control.enabled + PropertyChanges { + target: spinBoxBackground + border.color: control.style.border.disabled + } + } + ] + + Timer { + id: myTimer + repeat: false + running: false + interval: 400 + onTriggered: control.compressedRealValueModified() + } + + onRealValueChanged: { + control.setRealValue(control.realValue) // sanitize and clamp realValue + spinBoxInput.text = control.textFromValue(control.realValue, control.locale) + control.value = 0 // Without setting value back to 0, it can happen that one of + // the indicator will be disabled due to range logic. + } + onRealValueModified: myTimer.restart() + onFocusChanged: { + if (control.focus) + control.dirty = false + } + onDisplayTextChanged: spinBoxInput.text = control.displayText + onActiveFocusChanged: { + if (control.activeFocus) { // QTBUG-75862 && mySpinBox.focusReason === Qt.TabFocusReason) + control.preFocusText = spinBoxInput.text + spinBoxInput.selectAll() + } else { + // Make sure displayed value is correct after focus loss, as onEditingFinished + // doesn't trigger when value is something validator doesn't accept. + if (spinBoxInput.text === "") + spinBoxInput.text = control.textFromValue(control.realValue, control.locale) + + if (control.dirty) + spinBoxInput.handleEditingFinished() + } + } + onDecimalsChanged: spinBoxInput.text = control.textFromValue(control.realValue, control.locale) + + Keys.onPressed: function(event) { + if (event.key === Qt.Key_Up || event.key === Qt.Key_Down) { + event.accepted = true + + // Store current step size + var currStepSize = control.realStepSize + + // Set realStepSize according to used modifier key + if (event.modifiers & Qt.ControlModifier) + control.realStepSize = control.minStepSize + + if (event.modifiers & Qt.ShiftModifier) + control.realStepSize = control.maxStepSize + + if (event.key === Qt.Key_Up) + control.realIncrease() + else + control.realDecrease() + + // Reset realStepSize + control.realStepSize = currStepSize + } + + if (event.key === Qt.Key_Escape) { + spinBoxInput.text = control.preFocusText + control.dirty = true + spinBoxInput.handleEditingFinished() + } + } + + function clamp(v, lo, hi) { + return (v < lo || v > hi) ? Math.min(Math.max(lo, v), hi) : v + } + + function setValueFromInput() { + if (!control.dirty) + return + + // FIX: This is a temporary fix for QTBUG-74239 + var currValue = control.realValue + + // Call the function but don't use return value. The realValue property + // will be implicitly set inside the function/procedure. + control.valueFromText(spinBoxInput.text, control.locale) + + if (control.realValue !== currValue) { + control.realValueModified() + } else { + // Check if input text differs in format from the current value + var tmpInputValue = control.textFromValue(control.realValue, control.locale) + + if (tmpInputValue !== spinBoxInput.text) + spinBoxInput.text = tmpInputValue + } + + control.dirty = false + } + + function setRealValue(value) { + if (Number.isNaN(value)) + value = 0 + + if (control.decimals === 0) + value = Math.round(value) + + control.realValue = control.clamp(value, + control.validator.bottom, + control.validator.top) + } + + function realDecrease() { + // Store the current value for comparison + var currValue = control.realValue + control.valueFromText(spinBoxInput.text, control.locale) + + control.setRealValue(control.realValue - control.realStepSize) + + if (control.realValue !== currValue) + control.realValueModified() + } + + function realIncrease() { + // Store the current value for comparison + var currValue = control.realValue + control.valueFromText(spinBoxInput.text, control.locale) + + control.setRealValue(control.realValue + control.realStepSize) + + if (control.realValue !== currValue) + control.realValueModified() + } +} diff --git a/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/SpinBoxIndicator.qml b/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/SpinBoxIndicator.qml new file mode 100644 index 00000000000..6fd56d1a1b7 --- /dev/null +++ b/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/SpinBoxIndicator.qml @@ -0,0 +1,247 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Templates as T +import StudioTheme as StudioTheme + +Rectangle { + id: control + + property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle + + property bool hover: spinBoxIndicatorMouseArea.containsMouse + property bool pressed: spinBoxIndicatorMouseArea.containsPress + property bool released: false + property bool realEnabled: true + + property bool parentHover: false + property bool parentEdit: false + + signal realPressed + signal realPressAndHold + signal realReleased + + property alias iconFlip: spinBoxIndicatorIconScale.yScale + + + color: control.style.background.idle + border.width: 0 + + onEnabledChanged: control.syncEnabled() + onRealEnabledChanged: { + control.syncEnabled() + if (control.realEnabled === false) { + pressAndHoldTimer.stop() + spinBoxIndicatorMouseArea.pressedAndHeld = false + } + } + + // This function is meant to synchronize enabled with realEnabled to avoid + // the internal logic messing with the actual state. + function syncEnabled() { + control.enabled = control.realEnabled + } + + Timer { + id: pressAndHoldTimer + repeat: true + running: false + interval: 100 + onTriggered: control.realPressAndHold() + } + + // This MouseArea is a workaround to avoid some hover state related bugs + // when using the actual signal 'up.hovered'. QTBUG-74688 + MouseArea { + id: spinBoxIndicatorMouseArea + + property bool pressedAndHeld: false + + anchors.fill: parent + hoverEnabled: true + pressAndHoldInterval: 500 + onPressed: function(mouse) { + //if (control.__parentControl.activeFocus) + // control.forceActiveFocus() + + control.realPressed() + mouse.accepted = true + } + onPressAndHold: { + pressAndHoldTimer.restart() + spinBoxIndicatorMouseArea.pressedAndHeld = true + } + onReleased: function(mouse) { + // Only trigger real released when pressAndHold isn't active + if (!pressAndHoldTimer.running && containsMouse) + control.realReleased() + pressAndHoldTimer.stop() + mouse.accepted = true + spinBoxIndicatorMouseArea.pressedAndHeld = false + } + onEntered: { + if (spinBoxIndicatorMouseArea.pressedAndHeld) + pressAndHoldTimer.restart() + } + onExited: { + if (pressAndHoldTimer.running) + pressAndHoldTimer.stop() + } + } + + T.Label { + id: spinBoxIndicatorIcon + text: StudioTheme.Constants.upDownSquare2 + color: control.style.icon.idle + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: control.style.smallIconFontSize + font.family: StudioTheme.Constants.iconFont.family + anchors.fill: parent + transform: Scale { + id: spinBoxIndicatorIconScale + origin.x: 0 + origin.y: spinBoxIndicatorIcon.height / 2 + yScale: 1 + } + + states: [ + State { + name: "default" + when: control.enabled && !control.hover && !control.parentEdit && !control.parentHover + PropertyChanges { + target: spinBoxIndicatorIcon + color: control.style.icon.idle + } + }, + State { + name: "globalHover" + when: control.enabled && !control.hover && !control.parentEdit && control.parentHover + PropertyChanges { + target: spinBoxIndicatorIcon + color: control.style.icon.idle + } + }, + State { + name: "hover" + when: control.enabled && control.hover && !control.pressed && control.parentHover + PropertyChanges { + target: spinBoxIndicatorIcon + color: control.style.icon.hover + } + }, + State { + name: "press" + when: control.enabled && control.pressed + PropertyChanges { + target: spinBoxIndicatorIcon + color: control.style.icon.idle + } + }, + State { + name: "parentEdit" + when: control.parentEdit && control.enabled + PropertyChanges { + target: spinBoxIndicatorIcon + color: control.style.icon.idle + } + }, + State { + name: "disable" + when: !control.enabled + PropertyChanges { + target: spinBoxIndicatorIcon + color: control.style.icon.disabled + } + } + ] + } + + states: [ + State { + name: "default" + when: !control.parentEdit && !control.hover && !control.parentHover + PropertyChanges { + target: spinBoxIndicatorIcon + visible: false + } + PropertyChanges { + target: control + color: control.style.background.idle + } + }, + State { + name: "globalHover" + when: control.enabled && !control.hover && !control.parentEdit && control.parentHover + PropertyChanges { + target: spinBoxIndicatorIcon + visible: true + } + PropertyChanges { + target: control + color: control.style.background.globalHover + } + }, + State { + name: "hover" + when: control.enabled && control.hover && !control.pressed && control.parentHover + PropertyChanges { + target: spinBoxIndicatorIcon + visible: true + } + PropertyChanges { + target: control + color: control.style.background.hover + } + }, + State { + name: "press" + when: control.enabled && control.pressed + PropertyChanges { + target: spinBoxIndicatorIcon + visible: true + } + PropertyChanges { + target: control + color: control.style.interaction + } + }, + State { + name: "parentEdit" + when: control.parentEdit && control.enabled + PropertyChanges { + target: spinBoxIndicatorIcon + visible: true + } + PropertyChanges { + target: control + color: control.style.background.idle + } + }, + State { + name: "disable" + when: !control.enabled + PropertyChanges { + target: spinBoxIndicatorIcon + visible: false + } + PropertyChanges { + target: control + color: control.style.background.disabled + } + }, + State { + name: "limit" + when: !control.enabled && !control.realEnabled && control.parentHover + PropertyChanges { + target: spinBoxIndicatorIcon + visible: true + } + PropertyChanges { + target: control + color: control.style.background.idle + } + } + ] +} diff --git a/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/SpinBoxInput.qml b/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/SpinBoxInput.qml new file mode 100644 index 00000000000..a4870eb9491 --- /dev/null +++ b/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/SpinBoxInput.qml @@ -0,0 +1,100 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Templates as T +import StudioTheme as StudioTheme + +TextInput { + id: control + + property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle + + property bool edit: control.activeFocus + property bool drag: false + property bool hover: hoverHandler.hovered && control.enabled + + z: 2 + color: control.style.text.idle + selectionColor: control.style.text.selection + selectedTextColor: control.style.text.selectedText + + horizontalAlignment: Qt.AlignLeft + verticalAlignment: Qt.AlignVCenter + leftPadding: control.style.inputHorizontalPadding + rightPadding: control.style.inputHorizontalPadding + + selectByMouse: false + activeFocusOnPress: false + clip: true + + // TextInput focus needs to be set to activeFocus whenever it changes, + // otherwise TextInput will get activeFocus whenever the parent SpinBox gets + // activeFocus. This will lead to weird side effects. + onActiveFocusChanged: control.focus = control.activeFocus + + HoverHandler { id: hoverHandler } + + Rectangle { + id: textInputBackground + x: 0 + y: control.style.borderWidth + z: -1 + width: control.width + height: control.height - (control.style.borderWidth * 2) + color: control.style.background.idle + border.width: 0 + } + + // Ensure that we get Up and Down key press events first + Keys.onShortcutOverride: function(event) { + event.accepted = (event.key === Qt.Key_Up || event.key === Qt.Key_Down) + } + + states: [ + State { + name: "default" + when: control.enabled && !control.edit && !control.hover + PropertyChanges { + target: textInputBackground + color: control.style.background.idle + } + }, + State { + name: "globalHover" + when: !control.hover && !control.edit + PropertyChanges { + target: textInputBackground + color: control.style.background.globalHover + } + }, + State { + name: "hover" + when: control.hover && !control.edit + PropertyChanges { + target: textInputBackground + color: control.style.background.hover + } + }, + State { + name: "edit" + when: control.edit + PropertyChanges { + target: textInputBackground + color: control.style.background.interaction + } + }, + State { + name: "disable" + when: !control.enabled + PropertyChanges { + target: textInputBackground + color: control.style.background.disabled + } + PropertyChanges { + target: control + color: control.style.text.disabled + } + } + ] +} diff --git a/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/Switch.qml b/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/Switch.qml index 4ccdca4486c..6f98f90f72b 100644 --- a/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/Switch.qml +++ b/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/Switch.qml @@ -4,7 +4,7 @@ import QtQuick import QtQuick.Templates as T -import StudioTheme 1.0 as StudioTheme +import StudioTheme as StudioTheme T.Switch { id: control @@ -14,6 +14,7 @@ T.Switch { // This property is used to indicate the global hover state property bool hover: control.hovered && control.enabled property bool edit: false + property bool readonly: false property alias labelVisible: label.visible property alias labelColor: label.color @@ -33,6 +34,8 @@ T.Switch { hoverEnabled: true activeFocusOnTab: false + enabled: !control.readonly + indicator: Rectangle { id: switchBackground x: 0 @@ -60,6 +63,7 @@ T.Switch { color: control.style.icon.idle border.width: 0 } + } contentItem: T.Label { @@ -133,7 +137,7 @@ T.Switch { }, State { name: "disable" - when: !control.enabled && !control.checked + when: !control.enabled && !control.checked && !control.readonly PropertyChanges { target: switchBackground color: control.style.background.disabled @@ -186,8 +190,19 @@ T.Switch { }, State { name: "disableChecked" - when: !control.enabled && control.checked + when: !control.enabled && control.checked && !control.readonly extend: "disable" + }, + + State { + name: "readonly" + when: !control.enabled && !control.checked && control.readonly + extend: "default" + }, + State { + name: "readonlyChecked" + when: !control.enabled && control.checked && control.readonly + extend: "defaultChecked" } ] } diff --git a/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/qmldir b/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/qmldir index 0f9345ed7d2..4556e1a8f3d 100644 --- a/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/qmldir +++ b/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/qmldir @@ -1,3 +1,7 @@ +BindingIndicator 1.0 BindingIndicator.qml MenuItem 1.0 MenuItem.qml +SpinBox 1.0 SpinBox.qml +SpinBoxIndicator 1.0 SpinBoxIndicator.qml +SpinBoxInput 1.0 SpinBoxInput.qml Switch 1.0 Switch.qml TextField 1.0 TextField.qml diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml index 987934e295f..6d935dc39f3 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml @@ -596,14 +596,18 @@ Item { anchors.centerIn: parent text: { - if (!materialBrowserModel.isQt6Project) + if (!materialBrowserModel.isQt6Project) { qsTr("Material Browser is not supported in Qt5 projects.") - else if (!materialBrowserModel.hasQuick3DImport) - qsTr("To use Material Browser, first add the QtQuick3D module in the Components view.") - else if (!materialBrowserModel.hasMaterialLibrary) + } else if (!materialBrowserModel.hasQuick3DImport) { + qsTr('To use the Material Browser, add the QtQuick3D module and the View3D + component in the Components view, or click + + here.').arg(StudioTheme.Values.themeInteraction) + } else if (!materialBrowserModel.hasMaterialLibrary) { qsTr("Material Browser is disabled inside a non-visual component.") - else + } else { "" + } } textFormat: Text.RichText @@ -611,6 +615,8 @@ Item { font.pixelSize: StudioTheme.Values.mediumFontSize horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap + + onLinkActivated: rootView.addQtQuick3D() } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/EffectsSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/EffectsSection.qml index 635c2fd539d..4da7d9fb5d3 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/EffectsSection.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/EffectsSection.qml @@ -186,6 +186,8 @@ Section { spacing: 1 Section { + readonly property bool __isInEffectsSection: true // used by property search logic + sectionHeight: 37 anchors.left: parent.left anchors.right: parent.right @@ -232,6 +234,8 @@ Section { } Section { + readonly property bool __isInEffectsSection: true // used by property search logic + sectionHeight: 37 anchors.left: parent.left anchors.right: parent.right @@ -305,6 +309,8 @@ Section { Section { id: delegate + readonly property bool __isInEffectsSection: true // used by property search logic + property QtObject wrapper: modelNodeBackend.registerSubSelectionWrapper(modelData) property bool wasExpanded: false diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml index aeef8a9598e..536ba9dd90d 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml @@ -20,7 +20,7 @@ PropertyEditorPane { } DynamicPropertiesSection { - propertiesModel: SelectionDynamicPropertiesModel {} + propertiesModel: PropertyEditorDynamicPropertiesModel {} visible: !hasMultiSelection } @@ -90,9 +90,17 @@ PropertyEditorPane { StudioControls.TabButton { text: backendValues.__classNamePrivateInternal.value + onClicked: () => { + if (itemPane.searchBar.hasDoneSearch) + itemPane.searchBar.search(); + } } StudioControls.TabButton { text: qsTr("Layout") + onClicked: () => { + if (itemPane.searchBar.hasDoneSearch) + itemPane.searchBar.search(); + } } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/QtObjectPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/QtObjectPane.qml index 12be7b8f346..78e0c2fd480 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/QtObjectPane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/QtObjectPane.qml @@ -12,7 +12,7 @@ PropertyEditorPane { ComponentSection {} DynamicPropertiesSection { - propertiesModel: SelectionDynamicPropertiesModel {} + propertiesModel: PropertyEditorDynamicPropertiesModel {} visible: !hasMultiSelection } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ScriptActionSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ScriptActionSpecifics.qml index 5f063879a72..4ff2d990104 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ScriptActionSpecifics.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ScriptActionSpecifics.qml @@ -12,4 +12,9 @@ Column { AnimationSection { showDuration: false } + + ScriptSection { + anchors.left: parent.left + anchors.right: parent.right + } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ScriptSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ScriptSection.qml new file mode 100644 index 00000000000..422cdc533a9 --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ScriptSection.qml @@ -0,0 +1,102 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme +import ScriptEditorBackend +import ScriptsEditor as ScriptsEditor + +Section { + id: root + caption: qsTr("Script") + + SectionLayout { + PropertyLabel { + text: "" + tooltip: "" + } + + SecondColumnLayout { + Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth } + + AbstractButton { + id: editScriptButton + implicitWidth: StudioTheme.Values.singleControlColumnWidth + width: StudioTheme.Values.singleControlColumnWidth + buttonIcon: qsTr("Edit script") + iconFontFamily: StudioTheme.Constants.font.family + onClicked: dialog.show(root) + } + + ExpandingSpacer {} + } + } + + data: [ + StudioControls.PopupDialog { + id: dialog + + readonly property real horizontalSpacing: 10 + readonly property real verticalSpacing: 12 + readonly property real columnWidth: (dialog.width - dialog.horizontalSpacing - (2 * dialog.anchorGap)) / 2 + + property var backend: ScriptEditorBackend + + titleBar: Row { + id: titleBarRow + spacing: 30 + anchors.fill: parent + + Text { + color: StudioTheme.Values.themeTextColor + text: qsTr("Action") + font.pixelSize: StudioTheme.Values.myFontSize + anchors.verticalCenter: parent.verticalCenter + + ToolTipArea { + anchors.fill: parent + tooltip: qsTr("Sets an action that is associated with the selected ScriptAction.") + } + } + + ScriptsEditor.ActionsComboBox { + id: action + style: StudioTheme.Values.connectionPopupControlStyle + width: 180 + anchors.verticalCenter: parent.verticalCenter + backend: dialog.backend + } + } + + ScriptsEditor.ScriptEditorForm { + id: scriptEditor + + anchors.left: parent.left + anchors.right: parent.right + + horizontalSpacing: dialog.horizontalSpacing + verticalSpacing: dialog.verticalSpacing + columnWidth: dialog.columnWidth + spacing: dialog.verticalSpacing + + backend: dialog.backend + + currentAction: action.currentValue ?? StatementDelegate.Custom + } + }, + + Connections { + target: modelNodeBackend + function onSelectionChanged() { + dialog.backend.update() + } + }, + + TapHandler { + onTapped: scriptEditor.forceActiveFocus() + } + ] +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/emptyPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/emptyPane.qml index 3ec325d8f14..c797860c762 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/emptyPane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/emptyPane.qml @@ -13,6 +13,11 @@ Rectangle { height: 400 color: StudioTheme.Values.themePanelBackground + // Called from C++ to clear the search when the selected node changes + function clearSearch() { + // The function is empty, because it is a placeholder to match other panes + } + ColumnLayout { id: mainColumn anchors.fill: parent diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/CubeMapTextureSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/CubeMapTextureSpecifics.qml deleted file mode 100644 index f6716f32bb3..00000000000 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/CubeMapTextureSpecifics.qml +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only - -import QtQuick 2.15 -import QtQuick.Layouts 1.15 -import HelperWidgets 2.0 - -Column { - width: parent.width - - // CubeMapTexture inherits Texture but doesn't provide any extra properties itself - TextureSection { - width: parent.width - } -} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/CustomMaterialSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/CustomMaterialSpecifics.qml index 6911a9eb914..06294ff7128 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/CustomMaterialSpecifics.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/CustomMaterialSpecifics.qml @@ -11,8 +11,4 @@ Column { CustomMaterialSection { width: parent.width } - - MaterialSection { - width: parent.width - } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/DefaultMaterialSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/DefaultMaterialSpecifics.qml index 0e0c3fc2557..c40bcba6f12 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/DefaultMaterialSpecifics.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/DefaultMaterialSpecifics.qml @@ -11,8 +11,4 @@ Column { DefaultMaterialSection { width: parent.width } - - MaterialSection { - width: parent.width - } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/ColorEditorPopup.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/ColorEditorPopup.qml new file mode 100644 index 00000000000..6ea3428a71b --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/ColorEditorPopup.qml @@ -0,0 +1,62 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import StudioControls as StudioControls + +StudioControls.PopupDialog { + id: colorPopup + + property QtObject loaderItem: loader.item + property color originalColor + required property color currentColor + + signal activateColor(color : color) + + width: 260 + + onOriginalColorChanged: loader.updateOriginalColor() + onClosing: loader.active = false + + function open(showItem) { + loader.ensureActive() + colorPopup.show(showItem) + + loader.updateOriginalColor() + } + + Loader { + id: loader + + function ensureActive() { + if (!loader.active) + loader.active = true + } + + function updateOriginalColor() { + if (loader.status === Loader.Ready) + loader.item.originalColor = colorPopup.originalColor + } + + sourceComponent: StudioControls.ColorEditorPopup { + width: colorPopup.contentWidth + visible: colorPopup.visible + + onActivateColor: (color) => { + colorPopup.activateColor(color) + } + } + + Binding { + target: loader.item + property: "color" + value: colorPopup.currentColor + when: loader.status === Loader.Ready + } + + onLoaded: { + loader.updateOriginalColor() + colorPopup.titleBar = loader.item.titleBarContent + } + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/Preview.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/Preview.qml new file mode 100644 index 00000000000..8dd04372b06 --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/Preview.qml @@ -0,0 +1,266 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import HelperWidgets 2.0 as HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme + +Rectangle { + id: root + + property HelperWidgets.QmlMaterialNodeProxy backend: materialNodeBackend + property alias pinned: pinButton.checked + property alias showPinButton: pinButton.visible + + property StudioTheme.ControlStyle buttonStyle: StudioTheme.ViewBarButtonStyle { + // This is how you can override stuff from the control styles + baseIconFontSize: StudioTheme.Values.bigIconFontSize + } + + Connections { + target: HelperWidgets.Controller + + function onCloseContextMenu() { + root.closeContextMenu() + } + } + + implicitHeight: image.height + + clip: true + color: "#000000" + + // Called from C++ to close context menu on focus out + function closeContextMenu() + { + modelMenu.close() + envMenu.close() + } + + function refreshPreview() + { + image.source = "" + image.source = "image://nodeInstance/preview" + } + + + Connections { + target: root.backend + + function onPreviewEnvChanged() { + envMenu.updateEnvParams(backend.previewEnv) + root.refreshPreview() + } + + function onPreviewModelChanged() { + root.refreshPreview() + } + } + + Image { + id: image + + anchors.fill: parent + fillMode: Image.PreserveAspectFit + + source: "image://nodeInstance/preview" + cache: false + smooth: true + + sourceSize.width: image.width + sourceSize.height: image.height + + Rectangle { + id: toolbarRect + + radius: 10 + color: StudioTheme.Values.themeToolbarBackground + width: optionsToolbar.width + 2 * toolbarRect.radius + height: optionsToolbar.height + toolbarRect.radius + anchors.left: parent.left + anchors.leftMargin: -toolbarRect.radius + anchors.verticalCenter: parent.verticalCenter + + Column { + id: optionsToolbar + + spacing: 5 + anchors.centerIn: parent + anchors.horizontalCenterOffset: optionsToolbar.spacing + + HelperWidgets.AbstractButton { + id: pinButton + + style: root.buttonStyle + buttonIcon: pinButton.checked ? StudioTheme.Constants.pin : StudioTheme.Constants.unpin + checkable: true + } + + HelperWidgets.AbstractButton { + id: previewEnvMenuButton + + style: root.buttonStyle + buttonIcon: StudioTheme.Constants.textures_medium + tooltip: qsTr("Select preview environment.") + onClicked: envMenu.popup() + } + + HelperWidgets.AbstractButton { + id: previewModelMenuButton + + style: root.buttonStyle + buttonIcon: StudioTheme.Constants.cube_medium + tooltip: qsTr("Select preview model.") + onClicked: modelMenu.popup() + } + } + } + } + + StudioControls.Menu { + id: modelMenu + + closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside + + ListModel { + id: modelMenuModel + ListElement { + modelName: qsTr("Cone") + modelStr: "#Cone" + } + ListElement { + modelName: qsTr("Cube") + modelStr: "#Cube" + } + ListElement { + modelName: qsTr("Cylinder") + modelStr: "#Cylinder" + } + ListElement { + modelName: qsTr("Sphere") + modelStr: "#Sphere" + } + } + + Repeater { + model: modelMenuModel + StudioControls.MenuItemWithIcon { + text: modelName + onClicked: root.backend.previewModel = modelStr + checkable: true + checked: root.backend.previewModel === modelStr + } + } + } + + StudioControls.Menu { + id: envMenu + + property string previewEnvName + property string previewEnvValue + + signal envParametersChanged() + + closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside + + Component.onCompleted: envMenu.updateEnvParams(root.backend.previewEnv) + + function updateEnvParams(str: string) { + let eqFound = str.lastIndexOf("=") + let newEnvName = (eqFound > 0) ? str.substr(0, eqFound) : str + let newEnvValue = (eqFound > 0) ? str.substr(eqFound + 1, str.length - eqFound) : "" + + if (envMenu.previewEnvName !== newEnvName + || envMenu.previewEnvValue !== newEnvValue) { + envMenu.previewEnvName = newEnvName + envMenu.previewEnvValue = newEnvValue + envMenu.envParametersChanged() + } + } + + EnvMenuItem { + envName: qsTr("Basic") + envStr: "Basic" + } + + EnvMenuItem { + id: colorItem + + property color color + property bool colorIsValid: false + + envName: qsTr("Color") + envStr: "Color" + checked: false + + Component.onCompleted: update() + onColorIsValidChanged: updatePopupOriginalColor() + + onClicked: { + colorItem.updatePopupOriginalColor() + colorPopup.open(colorItem) + } + + onColorChanged: { + colorItem.envStr = colorItem.checked + ? "Color=" + color.toString() + : "Color" + colorItem.commit() + } + + function updatePopupOriginalColor() { + if (colorItem.colorIsValid) + colorPopup.originalColor = colorItem.color + } + + function update() { + colorItem.checked = envMenu.previewEnvName === "Color" + if (colorItem.checked && envMenu.previewEnvValue) { + colorItem.color = envMenu.previewEnvValue + colorItem.colorIsValid = true + } else { + colorItem.colorIsValid = false + } + } + + Connections { + target: envMenu + function onEnvParametersChanged() { + colorItem.update(); + } + } + } + + EnvMenuItem { + envName: qsTr("Studio") + envStr: "SkyBox=preview_studio" + } + + EnvMenuItem { + envName: qsTr("Landscape") + envStr: "SkyBox=preview_landscape" + } + } + + ColorEditorPopup { + id: colorPopup + + currentColor: colorItem.color + onActivateColor: (color) => colorItem.color = color + } + + component EnvMenuItem: StudioControls.MenuItemWithIcon { + required property string envName + property string envStr + + function commit() { + root.backend.previewEnv = envStr + } + + text: envName + onClicked: commit() + checkable: false + checked: root.backend.previewEnv === envStr + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/Toolbar.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/Toolbar.qml new file mode 100644 index 00000000000..b3e5a7e147c --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/Toolbar.qml @@ -0,0 +1,50 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import HelperWidgets 2.0 +import HelperWidgets 2.0 as HelperWidgets +import StudioTheme as StudioTheme +Rectangle { + id: root + + property HelperWidgets.QmlMaterialNodeProxy backend: materialNodeBackend + + color: StudioTheme.Values.themeToolbarBackground + height: StudioTheme.Values.toolbarHeight + + Row { + id: row + spacing: StudioTheme.Values.toolbarSpacing + anchors.verticalCenter: parent.verticalCenter + leftPadding: 6 + + HelperWidgets.AbstractButton { + style: StudioTheme.Values.viewBarButtonStyle + buttonIcon: StudioTheme.Constants.apply_medium + tooltip: qsTr("Apply material to selected model.") + onClicked: root.backend.toolBarAction(QmlMaterialNodeProxy.ApplyToSelected) + } + + HelperWidgets.AbstractButton { + style: StudioTheme.Values.viewBarButtonStyle + buttonIcon: StudioTheme.Constants.create_medium + tooltip: qsTr("Create new material.") + onClicked: backend.toolBarAction(QmlMaterialNodeProxy.AddNewMaterial) + } + + HelperWidgets.AbstractButton { + style: StudioTheme.Values.viewBarButtonStyle + buttonIcon: StudioTheme.Constants.delete_medium + tooltip: qsTr("Delete current material.") + onClicked: backend.toolBarAction(QmlMaterialNodeProxy.DeleteCurrentMaterial) + } + + HelperWidgets.AbstractButton { + style: StudioTheme.Values.viewBarButtonStyle + buttonIcon: StudioTheme.Constants.materialBrowser_medium + tooltip: qsTr("Open material browser.") + onClicked: backend.toolBarAction(QmlMaterialNodeProxy.OpenMaterialBrowser) + } + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/TopSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/TopSection.qml new file mode 100644 index 00000000000..b52befa0a0f --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/TopSection.qml @@ -0,0 +1,93 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import HelperWidgets 2.0 as HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme + +StudioControls.SplitView { + id: root + + property HelperWidgets.QmlMaterialNodeProxy backend: materialNodeBackend + property alias showImage: previewLoader.active + property Component previewComponent: null + + width: parent.width + implicitHeight: showImage ? previewLoader.implicitHeight + nameSection.implicitHeight : nameSection.implicitHeight + + orientation: Qt.Vertical + + Loader { + id: previewLoader + + SplitView.fillWidth: true + SplitView.minimumWidth: 152 + SplitView.preferredHeight: previewLoader.visible ? Math.min(root.width * 0.75, 400) : 0 + SplitView.minimumHeight: previewLoader.visible ? 150 : 0 + SplitView.maximumHeight: previewLoader.visible ? 600 : 0 + + visible: previewLoader.active && previewLoader.item + + sourceComponent: root.previewComponent + } + + HelperWidgets.Section { + id: nameSection + + // Section with hidden header is used so properties are aligned with the other sections' properties + hideHeader: true + SplitView.fillWidth: true + SplitView.preferredHeight: implicitHeight + SplitView.maximumHeight: implicitHeight + bottomPadding: StudioTheme.Values.sectionPadding * 2 + collapsible: false + + HelperWidgets.SectionLayout { + HelperWidgets.PropertyLabel { text: qsTr("Name") } + + HelperWidgets.SecondColumnLayout { + HelperWidgets.Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth } + + HelperWidgets.LineEdit { + id: matName + + implicitWidth: StudioTheme.Values.singleControlColumnWidth + width: StudioTheme.Values.singleControlColumnWidth + placeholderText: qsTr("Material name") + showTranslateCheckBox: false + showExtendedFunctionButton: false + + Timer { + running: true + interval: 0 + onTriggered: matName.backendValue = backendValues.objectName + // backendValues.objectName is not available yet without the Timer + } + + // allow only alphanumeric characters, underscores, no space at start, and 1 space between words + validator: RegularExpressionValidator { regularExpression: /^(\w+\s)*\w+$/ } + } + + HelperWidgets.ExpandingSpacer {} + } + + HelperWidgets.PropertyLabel { text: qsTr("Type") } + + HelperWidgets.SecondColumnLayout { + HelperWidgets.Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth } + + HelperWidgets.ComboBox { + currentIndex: backend.possibleTypeIndex + model: backend.possibleTypes + showExtendedFunctionButton: false + implicitWidth: StudioTheme.Values.singleControlColumnWidth + enabled: backend.possibleTypes.length > 1 + + onActivated: changeTypeName(currentValue) + } + } + } + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/MaterialPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/MaterialPane.qml new file mode 100644 index 00000000000..b8174ad4be6 --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/MaterialPane.qml @@ -0,0 +1,137 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtCore +import QtQuick +import QtQuick.Controls +import HelperWidgets 2.0 +import StudioControls 1.0 as StudioControls +import "Material" as Material + +Item { + id: root + + width: 420 + height: 420 + + // invoked from C++ to refresh material preview image + signal refreshPreview() + + // Called from C++ to close context menu on focus out + function closeContextMenu() + { + Controller.closeContextMenu() + } + + Material.Toolbar { + id: toolbar + + width: parent.width + } + + Settings { + id: settings + + property var topSection + property bool dockMode + } + + StudioControls.SplitView { + id: splitView + + readonly property bool isHorizontal: splitView.orientation == Qt.Horizontal + + anchors.top: toolbar.bottom + anchors.bottom: parent.bottom + width: parent.width + orientation: splitView.width > 1000 ? Qt.Horizontal : Qt.Vertical + clip: true + + Loader { + id: leftSideView + + SplitView.fillWidth: leftSideView.visible + SplitView.fillHeight: leftSideView.visible + SplitView.minimumWidth: leftSideView.visible ? 300 : 0 + SplitView.minimumHeight: leftSideView.visible ? 300 : 0 + + active: splitView.isHorizontal + visible: leftSideView.active && leftSideView.item + + sourceComponent: PreviewComponent {} + } + + PropertyEditorPane { + id: itemPane + + clip: true + SplitView.fillWidth: !leftSideView.visible + SplitView.fillHeight: true + SplitView.minimumWidth: leftSideView.visible ? 400 : 0 + SplitView.maximumWidth: leftSideView.visible ? 800 : -1 + + headerDocked: !leftSideView.visible && settings.dockMode + + headerComponent: Material.TopSection { + id: topSection + + Component.onCompleted: topSection.restoreState(settings.topSection) + Component.onDestruction: settings.topSection = topSection.saveState() + previewComponent: PreviewComponent {} + showImage: !splitView.isHorizontal + } + + DynamicPropertiesSection { + propertiesModel: PropertyEditorDynamicPropertiesModel {} + visible: !hasMultiSelection + } + + Loader { + id: specificsTwo + + property string theSource: specificQmlData + + width: itemPane.width + visible: specificsTwo.theSource !== "" + sourceComponent: specificQmlComponent + + onTheSourceChanged: { + specificsTwo.active = false + specificsTwo.active = true + } + } + + Item { // spacer + width: 1 + height: 10 + visible: specificsTwo.visible + } + + Loader { + id: specificsOne + width: itemPane.width + source: specificsUrl + } + + MaterialSection { + width: itemPane.width + } + } + } + + component PreviewComponent : Material.Preview { + id: previewItem + + pinned: settings.dockMode + showPinButton: !leftSideView.visible + onPinnedChanged: settings.dockMode = previewItem.pinned + + Connections { + target: root + + function onRefreshPreview() { + previewItem.refreshPreview() + } + } + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Object3DPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Object3DPane.qml index e4f7554442c..142bd08ef64 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Object3DPane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Object3DPane.qml @@ -18,7 +18,7 @@ PropertyEditorPane { anchors.right: parent.right DynamicPropertiesSection { - propertiesModel: SelectionDynamicPropertiesModel {} + propertiesModel: PropertyEditorDynamicPropertiesModel {} visible: !hasMultiSelection } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/PrincipledMaterialSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/PrincipledMaterialSpecifics.qml index ff10311f9bb..937c6b9996c 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/PrincipledMaterialSpecifics.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/PrincipledMaterialSpecifics.qml @@ -11,8 +11,4 @@ Column { PrincipledMaterialSection { width: parent.width } - - MaterialSection { - width: parent.width - } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/SpecularGlossyMaterialSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/SpecularGlossyMaterialSpecifics.qml index b84e824c543..a96dccecb6d 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/SpecularGlossyMaterialSpecifics.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/SpecularGlossyMaterialSpecifics.qml @@ -11,8 +11,4 @@ Column { SpecularGlossyMaterialSection { width: parent.width } - - MaterialSection { - width: parent.width - } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Texture/ToolBar.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Texture/ToolBar.qml new file mode 100644 index 00000000000..36ed80d6ca8 --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Texture/ToolBar.qml @@ -0,0 +1,54 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import HelperWidgets 2.0 +import StudioTheme as StudioTheme + +Rectangle { + id: root + + property QmlTextureNodeProxy backend: textureNodeBackend + + color: StudioTheme.Values.themeToolbarBackground + implicitHeight: StudioTheme.Values.toolbarHeight + + Row { + id: row + spacing: StudioTheme.Values.toolbarSpacing + anchors.verticalCenter: parent.verticalCenter + leftPadding: 6 + + AbstractButton { + style: StudioTheme.Values.viewBarButtonStyle + buttonIcon: StudioTheme.Constants.apply_medium + enabled: backend.hasTexture && backend.selectedNodeAcceptsMaterial && hasQuick3DImport && hasMaterialLibrary + tooltip: qsTr("Apply texture to selected model's material.") + onClicked: backend.toolbarAction(QmlTextureNodeProxy.ApplyToSelected) + } + + AbstractButton { + style: StudioTheme.Values.viewBarButtonStyle + buttonIcon: StudioTheme.Constants.create_medium + enabled: hasQuick3DImport && hasMaterialLibrary + tooltip: qsTr("Create new texture.") + onClicked: backend.toolbarAction(QmlTextureNodeProxy.AddNewTexture) + } + + AbstractButton { + style: StudioTheme.Values.viewBarButtonStyle + buttonIcon: StudioTheme.Constants.delete_medium + enabled: backend.hasTexture && hasQuick3DImport && hasMaterialLibrary + tooltip: qsTr("Delete current texture.") + onClicked: backend.toolbarAction(QmlTextureNodeProxy.DeleteCurrentTexture) + } + + AbstractButton { + style: StudioTheme.Values.viewBarButtonStyle + buttonIcon: StudioTheme.Constants.materialBrowser_medium + enabled: backend.hasTexture && hasQuick3DImport && hasMaterialLibrary + tooltip: qsTr("Open material browser.") + onClicked: backend.toolbarAction(QmlTextureNodeProxy.OpenMaterialBrowser) + } + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Texture/TopSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Texture/TopSection.qml new file mode 100644 index 00000000000..0ac948ddedc --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Texture/TopSection.qml @@ -0,0 +1,88 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import HelperWidgets 2.0 as HelperWidgets +import StudioTheme as StudioTheme + +Rectangle { + id: root + + property HelperWidgets.QmlTextureNodeProxy backend: textureNodeBackend + + readonly property string sourcePath: backendValues.source ? backendValues.source.valueToString : "" + readonly property string previewPath: "image://qmldesigner_thumbnails/" + backend.resolveResourcePath(root.sourcePath) + + function refreshPreview() + { + texturePreview.source = "" + texturePreview.source = root.previewPath + } + + color: StudioTheme.Values.themePanelBackground + implicitHeight: column.height + + Column { + id: column + + Item { implicitWidth: 1; implicitHeight: 10 } // spacer + + Rectangle { + id: previewRect + anchors.horizontalCenter: parent.horizontalCenter + width: 152 + height: 152 + color: "#000000" + + Image { + id: texturePreview + asynchronous: true + width: 150 + height: 150 + fillMode: Image.PreserveAspectFit + anchors.centerIn: parent + source: root.previewPath + } + } + + HelperWidgets.Section { + id: nameSection + + // Section with hidden header is used so properties are aligned with the other sections' properties + hideHeader: true + implicitWidth: root.width + bottomPadding: StudioTheme.Values.sectionPadding * 2 + collapsible: false + + HelperWidgets.SectionLayout { + HelperWidgets.PropertyLabel { text: qsTr("Name") } + + HelperWidgets.SecondColumnLayout { + HelperWidgets.Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth } + + HelperWidgets.LineEdit { + id: texName + + implicitWidth: StudioTheme.Values.singleControlColumnWidth + width: StudioTheme.Values.singleControlColumnWidth + placeholderText: qsTr("Texture name") + showTranslateCheckBox: false + showExtendedFunctionButton: false + + Timer { + running: true + interval: 0 + onTriggered: texName.backendValue = backendValues.objectName + // backendValues.objectName is not available yet without the Timer + } + + // allow only alphanumeric characters, underscores, no space at start, and 1 space between words + validator: RegularExpressionValidator { regularExpression: /^(\w+\s)*\w+$/ } + } + + HelperWidgets.ExpandingSpacer {} + } + } + } + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/TexturePane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/TexturePane.qml new file mode 100644 index 00000000000..e7c25024108 --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/TexturePane.qml @@ -0,0 +1,86 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Layouts +import HelperWidgets 2.0 +import StudioTheme as StudioTheme +import "Texture" as Texture + +Rectangle { + id: itemPane + + width: 420 + height: 420 + color: StudioTheme.Values.themePanelBackground + + // invoked from C++ to refresh material preview image + function refreshPreview() + { + topSection.refreshPreview() + } + + // Called from C++ to close context menu on focus out + function closeContextMenu() + { + Controller.closeContextMenu() + } + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + Texture.ToolBar { + Layout.fillWidth: true + } + + Texture.TopSection { + id: topSection + + Layout.fillWidth: true + } + + PropertyEditorPane { + Layout.fillWidth: true + Layout.fillHeight: true + + DynamicPropertiesSection { + propertiesModel: PropertyEditorDynamicPropertiesModel {} + visible: !hasMultiSelection + } + + Loader { + id: specificsTwo + + property string theSource: specificQmlData + + anchors.left: parent.left + anchors.right: parent.right + visible: specificsTwo.theSource !== "" + sourceComponent: specificQmlComponent + + onTheSourceChanged: { + specificsTwo.active = false + specificsTwo.active = true + } + } + + Item { + width: 1 + height: 10 + visible: specificsTwo.visible + } + + Loader { + id: specificsOne + anchors.left: parent.left + anchors.right: parent.right + source: specificsUrl + } + + TextureSection { + width: itemPane.width + } + } + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/TextureSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/TextureSpecifics.qml deleted file mode 100644 index a52924a093f..00000000000 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/TextureSpecifics.qml +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only - -import QtQuick 2.15 -import QtQuick.Layouts 1.15 -import HelperWidgets 2.0 - -Column { - width: parent.width - - TextureSection { - width: parent.width - } -} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml index dea81cab815..88237093c66 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml @@ -507,6 +507,8 @@ Section { } PropertyLabel { + readonly property bool __inDynamicPropertiesSection: true + text: propertyName tooltip: propertyType Layout.alignment: Qt.AlignTop diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/EditableListView.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/EditableListView.qml index b2f0498dc06..21271ae1ef0 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/EditableListView.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/EditableListView.qml @@ -24,9 +24,7 @@ Item { property real __actionIndicatorWidth: StudioTheme.Values.squareComponentWidth property real __actionIndicatorHeight: StudioTheme.Values.height property string typeFilter: "QtQuick3D.Material" - // This binding is a workaround to overcome the rather long adaption to new Qt versions. This - // should actually be fixed in the ModelSection.qml by setting the textRole: "idAndName". - property string textRole: (root.typeFilter === "QtQuick3D.Material") ? "idAndName" : "id" + property string textRole: "id" property string valueRole: "id" property int activatedReason: ComboBox.ActivatedReason.Other diff --git a/share/qtcreator/qmldesigner/connectionseditor/PopupLabel.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PopupLabel.qml similarity index 100% rename from share/qtcreator/qmldesigner/connectionseditor/PopupLabel.qml rename to share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PopupLabel.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertyEditorPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertyEditorPane.qml index fa0def82c7d..192c53cc5ce 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertyEditorPane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertyEditorPane.qml @@ -18,6 +18,7 @@ Rectangle { default property alias content: mainColumn.children property alias scrollView: mainScrollView + property alias searchBar: propertySearchBar property bool headerDocked: false readonly property Item headerItem: headerDocked ? dockedHeaderLoader.item : undockedHeaderLoader.item @@ -29,10 +30,23 @@ Rectangle { Controller.closeContextMenu() } + // Called from C++ to clear the search when the selected node changes + function clearSearch() { + propertySearchBar.clear(); + } + + PropertySearchBar { + id: propertySearchBar + + contentItem: mainColumn + width: parent.width + z: parent.z + 1 + } + Loader { id: dockedHeaderLoader - anchors.top: itemPane.top + anchors.top: propertySearchBar.bottom z: parent.z + 1 height: item ? item.implicitHeight : 0 width: parent.width @@ -123,6 +137,16 @@ Rectangle { HeaderBackground{} } + Label { + Layout.fillWidth: true + Layout.leftMargin: 10 + + visible: propertySearchBar.hasDoneSearch && !propertySearchBar.hasMatchSearch + text: qsTr("No match found.") + color: StudioTheme.Values.themeTextColor + font.pixelSize: StudioTheme.Values.baseFont + } + Column { id: mainColumn diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertyLabel.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertyLabel.qml index 85bded2924c..85874946b65 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertyLabel.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertyLabel.qml @@ -9,10 +9,13 @@ import StudioTheme 1.0 as StudioTheme T.Label { id: label + readonly property bool __isPropertyLabel: true // used by property search logic + property alias tooltip: toolTipArea.tooltip property bool blockedByContext: false property bool blockedByTemplate: false // MCU + property bool searchNoMatch: false width: StudioTheme.Values.propertyLabelWidth color: StudioTheme.Values.themeTextColor @@ -34,6 +37,14 @@ T.Label { } states: [ + State { + name: "searchNoMatch" + when: searchNoMatch + PropertyChanges { + target: label + visible: false + } + }, State { name: "disabled" when: !label.enabled && !(label.blockedByContext || label.blockedByTemplate) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertySearchBar.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertySearchBar.qml new file mode 100644 index 00000000000..0083eac6355 --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertySearchBar.qml @@ -0,0 +1,135 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import StudioControls as StudioControls +import StudioTheme as StudioTheme + +Rectangle { + id: root + + readonly property alias hasMatchSearch: internal.matched + readonly property alias hasDoneSearch: internal.searched + + property Item contentItem: null + + color: StudioTheme.Values.themeToolbarBackground + height: StudioTheme.Values.toolbarHeight + + function clear() { + internal.clear(); + searchBox.text = ""; + internal.timer.stop(); + } + + function search() { + internal.search(); + } + + StudioControls.SearchBox { + id: searchBox + + anchors.fill: parent + anchors.bottomMargin: StudioTheme.Values.toolbarVerticalMargin + anchors.topMargin: StudioTheme.Values.toolbarVerticalMargin + anchors.leftMargin: StudioTheme.Values.toolbarHorizontalMargin + anchors.rightMargin: StudioTheme.Values.toolbarHorizontalMargin + style: StudioTheme.Values.searchControlStyle + onSearchChanged: internal.timer.restart() + } + + QtObject { + id: internal + + readonly property var reverts: [] + readonly property Timer timer: Timer { + interval: 300 + repeat: false + + onTriggered: internal.search() + } + property bool matched: false + property bool searched: false + + function clear() { + internal.reverts.forEach(revert => revert()); + internal.reverts.length = 0; + internal.matched = false; + internal.searched = false; + } + + function search() { + internal.clear(); + const searchText = searchBox.text.toLowerCase(); + if (searchText.length > 0) { + internal.traverse(root.contentItem, searchText); + internal.searched = true; + } + } + + function disableSearchNoMatchAction(item) { + item.searchNoMatch = true; + internal.reverts.push(() => { + item.searchNoMatch = false; + }); + } + + function disableVisibleAction(item) { + item.visible = false; + internal.reverts.push(() => { + item.visible = true; + }); + } + + function enableSearchHideAction(item) { + item.searchHide = true; + internal.reverts.push(() => { + item.searchHide = false; + }); + } + + function expandSectionAction(item) { + internal.matched = true; + const prevValue = item.expanded; + item.expanded = true; + internal.reverts.push(() => { + item.expanded = prevValue; + }); + } + + function traverse(item, searchText) { + let hideSection = true; + let hideParentSection = true; + item.children.forEach((child, index, arr) => { + if (!child.visible) + return; + + if (child.__isPropertyLabel) { + const propertyLabel = child; + const text = propertyLabel.text.toLowerCase(); + if (!text.includes(searchText)) { + internal.disableSearchNoMatchAction(propertyLabel); + const action = propertyLabel.__inDynamicPropertiesSection ? internal.disableVisibleAction + : internal.disableSearchNoMatchAction; + const nextItem = arr[index + 1]; + action(nextItem); + } else { + hideSection = false; + } + } + hideSection &= internal.traverse(child, searchText); + if (child.__isSection) { + const action = hideSection ? internal.enableSearchHideAction + : internal.expandSectionAction; + action(child); + + if (child.__isInEffectsSection && !hideSection) + hideParentSection = false; + + hideSection = true; + } + }); + return hideParentSection && hideSection; + } + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SecondColumnLayout.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SecondColumnLayout.qml index e35317d0147..fa8d9a7e5c2 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SecondColumnLayout.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SecondColumnLayout.qml @@ -5,6 +5,21 @@ import QtQuick 2.15 import QtQuick.Layouts 1.15 RowLayout { + id: root + + property bool searchNoMatch: false + Layout.fillWidth: true spacing: 0 + + states: [ + State { + name: "searchNoMatch" + when: searchNoMatch + PropertyChanges { + target: root + visible: false + } + } + ] } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/Section.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/Section.qml index a24edc4904d..5af99822c71 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/Section.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/Section.qml @@ -10,6 +10,8 @@ import StudioTheme as StudioTheme Item { id: section + readonly property bool __isSection: true // used by property search logic + property string caption: "Title" property color labelColor: StudioTheme.Values.themeTextColor property int labelCapitalization: Font.AllUppercase @@ -58,6 +60,7 @@ Item { property bool dropEnabled: false property bool highlight: false property bool eyeEnabled: true // eye button enabled (on) + property bool searchHide: false property bool useDefaulContextMenu: true @@ -343,6 +346,14 @@ Item { } states: [ + State { + name: "Hide" + when: section.searchHide + PropertyChanges { + target: section + visible: false + } + }, State { name: "Collapsed" when: !section.expanded diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir index ddade5606a1..f363bab3bda 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir @@ -1,3 +1,4 @@ +module HelperWidgets AbstractButton 2.0 AbstractButton.qml ActionIndicator 2.0 ActionIndicator.qml AlignmentHorizontalButtons 2.0 AlignmentHorizontalButtons.qml @@ -51,6 +52,7 @@ MultiIconLabel 2.0 MultiIconLabel.qml OriginControl 2.0 OriginControl.qml OriginIndicator 2.0 OriginIndicator.qml OriginSelector 2.0 OriginSelector.qml +PopupLabel 2.0 PopupLabel.qml PropertyEditorPane 2.0 PropertyEditorPane.qml PropertyLabel 2.0 PropertyLabel.qml PaddingSection 2.0 PaddingSection.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ActionIndicator.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ActionIndicator.qml index 96a28d6d5c8..8f5a85734a1 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ActionIndicator.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ActionIndicator.qml @@ -3,9 +3,9 @@ import QtQuick import QtQuick.Templates as T -import StudioTheme 1.0 as StudioTheme +import StudioTheme as StudioTheme -Rectangle { +Item { id: control property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle @@ -18,8 +18,6 @@ Rectangle { property bool pressed: false property bool forceVisible: false - color: "transparent" - implicitWidth: control.style.actionIndicatorSize.width implicitHeight: control.style.actionIndicatorSize.height diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/DialogButton.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/DialogButton.qml index cce96797726..fff8db4ae84 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/DialogButton.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/DialogButton.qml @@ -3,27 +3,29 @@ import QtQuick import QtQuick.Templates as T -import StudioTheme 1.0 as StudioTheme +import StudioTheme as StudioTheme T.Button { id: control property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle - implicitWidth: Math.max(buttonBackground ? buttonBackground.implicitWidth : 0, - textItem.implicitWidth + leftPadding + rightPadding) - implicitHeight: Math.max(buttonBackground ? buttonBackground.implicitHeight : 0, - textItem.implicitHeight + topPadding + bottomPadding) + implicitWidth: Math.max(control.style.squareControlSize.width, + implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(control.style.squareControlSize.height, + implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding) + leftPadding: control.style.dialogPadding rightPadding: control.style.dialogPadding background: Rectangle { - id: buttonBackground - implicitWidth: 70 - implicitHeight: 20 + id: controlBackground color: control.style.background.idle border.color: control.style.border.idle - anchors.fill: parent + border.width: control.style.borderWidth + radius: control.style.radius } contentItem: Text { @@ -41,7 +43,7 @@ T.Button { when: control.enabled && !control.down && !control.hovered && !control.checked PropertyChanges { - target: buttonBackground + target: controlBackground color: control.highlighted ? control.style.interaction : control.style.background.idle border.color: control.style.border.idle @@ -56,7 +58,7 @@ T.Button { when: control.enabled && control.hovered && !control.checked && !control.down PropertyChanges { - target: buttonBackground + target: controlBackground color: control.style.background.hover border.color: control.style.border.hover } @@ -70,9 +72,9 @@ T.Button { when: control.enabled && (control.checked || control.down) PropertyChanges { - target: buttonBackground - color: control.style.background.interaction - border.color: control.style.border.interaction + target: controlBackground + color: control.style.interaction + border.color: control.style.interaction } PropertyChanges { target: textItem @@ -83,7 +85,7 @@ T.Button { name: "disable" when: !control.enabled PropertyChanges { - target: buttonBackground + target: controlBackground color: control.style.background.disabled border.color: control.style.border.disabled } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/qmldir b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/qmldir index d187fcfa14e..92fa4a2093c 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/qmldir +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/qmldir @@ -1,3 +1,4 @@ +module StudioControls AbstractButton 1.0 AbstractButton.qml ActionIndicator 1.0 ActionIndicator.qml Button 1.0 Button.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/qmldir b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/qmldir index e22b7d9a583..f52634892e2 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/qmldir +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/qmldir @@ -1,3 +1,4 @@ +module StudioTheme singleton Values 1.0 Values.qml singleton Constants 1.0 Constants.qml ControlStyle 1.0 ControlStyle.qml diff --git a/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/ActionsComboBox.qml b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/ActionsComboBox.qml new file mode 100644 index 00000000000..15f0222fd24 --- /dev/null +++ b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/ActionsComboBox.qml @@ -0,0 +1,54 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import StudioControls as StudioControls +import StudioTheme as StudioTheme +import ScriptEditorBackend + +StudioControls.TopLevelComboBox { + id: action + + style: StudioTheme.Values.connectionPopupControlStyle + textRole: "text" + valueRole: "value" + + property var backend + property int indexFromBackend: indexOfValue(backend.actionType) + + onIndexFromBackendChanged: action.currentIndex = action.indexFromBackend + onActivated: backend.changeActionType(action.currentValue) + + model: ListModel { + ListElement { + value: StatementDelegate.CallFunction + text: qsTr("Call Function") + enabled: true + } + ListElement { + value: StatementDelegate.Assign + text: qsTr("Assign") + enabled: true + } + ListElement { + value: StatementDelegate.ChangeState + text: qsTr("Change State") + enabled: true + } + ListElement { + value: StatementDelegate.SetProperty + text: qsTr("Set Property") + enabled: true + } + ListElement { + value: StatementDelegate.PrintMessage + text: qsTr("Print Message") + enabled: true + } + ListElement { + value: StatementDelegate.Custom + text: qsTr("Custom") + enabled: false + } + } +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/ExpressionBuilder.qml b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/ExpressionBuilder.qml similarity index 98% rename from share/qtcreator/qmldesigner/connectionseditor/ExpressionBuilder.qml rename to share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/ExpressionBuilder.qml index 54d8ee937f3..9e02424f692 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/ExpressionBuilder.qml +++ b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/ExpressionBuilder.qml @@ -4,13 +4,16 @@ import QtQuick import StudioControls as StudioControls import StudioTheme as StudioTheme +import ScriptEditorBackend Rectangle { id: root property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle - property var conditionListModel: ConnectionsEditorEditorBackend.connectionModel.delegate.conditionListModel + property var conditionListModel: ScriptEditorBackend.conditionListModel // connect a valid model here + property alias popupListModel: popup.listModel + property alias popupTreeModel: popup.treeModel property alias model: repeater.model property int shadowPillIndex: -1 diff --git a/share/qtcreator/qmldesigner/connectionseditor/MyListViewDelegate.qml b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/MyListViewDelegate.qml similarity index 100% rename from share/qtcreator/qmldesigner/connectionseditor/MyListViewDelegate.qml rename to share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/MyListViewDelegate.qml diff --git a/share/qtcreator/qmldesigner/connectionseditor/MyTreeViewDelegate.qml b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/MyTreeViewDelegate.qml similarity index 100% rename from share/qtcreator/qmldesigner/connectionseditor/MyTreeViewDelegate.qml rename to share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/MyTreeViewDelegate.qml diff --git a/share/qtcreator/qmldesigner/connectionseditor/Pill.qml b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/Pill.qml similarity index 99% rename from share/qtcreator/qmldesigner/connectionseditor/Pill.qml rename to share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/Pill.qml index aa6cba03639..43f2789cb3b 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/Pill.qml +++ b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/Pill.qml @@ -5,6 +5,7 @@ import QtQuick import StudioControls as StudioControls import StudioTheme as StudioTheme import HelperWidgets as HelperWidgets +import ScriptEditorBackend FocusScope { id: root diff --git a/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/ScriptEditorForm.qml b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/ScriptEditorForm.qml new file mode 100644 index 00000000000..e7fc3eb8a8b --- /dev/null +++ b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/ScriptEditorForm.qml @@ -0,0 +1,262 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import HelperWidgets as HelperWidgets +import StudioTheme as StudioTheme +import ScriptEditorBackend + +Column { + id: root + + property real horizontalSpacing + property real verticalSpacing + property real columnWidth + + property var backend + + property alias keepOpen: expressionDialogLoader.visible + + property int currentAction: StatementDelegate.Custom + + property string itemTooltip: qsTr("Sets the component that is affected by the action.") + property string methodTooltip: qsTr("Sets the item component's method.") + property string fromTooltip: qsTr("Sets the component and its property from which the value is copied.") + property string toTooltip: qsTr("Sets the component and its property to which the copied value is assigned.") + property string addConditionTooltip: qsTr("Sets a logical condition for the selected action.") + property string removeConditionTooltip: qsTr("Removes the logical condition for the action.") + property string stateGroupTooltip: qsTr("Sets a State Group that is accessed when the action is initiated.") + property string stateTooltip: qsTr("Sets a State within the assigned State Group that is accessed when the action is initiated.") + property string propertyTooltip: qsTr("Sets the property of the component that is affected by the action.") + property string valueTooltip: qsTr("Sets the value of the property of the component that is affected by the action.") + property string messageTooltip: qsTr("Sets a text that is printed when the action is initiated.") + + + StatementEditor { + width: root.width + actionType: root.currentAction + horizontalSpacing: root.horizontalSpacing + columnWidth: root.columnWidth + statement: root.backend.okStatement + backend: root.backend + spacing: root.verticalSpacing + itemTooltip: root.itemTooltip + methodTooltip: root.methodTooltip + fromTooltip: root.fromTooltip + toTooltip: root.toTooltip + stateGroupTooltip: root.stateGroupTooltip + stateTooltip: root.stateTooltip + propertyTooltip: root.propertyTooltip + valueTooltip: root.valueTooltip + messageTooltip: root.messageTooltip + } + + + HelperWidgets.AbstractButton { + id: addConditionButton + style: StudioTheme.Values.connectionPopupButtonStyle + width: 160 + buttonIcon: qsTr("Add Condition") + tooltip: root.addConditionTooltip + iconSize: StudioTheme.Values.baseFontSize + iconFontFamily: StudioTheme.Constants.font.family + anchors.horizontalCenter: parent.horizontalCenter + visible: root.currentAction !== StatementDelegate.Custom && !backend.hasCondition + + onClicked: backend.addCondition() + } + + HelperWidgets.AbstractButton { + style: StudioTheme.Values.connectionPopupButtonStyle + width: 160 + buttonIcon: qsTr("Remove Condition") + tooltip: root.removeConditionTooltip + iconSize: StudioTheme.Values.baseFontSize + iconFontFamily: StudioTheme.Constants.font.family + anchors.horizontalCenter: parent.horizontalCenter + visible: root.currentAction !== StatementDelegate.Custom && backend.hasCondition + + onClicked: backend.removeCondition() + } + + ExpressionBuilder { + style: StudioTheme.Values.connectionPopupControlStyle + width: root.width + + visible: backend.hasCondition + model: backend.conditionListModel + conditionListModel: backend.conditionListModel + popupListModel: backend.propertyListProxyModel + popupTreeModel: backend.propertyTreeModel + + onRemove: function(index) { + backend.conditionListModel.removeToken(index) + } + + onUpdate: function(index, value) { + backend.conditionListModel.updateToken(index, value) + } + + onAdd: function(value) { + backend.conditionListModel.appendToken(value) + } + + onInsert: function(index, value, type) { + if (type === ConditionListModel.Intermediate) + backend.conditionListModel.insertIntermediateToken(index, value) + else if (type === ConditionListModel.Shadow) + backend.conditionListModel.insertShadowToken(index, value) + else + backend.conditionListModel.insertToken(index, value) + } + + onSetValue: function(index, value) { + backend.conditionListModel.setShadowToken(index, value) + } + } + + HelperWidgets.AbstractButton { + style: StudioTheme.Values.connectionPopupButtonStyle + width: 160 + buttonIcon: qsTr("Add Else Statement") + tooltip: qsTr("Sets an alternate condition for the previously defined logical condition.") + iconSize: StudioTheme.Values.baseFontSize + iconFontFamily: StudioTheme.Constants.font.family + anchors.horizontalCenter: parent.horizontalCenter + visible: root.currentAcion !== StatementDelegate.Custom + && backend.hasCondition && !backend.hasElse + + onClicked: backend.addElse() + } + + HelperWidgets.AbstractButton { + style: StudioTheme.Values.connectionPopupButtonStyle + width: 160 + buttonIcon: qsTr("Remove Else Statement") + tooltip: qsTr("Removes the alternate logical condition for the previously defined logical condition.") + iconSize: StudioTheme.Values.baseFontSize + iconFontFamily: StudioTheme.Constants.font.family + anchors.horizontalCenter: parent.horizontalCenter + visible: root.currentAction !== StatementDelegate.Custom + && backend.hasCondition && backend.hasElse + + onClicked: backend.removeElse() + } + + //Else Statement + StatementEditor { + width: root.width + actionType: root.currentAction + horizontalSpacing: root.horizontalSpacing + columnWidth: root.columnWidth + statement: backend.koStatement + backend: root.backend + spacing: root.verticalSpacing + visible: action.currentValue !== StatementDelegate.Custom + && backend.hasCondition && backend.hasElse + itemTooltip: root.itemTooltip + methodTooltip: root.methodTooltip + fromTooltip: root.fromTooltip + toTooltip: root.toTooltip + stateGroupTooltip: root.stateGroupTooltip + stateTooltip: root.stateTooltip + propertyTooltip: root.propertyTooltip + valueTooltip: root.valueTooltip + messageTooltip: root.messageTooltip + } + + // code preview toolbar + Column { + id: miniToolbarEditor + width: parent.width + spacing: -2 + + Rectangle { + id: miniToolbar + width: parent.width + height: editorButton.height + 2 + radius: 4 + z: -1 + color: StudioTheme.Values.themeConnectionEditorMicroToolbar + + Row { + spacing: 2 + HelperWidgets.AbstractButton { + id: editorButton + style: StudioTheme.Values.microToolbarButtonStyle + buttonIcon: StudioTheme.Constants.codeEditor_medium + tooltip: qsTr("Write the conditions manually.") + onClicked: expressionDialogLoader.show() + } + HelperWidgets.AbstractButton { + id: jumpToCodeButton + style: StudioTheme.Values.microToolbarButtonStyle + buttonIcon: StudioTheme.Constants.jumpToCode_medium + tooltip: qsTr("Jump to the code.") + onClicked: backend.jumpToCode() + } + } + } + + // Editor + Rectangle { + id: editor + width: parent.width + height: 150 + color: StudioTheme.Values.themeConnectionCodeEditor + + Text { + id: code + anchors.fill: parent + anchors.margins: 4 + text: backend.indentedSource + color: StudioTheme.Values.themeTextColor + font.pixelSize: StudioTheme.Values.myFontSize + wrapMode: Text.Wrap + horizontalAlignment: code.lineCount === 1 ? Text.AlignHCenter : Text.AlignLeft + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + + Loader { + id: expressionDialogLoader + parent: editor + anchors.fill: parent + visible: false + active: visible + + function show() { + expressionDialogLoader.visible = true + } + + sourceComponent: Item { + id: bindingEditorParent + + Component.onCompleted: { + bindingEditor.showWidget() + bindingEditor.text = backend.source + bindingEditor.showControls(false) + bindingEditor.setMultilne(true) + bindingEditor.updateWindowName() + } + + HelperWidgets.ActionEditor { + id: bindingEditor + + onRejected: { + bindingEditor.hideWidget() + expressionDialogLoader.visible = false + } + + onAccepted: { + backend.setNewSource(bindingEditor.text) + bindingEditor.hideWidget() + expressionDialogLoader.visible = false + } + } + } + } + } + } +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/StatementEditor.qml b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/StatementEditor.qml similarity index 74% rename from share/qtcreator/qmldesigner/connectionseditor/StatementEditor.qml rename to share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/StatementEditor.qml index f198d84a103..47a89d6553b 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/StatementEditor.qml +++ b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/StatementEditor.qml @@ -6,7 +6,7 @@ import QtQuick.Controls import HelperWidgets as HelperWidgets import StudioControls as StudioControls import StudioTheme as StudioTheme -import ConnectionsEditorEditorBackend +import ScriptEditorBackend Column { id: root @@ -20,26 +20,37 @@ Column { property var backend + property string itemTooltip + property alias methodTooltip: methodPopupLabel.tooltip + property alias fromTooltip: fromPopupLabel.tooltip + property alias toTooltip: toPopupLabel.tooltip + property alias stateGroupTooltip: stateGroupPopupLabel.tooltip + property alias stateTooltip: statePopupLabel.tooltip + property alias propertyTooltip: propertyPopupLabel.tooltip + property alias valueTooltip: valuePopupLabel.tooltip + property alias messageTooltip: messagePopupLabel.tooltip + // Call Function Row { - visible: root.actionType === ConnectionModelStatementDelegate.CallFunction + visible: root.actionType === StatementDelegate.CallFunction spacing: root.horizontalSpacing - PopupLabel { + HelperWidgets.PopupLabel { + id: itemPopupLabel width: root.columnWidth text: qsTr("Item") - tooltip: qsTr("Sets the component that is affected by the action of the Target component's Signal.") + tooltip: root.itemTooltip } - PopupLabel { + HelperWidgets.PopupLabel { + id: methodPopupLabel width: root.columnWidth text: qsTr("Method") - tooltip: qsTr("Sets the item component's method that is affected by the Target component's Signal.") } } Row { - visible: root.actionType === ConnectionModelStatementDelegate.CallFunction + visible: root.actionType === StatementDelegate.CallFunction spacing: root.horizontalSpacing StudioControls.TopLevelComboBox { @@ -68,23 +79,23 @@ Column { // Assign Row { - visible: root.actionType === ConnectionModelStatementDelegate.Assign + visible: root.actionType === StatementDelegate.Assign spacing: root.horizontalSpacing - PopupLabel { + HelperWidgets.PopupLabel { + id: fromPopupLabel width: root.columnWidth text: qsTr("From") - tooltip: qsTr("Sets the component and its property from which the value is copied when the Target component initiates the Signal.") } - PopupLabel { + HelperWidgets.PopupLabel { + id: toPopupLabel width: root.columnWidth text: qsTr("To") - tooltip: qsTr("Sets the component and its property to which the copied value is assigned when the Target component initiates the Signal.") } } Row { - visible: root.actionType === ConnectionModelStatementDelegate.Assign + visible: root.actionType === StatementDelegate.Assign spacing: root.horizontalSpacing StudioControls.TopLevelComboBox { @@ -114,7 +125,7 @@ Column { } Row { - visible: root.actionType === ConnectionModelStatementDelegate.Assign + visible: root.actionType === StatementDelegate.Assign spacing: root.horizontalSpacing StudioControls.TopLevelComboBox { @@ -145,24 +156,24 @@ Column { // Change State Row { - visible: root.actionType === ConnectionModelStatementDelegate.ChangeState + visible: root.actionType === StatementDelegate.ChangeState spacing: root.horizontalSpacing - PopupLabel { + HelperWidgets.PopupLabel { + id: stateGroupPopupLabel width: root.columnWidth text: qsTr("State Group") - tooltip: qsTr("Sets a State Group that is accessed when the Target component initiates the Signal.") } - PopupLabel { + HelperWidgets.PopupLabel { + id: statePopupLabel width: root.columnWidth text: qsTr("State") - tooltip: qsTr("Sets a State within the assigned State Group that is accessed when the Target component initiates the Signal.") } } Row { - visible: root.actionType === ConnectionModelStatementDelegate.ChangeState + visible: root.actionType === StatementDelegate.ChangeState spacing: root.horizontalSpacing StudioControls.TopLevelComboBox { @@ -191,24 +202,24 @@ Column { // Set Property Row { - visible: root.actionType === ConnectionModelStatementDelegate.SetProperty + visible: root.actionType === StatementDelegate.SetProperty spacing: root.horizontalSpacing - PopupLabel { + HelperWidgets.PopupLabel { width: root.columnWidth text: qsTr("Item") - tooltip: qsTr("Sets the component that is affected by the action of the Target component's Signal.") + tooltip: root.itemTooltip } - PopupLabel { + HelperWidgets.PopupLabel { + id: propertyPopupLabel width: root.columnWidth text: qsTr("Property") - tooltip: qsTr("Sets the property of the component that is affected by the action of the Target component's Signal.") } } Row { - visible: root.actionType === ConnectionModelStatementDelegate.SetProperty + visible: root.actionType === StatementDelegate.SetProperty spacing: root.horizontalSpacing StudioControls.TopLevelComboBox { @@ -236,16 +247,16 @@ Column { } } - PopupLabel { + HelperWidgets.PopupLabel { + id: valuePopupLabel width: root.columnWidth - visible: root.actionType === ConnectionModelStatementDelegate.SetProperty + visible: root.actionType === StatementDelegate.SetProperty text: qsTr("Value") - tooltip: qsTr("Sets the value of the property of the component that is affected by the action of the Target component's Signal.") } StudioControls.TextField { id: setPropertyArgument - visible: root.actionType === ConnectionModelStatementDelegate.SetProperty + visible: root.actionType === StatementDelegate.SetProperty width: root.width actionIndicatorVisible: false translationIndicatorVisible: false @@ -257,16 +268,16 @@ Column { } // Print Message - PopupLabel { + HelperWidgets.PopupLabel { + id: messagePopupLabel width: root.columnWidth - visible: root.actionType === ConnectionModelStatementDelegate.PrintMessage + visible: root.actionType === StatementDelegate.PrintMessage text: qsTr("Message") - tooltip: qsTr("Sets a text that is printed when the Signal of the Target component initiates.") } StudioControls.TextField { id: messageString - visible: root.actionType === ConnectionModelStatementDelegate.PrintMessage + visible: root.actionType === StatementDelegate.PrintMessage width: root.width actionIndicatorVisible: false translationIndicatorVisible: false @@ -277,8 +288,8 @@ Column { } // Custom - PopupLabel { - visible: root.actionType === ConnectionModelStatementDelegate.Custom + HelperWidgets.PopupLabel { + visible: root.actionType === StatementDelegate.Custom text: qsTr("Custom Connections can only be edited with the binding editor") width: root.width horizontalAlignment: Text.AlignHCenter diff --git a/share/qtcreator/qmldesigner/connectionseditor/SuggestionPopup.qml b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/SuggestionPopup.qml similarity index 97% rename from share/qtcreator/qmldesigner/connectionseditor/SuggestionPopup.qml rename to share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/SuggestionPopup.qml index fad01c32c4d..fc40100ede8 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/SuggestionPopup.qml +++ b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/SuggestionPopup.qml @@ -6,15 +6,15 @@ import QtQuick.Controls as Controls import HelperWidgets as HelperWidgets import StudioTheme as StudioTheme import StudioControls as StudioControls -import ConnectionsEditorEditorBackend +import ScriptEditorBackend Controls.Popup { id: root property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle - property var listModel: ConnectionsEditorEditorBackend.connectionModel.delegate.propertyListProxyModel - property var treeModel: ConnectionsEditorEditorBackend.connectionModel.delegate.propertyTreeModel + property var listModel: ScriptEditorBackend.propertyListProxyModel // connect a valid model here + property var treeModel: ScriptEditorBackend.propertyTreeModel // connect a valid model here signal select(var value) signal entered(var value) diff --git a/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/qmldir b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/qmldir new file mode 100644 index 00000000000..18ffd98cad6 --- /dev/null +++ b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/qmldir @@ -0,0 +1,2 @@ +ScriptEditorForm 1.0 ScriptEditorForm.qml +ActionsComboBox 1.0 ActionsComboBox.qml diff --git a/share/qtcreator/qmldesigner/stateseditor/Main.qml b/share/qtcreator/qmldesigner/stateseditor/Main.qml index ac3c11d9fe1..f9058238a04 100644 --- a/share/qtcreator/qmldesigner/stateseditor/Main.qml +++ b/share/qtcreator/qmldesigner/stateseditor/Main.qml @@ -26,8 +26,8 @@ import QtQuick import QtQuick.Controls.Basic as Basic import StatesEditor -import HelperWidgets 2.0 as HelperWidgets -import StudioControls 1.0 as StudioControls +import HelperWidgets as HelperWidgets +import StudioControls as StudioControls import StudioTheme as StudioTheme import StatesEditorBackend @@ -313,7 +313,7 @@ Rectangle { StudioControls.Dialog { id: editDialog title: qsTr("Rename state group") - standardButtons: Dialog.Apply | Dialog.Cancel + standardButtons: Basic.Dialog.Apply | Basic.Dialog.Cancel x: editButton.x - Math.max(0, editButton.x + editDialog.width - root.width) y: toolBar.height width: Math.min(300, root.width) @@ -328,15 +328,11 @@ Rectangle { anchors.fill: parent onTextChanged: { - let btn = editDialog.standardButton(Dialog.Apply) + let btn = editDialog.standardButton(Basic.Dialog.Apply) if (!btn) return - if (editDialog.previousString !== editTextField.text) { - btn.enabled = true - } else { - btn.enabled = false - } + btn.enabled = (editDialog.previousString !== editTextField.text) } onAccepted: editDialog.accept() @@ -355,7 +351,7 @@ Rectangle { editTextField.text = StatesEditorBackend.statesEditorModel.activeStateGroup editDialog.previousString = StatesEditorBackend.statesEditorModel.activeStateGroup - let btn = editDialog.standardButton(Dialog.Apply) + let btn = editDialog.standardButton(Basic.Dialog.Apply) btn.enabled = false } } diff --git a/src/libs/qmljs/qmljsreformatter.cpp b/src/libs/qmljs/qmljsreformatter.cpp index 008968afc46..b29d4fd7827 100644 --- a/src/libs/qmljs/qmljsreformatter.cpp +++ b/src/libs/qmljs/qmljsreformatter.cpp @@ -526,6 +526,10 @@ protected: { out("pragma ", ast->pragmaToken); out(ast->name.toString()); + if (!ast->value.isEmpty()) { + out(": "); + out(ast->value.toString()); + } newLine(); return false; } diff --git a/src/libs/sqlite/sqlitealgorithms.h b/src/libs/sqlite/sqlitealgorithms.h index a410e59aa90..a48ebb0a958 100644 --- a/src/libs/sqlite/sqlitealgorithms.h +++ b/src/libs/sqlite/sqlitealgorithms.h @@ -36,6 +36,36 @@ void insertUpdateDelete(SqliteRange &&sqliteRange, auto endValueIterator = values.end(); auto lastValueIterator = endValueIterator; + auto doUpdate = [&](const auto &sqliteValue, const auto &value) { + UpdateChange updateChange = updateCallback(sqliteValue, value); + switch (updateChange) { + case UpdateChange::Update: + lastValueIterator = currentValueIterator; + break; + case UpdateChange::No: + lastValueIterator = endValueIterator; + break; + } + ++currentSqliteIterator; + ++currentValueIterator; + }; + + auto doInsert = [&](const auto &value) { + insertCallback(value); + ++currentValueIterator; + }; + + auto doDelete = [&](const auto &sqliteValue) { + if (lastValueIterator != endValueIterator) { + if (compareKey(sqliteValue, *lastValueIterator) != 0) + deleteCallback(sqliteValue); + lastValueIterator = endValueIterator; + } else { + deleteCallback(sqliteValue); + } + ++currentSqliteIterator; + }; + while (true) { bool hasMoreValues = currentValueIterator != endValueIterator; bool hasMoreSqliteValues = currentSqliteIterator != endSqliteIterator; @@ -43,44 +73,16 @@ void insertUpdateDelete(SqliteRange &&sqliteRange, auto &&sqliteValue = *currentSqliteIterator; auto &&value = *currentValueIterator; auto compare = compareKey(sqliteValue, value); - if (compare == 0) { - UpdateChange updateChange = updateCallback(sqliteValue, value); - switch (updateChange) { - case UpdateChange::Update: - lastValueIterator = currentValueIterator; - break; - case UpdateChange::No: - lastValueIterator = endValueIterator; - break; - } - ++currentSqliteIterator; - ++currentValueIterator; - } else if (compare > 0) { - insertCallback(value); - ++currentValueIterator; - } else if (compare < 0) { - if (lastValueIterator != endValueIterator) { - if (compareKey(sqliteValue, *lastValueIterator) != 0) - deleteCallback(sqliteValue); - lastValueIterator = endValueIterator; - } else { - deleteCallback(sqliteValue); - } - ++currentSqliteIterator; - } + if (compare == 0) + doUpdate(sqliteValue, value); + else if (compare > 0) + doInsert(value); + else if (compare < 0) + doDelete(sqliteValue); } else if (hasMoreValues) { - insertCallback(*currentValueIterator); - ++currentValueIterator; + doInsert(*currentValueIterator); } else if (hasMoreSqliteValues) { - auto &&sqliteValue = *currentSqliteIterator; - if (lastValueIterator != endValueIterator) { - if (compareKey(sqliteValue, *lastValueIterator) != 0) - deleteCallback(sqliteValue); - lastValueIterator = endValueIterator; - } else { - deleteCallback(sqliteValue); - } - ++currentSqliteIterator; + doDelete(*currentSqliteIterator); } else { break; } diff --git a/src/libs/sqlite/sqlstatementbuilder.cpp b/src/libs/sqlite/sqlstatementbuilder.cpp index 243fb6460af..6602c3903ac 100644 --- a/src/libs/sqlite/sqlstatementbuilder.cpp +++ b/src/libs/sqlite/sqlstatementbuilder.cpp @@ -191,7 +191,7 @@ void SqlStatementBuilder::clearSqlStatement() void SqlStatementBuilder::checkIfPlaceHolderExists(Utils::SmallStringView name) const { - if (name.size() < 2 || !name.startsWith('$') || !m_sqlTemplate.contains(name)) + if (name.size() < 2 || !name.starts_with('$') || !m_sqlTemplate.contains(name)) throwException("SqlStatementBuilder::bind: placeholder name does not exist!", name.data()); } diff --git a/src/libs/utils/smallstring.h b/src/libs/utils/smallstring.h index 56ab91a2f48..8f3f4eed310 100644 --- a/src/libs/utils/smallstring.h +++ b/src/libs/utils/smallstring.h @@ -112,13 +112,13 @@ public: ~BasicSmallString() noexcept { - if (Q_UNLIKELY(hasAllocatedMemory())) + if (hasAllocatedMemory()) [[unlikely]] Memory::deallocate(m_data.data()); } BasicSmallString(const BasicSmallString &other) noexcept { - if (Q_LIKELY(other.isShortString() || other.isReadOnlyReference())) + if (other.isShortString() || other.isReadOnlyReference()) [[likely]] m_data = other.m_data; else new (this) BasicSmallString{other.data(), other.size()}; @@ -126,10 +126,10 @@ public: BasicSmallString &operator=(const BasicSmallString &other) noexcept { - if (Q_LIKELY(this != &other)) { + if (this != &other) [[likely]] { this->~BasicSmallString(); - if (Q_LIKELY(other.isShortString() || other.isReadOnlyReference())) + if (other.isShortString() || other.isReadOnlyReference()) [[likely]] m_data = other.m_data; else new (this) BasicSmallString{other.data(), other.size()}; @@ -146,7 +146,7 @@ public: BasicSmallString &operator=(BasicSmallString &&other) noexcept { - if (Q_LIKELY(this != &other)) { + if (this != &other) [[likely]] { this->~BasicSmallString(); m_data = std::move(other.m_data); @@ -174,7 +174,7 @@ public: SmallStringView toStringView() const noexcept { return SmallStringView(data(), size()); } - operator SmallStringView() const noexcept { return SmallStringView(data(), size()); } + constexpr operator SmallStringView() const noexcept { return SmallStringView(data(), size()); } explicit operator QLatin1StringView() const noexcept { @@ -202,7 +202,7 @@ public: void reserve(size_type newCapacity) noexcept { if (fitsNotInCapacity(newCapacity)) { - if (Q_UNLIKELY(hasAllocatedMemory())) { + if (hasAllocatedMemory()) { m_data.setPointer(Memory::reallocate(m_data.data(), newCapacity)); m_data.setAllocatedCapacity(newCapacity); } else { diff --git a/src/libs/utils/smallstringview.h b/src/libs/utils/smallstringview.h index 09b2a2abaf7..9c8ab7cc125 100644 --- a/src/libs/utils/smallstringview.h +++ b/src/libs/utils/smallstringview.h @@ -13,12 +13,6 @@ #include #include -#if __cpp_lib_constexpr_string >= 201907L -#define constexpr_string constexpr -#else -#define constexpr_string -#endif - namespace Utils { template @@ -36,41 +30,14 @@ class SmallStringView : public std::string_view public: using std::string_view::string_view; - constexpr SmallStringView(const_iterator begin, const_iterator end) noexcept - : std::string_view{std::addressof(*begin), static_cast(std::distance(begin, end))} - {} - -#ifdef Q_CC_MSVC - constexpr SmallStringView(const char *const begin, const char *const end) noexcept - : std::string_view{begin, static_cast(std::distance(begin, end))} - {} -#endif - template = 0> constexpr SmallStringView(const String &string) noexcept : std::string_view{string.data(), static_cast(string.size())} {} - static constexpr SmallStringView fromUtf8(const char *const characterPointer) - { - return SmallStringView(characterPointer); - } - constexpr size_type isEmpty() const noexcept { return empty(); } - constexpr - SmallStringView mid(size_type position) const noexcept - { - return SmallStringView(data() + position, size() - position); - } - - constexpr - SmallStringView mid(size_type position, size_type length) const noexcept - { - return SmallStringView(data() + position, length); - } - - constexpr_string operator std::string() const { return std::string(data(), size()); } + operator std::string() const { return std::string(data(), size()); } explicit operator QString() const { return QString::fromUtf8(data(), int(size())); } @@ -89,57 +56,18 @@ public: { return QUtf8StringView(data(), Utils::ssize(*this)); } - constexpr bool startsWith(SmallStringView subStringToSearch) const noexcept - { - if (size() >= subStringToSearch.size()) - return !std::char_traits::compare(data(), - subStringToSearch.data(), - subStringToSearch.size()); - - return false; - } - - constexpr bool startsWith(char characterToSearch) const noexcept - { - return *begin() == characterToSearch; - } - - constexpr bool endsWith(SmallStringView ending) const noexcept - { - return size() >= ending.size() && std::equal(ending.rbegin(), ending.rend(), rbegin()); - } }; -constexpr bool operator!=(SmallStringView first, SmallStringView second) noexcept +inline constexpr auto operator<=>(const SmallStringView &first, const SmallStringView &second) { - return std::string_view{first} != std::string_view{second}; + return std::string_view{first} <=> std::string_view{second}; } -constexpr bool operator==(SmallStringView first, SmallStringView second) noexcept +inline constexpr bool operator==(const SmallStringView &first, const SmallStringView &second) { return std::string_view{first} == std::string_view{second}; } -constexpr bool operator<(SmallStringView first, SmallStringView second) noexcept -{ - return std::string_view{first} < std::string_view{second}; -} - -constexpr bool operator>(SmallStringView first, SmallStringView second) noexcept -{ - return std::string_view{first} > std::string_view{second}; -} - -constexpr bool operator<=(SmallStringView first, SmallStringView second) noexcept -{ - return std::string_view{first} <= std::string_view{second}; -} - -constexpr bool operator>=(SmallStringView first, SmallStringView second) noexcept -{ - return std::string_view{first} >= std::string_view{second}; -} - constexpr int compare(SmallStringView first, SmallStringView second) noexcept { return first.compare(second); diff --git a/src/plugins/insight/insightwidget.cpp b/src/plugins/insight/insightwidget.cpp index 37db4340ff8..21e75f04549 100644 --- a/src/plugins/insight/insightwidget.cpp +++ b/src/plugins/insight/insightwidget.cpp @@ -103,7 +103,6 @@ void InsightWidget::reloadQmlSource() { QString statesListQmlFilePath = qmlSourcesPath() + QStringLiteral("/Main.qml"); QTC_ASSERT(QFileInfo::exists(statesListQmlFilePath), return ); - engine()->clearComponentCache(); setSource(QUrl::fromLocalFile(statesListQmlFilePath)); if (!rootObject()) { diff --git a/src/plugins/mcusupport/mcubuildstep.cpp b/src/plugins/mcusupport/mcubuildstep.cpp index 4fb904e86e6..6d56d52a111 100644 --- a/src/plugins/mcusupport/mcubuildstep.cpp +++ b/src/plugins/mcusupport/mcubuildstep.cpp @@ -100,6 +100,11 @@ DeployMcuProcessStep::DeployMcuProcessStep(ProjectExplorer::BuildStepList *bc, I cmdLine.addArg(directory); return cmdLine; }); + + connect(this, &BuildStep::addOutput, this, [](const QString &str, OutputFormat fmt) { + if (fmt == OutputFormat::ErrorMessage) + showError(str); + }); } // Workaround for QDS-13763, when UL-10456 is completed this can be removed with the next LTS diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 0ba34894e2a..e2d3d8303d2 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -104,7 +104,6 @@ add_qtc_plugin(QmlDesigner qmldesignericons.h qmldesignerplugin.cpp qmldesignerplugin.h qmldesignerexternaldependencies.cpp qmldesignerexternaldependencies.h - qmldesignerprojectmanager.cpp qmldesignerprojectmanager.h settingspage.cpp settingspage.h shortcutmanager.cpp shortcutmanager.h designermcumanager.cpp designermcumanager.h @@ -133,6 +132,16 @@ if (QTC_STATIC_BUILD AND TARGET QmlDesigner) extend_qtc_target(QmlDesigner PUBLIC_DEPENDS TextEditor) endif() +extend_qtc_plugin(QmlDesigner + PUBLIC_INCLUDES project + SOURCES_PREFIX project + SOURCES + qmldesignerprojectmanager.cpp + qmldesignerprojectmanager.h + projectstorageerrornotifier.cpp + projectstorageerrornotifier.h +) + extend_qtc_plugin(QmlDesigner PUBLIC_INCLUDES instances SOURCES_PREFIX instances @@ -412,7 +421,9 @@ extend_qtc_plugin(QmlDesigner gradientpresetdefaultlistmodel.cpp gradientpresetdefaultlistmodel.h gradientpresetitem.cpp gradientpresetitem.h gradientpresetlistmodel.cpp gradientpresetlistmodel.h + instanceimageprovider.cpp instanceimageprovider.h propertyeditorcontextobject.cpp propertyeditorcontextobject.h + propertyeditordynamicpropertiesproxymodel.cpp propertyeditordynamicpropertiesproxymodel.h propertyeditorqmlbackend.cpp propertyeditorqmlbackend.h propertyeditortransaction.cpp propertyeditortransaction.h propertyeditorvalue.cpp propertyeditorvalue.h @@ -421,7 +432,9 @@ extend_qtc_plugin(QmlDesigner propertynamevalidator.cpp propertynamevalidator.h tooltip.cpp tooltip.h qmlanchorbindingproxy.cpp qmlanchorbindingproxy.h + qmlmaterialnodeproxy.cpp qmlmaterialnodeproxy.h qmlmodelnodeproxy.cpp qmlmodelnodeproxy.h + qmltexturenodeproxy.cpp qmltexturenodeproxy.h quick2propertyeditorview.cpp quick2propertyeditorview.h propertyeditorutils.cpp propertyeditorutils.h ) @@ -477,7 +490,6 @@ extend_qtc_plugin(QmlDesigner materialbrowserwidget.cpp materialbrowserwidget.h materialbrowsermodel.cpp materialbrowsermodel.h materialbrowsertexturesmodel.cpp materialbrowsertexturesmodel.h - materialutils.cpp materialutils.h ) extend_qtc_plugin(QmlDesigner @@ -571,6 +583,16 @@ extend_qtc_plugin(QmlDesigner annotationeditor.qrc ) +extend_qtc_plugin(QmlDesigner + SOURCES_PREFIX components/scripteditor + SOURCES + scripteditorstatements.cpp scripteditorstatements.h + scripteditorevaluator.cpp scripteditorevaluator.h + scripteditorutils.cpp scripteditorutils.h + propertytreemodel.cpp propertytreemodel.h + scripteditorbackend.cpp scripteditorbackend.h +) + extend_qtc_plugin(QmlDesigner SOURCES_PREFIX components/connectioneditor SOURCES @@ -578,15 +600,12 @@ extend_qtc_plugin(QmlDesigner bindingmodel.cpp bindingmodel.h bindingmodelitem.cpp bindingmodelitem.h connectioneditor.qrc - connectioneditorevaluator.cpp connectioneditorevaluator.h - connectioneditorstatements.cpp connectioneditorstatements.h connectionmodel.cpp connectionmodel.h connectionview.cpp connectionview.h dynamicpropertiesmodel.cpp dynamicpropertiesmodel.h dynamicpropertiesitem.cpp dynamicpropertiesitem.h - connectioneditorutils.cpp connectioneditorutils.h selectiondynamicpropertiesproxymodel.cpp selectiondynamicpropertiesproxymodel.h - propertytreemodel.cpp propertytreemodel.h + connectioneditorlogging.cpp connectioneditorlogging.h ) extend_qtc_plugin(QmlDesigner @@ -780,39 +799,6 @@ extend_qtc_plugin(QmlDesigner dvconnector.cpp dvconnector.h ) -add_qtc_plugin(assetexporterplugin - PLUGIN_CLASS AssetExporterPlugin - CONDITION TARGET QmlDesigner - PLUGIN_DEPENDS - Core ProjectExplorer QmlDesigner - DEPENDS Utils Qt::Qml Qt::QuickPrivate - PUBLIC_INCLUDES assetexporterplugin - PLUGIN_PATH ${QmlDesignerPluginInstallPrefix} -) - -extend_qtc_plugin(assetexporterplugin - CONDITION ENABLE_COMPILE_WARNING_AS_ERROR - PROPERTIES COMPILE_WARNING_AS_ERROR ON -) - -extend_qtc_plugin(assetexporterplugin - SOURCES_PREFIX assetexporterplugin - SOURCES - assetexportdialog.h assetexportdialog.cpp - assetexporter.h assetexporter.cpp - assetexporterplugin.h assetexporterplugin.cpp - assetexporterview.h assetexporterview.cpp - assetexportpluginconstants.h - componentexporter.h componentexporter.cpp - exportnotification.h exportnotification.cpp - filepathmodel.h filepathmodel.cpp - dumpers/assetnodedumper.h dumpers/assetnodedumper.cpp - dumpers/itemnodedumper.h dumpers/itemnodedumper.cpp - dumpers/nodedumper.h dumpers/nodedumper.cpp - dumpers/textnodedumper.h dumpers/textnodedumper.cpp - assetexporterplugin.qrc -) - add_qtc_plugin(componentsplugin PLUGIN_CLASS ComponentsPlugin CONDITION TARGET QmlDesigner diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp b/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp deleted file mode 100644 index 658bbc88437..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "assetexportdialog.h" - -#include "../qmldesignertr.h" -#include "assetexportpluginconstants.h" -#include "filepathmodel.h" - -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -using namespace ProjectExplorer; -using namespace Utils; - -namespace QmlDesigner { - -static void addFormattedMessage(OutputFormatter *formatter, const QString &str, OutputFormat format) -{ - if (!formatter) - return; - - QPlainTextEdit *edit = formatter->plainTextEdit(); - QScrollBar *scroll = edit->verticalScrollBar(); - bool isAtBottom = scroll && scroll->value() == scroll->maximum(); - - QString msg = str + "\n"; - formatter->appendMessage(msg, format); - - if (isAtBottom) - scroll->setValue(scroll->maximum()); -} - -AssetExportDialog::AssetExportDialog(const FilePath &exportPath, - AssetExporter &assetExporter, FilePathModel &model, - QWidget *parent) : - QDialog(parent), - m_assetExporter(assetExporter), - m_filePathModel(model), - m_filesView(new QListView), - m_exportLogs(new QPlainTextEdit), - m_outputFormatter(new Utils::OutputFormatter()) -{ - resize(768, 480); - setWindowTitle(Tr::tr("Export Components")); - - m_stackedWidget = new QStackedWidget; - - m_exportProgress = new QProgressBar; - m_exportProgress->setRange(0,0); - - auto optionsWidget = new QWidget; - - auto advancedOptions = new DetailsWidget; - advancedOptions->setSummaryText(tr("Advanced Options")); - advancedOptions->setWidget(optionsWidget); - - m_buttonBox = new QDialogButtonBox; - m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Close); - - m_exportPath = new PathChooser; - m_exportPath->setExpectedKind(PathChooser::Kind::SaveFile); - m_exportPath->setFilePath( - exportPath.pathAppended( - ProjectExplorer::ProjectManager::startupProject()->displayName() + ".metadata" - )); - m_exportPath->setPromptDialogTitle(tr("Choose Export File")); - m_exportPath->setPromptDialogFilter(tr("Metadata file (*.metadata)")); - m_exportPath->lineEdit()->setReadOnly(true); - m_exportPath->addButton(tr("Open"), this, [this] { - Core::FileUtils::showInGraphicalShell(m_exportPath->filePath()); - }); - - m_exportAssetsCheck = new QCheckBox(tr("Export assets"), this); - m_exportAssetsCheck->setChecked(true); - - m_perComponentExportCheck = new QCheckBox(tr("Export components separately"), this); - m_perComponentExportCheck->setChecked(false); - - m_buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false); - - m_stackedWidget->addWidget(m_filesView); - m_filesView->setModel(&m_filePathModel); - - m_exportLogs->setReadOnly(true); - m_outputFormatter->setPlainTextEdit(m_exportLogs); - m_stackedWidget->addWidget(m_exportLogs); - switchView(false); - - connect(m_buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, [this] { - m_buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false); - m_assetExporter.cancel(); - }); - - m_exportBtn = m_buttonBox->addButton(tr("Export"), QDialogButtonBox::AcceptRole); - m_exportBtn->setEnabled(false); - connect(m_exportBtn, &QPushButton::clicked, this, &AssetExportDialog::onExport); - connect(&m_filePathModel, &FilePathModel::modelReset, this, [this] { - m_exportProgress->setRange(0, 1000); - m_exportProgress->setValue(0); - m_exportBtn->setEnabled(true); - }); - - connect(m_buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, [this] { - close(); - }); - m_buttonBox->button(QDialogButtonBox::Close)->setVisible(false); - - connect(&m_assetExporter, &AssetExporter::stateChanged, - this, &AssetExportDialog::onExportStateChanged); - connect(&m_assetExporter, &AssetExporter::exportProgressChanged, - this, &AssetExportDialog::updateExportProgress); - - connect(&taskHub(), &TaskHub::taskAdded, this, &AssetExportDialog::onTaskAdded); - - using namespace Layouting; - - Column { - m_exportAssetsCheck, - m_perComponentExportCheck, - st, - noMargin, - }.attachTo(optionsWidget); - - Column { - Form { Tr::tr("Export path:"), m_exportPath }, - advancedOptions, - m_stackedWidget, - m_exportProgress, - m_buttonBox, - }.attachTo(this); -} - -AssetExportDialog::~AssetExportDialog() -{ - m_assetExporter.cancel(); -} - -void AssetExportDialog::onExport() -{ - switchView(true); - - updateExportProgress(0.0); - TaskHub::clearTasks(Constants::TASK_CATEGORY_ASSET_EXPORT); - m_exportLogs->clear(); - - Utils::FilePath selectedPath = m_exportPath->filePath(); - Utils::FilePath exportPath = m_perComponentExportCheck->isChecked() ? - (selectedPath.isDir() ? selectedPath : selectedPath.parentDir()) : - selectedPath; - - m_assetExporter.exportQml(m_filePathModel.files(), exportPath, - m_exportAssetsCheck->isChecked(), - m_perComponentExportCheck->isChecked()); -} - -void AssetExportDialog::onExportStateChanged(AssetExporter::ParsingState newState) -{ - switch (newState) { - case AssetExporter::ParsingState::ExportingDone: - m_exportBtn->setVisible(false); - m_buttonBox->button(QDialogButtonBox::Close)->setVisible(true); - break; - default: - break; - } - - m_exportBtn->setEnabled(newState == AssetExporter::ParsingState::ExportingDone); - m_buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(m_assetExporter.isBusy()); -} - -void AssetExportDialog::updateExportProgress(double value) -{ - value = std::max(0.0, std::min(1.0, value)); - m_exportProgress->setValue(std::round(value * 1000)); -} - -void AssetExportDialog::switchView(bool showExportView) -{ - if (showExportView) - m_stackedWidget->setCurrentWidget(m_exportLogs); - else - m_stackedWidget->setCurrentWidget(m_filesView); -} - -void AssetExportDialog::onTaskAdded(const ProjectExplorer::Task &task) -{ - Utils::OutputFormat format = Utils::NormalMessageFormat; - if (task.category == Constants::TASK_CATEGORY_ASSET_EXPORT) { - switch (task.type) { - case ProjectExplorer::Task::Error: - format = Utils::StdErrFormat; - break; - case ProjectExplorer::Task::Warning: - format = Utils::StdOutFormat; - break; - default: - format = Utils::NormalMessageFormat; - } - addFormattedMessage(m_outputFormatter, task.description(), format); - } -} - -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.h b/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.h deleted file mode 100644 index bf277f82bbe..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.h +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "assetexporter.h" - -#include - -#include -#include - -QT_BEGIN_NAMESPACE -class QPushButton; -class QCheckBox; -class QDialogButtonBox; -class QListView; -class QPlainTextEdit; -class QProgressBar; -class QStackedWidget; -QT_END_NAMESPACE - -namespace Utils { -class OutputFormatter; -class PathChooser; -} - -namespace ProjectExplorer { -class Task; -} - -namespace QmlDesigner { - -class FilePathModel; - -class AssetExportDialog : public QDialog -{ - Q_OBJECT - -public: - explicit AssetExportDialog(const Utils::FilePath &exportPath, AssetExporter &assetExporter, - FilePathModel& model, QWidget *parent = nullptr); - ~AssetExportDialog(); - -private: - void onExport(); - void onExportStateChanged(AssetExporter::ParsingState newState); - void updateExportProgress(double value); - void switchView(bool showExportView); - void onTaskAdded(const ProjectExplorer::Task &task); - -private: - AssetExporter &m_assetExporter; - FilePathModel &m_filePathModel; - QPushButton *m_exportBtn = nullptr; - QCheckBox *m_exportAssetsCheck = nullptr; - QCheckBox *m_perComponentExportCheck = nullptr; - QListView *m_filesView = nullptr; - QPlainTextEdit *m_exportLogs = nullptr; - Utils::OutputFormatter *m_outputFormatter = nullptr; - Utils::PathChooser *m_exportPath = nullptr; - QDialogButtonBox *m_buttonBox = nullptr; - QStackedWidget *m_stackedWidget = nullptr; - QProgressBar *m_exportProgress = nullptr; -}; - -} // QmlDesigner diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp b/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp deleted file mode 100644 index 874618ccd31..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp +++ /dev/null @@ -1,550 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "assetexporter.h" -#include "componentexporter.h" -#include "exportnotification.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -using namespace ProjectExplorer; -using namespace std; -namespace { -bool makeParentPath(const Utils::FilePath &path) -{ - QDir d; - return d.mkpath(path.toFileInfo().absolutePath()); -} - -QByteArray generateHash(const QString &token) { - static uint counter = 0; - std::mt19937 gen(std::random_device().operator()()); - std::uniform_int_distribution<> distribution(1, 99999); - QByteArray data = QString("%1%2%3").arg(token).arg(++counter).arg(distribution(gen)).toLatin1(); - return QCryptographicHash::hash(data, QCryptographicHash::Md5).toHex(); -} - -Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.assetExporter", QtInfoMsg) -Q_LOGGING_CATEGORY(loggerWarn, "qtc.designer.assetExportPlugin.assetExporter", QtWarningMsg) -Q_LOGGING_CATEGORY(loggerError, "qtc.designer.assetExportPlugin.assetExporter", QtCriticalMsg) -} - -namespace QmlDesigner { - -class AssetDumper -{ -public: - AssetDumper(); - ~AssetDumper(); - - void dumpAsset(const QPixmap &p, const Utils::FilePath &path); - - /* Keeps on dumping until all assets are dumped, then quits */ - void quitDumper(); - - /* Aborts dumping */ - void abortDumper(); - -private: - void addAsset(const QPixmap &p, const Utils::FilePath &path); - void doDumping(QPromise &promise); - void savePixmap(const QPixmap &p, Utils::FilePath &path) const; - - QFuture m_dumpFuture; - QMutex m_queueMutex; - QWaitCondition m_queueCondition; - std::queue> m_assets; - std::atomic m_quitDumper; -}; - -AssetExporter::AssetExporter(AssetExporterView *view, - ProjectExplorer::Project *project, - ProjectStorageDependencies projectStorageDependencies) - : m_currentState(*this) - , m_project(project) - , m_view(view) - , m_projectStorageDependencies{projectStorageDependencies} -{ - connect(m_view, &AssetExporterView::loadingFinished, this, &AssetExporter::onQmlFileLoaded); - connect(m_view, &AssetExporterView::loadingError, this, &AssetExporter::notifyLoadError); -} - -AssetExporter::~AssetExporter() -{ - cancel(); -} - -void AssetExporter::exportQml(const Utils::FilePaths &qmlFiles, const Utils::FilePath &exportPath, - bool exportAssets, bool perComponentExport) -{ - m_perComponentExport = perComponentExport; - ExportNotification::addInfo(tr("Export root directory: %1.\nExporting assets: %2") - .arg(exportPath.isDir() - ? exportPath.toUserOutput() - : exportPath.parentDir().toUserOutput()) - .arg(exportAssets? tr("Yes") : tr("No"))); - - if (m_perComponentExport) - ExportNotification::addInfo(tr("Each component is exported separately.")); - - notifyProgress(0.0); - m_exportFiles = qmlFiles; - m_totalFileCount = m_exportFiles.count(); - m_components.clear(); - m_componentUuidCache.clear(); - m_exportPath = exportPath.isDir() ? exportPath : exportPath.parentDir(); - m_exportFile = exportPath.fileName(); - m_currentState.change(ParsingState::Parsing); - if (exportAssets) - m_assetDumper = make_unique(); - else - m_assetDumper.reset(); - - QTimer::singleShot(0, this, &AssetExporter::beginExport); -} - -void AssetExporter::beginExport() -{ - for (const Utils::FilePath &p : std::as_const(m_exportFiles)) { - if (m_cancelled) - break; - preprocessQmlFile(p); - } - - if (!m_cancelled) - triggerLoadNextFile(); -} - -void AssetExporter::cancel() -{ - if (!m_cancelled) { - ExportNotification::addInfo(tr("Canceling export.")); - m_assetDumper.reset(); - m_cancelled = true; - } -} - -bool AssetExporter::isBusy() const -{ - return m_currentState == AssetExporter::ParsingState::Parsing || - m_currentState == AssetExporter::ParsingState::ExportingAssets || - m_currentState == AssetExporter::ParsingState::WritingJson; -} - -const QPixmap &AssetExporter::generateAsset(const ModelNode &node) -{ - static QPixmap nullPixmap; - if (m_cancelled) - return nullPixmap; - - const QString uuid = node.auxiliaryDataWithDefault(uuidProperty).toString(); - QTC_ASSERT(!uuid.isEmpty(), return nullPixmap); - - if (!m_assets.contains(uuid)) { - // Generate asset. - QmlObjectNode objectNode(node); - QPixmap asset = objectNode.toQmlItemNode().instanceRenderPixmap(); - m_assets[uuid] = asset; - } - return m_assets[uuid]; -} - -Utils::FilePath AssetExporter::assetPath(const ModelNode &node, const Component *component, - const QString &suffix) const -{ - const QString uuid = node.auxiliaryDataWithDefault(uuidProperty).toString(); - if (!component || uuid.isEmpty()) - return {}; - - const Utils::FilePath assetExportDir = - m_perComponentExport ? componentExportDir(component) : m_exportPath; - const Utils::FilePath assetPath = assetExportDir.pathAppended("assets") - .pathAppended(uuid + suffix + ".png"); - - return assetPath; -} - -void AssetExporter::exportAsset(const QPixmap &asset, const Utils::FilePath &path) -{ - if (m_cancelled || !m_assetDumper) - return; - - m_assetDumper->dumpAsset(asset, path); -} - -void AssetExporter::exportComponent(const ModelNode &rootNode) -{ - qCDebug(loggerInfo) << "Exporting component" << rootNode.id(); - m_components.push_back(make_unique(*this, rootNode)); - m_components.back()->exportComponent(); -} - -void AssetExporter::notifyLoadError(AssetExporterView::LoadState state) -{ - QString errorStr = tr("Unknown error."); - switch (state) { - case AssetExporterView::LoadState::Exausted: - errorStr = tr("Loading file is taking too long."); - break; - case AssetExporterView::LoadState::QmlErrorState: - errorStr = tr("Cannot parse. The file contains coding errors."); - break; - default: - return; - } - qCDebug(loggerError) << "QML load error:" << errorStr; - ExportNotification::addError(tr("Loading components failed. %1").arg(errorStr)); -} - -void AssetExporter::notifyProgress(double value) const -{ - emit exportProgressChanged(value); -} - -void AssetExporter::onQmlFileLoaded() -{ - QTC_ASSERT(m_view && m_view->model(), qCDebug(loggerError) << "Null model"; return); - qCDebug(loggerInfo) << "Qml file load done" << m_view->model()->fileUrl(); - - QmlDesigner::DesignDocument *designDocument = QmlDesigner::QmlDesignerPlugin::instance() - ->documentManager() - .currentDesignDocument(); - if (designDocument->hasQmlParseErrors()) { - ExportNotification::addError(tr("Cannot export component. Document \"%1\" has parsing errors.") - .arg(designDocument->displayName())); - } else { - exportComponent(m_view->rootModelNode()); - if (Utils::Result res = m_view->saveQmlFile(); !res) { - ExportNotification::addError(tr("Error saving component file. %1") - .arg(res.error().isEmpty()? tr("Unknown") : res.error())); - } - } - notifyProgress((m_totalFileCount - m_exportFiles.count()) * 0.8 / m_totalFileCount); - triggerLoadNextFile(); -} - -Utils::FilePath AssetExporter::componentExportDir(const Component *component) const -{ - return m_exportPath.pathAppended(component->name()); -} - -void AssetExporter::preprocessQmlFile(const Utils::FilePath &path) -{ - // Load the QML file and assign UUIDs to items having none. - // Meanwhile cache the Component UUIDs as well -#ifdef QDS_USE_PROJECTSTORAGE - ModelPointer model = Model::create(m_projectStorageDependencies, - "Item", - {Import::createLibraryImport("QtQuick")}, - path.path()); -#else - ModelPointer model = Model::create("Item", 2, 7); -#endif - Utils::FileReader reader; - if (!reader.fetch(path)) { - ExportNotification::addError(tr("Cannot preprocess file: %1. Error %2") - .arg(path.toUserOutput()).arg(reader.errorString())); - return; - } - - QPlainTextEdit textEdit; - textEdit.setPlainText(QString::fromUtf8(reader.data())); - NotIndentingTextEditModifier *modifier = new NotIndentingTextEditModifier(textEdit.document()); - modifier->setParent(model.get()); - auto rewriterView = std::make_unique(m_view->externalDependencies(), - QmlDesigner::RewriterView::Validate); - rewriterView->setCheckSemanticErrors(false); - rewriterView->setTextModifier(modifier); - model->attachView(rewriterView.get()); - rewriterView->restoreAuxiliaryData(); - ModelNode rootNode = rewriterView->rootModelNode(); - if (!rootNode.isValid()) { - ExportNotification::addError(tr("Cannot preprocess file: %1").arg(path.toUrlishString())); - return; - } - - if (assignUuids(rootNode)) { - // Some UUIDs were assigned. Rewrite the file. - rewriterView->writeAuxiliaryData(); - const QByteArray data = textEdit.toPlainText().toUtf8(); - Utils::FileSaver saver(path, QIODevice::Text); - saver.write(data); - if (!saver.finalize()) { - ExportNotification::addError(tr("Cannot update %1.\n%2") - .arg(path.toUserOutput()).arg(saver.errorString())); - return; - } - - // Close the document if already open. - // UUIDS are changed and editor must reopen the document, otherwise stale state of the - // document is loaded. - for (Core::IDocument *doc : Core::DocumentModel::openedDocuments()) { - if (doc->filePath() == path) { - Core::EditorManager::closeDocuments({doc}, false); - break; - } - } - } - - // Cache component UUID - const QString uuid = rootNode.auxiliaryDataWithDefault(uuidProperty).toString(); - m_componentUuidCache[path.toUrlishString()] = uuid; -} - -bool AssetExporter::assignUuids(const ModelNode &root) -{ - // Assign an UUID to the node without one. - // Return true if an assignment takes place. - bool changed = false; - for (const ModelNode &node : root.allSubModelNodesAndThisNode()) { - const QString uuid = node.auxiliaryDataWithDefault(uuidProperty).toString(); - if (uuid.isEmpty()) { - // Assign an unique identifier to the node. - QByteArray uuid = generateUuid(node); - node.setAuxiliaryData(uuidProperty, QString::fromLatin1(uuid)); - changed = true; - } - } - return changed; -} - -QByteArray AssetExporter::generateUuid(const ModelNode &node) -{ - QByteArray uuid; - do { - uuid = generateHash(node.id()); - } while (m_usedHashes.contains(uuid)); - m_usedHashes.insert(uuid); - return uuid; -} - -QString AssetExporter::componentUuid(const ModelNode &instance) const -{ - // Returns the UUID of the component's root node - // Empty string is returned if the node is not an instance of a component within - // the project. - if (instance) { - const QString path = ModelUtils::componentFilePath(instance); - return m_componentUuidCache.value(path); - } - - return {}; -} - -void AssetExporter::triggerLoadNextFile() -{ - QTimer::singleShot(0, this, &AssetExporter::loadNextFile); -} - -void AssetExporter::loadNextFile() -{ - if (m_cancelled || m_exportFiles.isEmpty()) { - notifyProgress(0.8); - m_currentState.change(ParsingState::ParsingFinished); - writeMetadata(); - return; - } - - // Load the next pending file. - const Utils::FilePath file = m_exportFiles.takeFirst(); - ExportNotification::addInfo(tr("Exporting file %1.").arg(file.toUserOutput())); - qCDebug(loggerInfo) << "Loading next file" << file; - m_view->loadQmlFile(file); -} - -void AssetExporter::writeMetadata() const -{ - if (m_cancelled) { - notifyProgress(1.0); - ExportNotification::addInfo(tr("Export canceled.")); - m_currentState.change(ParsingState::ExportingDone); - return; - } - - - auto writeFile = [](const Utils::FilePath &path, const QJsonArray &artboards) { - if (!makeParentPath(path)) { - ExportNotification::addError(tr("Writing metadata failed. Cannot create file %1"). - arg(path.toUrlishString())); - return; - } - - ExportNotification::addInfo(tr("Writing metadata to file %1.").arg(path.toUserOutput())); - - QJsonObject jsonRoot; // TODO: Write plugin info to root - jsonRoot.insert("artboards", artboards); - QJsonDocument doc(jsonRoot); - if (doc.isNull() || doc.isEmpty()) { - ExportNotification::addError(tr("Empty JSON document.")); - return; - } - - Utils::FileSaver saver(path, QIODevice::Text); - saver.write(doc.toJson(QJsonDocument::Indented)); - if (!saver.finalize()) { - ExportNotification::addError(tr("Writing metadata failed. %1"). - arg(saver.errorString())); - } - }; - - m_currentState.change(ParsingState::WritingJson); - - auto const startupProject = ProjectExplorer::ProjectManager::startupProject(); - QTC_ASSERT(startupProject, return); - const QString projectName = startupProject->displayName(); - - if (m_perComponentExport) { - for (auto &component : m_components) { - const Utils::FilePath path = componentExportDir(component.get()); - writeFile(path.pathAppended(component->name() + ".metadata"), {component->json()}); - } - } else { - QJsonArray artboards; - std::transform(m_components.cbegin(), m_components.cend(), back_inserter(artboards), - [](const unique_ptr &c) {return c->json(); }); - writeFile(m_exportPath.pathAppended(m_exportFile), artboards); - } - notifyProgress(1.0); - ExportNotification::addInfo(tr("Export finished.")); - if (m_assetDumper) - m_assetDumper->quitDumper(); - m_currentState.change(ParsingState::ExportingDone); -} - -AssetExporter::State::State(AssetExporter &exporter) : - m_assetExporter(exporter) -{ - -} - -void AssetExporter::State::change(const ParsingState &state) -{ - qCDebug(loggerInfo()) << "Assetimporter State change: Old: " << m_state << "New: " << state; - if (m_state != state) { - m_state = state; - emit m_assetExporter.stateChanged(m_state); - } -} - -QDebug operator<<(QDebug os, const AssetExporter::ParsingState &s) -{ - os << static_cast::type>(s); - return os; -} - -AssetDumper::AssetDumper(): - m_quitDumper(false) -{ - m_dumpFuture = Utils::asyncRun(&AssetDumper::doDumping, this); -} - -AssetDumper::~AssetDumper() -{ - abortDumper(); -} - -void AssetDumper::dumpAsset(const QPixmap &p, const Utils::FilePath &path) -{ - addAsset(p, path); -} - -void AssetDumper::quitDumper() -{ - m_quitDumper = true; - m_queueCondition.wakeAll(); - if (!m_dumpFuture.isFinished()) - m_dumpFuture.waitForFinished(); -} - -void AssetDumper::abortDumper() -{ - if (!m_dumpFuture.isFinished()) { - m_dumpFuture.cancel(); - m_queueCondition.wakeAll(); - m_dumpFuture.waitForFinished(); - } -} - -void AssetDumper::addAsset(const QPixmap &p, const Utils::FilePath &path) -{ - QMutexLocker locker(&m_queueMutex); - qDebug() << "Save Asset:" << path; - m_assets.push({p, path}); -} - -void AssetDumper::doDumping(QPromise &promise) -{ - auto haveAsset = [this] (std::pair *asset) { - QMutexLocker locker(&m_queueMutex); - if (m_assets.empty()) - return false; - *asset = m_assets.front(); - m_assets.pop(); - return true; - }; - - forever { - std::pair asset; - if (haveAsset(&asset)) { - if (promise.isCanceled()) - break; - savePixmap(asset.first, asset.second); - } else { - if (m_quitDumper) - break; - QMutexLocker locker(&m_queueMutex); - m_queueCondition.wait(&m_queueMutex); - } - - if (promise.isCanceled()) - break; - } -} - -void AssetDumper::savePixmap(const QPixmap &p, Utils::FilePath &path) const -{ - if (p.isNull()) { - qCDebug(loggerWarn) << "Dumping null pixmap" << path; - return; - } - - if (!makeParentPath(path)) { - ExportNotification::addError(Tr::tr("Error creating asset directory. %1").arg(path.fileName())); - return; - } - - if (!p.save(path.toUrlishString())) { - ExportNotification::addError(Tr::tr("Error saving asset. %1").arg(path.fileName())); - } -} - -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporter.h b/src/plugins/qmldesigner/assetexporterplugin/assetexporter.h deleted file mode 100644 index 36e7d29136b..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporter.h +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "assetexporterview.h" - -#include -#include - -#include - -namespace ProjectExplorer { class Project; } - -namespace QmlDesigner { -class AssetDumper; -class Component; - -class AssetExporter : public QObject -{ - Q_OBJECT - -public: - - enum class ParsingState { - Idle = 0, - Parsing, - ParsingFinished, - ExportingAssets, - ExportingAssetsFinished, - WritingJson, - ExportingDone - }; - - AssetExporter(AssetExporterView *view, - ProjectExplorer::Project *project, - ProjectStorageDependencies projectStorageDependencies); - ~AssetExporter(); - - void exportQml(const Utils::FilePaths &qmlFiles, const Utils::FilePath &exportPath, - bool exportAssets, bool perComponentExport); - - void cancel(); - bool isBusy() const; - - const QPixmap &generateAsset(const ModelNode &node); - Utils::FilePath assetPath(const ModelNode &node, const Component *component, - const QString &suffix = {}) const; - void exportAsset(const QPixmap &asset, const Utils::FilePath &path); - QByteArray generateUuid(const ModelNode &node); - QString componentUuid(const ModelNode &instance) const; - -signals: - void stateChanged(ParsingState); - void exportProgressChanged(double) const; - -private: - ParsingState currentState() const { return m_currentState.m_state; } - void exportComponent(const ModelNode &rootNode); - void writeMetadata() const; - void notifyLoadError(AssetExporterView::LoadState state); - void notifyProgress(double value) const; - void triggerLoadNextFile(); - void loadNextFile(); - - void onQmlFileLoaded(); - Utils::FilePath componentExportDir(const Component *component) const; - - void beginExport(); - void preprocessQmlFile(const Utils::FilePath &path); - bool assignUuids(const ModelNode &root); - -private: - mutable class State { - public: - State(AssetExporter&); - void change(const ParsingState &state); - operator ParsingState() const { return m_state; } - AssetExporter &m_assetExporter; - ParsingState m_state = ParsingState::Idle; - } m_currentState; - ProjectExplorer::Project *m_project = nullptr; - AssetExporterView *m_view = nullptr; - Utils::FilePaths m_exportFiles; - unsigned int m_totalFileCount = 0; - Utils::FilePath m_exportPath; - QString m_exportFile; - bool m_perComponentExport = false; - std::vector> m_components; - QHash m_componentUuidCache; - QSet m_usedHashes; - QHash m_assets; - ProjectStorageDependencies m_projectStorageDependencies; - std::unique_ptr m_assetDumper; - bool m_cancelled = false; -}; -QDebug operator<< (QDebug os, const QmlDesigner::AssetExporter::ParsingState& s); - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.cpp b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.cpp deleted file mode 100644 index 6246e0ed36f..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.cpp +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "assetexporterplugin.h" - -#include "assetexportdialog.h" -#include "assetexporter.h" -#include "assetexporterview.h" -#include "assetexportpluginconstants.h" -#include "componentexporter.h" -#include "filepathmodel.h" -#include - -#include "dumpers/itemnodedumper.h" -#include "dumpers/textnodedumper.h" -#include "dumpers/assetnodedumper.h" - -#include "coreplugin/actionmanager/actionmanager.h" -#include "coreplugin/actionmanager/actioncontainer.h" -#include "coreplugin/documentmanager.h" -#include "qmldesigner/qmldesignerplugin.h" -#include "projectexplorer/projectexplorerconstants.h" -#include "projectexplorer/projectmanager.h" -#include "projectexplorer/project.h" -#include "projectexplorer/projectmanager.h" -#include "projectexplorer/taskhub.h" - -#include "extensionsystem/pluginmanager.h" -#include "extensionsystem/pluginspec.h" - -#include "utils/algorithm.h" - -#include -#include - -#include - -namespace QmlDesigner { - -AssetExporterPlugin::AssetExporterPlugin() - : m_projectManager{QmlDesigner::QmlDesignerPlugin::projectManagerForPluginInitializationOnly()} -{ - ProjectExplorer::TaskHub::addCategory({Constants::TASK_CATEGORY_ASSET_EXPORT, - tr("Asset Export"), - tr("Issues with exporting assets."), - false}); - - auto *designerPlugin = QmlDesigner::QmlDesignerPlugin::instance(); - - auto &viewManager = designerPlugin->viewManager(); - m_view = viewManager.registerView(std::make_unique( - designerPlugin->externalDependenciesForPluginInitializationOnly())); - - // Add dumper templates for factory instantiation. - Component::addNodeDumper(); - Component::addNodeDumper(); - Component::addNodeDumper(); - - // Instantiate actions created by the plugin. - addActions(); - - connect(ProjectExplorer::ProjectManager::instance(), - &ProjectExplorer::ProjectManager::startupProjectChanged, - this, &AssetExporterPlugin::updateActions); - - updateActions(); -} - -QString AssetExporterPlugin::pluginName() const -{ - return QLatin1String("AssetExporterPlugin"); -} - -void AssetExporterPlugin::onExport() -{ - auto startupProject = ProjectExplorer::ProjectManager::startupProject(); - if (!startupProject) - return; - - FilePathModel model(startupProject); - auto exportDir = startupProject->projectFilePath().parentDir(); - if (!exportDir.parentDir().isEmpty()) - exportDir = exportDir.parentDir(); - exportDir = exportDir.pathAppended(startupProject->displayName() + "_export"); - AssetExporter assetExporter(m_view, startupProject, m_projectManager.projectStorageDependencies()); - AssetExportDialog assetExporterDialog(exportDir, assetExporter, model); - assetExporterDialog.exec(); -} - -void AssetExporterPlugin::addActions() -{ - auto exportAction = new QAction(tr("Export Components"), this); - exportAction->setToolTip(tr("Export components in the current project.")); - connect(exportAction, &QAction::triggered, this, &AssetExporterPlugin::onExport); - Core::Command *cmd = Core::ActionManager::registerAction(exportAction, Constants::EXPORT_QML); - - // Add action to build menu - Core::ActionContainer *buildMenu = - Core::ActionManager::actionContainer(ProjectExplorer::Constants::M_BUILDPROJECT); - buildMenu->addAction(cmd, ProjectExplorer::Constants::G_BUILD_RUN); -} - -void AssetExporterPlugin::updateActions() -{ - auto project = ProjectExplorer::ProjectManager::startupProject(); - QAction* const exportAction = Core::ActionManager::command(Constants::EXPORT_QML)->action(); - exportAction->setEnabled(project && !project->needsConfiguration()); -} - -QString AssetExporterPlugin::metaInfo() const -{ - return QLatin1String(":/assetexporterplugin/assetexporterplugin.metainfo"); -} - -} //QmlDesigner diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.h b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.h deleted file mode 100644 index 1c2b779138e..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.h +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#pragma once - -#include - - -namespace QmlDesigner { -class AssetExporter; -class AssetExporterView; -class AssetExporterPlugin : public QObject, QmlDesigner::IWidgetPlugin -{ - Q_OBJECT - - Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QmlDesignerPlugin" FILE "assetexporterplugin.json") - - Q_DISABLE_COPY(AssetExporterPlugin) - Q_INTERFACES(QmlDesigner::IWidgetPlugin) - -public: - AssetExporterPlugin(); - - QString metaInfo() const final; - QString pluginName() const final; - -private: - void onExport(); - void addActions(); - void updateActions(); - - AssetExporterView *m_view = nullptr; - class QmlDesignerProjectManager &m_projectManager; -}; - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.json b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.json deleted file mode 100644 index a925eaca8e1..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "Vendor" : "The Qt Company Ltd", - "Category" : "Qt Quick", - "Description" : "Plugin for exporting assets and QML from QmlDesigner", - "Url" : "http://www.qt.io" -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.metainfo b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.metainfo deleted file mode 100644 index 5bfe70cffdf..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.metainfo +++ /dev/null @@ -1,2 +0,0 @@ -MetaInfo { -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.qrc b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.qrc deleted file mode 100644 index 8db1e0adafb..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.qrc +++ /dev/null @@ -1,5 +0,0 @@ - - - assetexporterplugin.metainfo - - diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterview.cpp b/src/plugins/qmldesigner/assetexporterplugin/assetexporterview.cpp deleted file mode 100644 index ef41f435adb..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporterview.cpp +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "assetexporterview.h" - -#include "qmlitemnode.h" -#include "rewriterview.h" - -#include "coreplugin/editormanager/editormanager.h" -#include "coreplugin/editormanager/ieditor.h" -#include "coreplugin/modemanager.h" -#include "coreplugin/coreconstants.h" - -#include - -namespace { -Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.view", QtInfoMsg) -Q_LOGGING_CATEGORY(loggerWarn, "qtc.designer.assetExportPlugin.view", QtWarningMsg) -//Q_LOGGING_CATEGORY(loggerError, "qtc.designer.assetExportPlugin.view", QtCriticalMsg) - -const int RetryIntervalMs = 500; -const int MinRetry = 2; -} - -namespace QmlDesigner { - -AssetExporterView::AssetExporterView(ExternalDependenciesInterface &externalDependencies) - : AbstractView(externalDependencies) - , m_timer(this) -{ - m_timer.setInterval(RetryIntervalMs); - // We periodically check if file is loaded. - connect(&m_timer, &QTimer::timeout, this, &AssetExporterView::handleTimerTimeout); -} - - -bool AssetExporterView::loadQmlFile(const Utils::FilePath &path, uint timeoutSecs) -{ - qCDebug(loggerInfo) << "Load file" << path; - if (loadingState() == LoadState::Busy) - return false; - - setState(LoadState::Busy); - m_retryCount = std::max(MinRetry, static_cast((timeoutSecs * 1000) / RetryIntervalMs)); - m_currentEditor = Core::EditorManager::openEditor(path, Utils::Id(), - Core::EditorManager::DoNotMakeVisible); - Core::ModeManager::activateMode(Core::Constants::MODE_DESIGN); - Core::ModeManager::setFocusToCurrentMode(); - m_timer.start(); - return true; -} - -Utils::Result AssetExporterView::saveQmlFile() const -{ - if (!m_currentEditor) { - qCDebug(loggerWarn) << "Saving QML file failed. No editor."; - return Utils::Result::Error("Saving QML file failed. No editor."); - } - - return m_currentEditor->document()->save(); -} - -void AssetExporterView::modelAttached(Model *model) -{ - if (model->rewriterView() && !model->rewriterView()->errors().isEmpty()) - setState(LoadState::QmlErrorState); - - AbstractView::modelAttached(model); -} - -void AssetExporterView:: -instanceInformationsChanged(const QMultiHash &informationChangeHash) -{ - if (inErrorState() || loadingState() == LoadState::Loaded) - return; // Already reached error or connected state. - - // We expect correct dimensions are available if the rootnode's - // information change message is received. - const auto nodes = informationChangeHash.keys(); - bool hasRootNode = std::any_of(nodes.begin(), nodes.end(), [](const ModelNode &n) { - return n.isRootNode(); - }); - if (hasRootNode) - handleMaybeDone(); -} - -void AssetExporterView::instancesPreviewImageChanged([[maybe_unused]] const QVector &nodeList) -{ - emit previewChanged(); -} - -bool AssetExporterView::inErrorState() const -{ - return m_state == LoadState::Exausted || m_state == LoadState::QmlErrorState; -} - -bool AssetExporterView::isLoaded() const -{ - return isAttached() && QmlItemNode(rootModelNode()).isValid(); -} - -void AssetExporterView::setState(AssetExporterView::LoadState state) -{ - if (state != m_state) { - m_state = state; - qCDebug(loggerInfo) << "Loading state changed" << m_state; - if (inErrorState() || m_state == LoadState::Loaded) { - m_timer.stop(); - // TODO: Send the loaded signal with a delay. The assumption that model attached and a - // valid root object is enough to declare a QML file is ready is incorrect. A ideal - // solution would be that the QML Puppet notifies file ready signal. - if (m_state == LoadState::Loaded) - QTimer::singleShot(2000, this, &AssetExporterView::loadingFinished); - else - emit loadingError(m_state); - } - } -} - -void AssetExporterView::handleMaybeDone() -{ - if (isLoaded()) - setState(LoadState::Loaded); -} - -void AssetExporterView::handleTimerTimeout() -{ - if (!inErrorState() && loadingState() != LoadState::Loaded) - handleMaybeDone(); - - if (--m_retryCount < 0) - setState(LoadState::Exausted); -} - -} - -QT_BEGIN_NAMESPACE -QDebug operator<<(QDebug os, const QmlDesigner::AssetExporterView::LoadState &s) -{ - os << static_cast::type>(s); - return os; -} -QT_END_NAMESPACE diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterview.h b/src/plugins/qmldesigner/assetexporterplugin/assetexporterview.h deleted file mode 100644 index 7ed01126e21..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporterview.h +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#pragma once - -#include "abstractview.h" - -#include - -#include -#include - -namespace Core { class IEditor; } - -namespace QmlDesigner { - -class AssetExporterView : public AbstractView -{ - Q_OBJECT -public: - enum class LoadState { - Idle = 1, - Busy, - Exausted, - QmlErrorState, - Loaded - }; - - AssetExporterView(ExternalDependenciesInterface &externalDependencies); - - bool loadQmlFile(const Utils::FilePath &path, uint timeoutSecs = 10); - Utils::Result saveQmlFile() const; - - void modelAttached(Model *model) override; - void instanceInformationsChanged(const QMultiHash &informationChangeHash) override; - void instancesPreviewImageChanged(const QVector &nodeList) override; - - LoadState loadingState() const { return m_state; } - bool inErrorState() const; - -signals: - void loadingFinished(); - void loadingError(LoadState); - void previewChanged(); - -private: - bool isLoaded() const; - void setState(LoadState state); - void handleMaybeDone(); - void handleTimerTimeout(); - - Core::IEditor *m_currentEditor = nullptr; - QTimer m_timer; - int m_retryCount = 0; - LoadState m_state = LoadState::Idle; - bool m_waitForPuppet = false; -}; - -} - -QT_BEGIN_NAMESPACE -QDebug operator<<(QDebug os, const QmlDesigner::AssetExporterView::LoadState &s); -QT_END_NAMESPACE diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h b/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h deleted file mode 100644 index 02d56b4eab6..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#pragma once - -#include - -namespace QmlDesigner { -namespace Constants { - -const char EXPORT_QML[] = "Designer.ExportPlugin.ExportQml"; - -const char TASK_CATEGORY_ASSET_EXPORT[] = "AssetExporter.Export"; - -//*************************************************************************** -// Metadata tags -//*************************************************************************** -// Plugin info tags -const char PluginInfoTag[] = "pluginInfo"; -const char MetadataVersionTag[] = "metadataVersion"; - -const char DocumentInfoTag[] = "documentInfo"; -const char DocumentNameTag[] = "name"; - -// Layer data tags -const char ArtboardListTag[] = "artboards"; - -const char NameTag[] = "name"; - -const char XPosTag[] = "x"; -const char YPosTag[] = "y"; -const char WidthTag[] = "width"; -const char HeightTag[] = "height"; - -const char MetadataTag[] = "metadata"; -const char ChildrenTag[] = "children"; -const char CustomIdTag[] = "customId"; -const char QmlIdTag[] = "qmlId"; -const char ExportTypeTag[] = "exportType"; -const char ExportTypeComponent[] = "component"; -const char ExportTypeChild[] = "child"; -const char QmlPropertiesTag[] = "qmlProperties"; -const char ImportsTag[] = "extraImports"; -const char UuidTag[] = "uuid"; -const char ClipTag[] = "clip"; -const char AssetDataTag[] = "assetData"; -const char ReferenceAssetTag[] = "referenceAsset"; -const char AssetPathTag[] = "assetPath"; -const char AssetBoundsTag[] = "assetBounds"; -const char OpacityTag[] = "opacity"; -const char TypeNameTag[] = "typeName"; -const char TypeIdTag[] = "typeId"; - -const char TextDetailsTag[] = "textDetails"; -const char FontFamilyTag[] = "fontFamily"; -const char FontSizeTag[] = "fontSize"; -const char FontStyleTag[] = "fontStyle"; -const char LetterSpacingTag[] = "kerning"; -const char TextColorTag[] = "textColor"; -const char TextContentTag[] = "contents"; -const char IsMultilineTag[] = "multiline"; -const char LineHeightTag[] = "lineHeight"; -const char HAlignTag[] = "horizontalAlignment"; -const char VAlignTag[] = "verticalAlignment"; - -} -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/componentexporter.cpp b/src/plugins/qmldesigner/assetexporterplugin/componentexporter.cpp deleted file mode 100644 index 7530bd9a7ba..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/componentexporter.cpp +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "componentexporter.h" -#include "assetexporter.h" -#include "assetexportpluginconstants.h" -#include "exportnotification.h" -#include "dumpers/nodedumper.h" - -#include "model.h" -#include "nodeabstractproperty.h" -#include "nodemetainfo.h" -#include "qmlitemnode.h" -#include "rewriterview.h" - -#include "utils/qtcassert.h" - -#include -#include -#include -#include - -namespace { -Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.modelExporter", QtInfoMsg) -} - -namespace QmlDesigner { -using namespace Constants; - -std::vector> Component::m_readers; -Component::Component(AssetExporter &exporter, const ModelNode &rootNode): - m_exporter(exporter), - m_rootNode(rootNode) -{ - m_name = m_rootNode.id(); - if (m_name.isEmpty()) - m_name = QString::fromUtf8(m_rootNode.type()); -} - -QJsonObject Component::json() const -{ - return m_json; -} - -AssetExporter &Component::exporter() -{ - return m_exporter; -} - -void Component::exportComponent() -{ - QTC_ASSERT(m_rootNode.isValid(), return); - m_json = nodeToJson(m_rootNode); - // Change the export type to component - QJsonObject metadata = m_json.value(MetadataTag).toObject(); - metadata.insert(ExportTypeTag, ExportTypeComponent); - addReferenceAsset(metadata); - m_json.insert(MetadataTag, metadata); - addImports(); -} - -const QString &Component::name() const -{ - return m_name; -} - -NodeDumper *Component::createNodeDumper(const ModelNode &node) const -{ - std::unique_ptr reader; - for (auto &dumperCreator: m_readers) { - std::unique_ptr r(dumperCreator->instance(node)); - if (r->isExportable()) { - if (reader) { - if (reader->priority() < r->priority()) - reader = std::move(r); - } else { - reader = std::move(r); - } - } - } - - if (!reader) - qCDebug(loggerInfo()) << "No dumper for node" << node; - - return reader.release(); -} - -QJsonObject Component::nodeToJson(const ModelNode &node) -{ - QJsonObject jsonObject; - - // Don't export States, Connection, Timeline etc nodes. - if (!node.metaInfo().isQtQuickItem()) - return {}; - - std::unique_ptr dumper(createNodeDumper(node)); - if (dumper) { - jsonObject = dumper->json(*this); - } else { - ExportNotification::addError(tr("Error exporting node %1. Cannot parse type %2.") - .arg(node.id()).arg(QString::fromUtf8(node.type()))); - } - - QJsonArray children; - for (const ModelNode &childnode : node.directSubModelNodes()) { - const QJsonObject childJson = nodeToJson(childnode); - if (!childJson.isEmpty()) - children.append(childJson); - } - - if (!children.isEmpty()) - jsonObject.insert(ChildrenTag, children); - - return jsonObject; -} - -void Component::addReferenceAsset(QJsonObject &metadataObject) const -{ - QPixmap refAsset = m_exporter.generateAsset(m_rootNode); - stichChildrendAssets(m_rootNode, refAsset); - Utils::FilePath refAssetPath = m_exporter.assetPath(m_rootNode, this, "_ref"); - m_exporter.exportAsset(refAsset, refAssetPath); - QJsonObject assetData; - if (metadataObject.contains(AssetDataTag)) - assetData = metadataObject[AssetDataTag].toObject(); - assetData.insert(ReferenceAssetTag, refAssetPath.toUrlishString()); - metadataObject.insert(AssetDataTag, assetData); -} - -void Component::stichChildrendAssets(const ModelNode &node, QPixmap &parentPixmap) const -{ - if (!node.hasAnySubModelNodes()) - return; - - QPainter painter(&parentPixmap); - for (const ModelNode &child : node.directSubModelNodes()) { - QPixmap childPixmap = m_exporter.generateAsset(child); - if (childPixmap.isNull()) - continue; - stichChildrendAssets(child, childPixmap); - QTransform cTransform = QmlObjectNode(child).toQmlItemNode().instanceTransform(); - painter.setTransform(cTransform); - painter.drawPixmap(QPoint(0, 0), childPixmap); - } - painter.end(); -} - -void Component::addImports() -{ - QJsonArray importsArray; - for (const Import &import : m_rootNode.model()->imports()) - importsArray.append(import.toString()); - - if (!importsArray.empty()) - m_json.insert(Constants::ImportsTag, importsArray); -} - - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/assetexporterplugin/componentexporter.h b/src/plugins/qmldesigner/assetexporterplugin/componentexporter.h deleted file mode 100644 index 59ee8a2e677..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/componentexporter.h +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#pragma once - -#include -#include -#include - -#include -#include - -#include "utils/qtcassert.h" - -QT_BEGIN_NAMESPACE -class QJsonArray; -QT_END_NAMESPACE - -namespace QmlDesigner { -class AssetExporter; -class ModelNode; -class Component; -class NodeDumper; - -namespace Internal { -class NodeDumperCreatorBase -{ -public: - virtual ~NodeDumperCreatorBase() {} -protected: - virtual NodeDumper *instance(const ModelNode &) const = 0; - friend Component; -}; - -template -class NodeDumperCreator : public NodeDumperCreatorBase -{ -public: - NodeDumperCreator() = default; - ~NodeDumperCreator() = default; - -protected: - NodeDumper *instance(const ModelNode &node) const { return new T(node); } -}; -} //Internal - -class Component -{ - Q_DECLARE_TR_FUNCTIONS(Component); - -public: - Component(AssetExporter& exporter, const ModelNode &rootNode); - - void exportComponent(); - const QString &name() const; - QJsonObject json() const; - - AssetExporter &exporter(); - - template static void addNodeDumper() - { - QTC_ASSERT((std::is_base_of::value), return); - m_readers.push_back(std::make_unique>()); - } -private: - NodeDumper* createNodeDumper(const ModelNode &node) const; - QJsonObject nodeToJson(const ModelNode &node); - void addReferenceAsset(QJsonObject &metadataObject) const; - void stichChildrendAssets(const ModelNode &node, QPixmap &parentPixmap) const; - void addImports(); - -private: - AssetExporter& m_exporter; - const ModelNode &m_rootNode; - QString m_name; - QJsonObject m_json; - static std::vector> m_readers; -}; -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/dumpers/assetnodedumper.cpp b/src/plugins/qmldesigner/assetexporterplugin/dumpers/assetnodedumper.cpp deleted file mode 100644 index 9561044c3ac..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/dumpers/assetnodedumper.cpp +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "assetnodedumper.h" -#include "assetexportpluginconstants.h" -#include "assetexporter.h" - -#include "qmlitemnode.h" -#include "componentexporter.h" - -#include "utils/fileutils.h" - -#include - -namespace QmlDesigner { -using namespace Constants; - -AssetNodeDumper::AssetNodeDumper(const ModelNode &node) - : ItemNodeDumper(node) -{ - -} - -bool AssetNodeDumper::isExportable() const -{ - auto qtQuickImageMetaInfo = model()->qtQuickImageMetaInfo(); - auto qtQuickRectangleMetaInfo = model()->qtQuickRectangleMetaInfo(); - return metaInfo().isBasedOn(qtQuickImageMetaInfo, qtQuickRectangleMetaInfo); -} - -QJsonObject AssetNodeDumper::json(Component &component) const -{ - QJsonObject jsonObject = ItemNodeDumper::json(component); - - AssetExporter &exporter = component.exporter(); - const Utils::FilePath assetPath = exporter.assetPath(m_node, &component); - exporter.exportAsset(exporter.generateAsset(m_node), assetPath); - - QJsonObject assetData; - assetData.insert(AssetPathTag, assetPath.toUrlishString()); - QJsonObject metadata = jsonObject.value(MetadataTag).toObject(); - metadata.insert(AssetDataTag, assetData); - jsonObject.insert(MetadataTag, metadata); - return jsonObject; -} -} - diff --git a/src/plugins/qmldesigner/assetexporterplugin/dumpers/assetnodedumper.h b/src/plugins/qmldesigner/assetexporterplugin/dumpers/assetnodedumper.h deleted file mode 100644 index 1a5449efafc..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/dumpers/assetnodedumper.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#pragma once - -#include "itemnodedumper.h" - -namespace QmlDesigner { -class Component; - -class AssetNodeDumper : public ItemNodeDumper -{ -public: - AssetNodeDumper(const ModelNode &node); - ~AssetNodeDumper() override = default; - - bool isExportable() const override; - int priority() const override { return 200; } - QJsonObject json(Component &component) const override; -}; -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/dumpers/itemnodedumper.cpp b/src/plugins/qmldesigner/assetexporterplugin/dumpers/itemnodedumper.cpp deleted file mode 100644 index e7159d76626..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/dumpers/itemnodedumper.cpp +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "itemnodedumper.h" -#include "assetexportpluginconstants.h" -#include "assetexporter.h" -#include "componentexporter.h" - -#include "qmlitemnode.h" -#include "annotation.h" - -namespace { -static QString capitalize(const QString &str) -{ - if (str.isEmpty()) - return {}; - QString tmp = str; - tmp[0] = QChar(str[0]).toUpper().toLatin1(); - return tmp; -} -} - -namespace QmlDesigner { -using namespace Constants; - -ItemNodeDumper::ItemNodeDumper(const ModelNode &node) - : NodeDumper(node) -{ - -} - -bool QmlDesigner::ItemNodeDumper::isExportable() const -{ - return metaInfo().isQtQuickItem(); -} - -QJsonObject QmlDesigner::ItemNodeDumper::json([[maybe_unused]] QmlDesigner::Component &component) const -{ - const QmlObjectNode &qmlObjectNode = objectNode(); - QJsonObject jsonObject; - - const QString qmlId = qmlObjectNode.id(); - QString name = m_node.simplifiedTypeName(); - if (!qmlId.isEmpty()) - name.append("_" + capitalize(qmlId)); - - jsonObject.insert(NameTag, name); - - // Position relative to parent - QmlItemNode itemNode = qmlObjectNode.toQmlItemNode(); - QPointF pos = itemNode.instancePosition(); - jsonObject.insert(XPosTag, pos.x()); - jsonObject.insert(YPosTag, pos.y()); - - // size - QSizeF size = itemNode.instanceSize(); - jsonObject.insert(WidthTag, size.width()); - jsonObject.insert(HeightTag, size.height()); - - QJsonObject metadata; - metadata.insert(QmlIdTag, qmlId); - metadata.insert(UuidTag, uuid()); - metadata.insert(ExportTypeTag, ExportTypeChild); - metadata.insert(TypeNameTag, QString::fromLatin1(m_node.type())); - - if (m_node.hasCustomId()) - metadata.insert(CustomIdTag, m_node.customId()); - - QString typeId = component.exporter().componentUuid(m_node); - if (!typeId.isEmpty()) - metadata.insert(TypeIdTag, typeId); - - jsonObject.insert(MetadataTag, metadata); - return jsonObject; -} -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/dumpers/itemnodedumper.h b/src/plugins/qmldesigner/assetexporterplugin/dumpers/itemnodedumper.h deleted file mode 100644 index 9752d37caef..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/dumpers/itemnodedumper.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#pragma once - -#include "nodedumper.h" - -namespace QmlDesigner { -class ModelNode; -class Component; - -class ItemNodeDumper : public NodeDumper -{ -public: - ItemNodeDumper(const ModelNode &node); - - ~ItemNodeDumper() override = default; - - int priority() const override { return 100; } - bool isExportable() const override; - QJsonObject json(Component &component) const override; -}; -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/dumpers/nodedumper.cpp b/src/plugins/qmldesigner/assetexporterplugin/dumpers/nodedumper.cpp deleted file mode 100644 index 521180f2f28..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/dumpers/nodedumper.cpp +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "nodedumper.h" -#include "assetexportpluginconstants.h" - -#include - -namespace QmlDesigner { -NodeDumper::NodeDumper(const ModelNode &node) - : m_node(node) - , m_objectNode(node) - , m_metaInfo(node.metaInfo()) - , m_model{node.model()} -{ - -} - -QVariant NodeDumper::propertyValue(const PropertyName &name) const -{ - return m_objectNode.instanceValue(name); -} - -QString NodeDumper::uuid() const -{ - return m_node.auxiliaryDataWithDefault(uuidProperty).toString(); -} - -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/dumpers/nodedumper.h b/src/plugins/qmldesigner/assetexporterplugin/dumpers/nodedumper.h deleted file mode 100644 index 17bace9ad61..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/dumpers/nodedumper.h +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#pragma once - -#include "qmlobjectnode.h" - -#include -#include - -namespace QmlDesigner { -class Component; -class ModelNode; - -class NodeDumper -{ -public: - NodeDumper(const ModelNode &node); - - virtual ~NodeDumper() = default; - - virtual int priority() const = 0; - virtual bool isExportable() const = 0; - virtual QJsonObject json(Component& component) const = 0; - - const NodeMetaInfo &metaInfo() const { return m_metaInfo; } - const QmlObjectNode& objectNode() const { return m_objectNode; } - QVariant propertyValue(const PropertyName &name) const; - QString uuid() const; - - Model *model() const { return m_model; } - -protected: - const ModelNode &m_node; - -private: - QmlObjectNode m_objectNode; - NodeMetaInfo m_metaInfo; - Model *m_model = nullptr; -}; -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/dumpers/textnodedumper.cpp b/src/plugins/qmldesigner/assetexporterplugin/dumpers/textnodedumper.cpp deleted file mode 100644 index 5f96d3e2175..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/dumpers/textnodedumper.cpp +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "textnodedumper.h" -#include "assetexportpluginconstants.h" - -#include - -#include -#include -#include -#include -#include - -#include - -#include - -namespace { -const QHash AlignMapping{ - {"AlignRight", "RIGHT"}, - {"AlignHCenter", "CENTER"}, - {"AlignJustify", "JUSTIFIED"}, - {"AlignLeft", "LEFT"}, - {"AlignTop", "TOP"}, - {"AlignVCenter", "CENTER"}, - {"AlignBottom", "BOTTOM"} -}; - -QString toJsonAlignEnum(QString value) { - if (value.isEmpty() || !AlignMapping.contains(value)) - return {}; - return AlignMapping[value]; -} -} - - -namespace QmlDesigner { -using namespace Constants; - -TextNodeDumper::TextNodeDumper(const ModelNode &node) - : ItemNodeDumper(node) -{ - -} - -bool TextNodeDumper::isExportable() const -{ - auto qtQuickTextMetaInfo = model()->qtQuickTextMetaInfo(); - auto qtQuickControlsLabelMetaInfo = model()->qtQuickControlsLabelMetaInfo(); - return metaInfo().isBasedOn(qtQuickTextMetaInfo, qtQuickControlsLabelMetaInfo); -} - -QJsonObject TextNodeDumper::json([[maybe_unused]] Component &component) const -{ - QJsonObject jsonObject = ItemNodeDumper::json(component); - - QJsonObject textDetails; - textDetails.insert(TextContentTag, propertyValue("text").toString()); - - QFont font = propertyValue("font").value(); - QFontInfo fontInfo(font); - textDetails.insert(FontFamilyTag, fontInfo.family()); - textDetails.insert(FontStyleTag, fontInfo.styleName()); - textDetails.insert(FontSizeTag, fontInfo.pixelSize()); - textDetails.insert(LetterSpacingTag, font.letterSpacing()); - - - QColor fontColor(propertyValue("font.color").toString()); - textDetails.insert(TextColorTag, fontColor.name(QColor::HexArgb)); - - textDetails.insert(HAlignTag, toJsonAlignEnum(propertyValue("horizontalAlignment").toString())); - textDetails.insert(VAlignTag, toJsonAlignEnum(propertyValue("verticalAlignment").toString())); - - textDetails.insert(IsMultilineTag, propertyValue("wrapMode").toString().compare("NoWrap") != 0); - - // Calculate line height in pixels - QFontMetricsF fm(font); - auto lineHeightMode = propertyValue("lineHeightMode").value(); - double lineHeight = propertyValue("lineHeight").toDouble(); - qreal lineHeightPx = (lineHeightMode == QQuickText::FixedHeight) ? - lineHeight : qCeil(fm.height()) * lineHeight; - textDetails.insert(LineHeightTag, lineHeightPx); - - QJsonObject metadata = jsonObject.value(MetadataTag).toObject(); - metadata.insert(TextDetailsTag, textDetails); - jsonObject.insert(MetadataTag, metadata); - return jsonObject; -} -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/dumpers/textnodedumper.h b/src/plugins/qmldesigner/assetexporterplugin/dumpers/textnodedumper.h deleted file mode 100644 index b084bdae48c..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/dumpers/textnodedumper.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#pragma once - -#include "itemnodedumper.h" - -namespace QmlDesigner { -class Component; - -class TextNodeDumper : public ItemNodeDumper -{ -public: - TextNodeDumper(const ModelNode &node); - ~TextNodeDumper() override = default; - - bool isExportable() const override; - int priority() const override { return 200; } - QJsonObject json(Component &component) const override; -}; - -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/exportnotification.cpp b/src/plugins/qmldesigner/assetexporterplugin/exportnotification.cpp deleted file mode 100644 index e0dc335f3a5..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/exportnotification.cpp +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "exportnotification.h" -#include "assetexportpluginconstants.h" - -#include "projectexplorer/taskhub.h" - -#include - -namespace { -Q_LOGGING_CATEGORY(loggerDebug, "qtc.designer.assetExportPlugin.exportNotification", QtDebugMsg) -} - -using namespace ProjectExplorer; -namespace { -static void addTask(Task::TaskType type, const QString &desc) -{ - qCDebug(loggerDebug) << desc; - Task task(type, desc, {}, -1, QmlDesigner::Constants::TASK_CATEGORY_ASSET_EXPORT); - TaskHub::addTask(task); -} -} - -namespace QmlDesigner { - -void ExportNotification::addError(const QString &errMsg) -{ - addTask(Task::Error, errMsg); -} - -void ExportNotification::addWarning(const QString &warningMsg) -{ - addTask(Task::Warning, warningMsg); -} - -void ExportNotification::addInfo(const QString &infoMsg) -{ - addTask(Task::Unknown, infoMsg); -} -} // QmlDesigner diff --git a/src/plugins/qmldesigner/assetexporterplugin/exportnotification.h b/src/plugins/qmldesigner/assetexporterplugin/exportnotification.h deleted file mode 100644 index e27124ce530..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/exportnotification.h +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include - -namespace QmlDesigner { -class ExportNotification -{ -public: - static void addError(const QString &errMsg); - static void addWarning(const QString &warningMsg); - static void addInfo(const QString &infoMsg); -}; -} // QmlDesigner diff --git a/src/plugins/qmldesigner/assetexporterplugin/filepathmodel.cpp b/src/plugins/qmldesigner/assetexporterplugin/filepathmodel.cpp deleted file mode 100644 index f5df42df396..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/filepathmodel.cpp +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "filepathmodel.h" - -#include "exportnotification.h" - -#include -#include - -#include - -#include -#include - -using namespace ProjectExplorer; - -namespace { -Q_LOGGING_CATEGORY(loggerError, "qtc.designer.assetExportPlugin.filePathModel", QtCriticalMsg) -Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.filePathModel", QtInfoMsg) - -void findQmlFiles(QPromise &promise, const Project *project) -{ - if (!project || promise.isCanceled()) - return; - - int index = 0; - project->files([&promise, &index](const Node* node) ->bool { - if (promise.isCanceled()) - return false; - Utils::FilePath path = node->filePath(); - bool isComponent = !path.fileName().isEmpty() && path.fileName().front().isUpper(); - if (isComponent && node->filePath().endsWith(".ui.qml")) - promise.addResult(path, index++); - return true; - }); -} -} - -namespace QmlDesigner { - -FilePathModel::FilePathModel(ProjectExplorer::Project *project, QObject *parent) - : QAbstractListModel(parent), - m_project(project) -{ - QTimer::singleShot(0, this, &FilePathModel::processProject); -} - -FilePathModel::~FilePathModel() -{ - if (m_preprocessWatcher && !m_preprocessWatcher->isCanceled() && - !m_preprocessWatcher->isFinished()) { - ExportNotification::addInfo(tr("Canceling file preparation.")); - m_preprocessWatcher->cancel(); - m_preprocessWatcher->waitForFinished(); - qCDebug(loggerInfo) << "Canceled file preparation."; - } -} - -Qt::ItemFlags FilePathModel::flags(const QModelIndex &index) const -{ - Qt::ItemFlags itemFlags = QAbstractListModel::flags(index); - if (index.isValid()) - itemFlags |= Qt::ItemIsUserCheckable; - return itemFlags; -} - -int FilePathModel::rowCount(const QModelIndex &parent) const -{ - if (!parent.isValid()) - return m_files.size(); - return 0; -} - -QVariant FilePathModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return {}; - - switch (role) { - case Qt::DisplayRole: - return m_files[index.row()].toUserOutput(); - case Qt::CheckStateRole: - return m_skipped.count(m_files[index.row()]) ? Qt::Unchecked : Qt::Checked; - default: - break; - } - - return {}; -} - -bool FilePathModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - if (!index.isValid() || role != Qt::CheckStateRole) - return false; - - const Utils::FilePath path = m_files[index.row()]; - if (value == Qt::Checked) - m_skipped.erase(path); - else - m_skipped.insert(path); - - emit dataChanged(index, index); - return true; -} - -Utils::FilePaths FilePathModel::files() const -{ - Utils::FilePaths selectedPaths; - std::copy_if(m_files.begin(), m_files.end(), std::back_inserter(selectedPaths), - [this](const Utils::FilePath &path) { - return !m_skipped.count(path); - }); - return selectedPaths; -} - -void FilePathModel::processProject() -{ - if (m_preprocessWatcher && !m_preprocessWatcher->isCanceled() && - !m_preprocessWatcher->isFinished()) { - qCDebug(loggerError) << "Previous model load not finished."; - return; - } - - beginResetModel(); - m_preprocessWatcher.reset(new QFutureWatcher(this)); - connect(m_preprocessWatcher.get(), - &QFutureWatcher::resultReadyAt, - this, - [this](int resultIndex) { - beginInsertRows(index(0, 0), m_files.size(), m_files.size()); - m_files.append(m_preprocessWatcher->resultAt(resultIndex)); - endInsertRows(); - }); - - connect(m_preprocessWatcher.get(), &QFutureWatcher::finished, - this, &FilePathModel::endResetModel); - - QFuture f = Utils::asyncRun(&findQmlFiles, m_project); - m_preprocessWatcher->setFuture(f); -} - - -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/filepathmodel.h b/src/plugins/qmldesigner/assetexporterplugin/filepathmodel.h deleted file mode 100644 index 8158c578182..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/filepathmodel.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include - -#include -#include -#include - -#include -#include - -namespace ProjectExplorer { -class Project; -} - -namespace QmlDesigner { -class FilePathModel : public QAbstractListModel -{ - Q_DECLARE_TR_FUNCTIONS(QmlDesigner::FilePathModel) - -public: - FilePathModel(ProjectExplorer::Project *project, QObject *parent = nullptr); - ~FilePathModel() override; - - Qt::ItemFlags flags(const QModelIndex &index) const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - bool setData(const QModelIndex &index, const QVariant &value, int role) override; - - Utils::FilePaths files() const; -private: - void processProject(); - - ProjectExplorer::Project *m_project = nullptr; - std::unique_ptr> m_preprocessWatcher; - std::unordered_set m_skipped; - Utils::FilePaths m_files; -}; - -} diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp index 2191b1c981d..baf5826b622 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp @@ -617,6 +617,14 @@ void AssetsLibraryWidget::editAssetComponent(const QString &filePath) DocumentManager::goIntoComponent(fullPath.toFSPathString()); } +void AssetsLibraryWidget::updateAssetComponent(const QString &filePath) +{ + Utils::FilePath qml = QmlDesignerPlugin::instance()->documentManager() + .generatedComponentUtils().getImported3dQml(filePath); + if (qml.exists()) + m_assetsView->emitCustomNotification("UpdateImported3DAsset", {}, {qml.toFSPathString()}); +} + QString AssetsLibraryWidget::qmlSourcesPath() { #ifdef SHARE_QML_PATH diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h index 29952579af6..e4a457253c0 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h @@ -85,6 +85,7 @@ public: Q_INVOKABLE QSet supportedAssetSuffixes(bool complex); Q_INVOKABLE void openEffectComposer(const QString &filePath); Q_INVOKABLE void editAssetComponent(const QString &filePath); + Q_INVOKABLE void updateAssetComponent(const QString &filePath); Q_INVOKABLE int qtVersion() const; Q_INVOKABLE void invalidateThumbnail(const QString &id); Q_INVOKABLE QSize imageSize(const QString &id); diff --git a/src/plugins/qmldesigner/components/bindingeditor/actioneditordialog.cpp b/src/plugins/qmldesigner/components/bindingeditor/actioneditordialog.cpp index 86824ac2656..7361826d88f 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/actioneditordialog.cpp +++ b/src/plugins/qmldesigner/components/bindingeditor/actioneditordialog.cpp @@ -23,7 +23,7 @@ static Q_LOGGING_CATEGORY(ceLog, "qtc.qmldesigner.connectioneditor", QtWarningMs namespace QmlDesigner { ActionEditorDialog::ActionEditorDialog(QWidget *parent) - : AbstractEditorDialog(parent, tr("Connection Editor")) + : AbstractEditorDialog(parent, tr("Action Editor")) { setupUIComponents(); diff --git a/src/plugins/qmldesigner/components/componentcore/bundlehelper.cpp b/src/plugins/qmldesigner/components/componentcore/bundlehelper.cpp index 3413fca197c..da14d2e0a63 100644 --- a/src/plugins/qmldesigner/components/componentcore/bundlehelper.cpp +++ b/src/plugins/qmldesigner/components/componentcore/bundlehelper.cpp @@ -182,7 +182,6 @@ void BundleHelper::importBundleToProject() // TODO: before overwriting remove old item's dependencies (not harmful but for cleanup) } - // add entry to model QStringList files = itemObj.value("files").toVariant().toStringList(); QString icon = itemObj.value("icon").toString(); @@ -207,28 +206,60 @@ void BundleHelper::importBundleToProject() zipReader.close(); } -void BundleHelper::exportBundle(const ModelNode &node, const QPixmap &iconPixmap) +void BundleHelper::exportBundle(const QList &nodes, const QPixmap &iconPixmap) { - if (node.isComponent()) - exportComponent(node); - else - exportNode(node, iconPixmap); -} + QTC_ASSERT(!nodes.isEmpty(), return); -void BundleHelper::exportComponent(const ModelNode &node) -{ - QString exportPath = getExportPath(node); + QString exportPath = getExportPath(nodes.at(0)); if (exportPath.isEmpty()) return; m_zipWriter = std::make_unique(exportPath); - Utils::FilePath compFilePath = componentPath(node); + m_tempDir = std::make_unique(); + QTC_ASSERT(m_tempDir->isValid(), return); + + auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); + QJsonObject jsonObj; + jsonObj["version"] = BUNDLE_VERSION; + QJsonArray itemsArr; + + // remove nested nodes (they will be exported anyway as dependency of the parent) + QList nodesToExport; + for (const ModelNode &node : nodes) { + bool isChild = std::ranges::any_of(nodes, [&](const ModelNode &possibleParent) { + return &node != &possibleParent && possibleParent.isAncestorOf(node); + }); + + if (!isChild) + nodesToExport.append(node); + } + jsonObj["id"] = !nodesToExport.isEmpty() && nodesToExport[0].metaInfo().isQtQuick3DMaterial() + ? compUtils.userMaterialsBundleId() + : compUtils.user3DBundleId(); + + m_remainingFiles = nodesToExport.size() + 1; + + for (const ModelNode &node : std::as_const(nodesToExport)) { + if (node.isComponent()) + itemsArr.append(exportComponent(node)); + else + itemsArr.append(exportNode(node, iconPixmap)); + } + + jsonObj["items"] = itemsArr; + m_zipWriter->addFile(Constants::BUNDLE_JSON_FILENAME, QJsonDocument(jsonObj).toJson()); + maybeCloseZip(); +} + +QJsonObject BundleHelper::exportComponent(const ModelNode &node) +{ + Utils::FilePath compFilePath = Utils::FilePath::fromString(ModelUtils::componentFilePath(node)); Utils::FilePath compDir = compFilePath.parentDir(); QString compBaseName = compFilePath.completeBaseName(); QString compFileName = compFilePath.fileName(); - m_iconPath = QLatin1String("icons/%1").arg(UniqueName::generateId(compBaseName) + ".png"); + QString iconPath = QLatin1String("icons/%1").arg(UniqueName::generateId(compBaseName) + ".png"); const QSet compDependencies = getComponentDependencies(compFilePath, compDir); @@ -250,43 +281,25 @@ void BundleHelper::exportComponent(const ModelNode &node) filesList.append(asset.relativePath); } - // add the item to the bundle json - QJsonObject jsonObj; - QJsonArray itemsArr; - itemsArr.append(QJsonObject { + // add icon + QString filePath = compFilePath.path(); + getImageFromCache(filePath, [this, iconPath](const QImage &image) { + addIconToZip(iconPath, image); + }); + + return { {"name", node.simplifiedTypeName()}, {"qml", compFileName}, - {"icon", m_iconPath}, + {"icon", iconPath}, {"files", QJsonArray::fromStringList(filesList)} - }); - - jsonObj["items"] = itemsArr; - - auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); - jsonObj["id"] = compUtils.user3DBundleId(); - jsonObj["version"] = BUNDLE_VERSION; - - m_zipWriter->addFile(Constants::BUNDLE_JSON_FILENAME, QJsonDocument(jsonObj).toJson()); - - // add icon - getImageFromCache(compFilePath.path(), [&](const QImage &image) { - addIconAndCloseZip(image); - }); + }; } -void BundleHelper::exportNode(const ModelNode &node, const QPixmap &iconPixmap) +QJsonObject BundleHelper::exportNode(const ModelNode &node, const QPixmap &iconPixmap) { - QString exportPath = getExportPath(node); - if (exportPath.isEmpty()) - return; - - // targetPath is a temp path for collecting and zipping assets, actual export target is where + // tempPath is a temp path for collecting and zipping assets, actual export target is where // the user chose to export (i.e. exportPath) - m_tempDir = std::make_unique(); - QTC_ASSERT(m_tempDir->isValid(), return); - auto targetPath = Utils::FilePath::fromString(m_tempDir->path()); - - m_zipWriter = std::make_unique(exportPath); + auto tempPath = Utils::FilePath::fromString(m_tempDir->path()); QString name = node.variantProperty("objectName").value().toString(); if (name.isEmpty()) @@ -294,7 +307,7 @@ void BundleHelper::exportNode(const ModelNode &node, const QPixmap &iconPixmap) QString qml = nodeNameToComponentFileName(name); QString iconBaseName = UniqueName::generateId(name); - m_iconPath = QLatin1String("icons/%1.png").arg(iconBaseName); + QString iconPath = QLatin1String("icons/%1.png").arg(iconBaseName); // generate and save Qml file auto [qmlString, depAssets] = modelNodeToQmlString(node); @@ -304,37 +317,17 @@ void BundleHelper::exportNode(const ModelNode &node, const QPixmap &iconPixmap) for (const AssetPath &assetPath : depAssetsList) depAssetsRelativePaths.append(assetPath.relativePath); - auto qmlFilePath = targetPath.pathAppended(qml); + auto qmlFilePath = tempPath.pathAppended(qml); auto result = qmlFilePath.writeFileContents(qmlString.toUtf8()); - QTC_ASSERT_EXPECTED(result, return); + QTC_ASSERT_EXPECTED(result, return {}); m_zipWriter->addFile(qmlFilePath.fileName(), qmlString.toUtf8()); - // add the item to the bundle json - QJsonObject jsonObj; - QJsonArray itemsArr; - itemsArr.append(QJsonObject { - {"name", name}, - {"qml", qml}, - {"icon", m_iconPath}, - {"files", QJsonArray::fromStringList(depAssetsRelativePaths)} - }); - - jsonObj["items"] = itemsArr; - - auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); - jsonObj["id"] = node.metaInfo().isQtQuick3DMaterial() ? compUtils.userMaterialsBundleId() - : compUtils.user3DBundleId(); - jsonObj["version"] = BUNDLE_VERSION; - - Utils::FilePath jsonFilePath = targetPath.pathAppended(Constants::BUNDLE_JSON_FILENAME); - m_zipWriter->addFile(jsonFilePath.fileName(), QJsonDocument(jsonObj).toJson()); - // add item's dependency assets to the bundle zip and target path (for icon generation) for (const AssetPath &assetPath : depAssetsList) { QByteArray assetContent = assetPath.fileContent(); m_zipWriter->addFile(assetPath.relativePath, assetContent); - Utils::FilePath assetTargetPath = targetPath.pathAppended(assetPath.relativePath); + Utils::FilePath assetTargetPath = tempPath.pathAppended(assetPath.relativePath); assetTargetPath.parentDir().ensureWritableDir(); assetTargetPath.writeFileContents(assetContent); } @@ -353,12 +346,25 @@ void BundleHelper::exportNode(const ModelNode &node, const QPixmap &iconPixmap) } if (iconPixmapToSave.isNull()) { - getImageFromCache(qmlFilePath.toFSPathString(), [&](const QImage &image) { - addIconAndCloseZip(image); + getImageFromCache(qmlFilePath.toFSPathString(), [this, iconPath](const QImage &image) { + addIconToZip(iconPath, image); }); } else { - addIconAndCloseZip(iconPixmapToSave); + addIconToZip(iconPath, iconPixmapToSave); } + + return { + {"name", name}, + {"qml", qml}, + {"icon", iconPath}, + {"files", QJsonArray::fromStringList(depAssetsRelativePaths)} + }; +} + +void BundleHelper::maybeCloseZip() +{ + if (--m_remainingFiles <= 0) + m_zipWriter->close(); } QPair> BundleHelper::modelNodeToQmlString(const ModelNode &node, int depth) @@ -544,14 +550,14 @@ void BundleHelper::getImageFromCache(const QString &qmlPath, }); } -void BundleHelper::addIconAndCloseZip(const auto &image) { // auto: QImage or QPixmap +void BundleHelper::addIconToZip(const QString &iconPath, const auto &image) { // auto: QImage or QPixmap QByteArray iconByteArray; QBuffer buffer(&iconByteArray); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "PNG"); - m_zipWriter->addFile(m_iconPath, iconByteArray); - m_zipWriter->close(); + m_zipWriter->addFile(iconPath, iconByteArray); + maybeCloseZip(); }; QString BundleHelper::getImportPath() const @@ -689,6 +695,10 @@ QSet BundleHelper::getComponentDependencies(const Utils::FilePath &fi parseNode = [&](const ModelNode &node) { // workaround node.isComponent() as it is not working here QString nodeType = QString::fromLatin1(node.type()); + +#ifdef QDS_USE_PROJECTSTORAGE + // TODO +#else if (!nodeType.startsWith("QtQuick")) { Utils::FilePath compFilPath = getComponentFilePath(nodeType, mainCompDir); if (!compFilPath.isEmpty()) { @@ -705,6 +715,7 @@ QSet BundleHelper::getComponentDependencies(const Utils::FilePath &fi return; } } +#endif const QList nodeProps = node.properties(); for (const AbstractProperty &p : nodeProps) { diff --git a/src/plugins/qmldesigner/components/componentcore/bundlehelper.h b/src/plugins/qmldesigner/components/componentcore/bundlehelper.h index 14d90ef951e..edeb91f76e6 100644 --- a/src/plugins/qmldesigner/components/componentcore/bundlehelper.h +++ b/src/plugins/qmldesigner/components/componentcore/bundlehelper.h @@ -57,7 +57,7 @@ public: ~BundleHelper(); void importBundleToProject(); - void exportBundle(const ModelNode &node, const QPixmap &iconPixmap = QPixmap()); + void exportBundle(const QList &nodes, const QPixmap &iconPixmap = QPixmap()); void getImageFromCache(const QString &qmlPath, std::function successCallback); QString nodeNameToComponentFileName(const QString &name) const; @@ -71,18 +71,19 @@ private: QString getExportPath(const ModelNode &node) const; bool isMaterialBundle(const QString &bundleId) const; bool isItemBundle(const QString &bundleId) const; - void addIconAndCloseZip(const auto &image); + void addIconToZip(const QString &iconPath, const auto &image); Utils::FilePath componentPath(const ModelNode &node) const; QSet getBundleComponentDependencies(const ModelNode &node) const; - void exportComponent(const ModelNode &node); - void exportNode(const ModelNode &node, const QPixmap &iconPixmap = QPixmap()); + QJsonObject exportComponent(const ModelNode &node); + QJsonObject exportNode(const ModelNode &node, const QPixmap &iconPixmap = QPixmap()); + void maybeCloseZip(); QPointer m_view; QPointer m_widget; Utils::UniqueObjectPtr m_importer; std::unique_ptr m_zipWriter; std::unique_ptr m_tempDir; - QString m_iconPath; + int m_remainingFiles = 0; static constexpr char BUNDLE_VERSION[] = "1.0"; }; diff --git a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h index 291fb9a1224..54729d9075a 100644 --- a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h +++ b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h @@ -167,9 +167,9 @@ inline constexpr char editMaterialDisplayName[] = QT_TRANSLATE_NOOP("QmlDesigner inline constexpr char addToContentLibraryDisplayName[] = QT_TRANSLATE_NOOP( "QmlDesignerContextMenu", "Add to Content Library"); inline constexpr char importComponentDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", - "Import Component"); + "Import Bundle"); inline constexpr char exportComponentDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", - "Export Component"); + "Export Bundle"); inline constexpr char editAnnotationsDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit Annotations"); inline constexpr char addMouseAreaFillDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", @@ -182,7 +182,7 @@ inline constexpr char editInEffectComposerDisplayName[] = QT_TRANSLATE_NOOP("Qml inline constexpr char openSignalDialogDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Open Signal Dialog"); inline constexpr char update3DAssetDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", - "Update 3D Asset"); + "Reimport 3D Asset"); inline constexpr char setIdDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Set Id"); diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp index fe687a99ce1..6cb88e5f411 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -2033,10 +2033,10 @@ void DesignerActionManager::createDefaultDesignerActions() QKeySequence(), Priorities::ExportComponent, [&](const SelectionContext &context) { - m_bundleHelper->exportBundle(context.currentSingleSelectedNode()); + m_bundleHelper->exportBundle(context.selectedModelNodes()); }, - &is3DNode, - &is3DNode)); + &are3DNodes, + &are3DNodes)); addDesignerAction(new ModelNodeContextMenuAction( editMaterialCommandId, diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h b/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h index 95cf541340b..fbf27083127 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h +++ b/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h @@ -80,11 +80,15 @@ inline bool enableAddToContentLib(const SelectionContext &selectionState) return isNode3D && !isInBundle; } -inline bool is3DNode(const SelectionContext &selectionState) +inline bool are3DNodes(const SelectionContext &selectionState) { - ModelNode modelNode = selectionState.currentSingleSelectedNode(); + const QList nodes = selectionState.selectedModelNodes(); + if (nodes.isEmpty()) + return false; - return modelNode.metaInfo().isQtQuick3DNode(); + return std::all_of(nodes.cbegin(), nodes.cend(), [](const ModelNode &node) { + return node.metaInfo().isQtQuick3DNode(); + }); } inline bool hasEditableMaterial(const SelectionContext &selectionState) diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp index d59b69f63ae..cd67e806dd8 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -1130,7 +1129,11 @@ static QString getAssetDefaultDirectory(const QString &assetDir, const QString & AddFilesResult addFontToProject(const QStringList &fileNames, const QString &defaultDir, bool showDialog) { - return addFilesToProject(fileNames, getAssetDefaultDirectory("fonts", defaultDir), showDialog); + const AddFilesResult result = addFilesToProject(fileNames, + getAssetDefaultDirectory("fonts", defaultDir), + showDialog); + QmlDesignerPlugin::viewManager().view()->resetPuppet(); + return result; } AddFilesResult addSoundToProject(const QStringList &fileNames, const QString &defaultDir, bool showDialog) @@ -2093,7 +2096,7 @@ void handleMaterialDrop(const QMimeData *mimeData, const ModelNode &targetNode) ModelNode matNode = view->modelNodeForInternalId(internalId); view->executeInTransaction(__FUNCTION__, [&] { - MaterialUtils::assignMaterialTo3dModel(view, targetNode, matNode); + Utils3D::assignMaterialTo3dModel(view, targetNode, matNode); }); } diff --git a/src/plugins/qmldesigner/components/componentcore/utils3d.cpp b/src/plugins/qmldesigner/components/componentcore/utils3d.cpp index 465514bbb6c..1673c854d6b 100644 --- a/src/plugins/qmldesigner/components/componentcore/utils3d.cpp +++ b/src/plugins/qmldesigner/components/componentcore/utils3d.cpp @@ -3,14 +3,20 @@ #include "utils3d.h" +#include #include #include #include #include #include +#include +#include +#include #include #include +#include + #include #include @@ -342,5 +348,111 @@ ModelNode createMaterial(AbstractView *view, const NodeMetaInfo &metaInfo) } #endif +void addQuick3DImportAndView3D(AbstractView *view) +{ + DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument(); + if (!view || !view->model() || !document || document->inFileComponentModelActive()) { + Core::AsynchronousMessageBox::warning(Tr::tr("Failed to Add Import"), + Tr::tr("Could not add QtQuick3D import to the document.")); + return; + } + + QString importName{"QtQuick3D"}; + if (view->model()->hasImport(importName)) + return; + + view->executeInTransaction(__FUNCTION__, [&] { + Import import = Import::createLibraryImport(importName); + view->model()->changeImports({import}, {}); + + if (!view->rootModelNode().metaInfo().isQtQuickItem()) + return; + + ensureMaterialLibraryNode(view); +#ifndef QDS_USE_PROJECTSTORAGE + }); + view->executeInTransaction(__FUNCTION__, [&] { +#endif + NodeMetaInfo view3dInfo = view->model()->qtQuick3DView3DMetaInfo(); + if (!view->allModelNodesOfType(view3dInfo).isEmpty()) + return; + + const QList entries = view->model()->itemLibraryEntries(); + // Use template file to identify correct entry, as name could be localized in the future + const QString view3dSource{"extendedview3D_template.qml"}; + auto templateMatch = [&view3dSource](const ItemLibraryEntry &entry) -> bool { + return entry.templatePath().endsWith(view3dSource); + }; + auto iter = std::ranges::find_if(entries, templateMatch); + if (iter == entries.end()) + return; + + NodeAbstractProperty targetProp = view->rootModelNode().defaultNodeAbstractProperty(); + QmlObjectNode newQmlObjectNode = QmlItemNode::createQmlObjectNode( + view, *iter, QPointF(), targetProp, false); + + const QList models = newQmlObjectNode.modelNode().subModelNodesOfType( + view->model()->qtQuick3DModelMetaInfo()); + if (!models.isEmpty()) + assignMaterialTo3dModel(view, models.at(0)); + }); +} + +// Assigns given material to a 3D model. +// The assigned material is also inserted into material library if not already there. +// If given material is not valid, first existing material from material library is used, +// or if material library is empty, a new material is created. +// This function should be called only from inside a transaction, as it potentially does many +// changes to model. +void assignMaterialTo3dModel(AbstractView *view, const ModelNode &modelNode, + const ModelNode &materialNode) +{ + QTC_ASSERT(modelNode.metaInfo().isQtQuick3DModel(), return); + + ModelNode matLib = Utils3D::materialLibraryNode(view); + + if (!matLib) + return; + + ModelNode newMaterialNode; + + if (materialNode.metaInfo().isQtQuick3DMaterial()) { + newMaterialNode = materialNode; + } else { + const QList materials = matLib.directSubModelNodes(); + auto isMaterial = [](const ModelNode &node) -> bool { + return node.metaInfo().isQtQuick3DMaterial(); + }; + if (auto iter = std::ranges::find_if(materials, isMaterial); iter != materials.end()) + newMaterialNode = *iter; + + // if no valid material, create a new default material + if (!newMaterialNode) { +#ifdef QDS_USE_PROJECTSTORAGE + newMaterialNode = view->createModelNode("PrincipledMaterial"); +#else + NodeMetaInfo metaInfo = view->model()->qtQuick3DPrincipledMaterialMetaInfo(); + newMaterialNode = view->createModelNode("QtQuick3D.PrincipledMaterial", + metaInfo.majorVersion(), + metaInfo.minorVersion()); +#endif + newMaterialNode.ensureIdExists(); + } + } + + QTC_ASSERT(newMaterialNode, return); + + VariantProperty matNameProp = newMaterialNode.variantProperty("objectName"); + if (matNameProp.value().isNull()) + matNameProp.setValue("New Material"); + + if (!newMaterialNode.hasParentProperty() + || newMaterialNode.parentProperty() != matLib.defaultNodeListProperty()) { + matLib.defaultNodeListProperty().reparentHere(newMaterialNode); + } + + QmlObjectNode(modelNode).setBindingProperty("materials", newMaterialNode.id()); +} + } // namespace Utils3D } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/utils3d.h b/src/plugins/qmldesigner/components/componentcore/utils3d.h index 6a7899589c3..b14784d0291 100644 --- a/src/plugins/qmldesigner/components/componentcore/utils3d.h +++ b/src/plugins/qmldesigner/components/componentcore/utils3d.h @@ -54,5 +54,10 @@ ModelNode createMaterial(AbstractView *view, const TypeName &typeName); ModelNode createMaterial(AbstractView *view, const NodeMetaInfo &metaInfo); #endif +void addQuick3DImportAndView3D(AbstractView *view); +void assignMaterialTo3dModel(AbstractView *view, const ModelNode &modelNode, + const ModelNode &materialNode = {}); + + } // namespace Utils3D } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp index c2feedd3453..062aefde089 100644 --- a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp @@ -85,7 +85,7 @@ public: QmlModelState savedState; Internal::DebugView debugView; Sqlite::Database auxiliaryDataDatabase{ - Utils::PathString{Core::ICore::userResourcePath("auxiliary_data.db").toUrlishString()}, + Utils::PathString{Core::ICore::userResourcePath("auxiliary_data.db").path()}, Sqlite::JournalMode::Wal, Sqlite::LockingMode::Normal}; AuxiliaryPropertyStorageView auxiliaryDataKeyView; @@ -512,6 +512,11 @@ const AbstractView *ViewManager::view() const return &d->nodeInstanceView; } +AbstractView *ViewManager::view() +{ + return &d->nodeInstanceView; +} + TextEditorView *ViewManager::textEditorView() { return &d->textEditorView; diff --git a/src/plugins/qmldesigner/components/componentcore/viewmanager.h b/src/plugins/qmldesigner/components/componentcore/viewmanager.h index 08bbb313053..1ca067f213d 100644 --- a/src/plugins/qmldesigner/components/componentcore/viewmanager.h +++ b/src/plugins/qmldesigner/components/componentcore/viewmanager.h @@ -72,6 +72,7 @@ public: void nextFileIsCalledInternally(); const AbstractView *view() const; + AbstractView *view(); TextEditorView *textEditorView(); void emitCustomNotification(const QString &identifier, const QList &nodeList, diff --git a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp index 4ef8a4ed378..e7996ba0a10 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp @@ -3,10 +3,11 @@ #include "bindingmodel.h" #include "bindingmodelitem.h" -#include "connectioneditorutils.h" +#include "connectioneditorlogging.h" #include "connectionview.h" #include "modelfwd.h" +#include #include #include #include @@ -64,12 +65,20 @@ BindingProperty BindingModel::propertyForRow(int row) const return {}; } +// TODO: add support for scripts in the binding editor (QDS-13510) +static bool isSupportedProperty(const PropertyNameView &propertyName) +{ + return propertyName != QLatin1String("script"); +} + static PropertyName unusedProperty(const ModelNode &modelNode) { if (modelNode.metaInfo().isValid()) { for (const auto &property : modelNode.metaInfo().properties()) { - if (property.isWritable() && !modelNode.hasProperty(property.name())) + if (property.isWritable() && !modelNode.hasProperty(property.name()) + && isSupportedProperty(property.name())) { return property.name(); + } } } return "none"; @@ -147,7 +156,7 @@ void BindingModel::updateItem(const BindingProperty &property) item->updateProperty(property); } else { ModelNode node = property.parentModelNode(); - if (connectionView()->isSelectedModelNode(node)) { + if (connectionView()->isSelectedModelNode(node) && isSupportedProperty(property.name())) { appendRow(new BindingModelItem(property)); setCurrentProperty(property); } @@ -243,8 +252,10 @@ void BindingModel::addModelNode(const ModelNode &node) return; const QList bindingProperties = node.bindingProperties(); - for (const BindingProperty &property : bindingProperties) - appendRow(new BindingModelItem(property)); + for (const BindingProperty &property : bindingProperties) { + if (isSupportedProperty(property.name())) + appendRow(new BindingModelItem(property)); + } } BindingModelBackendDelegate::BindingModelBackendDelegate(BindingModel &model) diff --git a/src/plugins/qmldesigner/components/connectioneditor/bindingmodelitem.cpp b/src/plugins/qmldesigner/components/connectioneditor/bindingmodelitem.cpp index e5e1372dd65..a0976564e90 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/bindingmodelitem.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/bindingmodelitem.cpp @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "bindingmodelitem.h" -#include "connectioneditorutils.h" +#include #include #include diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorlogging.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorlogging.cpp new file mode 100644 index 00000000000..0ed91b33ab3 --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorlogging.cpp @@ -0,0 +1,10 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "connectioneditorlogging.h" + +namespace QmlDesigner { + +Q_LOGGING_CATEGORY(ConnectionEditorLog, "qtc.qtquickdesigner.connectioneditor", QtWarningMsg) + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorlogging.h b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorlogging.h new file mode 100644 index 00000000000..673a5b1a32f --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorlogging.h @@ -0,0 +1,12 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +namespace QmlDesigner { + +Q_DECLARE_LOGGING_CATEGORY(ConnectionEditorLog) + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp index f4462f911f5..c0a13a788e8 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp @@ -2,41 +2,22 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "connectionmodel.h" -#include "connectioneditorutils.h" +#include "connectioneditorlogging.h" #include "connectionview.h" -#include "utils/algorithm.h" -#include -#include -#include -#include -#include #include -#include #include -#include #include #include #include #include -#include +#include +#include #include -#include - -#include #include -#include -#include -#include -#include -#include -#include namespace { - -const char defaultCondition[] = "condition"; - QStringList propertyNameListToStringList(const QmlDesigner::PropertyNameList &propertyNameList) { QStringList stringList; @@ -308,8 +289,7 @@ void ConnectionModel::updateCustomData(QStandardItem *item, const SignalHandlerP //anything else is assignment // e.g. foo.bal = foo2.bula ; foo.bal = "literal" ; goo.gal = true - item->setData(tr(ConnectionEditorEvaluator::getDisplayStringForType( - signalHandlerProperty.source()) + item->setData(tr(ScriptEditorEvaluator::getDisplayStringForType(signalHandlerProperty.source()) .toLatin1()), UserRoles::ActionTypeRole); } @@ -344,21 +324,6 @@ ModelNode ConnectionModel::getTargetNodeForConnection(const ModelNode &connectio return result; } -static QString addOnToSignalName(const QString &signal) -{ - if (signal.isEmpty()) - return {}; - - static const QRegularExpression rx("^on[A-Z]"); - if (rx.match(signal).hasMatch()) - return signal; - - QString ret = signal; - ret[0] = ret.at(0).toUpper(); - ret.prepend("on"); - return ret; -} - static PropertyName getFirstSignalForTarget(const NodeMetaInfo &target) { PropertyName ret = "clicked"; @@ -738,208 +703,22 @@ QHash ConnectionModel::roleNames() const return roleNames; } -ConnectionModelBackendDelegate::ConnectionModelBackendDelegate(ConnectionModel *model) - : m_signalDelegate(model->connectionView()) - , m_okStatementDelegate(model) - , m_koStatementDelegate(model) - , m_conditionListModel(model) - , m_propertyTreeModel(model->connectionView()) - , m_propertyListProxyModel(&m_propertyTreeModel) - , m_model(model) -{ - connect(&m_signalDelegate, &PropertyTreeModelDelegate::commitData, this, [this] { - handleTargetChanged(); - }); - - connect(&m_okStatementDelegate, - &ConnectionModelStatementDelegate::statementChanged, - this, - [this] { handleOkStatementChanged(); }); - - connect(&m_koStatementDelegate, - &ConnectionModelStatementDelegate::statementChanged, - this, - [this] { handleKOStatementChanged(); }); - - connect(&m_conditionListModel, &ConditionListModel::conditionChanged, this, [this] { - handleConditionChanged(); - }); - - m_signalDelegate.setPropertyType(PropertyTreeModel::SignalType); -} - -QString generateDefaultStatement(ConnectionModelBackendDelegate::ActionType actionType, - const QString &rootId) -{ - switch (actionType) { - case ConnectionModelStatementDelegate::CallFunction: - return "Qt.quit()"; - case ConnectionModelStatementDelegate::Assign: - return QString("%1.visible = %1.visible").arg(rootId); - case ConnectionModelStatementDelegate::ChangeState: - return QString("%1.state = \"\"").arg(rootId); - case ConnectionModelStatementDelegate::SetProperty: - return QString("%1.visible = true").arg(rootId); - case ConnectionModelStatementDelegate::PrintMessage: - return QString("console.log(\"test\")").arg(rootId); - case ConnectionModelStatementDelegate::Custom: - return {}; - }; - - //Qt.quit() - //console.log("test") - //root.state = "" - //root.visible = root.visible - //root.visible = true - - return {}; -} - -void ConnectionModelBackendDelegate::changeActionType(ActionType actionType) -{ - QTC_ASSERT(actionType != ConnectionModelStatementDelegate::Custom, return ); - - ConnectionModel *model = m_model; - - QTC_ASSERT(model, return ); - QTC_ASSERT(model->connectionView()->isAttached(), return ); - - const QString validId = model->connectionView()->rootModelNode().validId(); - - SignalHandlerProperty signalHandlerProperty = model->signalHandlerPropertyForRow(currentRow()); - - // Do not take ko into account for now - - model->connectionView() - ->executeInTransaction("ConnectionModelBackendDelegate::removeCondition", [&]() { - ConnectionEditorStatements::MatchedStatement &okStatement - = ConnectionEditorStatements::okStatement(m_handler); - - ConnectionEditorStatements::MatchedStatement &koStatement - = ConnectionEditorStatements::koStatement(m_handler); - - koStatement = ConnectionEditorStatements::EmptyBlock(); - - //We expect a valid id on the root node - const QString validId = model->connectionView()->rootModelNode().validId(); - QString statementSource = generateDefaultStatement(actionType, validId); - - auto tempHandler = ConnectionEditorEvaluator::parseStatement(statementSource); - - auto newOkStatement = ConnectionEditorStatements::okStatement(tempHandler); - - QTC_ASSERT(!ConnectionEditorStatements::isEmptyStatement(newOkStatement), return ); - - okStatement = newOkStatement; - - QString newSource = ConnectionEditorStatements::toJavascript(m_handler); - - signalHandlerProperty.setSource(newSource); - }); - - setSource(signalHandlerProperty.source()); - - setupHandlerAndStatements(); - setupCondition(); -} - -void ConnectionModelBackendDelegate::addCondition() -{ - ConnectionEditorStatements::MatchedStatement okStatement - = ConnectionEditorStatements::okStatement(m_handler); - - ConnectionEditorStatements::MatchedCondition newCondition; - - ConnectionEditorStatements::Variable variable; - variable.nodeId = defaultCondition; - newCondition.statements.append(variable); - - ConnectionEditorStatements::ConditionalStatement conditionalStatement; - - conditionalStatement.ok = okStatement; - conditionalStatement.condition = newCondition; - - m_handler = conditionalStatement; - - QString newSource = ConnectionEditorStatements::toJavascript(m_handler); - - commitNewSource(newSource); - - setupHandlerAndStatements(); - setupCondition(); -} - -void ConnectionModelBackendDelegate::removeCondition() -{ - ConnectionEditorStatements::MatchedStatement okStatement - = ConnectionEditorStatements::okStatement(m_handler); - - m_handler = okStatement; - - QString newSource = ConnectionEditorStatements::toJavascript(m_handler); - - commitNewSource(newSource); - - setupHandlerAndStatements(); - setupCondition(); -} - -void ConnectionModelBackendDelegate::addElse() -{ - ConnectionEditorStatements::MatchedStatement okStatement - = ConnectionEditorStatements::okStatement(m_handler); - - auto &condition = ConnectionEditorStatements::conditionalStatement(m_handler); - condition.ko = condition.ok; - - QString newSource = ConnectionEditorStatements::toJavascript(m_handler); - - - commitNewSource(newSource); - setupHandlerAndStatements(); -} - -void ConnectionModelBackendDelegate::removeElse() -{ - ConnectionEditorStatements::MatchedStatement okStatement - = ConnectionEditorStatements::okStatement(m_handler); - - auto &condition = ConnectionEditorStatements::conditionalStatement(m_handler); - condition.ko = ConnectionEditorStatements::EmptyBlock(); - - QString newSource = ConnectionEditorStatements::toJavascript(m_handler); - - - commitNewSource(newSource); - setupHandlerAndStatements(); -} - -void ConnectionModelBackendDelegate::setNewSource(const QString &newSource) -{ - setSource(newSource); - commitNewSource(newSource); - setupHandlerAndStatements(); - setupCondition(); -} - int ConnectionModelBackendDelegate::currentRow() const { return m_currentRow; } -static QString removeOnFromSignalName(const QString &signal) +ConnectionModelBackendDelegate::ConnectionModelBackendDelegate(ConnectionModel *model) + : ScriptEditorBackend(model->connectionView()) + , m_signalDelegate(model->connectionView()) + , m_model(model) { - if (signal.isEmpty()) - return {}; + connect(&m_signalDelegate, + &PropertyTreeModelDelegate::commitData, + this, + &ConnectionModelBackendDelegate::handleTargetChanged); - static const QRegularExpression rx("^on[A-Z]"); - if (!rx.match(signal).hasMatch()) - return signal; - - QString ret = signal; - ret.remove(0, 2); - ret[0] = ret.at(0).toLower(); - return ret; + m_signalDelegate.setPropertyType(PropertyTreeModel::SignalType); } void ConnectionModelBackendDelegate::setCurrentRow(int i) @@ -954,22 +733,22 @@ void ConnectionModelBackendDelegate::setCurrentRow(int i) void ConnectionModelBackendDelegate::update() { - if (m_blockReflection) - return; - if (m_currentRow == -1) return; - m_propertyTreeModel.resetModel(); - m_propertyListProxyModel.setRowAndInternalId(0, internalRootIndex); + if (blockReflection()) { + return; + } + + ScriptEditorBackend::update(); ConnectionModel *model = m_model; - QTC_ASSERT(model, return ); + QTC_ASSERT(model, return); if (!model->connectionView()->isAttached()) return; - SignalHandlerProperty signalHandlerProperty = model->signalHandlerPropertyForRow(currentRow()); + auto signalHandlerProperty = model->signalHandlerPropertyForRow(currentRow()); QStringList targetNodes; @@ -988,65 +767,8 @@ void ConnectionModelBackendDelegate::update() if (!targetNodes.contains(targetNodeName)) targetNodes.append(targetNodeName); - setSource(signalHandlerProperty.source()); - m_signalDelegate.setup(targetNodeName, removeOnFromSignalName(QString::fromUtf8(signalHandlerProperty.name()))); - - setupHandlerAndStatements(); - - setupCondition(); - - QTC_ASSERT(model, return ); -} - -void ConnectionModelBackendDelegate::jumpToCode() -{ - ConnectionModel *model = m_model; - - QTC_ASSERT(model, return ); - QTC_ASSERT(model->connectionView()->isAttached(), return ); - SignalHandlerProperty signalHandlerProperty = model->signalHandlerPropertyForRow(currentRow()); - - ModelNodeOperations::jumpToCode(signalHandlerProperty.parentModelNode()); -} - -void ConnectionModelBackendDelegate::handleException() -{ - QMessageBox::warning(nullptr, tr("Error"), m_exceptionError); -} - -bool ConnectionModelBackendDelegate::hasCondition() const -{ - return m_hasCondition; -} - -bool ConnectionModelBackendDelegate::hasElse() const -{ - return m_hasElse; -} - -void ConnectionModelBackendDelegate::setHasCondition(bool b) -{ - if (b == m_hasCondition) - return; - - m_hasCondition = b; - emit hasConditionChanged(); -} - -void ConnectionModelBackendDelegate::setHasElse(bool b) -{ - if (b == m_hasElse) - return; - - m_hasElse = b; - emit hasElseChanged(); -} - -ConnectionModelBackendDelegate::ActionType ConnectionModelBackendDelegate::actionType() const -{ - return m_actionType; } PropertyTreeModelDelegate *ConnectionModelBackendDelegate::signal() @@ -1054,135 +776,21 @@ PropertyTreeModelDelegate *ConnectionModelBackendDelegate::signal() return &m_signalDelegate; } -ConnectionModelStatementDelegate *ConnectionModelBackendDelegate::okStatement() -{ - return &m_okStatementDelegate; -} - -ConnectionModelStatementDelegate *ConnectionModelBackendDelegate::koStatement() -{ - return &m_koStatementDelegate; -} - -ConditionListModel *ConnectionModelBackendDelegate::conditionListModel() -{ - return &m_conditionListModel; -} - -QString ConnectionModelBackendDelegate::indentedSource() const -{ - if (m_source.isEmpty()) - return {}; - - QTextDocument doc(m_source); - IndentingTextEditModifier mod(&doc); - - mod.indent(0, m_source.length() - 1); - return mod.text(); -} - -QString ConnectionModelBackendDelegate::source() const -{ - return m_source; -} - -void ConnectionModelBackendDelegate::setSource(const QString &source) -{ - if (source == m_source) - return; - - m_source = source; - emit sourceChanged(); -} - -PropertyTreeModel *ConnectionModelBackendDelegate::propertyTreeModel() -{ - return &m_propertyTreeModel; -} - -PropertyListProxyModel *ConnectionModelBackendDelegate::propertyListProxyModel() -{ - return &m_propertyListProxyModel; -} - -void ConnectionModelBackendDelegate::setupCondition() -{ - auto &condition = ConnectionEditorStatements::matchedCondition(m_handler); - m_conditionListModel.setCondition(ConnectionEditorStatements::matchedCondition(m_handler)); - setHasCondition(!condition.statements.isEmpty()); -} - -void ConnectionModelBackendDelegate::setupHandlerAndStatements() -{ - ConnectionModel *model = m_model; - QTC_ASSERT(model, return ); - SignalHandlerProperty signalHandlerProperty = model->signalHandlerPropertyForRow(currentRow()); - - if (signalHandlerProperty.source().isEmpty()) { - m_actionType = ConnectionModelStatementDelegate::Custom; - m_handler = ConnectionEditorStatements::EmptyBlock(); - } else { - m_handler = ConnectionEditorEvaluator::parseStatement(signalHandlerProperty.source()); - - const QString statementType = QmlDesigner::ConnectionEditorStatements::toDisplayName( - m_handler); - - if (statementType == ConnectionEditorStatements::EMPTY_DISPLAY_NAME) { - m_actionType = ConnectionModelStatementDelegate::Custom; - } else if (statementType == ConnectionEditorStatements::ASSIGNMENT_DISPLAY_NAME) { - m_actionType = ConnectionModelStatementDelegate::Assign; - //setupAssignment(); - } else if (statementType == ConnectionEditorStatements::SETPROPERTY_DISPLAY_NAME) { - m_actionType = ConnectionModelStatementDelegate::SetProperty; - //setupSetProperty(); - } else if (statementType == ConnectionEditorStatements::FUNCTION_DISPLAY_NAME) { - m_actionType = ConnectionModelStatementDelegate::CallFunction; - //setupCallFunction(); - } else if (statementType == ConnectionEditorStatements::SETSTATE_DISPLAY_NAME) { - m_actionType = ConnectionModelStatementDelegate::ChangeState; - //setupChangeState(); - } else if (statementType == ConnectionEditorStatements::LOG_DISPLAY_NAME) { - m_actionType = ConnectionModelStatementDelegate::PrintMessage; - //setupPrintMessage(); - } else { - m_actionType = ConnectionModelStatementDelegate::Custom; - } - } - - ConnectionEditorStatements::MatchedStatement &okStatement - = ConnectionEditorStatements::okStatement(m_handler); - m_okStatementDelegate.setStatement(okStatement); - m_okStatementDelegate.setActionType(m_actionType); - - ConnectionEditorStatements::MatchedStatement &koStatement - = ConnectionEditorStatements::koStatement(m_handler); - - if (!ConnectionEditorStatements::isEmptyStatement(koStatement)) { - m_koStatementDelegate.setStatement(koStatement); - m_koStatementDelegate.setActionType(m_actionType); - } - - ConnectionEditorStatements::isEmptyStatement(koStatement); - setHasElse(!ConnectionEditorStatements::isEmptyStatement(koStatement)); - - emit actionTypeChanged(); -} - void ConnectionModelBackendDelegate::handleTargetChanged() { ConnectionModel *model = m_model; - QTC_ASSERT(model, return ); + QTC_ASSERT(model, return); - QTC_ASSERT(model->connectionView()->isAttached(), return ); + QTC_ASSERT(model->connectionView()->isAttached(), return); - SignalHandlerProperty signalHandlerProperty = model->signalHandlerPropertyForRow(currentRow()); + SignalHandlerProperty signalHandlerProperty = getSignalHandlerProperty(); const PropertyName handlerName = addOnToSignalName(m_signalDelegate.name()).toUtf8(); const auto parentModelNode = signalHandlerProperty.parentModelNode(); - QTC_ASSERT(parentModelNode.isValid(), return ); + QTC_ASSERT(parentModelNode.isValid(), return); const auto newId = m_signalDelegate.id(); @@ -1190,8 +798,9 @@ void ConnectionModelBackendDelegate::handleTargetChanged() model->connectionView() ->executeInTransaction("ConnectionModelBackendDelegate::handleTargetChanged", [&]() { - const auto oldTargetNodeName - = parentModelNode.bindingProperty("target").resolveToModelNode().id(); + const auto oldTargetNodeName = parentModelNode.bindingProperty("target") + .resolveToModelNode() + .id(); if (signalHandlerProperty.name() != handlerName) { const auto expression = signalHandlerProperty.source(); @@ -1212,996 +821,38 @@ void ConnectionModelBackendDelegate::handleTargetChanged() } }); - model->selectProperty(model->connectionView() - ->modelNodeForInternalId(internalId) - .signalHandlerProperty(handlerName)); + model->selectProperty( + model->connectionView()->modelNodeForInternalId(internalId).signalHandlerProperty(handlerName)); } -void ConnectionModelBackendDelegate::handleOkStatementChanged() +AbstractProperty ConnectionModelBackendDelegate::getSourceProperty() const { - ConnectionEditorStatements::MatchedStatement &okStatement - = ConnectionEditorStatements::okStatement(m_handler); - - okStatement = m_okStatementDelegate.statement(); //TODO why? - - QString newSource = ConnectionEditorStatements::toJavascript(m_handler); - - commitNewSource(newSource); + return getSignalHandlerProperty(); } -void ConnectionModelBackendDelegate::handleKOStatementChanged() +void ConnectionModelBackendDelegate::setPropertySource(const QString &source) { - ConnectionEditorStatements::MatchedStatement &koStatement - = ConnectionEditorStatements::koStatement(m_handler); + auto property = getSourceProperty(); - koStatement = m_koStatementDelegate.statement(); //TODO why? + QTC_ASSERT(property.isValid(), return); - QString newSource = ConnectionEditorStatements::toJavascript(m_handler); - - commitNewSource(newSource); -} - -void ConnectionModelBackendDelegate::handleConditionChanged() -{ - ConnectionModel *model = m_model; - - QTC_ASSERT(model, return ); - QTC_ASSERT(model->connectionView()->isAttached(), return ); - - ConnectionEditorStatements::MatchedCondition &condition - = ConnectionEditorStatements::matchedCondition(m_handler); - condition = m_conditionListModel.condition(); //why? - QString newSource = ConnectionEditorStatements::toJavascript(m_handler); - - commitNewSource(newSource); -} - -void ConnectionModelBackendDelegate::commitNewSource(const QString &source) -{ - ConnectionModel *model = m_model; - - QTC_ASSERT(model, return ); - - QTC_ASSERT(model->connectionView()->isAttached(), return ); - - SignalHandlerProperty signalHandlerProperty = model->signalHandlerPropertyForRow(currentRow()); - - m_blockReflection = true; - model->connectionView()->executeInTransaction("ConnectionModelBackendDelegate::commitNewSource", - [&]() { - signalHandlerProperty.setSource(source); - }); - - setSource(signalHandlerProperty.source()); - m_blockReflection = false; -} - -static ConnectionEditorStatements::MatchedStatement emptyStatement; - -ConnectionModelStatementDelegate::ConnectionModelStatementDelegate(ConnectionModel *model) - : m_functionDelegate(model->connectionView()) - , m_lhsDelegate(model->connectionView()) - , m_rhsAssignmentDelegate(model->connectionView()) - , m_statement(emptyStatement) - , m_model(model) -{ - m_functionDelegate.setPropertyType(PropertyTreeModel::SlotType); - - connect(&m_functionDelegate, &PropertyTreeModelDelegate::commitData, this, [this] { - handleFunctionChanged(); - }); - - connect(&m_rhsAssignmentDelegate, &PropertyTreeModelDelegate::commitData, this, [this] { - handleRhsAssignmentChanged(); - }); - - connect(&m_lhsDelegate, &PropertyTreeModelDelegate::commitData, this, [this] { - handleLhsChanged(); - }); - - connect(&m_stringArgument, &StudioQmlTextBackend::activated, this, [this] { - handleStringArgumentChanged(); - }); - - connect(&m_states, &StudioQmlComboBoxBackend::activated, this, [this] { - handleStateChanged(); - }); - - connect(&m_stateTargets, &StudioQmlComboBoxBackend::activated, this, [this] { - handleStateTargetsChanged(); - }); -} - -void ConnectionModelStatementDelegate::setActionType(ActionType type) -{ - if (m_actionType == type) + if (source.isEmpty()) return; - m_actionType = type; - emit actionTypeChanged(); - setup(); -} - -void ConnectionModelStatementDelegate::setup() -{ - switch (m_actionType) { - case CallFunction: - setupCallFunction(); - break; - case Assign: - setupAssignment(); - break; - case ChangeState: - setupChangeState(); - break; - case SetProperty: - setupSetProperty(); - break; - case PrintMessage: - setupPrintMessage(); - break; - case Custom: - break; - }; -} - -void ConnectionModelStatementDelegate::setStatement( - ConnectionEditorStatements::MatchedStatement &statement) -{ - m_statement = statement; - setup(); -} - -ConnectionEditorStatements::MatchedStatement &ConnectionModelStatementDelegate::statement() -{ - return m_statement; -} - -ConnectionModelStatementDelegate::ActionType ConnectionModelStatementDelegate::actionType() const -{ - return m_actionType; -} - -PropertyTreeModelDelegate *ConnectionModelStatementDelegate::function() -{ - return &m_functionDelegate; -} - -PropertyTreeModelDelegate *ConnectionModelStatementDelegate::lhs() -{ - return &m_lhsDelegate; -} - -PropertyTreeModelDelegate *ConnectionModelStatementDelegate::rhsAssignment() -{ - return &m_rhsAssignmentDelegate; -} - -StudioQmlTextBackend *ConnectionModelStatementDelegate::stringArgument() -{ - return &m_stringArgument; -} - -StudioQmlComboBoxBackend *ConnectionModelStatementDelegate::stateTargets() -{ - return &m_stateTargets; -} - -StudioQmlComboBoxBackend *ConnectionModelStatementDelegate::states() -{ - return &m_states; -} - -void ConnectionModelStatementDelegate::handleFunctionChanged() -{ - QTC_ASSERT(std::holds_alternative(m_statement), - return ); - - ConnectionEditorStatements::MatchedFunction &functionStatement - = std::get(m_statement); - - functionStatement.functionName = m_functionDelegate.name(); - functionStatement.nodeId = m_functionDelegate.id(); - - emit statementChanged(); -} - -void ConnectionModelStatementDelegate::handleLhsChanged() -{ - if (m_actionType == Assign) { - QTC_ASSERT(std::holds_alternative(m_statement), - return ); - - ConnectionEditorStatements::Assignment &assignmentStatement - = std::get(m_statement); - - assignmentStatement.lhs.nodeId = m_lhsDelegate.id(); - assignmentStatement.lhs.propertyName = m_lhsDelegate.name(); - - } else if (m_actionType == SetProperty) { - QTC_ASSERT(std::holds_alternative(m_statement), - return ); - - ConnectionEditorStatements::PropertySet &setPropertyStatement - = std::get(m_statement); - - setPropertyStatement.lhs.nodeId = m_lhsDelegate.id(); - setPropertyStatement.lhs.propertyName = m_lhsDelegate.name(); - } else { - QTC_ASSERT(false, return ); - } - - emit statementChanged(); -} - -void ConnectionModelStatementDelegate::handleRhsAssignmentChanged() -{ - QTC_ASSERT(std::holds_alternative(m_statement), return ); - - ConnectionEditorStatements::Assignment &assignmentStatement - = std::get(m_statement); - - assignmentStatement.rhs.nodeId = m_rhsAssignmentDelegate.id(); - assignmentStatement.rhs.propertyName = m_rhsAssignmentDelegate.name(); - - setupPropertyType(); - - emit statementChanged(); -} - -static ConnectionEditorStatements::Literal parseTextArgument(const QString &text) -{ - if (text.startsWith("\"") && text.endsWith("\"")) { - QString ret = text; - ret.remove(0, 1); - ret.chop(1); - return ret; - } - - if (text == "true") - return true; - - if (text == "false") - return false; - - bool ok = true; - double d = text.toDouble(&ok); - if (ok) - return d; - - return text; -} - -static ConnectionEditorStatements::ComparativeStatement parseTextArgumentComparativeStatement( - const QString &text) -{ - if (text.startsWith("\"") && text.endsWith("\"")) { - QString ret = text; - ret.remove(0, 1); - ret.chop(1); - return ret; - } - - if (text == "true") - return true; - - if (text == "false") - return false; - - bool ok = true; - double d = text.toDouble(&ok); - if (ok) - return d; - - return text; -} - -static ConnectionEditorStatements::RightHandSide parseLogTextArgument(const QString &text) -{ - if (text.startsWith("\"") && text.endsWith("\"")) { - QString ret = text; - ret.remove(0, 1); - ret.chop(1); - return ret; - } - - if (text == "true") - return true; - - if (text == "false") - return true; - - bool ok = true; - double d = text.toDouble(&ok); - if (ok) - return d; - - //TODO variables and function calls - return text; -} - -void ConnectionModelStatementDelegate::handleStringArgumentChanged() -{ - if (m_actionType == SetProperty) { - QTC_ASSERT(std::holds_alternative(m_statement), - return ); - - ConnectionEditorStatements::PropertySet &propertySet - = std::get(m_statement); - - propertySet.rhs = parseTextArgument(m_stringArgument.text()); - - } else if (m_actionType == PrintMessage) { - QTC_ASSERT(std::holds_alternative(m_statement), - return ); - - ConnectionEditorStatements::ConsoleLog &consoleLog - = std::get(m_statement); - - consoleLog.argument = parseLogTextArgument(m_stringArgument.text()); - } else { - QTC_ASSERT(false, return ); - } - - emit statementChanged(); -} - -void ConnectionModelStatementDelegate::handleStateChanged() -{ - QTC_ASSERT(std::holds_alternative(m_statement), return ); - - ConnectionEditorStatements::StateSet &stateSet = std::get( - m_statement); - - QString stateName = m_states.currentText(); - if (stateName == baseStateName()) - stateName = ""; - stateSet.stateName = "\"" + stateName + "\""; - - emit statementChanged(); -} - -void ConnectionModelStatementDelegate::handleStateTargetsChanged() -{ - QTC_ASSERT(std::holds_alternative(m_statement), return ); - - ConnectionEditorStatements::StateSet &stateSet = std::get( - m_statement); - - stateSet.nodeId = m_stateTargets.currentText(); - stateSet.stateName = "\"\""; - - setupStates(); - - emit statementChanged(); -} - -void ConnectionModelStatementDelegate::setupAssignment() -{ - QTC_ASSERT(std::holds_alternative(m_statement), return ); - - const auto assignment = std::get(m_statement); - m_lhsDelegate.setup(assignment.lhs.nodeId, assignment.lhs.propertyName); - m_rhsAssignmentDelegate.setup(assignment.rhs.nodeId, assignment.rhs.propertyName); - setupPropertyType(); -} - -void ConnectionModelStatementDelegate::setupSetProperty() -{ - QTC_ASSERT(std::holds_alternative(m_statement), - return ); - - const auto propertySet = std::get(m_statement); - m_lhsDelegate.setup(propertySet.lhs.nodeId, propertySet.lhs.propertyName); - m_stringArgument.setText(ConnectionEditorStatements::toString(propertySet.rhs)); -} - -void ConnectionModelStatementDelegate::setupCallFunction() -{ - QTC_ASSERT(std::holds_alternative(m_statement), - return ); - - const auto functionStatement = std::get( - m_statement); - m_functionDelegate.setup(functionStatement.nodeId, functionStatement.functionName); -} - -void ConnectionModelStatementDelegate::setupChangeState() -{ - QTC_ASSERT(std::holds_alternative(m_statement), return ); - - QTC_ASSERT(m_model->connectionView()->isAttached(), return ); - - auto model = m_model->connectionView()->model(); - const auto items = Utils::filtered(m_model->connectionView()->allModelNodesOfType( - model->qtQuickItemMetaInfo()), - [](const ModelNode &node) { - QmlItemNode item(node); - return node.hasId() && item.isValid() - && !item.allStateNames().isEmpty(); - }); - - QStringList itemIds = Utils::transform(items, &ModelNode::id); - const auto groups = m_model->connectionView()->allModelNodesOfType( - model->qtQuickStateGroupMetaInfo()); - - const auto rootId = m_model->connectionView()->rootModelNode().id(); - itemIds.removeAll(rootId); - - QStringList groupIds = Utils::transform(groups, &ModelNode::id); - - Utils::sort(itemIds); - Utils::sort(groupIds); - - if (!rootId.isEmpty()) - groupIds.prepend(rootId); - - const QStringList stateGroupModel = groupIds + itemIds; - m_stateTargets.setModel(stateGroupModel); - - const auto stateSet = std::get(m_statement); - - m_stateTargets.setCurrentText(stateSet.nodeId); - setupStates(); -} -QString stripQuotesFromState(const QString &input) -{ - if (input.startsWith("\"") && input.endsWith("\"")) { - QString ret = input; - ret.remove(0, 1); - ret.chop(1); - return ret; - } - return input; -} -void ConnectionModelStatementDelegate::setupStates() -{ - QTC_ASSERT(std::holds_alternative(m_statement), return ); - QTC_ASSERT(m_model->connectionView()->isAttached(), return ); - - const auto stateSet = std::get(m_statement); - - const QString nodeId = m_stateTargets.currentText(); - - const ModelNode node = m_model->connectionView()->modelNodeForId(nodeId); - - QStringList states; - if (node.metaInfo().isQtQuickItem()) { - QmlItemNode item(node); - QTC_ASSERT(item.isValid(), return ); - if (item.isRootNode()) - states = item.states().names(); //model - else - states = item.allStateNames(); //instances - } else { - QmlModelStateGroup group(node); - states = group.names(); //model - } - - const QString stateName = stripQuotesFromState(stateSet.stateName); - - states.prepend(baseStateName()); - m_states.setModel(states); - if (stateName.isEmpty()) - m_states.setCurrentText(baseStateName()); + auto normalizedSource = QmlDesigner::SignalHandlerProperty::normalizedSourceWithBraces(source); + if (property.exists()) + property.toSignalHandlerProperty().setSource(normalizedSource); else - m_states.setCurrentText(stateName); + property.parentModelNode().signalHandlerProperty(property.name()).setSource(normalizedSource); } -void ConnectionModelStatementDelegate::setupPrintMessage() +SignalHandlerProperty ConnectionModelBackendDelegate::getSignalHandlerProperty() const { - QTC_ASSERT(std::holds_alternative(m_statement), return ); + ConnectionModel *model = m_model; + QTC_ASSERT(model, return {}); + QTC_ASSERT(model->connectionView()->isAttached(), return {}); - const auto consoleLog = std::get(m_statement); - m_stringArgument.setText(ConnectionEditorStatements::toString(consoleLog.argument)); -} - -void ConnectionModelStatementDelegate::setupPropertyType() -{ - PropertyTreeModel::PropertyTypes type = PropertyTreeModel::AllTypes; - - const NodeMetaInfo metaInfo = m_rhsAssignmentDelegate.propertyMetaInfo(); - - if (metaInfo.isBool()) - type = PropertyTreeModel::BoolType; - else if (metaInfo.isNumber()) - type = PropertyTreeModel::NumberType; - else if (metaInfo.isColor()) - type = PropertyTreeModel::ColorType; - else if (metaInfo.isString()) - type = PropertyTreeModel::StringType; - else if (metaInfo.isUrl()) - type = PropertyTreeModel::UrlType; - - m_lhsDelegate.setPropertyType(type); -} - -QString ConnectionModelStatementDelegate::baseStateName() const -{ - return tr("Base State"); -} - -static ConnectionEditorStatements::MatchedCondition emptyCondition; - -ConditionListModel::ConditionListModel(ConnectionModel *model) - : m_connectionModel(model) - , m_condition(emptyCondition) -{} - -int ConditionListModel::rowCount(const QModelIndex & /*parent*/) const -{ - return m_tokens.size(); -} - -QHash ConditionListModel::roleNames() const -{ - static QHash roleNames{{Qt::UserRole + 1, "type"}, {Qt::UserRole + 2, "value"}}; - return roleNames; -} - -QVariant ConditionListModel::data(const QModelIndex &index, int role) const -{ - if (index.isValid() && index.row() < rowCount()) { - if (role == Qt::UserRole + 1) { - return m_tokens.at(index.row()).type; - } else if (role == Qt::UserRole + 2) { - return m_tokens.at(index.row()).value; - } - - qCWarning(ConnectionEditorLog) << __FUNCTION__ << "invalid role"; - } else { - qCWarning(ConnectionEditorLog) << __FUNCTION__ << "invalid index"; - } - - return QVariant(); -} - -void ConditionListModel::setup() -{ - m_tokens.clear(); - - internalSetup(); - - emit validChanged(); - emit emptyChanged(); - - beginResetModel(); - endResetModel(); -} - -void ConditionListModel::setCondition(ConnectionEditorStatements::MatchedCondition &condition) -{ - m_condition = condition; - setup(); -} - -ConnectionEditorStatements::MatchedCondition &ConditionListModel::condition() -{ - return m_condition; -} - -ConditionListModel::ConditionToken ConditionListModel::tokenFromConditionToken( - const ConnectionEditorStatements::ConditionToken &token) -{ - ConditionToken ret; - ret.type = Operator; - ret.value = ConnectionEditorStatements::toJavascript(token); - - return ret; -} - -ConditionListModel::ConditionToken ConditionListModel::tokenFromComparativeStatement( - const ConnectionEditorStatements::ComparativeStatement &token) -{ - ConditionToken ret; - - if (auto *variable = std::get_if(&token)) { - ret.type = Variable; - ret.value = variable->expression(); - return ret; - } else if (auto *literal = std::get_if(&token)) { - ret.type = Literal; - ret.value = "\"" + *literal + "\""; - return ret; - } else if (auto *literal = std::get_if(&token)) { - ret.type = Literal; - if (*literal) - ret.value = "true"; - else - ret.value = "false"; - return ret; - } else if (auto *literal = std::get_if(&token)) { - ret.type = Literal; - ret.value = QString::number(*literal); - return ret; - } - - ret.type = Invalid; - ret.value = "invalid"; - return {}; -} - -void ConditionListModel::insertToken(int index, const QString &value) -{ - beginInsertRows({}, index, index); - - m_tokens.insert(index, valueToToken(value)); - validateAndRebuildTokens(); - - endInsertRows(); - //resetModel(); -} - -void ConditionListModel::updateToken(int index, const QString &value) -{ - m_tokens[index] = valueToToken(value); - validateAndRebuildTokens(); - - dataChanged(createIndex(index, 0), createIndex(index, 0)); - //resetModel(); -} - -void ConditionListModel::appendToken(const QString &value) -{ - beginInsertRows({}, rowCount() - 1, rowCount() - 1); - - insertToken(rowCount(), value); - validateAndRebuildTokens(); - - endInsertRows(); - //resetModel(); -} - -void ConditionListModel::removeToken(int index) -{ - QTC_ASSERT(index < m_tokens.count(), return ); - beginRemoveRows({}, index, index); - - m_tokens.remove(index, 1); - validateAndRebuildTokens(); - - endRemoveRows(); - - //resetModel(); -} - -void ConditionListModel::insertIntermediateToken(int index, const QString &value) -{ - beginInsertRows({}, index, index); - - ConditionToken token; - token.type = Intermediate; - token.value = value; - - m_tokens.insert(index, token); - - endInsertRows(); - //resetModel(); -} - -void ConditionListModel::insertShadowToken(int index, const QString &value) -{ - beginInsertRows({}, index, index); - - ConditionToken token; - token.type = Shadow; - token.value = value; - - m_tokens.insert(index, token); - - endInsertRows(); - - //resetModel(); -} - -void ConditionListModel::setShadowToken(int index, const QString &value) -{ - m_tokens[index].type = Shadow; - m_tokens[index].value = value; - - dataChanged(createIndex(index, 0), createIndex(index, 0)); - //resetModel(); -} - -bool ConditionListModel::valid() const -{ - return m_valid; -} - -bool ConditionListModel::empty() const -{ - return m_tokens.isEmpty(); -} - -void ConditionListModel::command(const QString &string) -{ - //TODO remove from prodcution code - QStringList list = string.split("%", Qt::SkipEmptyParts); - - if (list.size() < 2) - return; - - if (list.size() == 2) { - if (list.first() == "A") { - appendToken(list.last()); - } else if (list.first() == "R") { - bool ok = true; - int index = list.last().toInt(&ok); - - if (ok) - removeToken(index); - } - } - - if (list.size() == 3) { - if (list.first() == "U") { - bool ok = true; - int index = list.at(1).toInt(&ok); - - if (ok) - updateToken(index, list.last()); - } else if (list.first() == "I") { - bool ok = true; - int index = list.at(1).toInt(&ok); - - if (ok) - insertToken(index, list.last()); - } - } -} - -void ConditionListModel::setInvalid(const QString &errorMessage, int index) -{ - m_valid = false; - m_errorMessage = errorMessage; - - emit errorChanged(); - emit validChanged(); - - if (index != -1) { - m_errorIndex = index; - emit errorIndexChanged(); - } -} - -void ConditionListModel::setValid() -{ - m_valid = true; - m_errorMessage.clear(); - m_errorIndex = -1; - - emit errorChanged(); - emit validChanged(); - emit errorIndexChanged(); -} - -QString ConditionListModel::error() const -{ - return m_errorMessage; -} - -int ConditionListModel::errorIndex() const -{ - return m_errorIndex; -} - -bool ConditionListModel::operatorAllowed(int cursorPosition) -{ - if (m_tokens.empty()) - return false; - - int tokenIdx = cursorPosition - 1; - - if (tokenIdx >= 0 && tokenIdx < m_tokens.length() && m_tokens[tokenIdx].type != Operator) - return true; - - return false; -} - -void ConditionListModel::internalSetup() -{ - setInvalid(tr("No Valid Condition")); - if (!m_condition.statements.size() && !m_condition.tokens.size()) - return; - - if (m_condition.statements.size() != m_condition.tokens.size() + 1) - return; - - if (m_condition.statements.size() == 1 && m_condition.tokens.isEmpty()) { - auto token = tokenFromComparativeStatement(m_condition.statements.first()); - if (token.value == defaultCondition) - return; - } - - auto s_it = m_condition.statements.begin(); - auto o_it = m_condition.tokens.begin(); - - while (o_it != m_condition.tokens.end()) { - m_tokens.append(tokenFromComparativeStatement(*s_it)); - m_tokens.append(tokenFromConditionToken(*o_it)); - - s_it++; - o_it++; - } - m_tokens.append(tokenFromComparativeStatement(*s_it)); - - setValid(); -} - -ConditionListModel::ConditionToken ConditionListModel::valueToToken(const QString &value) -{ - const QStringList operators = {"&&", "||", "===", "!==", ">", ">=", "<", "<="}; - - if (operators.contains(value)) { - ConditionToken token; - token.type = Operator; - token.value = value; - return token; - } - - bool ok = false; - value.toDouble(&ok); - - if (value == "true" || value == "false" || ok - || (value.startsWith("\"") && value.endsWith("\""))) { - ConditionToken token; - token.type = Literal; - token.value = value; - return token; - } - - static QRegularExpression regexp("^[a-z_]\\w*|^[A-Z]\\w*\\.{1}([a-z_]\\w*\\.?)+"); - QRegularExpressionMatch match = regexp.match(value); - - if (match.hasMatch()) { //variable - ConditionToken token; - token.type = Variable; - token.value = value; - return token; - } - - ConditionToken token; - token.type = Invalid; - token.value = value; - - return token; -} - -void ConditionListModel::resetModel() -{ - beginResetModel(); - endResetModel(); -} - -int ConditionListModel::checkOrder() const -{ - auto it = m_tokens.begin(); - - bool wasOperator = true; - - int ret = 0; - while (it != m_tokens.end()) { - if (wasOperator && it->type == Operator) - return ret; - if (!wasOperator && it->type == Literal) - return ret; - if (!wasOperator && it->type == Variable) - return ret; - wasOperator = it->type == Operator; - it++; - ret++; - } - - if (wasOperator) - return ret; - - return -1; -} - -void ConditionListModel::validateAndRebuildTokens() -{ - /// NEW - auto it = m_tokens.begin(); - - while (it != m_tokens.end()) { - if (it->type == Intermediate) - *it = valueToToken(it->value); - - it++; - } - // NEW - - QString invalidValue; - const bool invalidToken = Utils::contains(m_tokens, - [&invalidValue](const ConditionToken &token) { - if (token.type == Invalid) - invalidValue = token.value; - return token.type == Invalid; - }); - - if (invalidToken) { - setInvalid(tr("Invalid token %1").arg(invalidValue)); - return; - } - - if (int firstError = checkOrder() != -1) { - setInvalid(tr("Invalid order at %1").arg(firstError), firstError); - return; - } - - setValid(); - - rebuildTokens(); -} - -void ConditionListModel::rebuildTokens() -{ - QTC_ASSERT(m_valid, return ); - - m_condition.statements.clear(); - m_condition.tokens.clear(); - - auto it = m_tokens.begin(); - - while (it != m_tokens.end()) { - QTC_ASSERT(it->type != Invalid, return ); - if (it->type == Operator) - m_condition.tokens.append(toOperatorStatement(*it)); - else if (it->type == Literal || it->type == Variable) - m_condition.statements.append(toStatement(*it)); - - it++; - } - - emit conditionChanged(); -} - -ConnectionEditorStatements::ConditionToken ConditionListModel::toOperatorStatement( - const ConditionToken &token) -{ - if (token.value == "&&") - return ConnectionEditorStatements::ConditionToken::And; - - if (token.value == "||") - return ConnectionEditorStatements::ConditionToken::Or; - - if (token.value == "===") - return ConnectionEditorStatements::ConditionToken::Equals; - - if (token.value == "!==") - return ConnectionEditorStatements::ConditionToken::Not; - - if (token.value == ">") - return ConnectionEditorStatements::ConditionToken::LargerThan; - - if (token.value == ">=") - return ConnectionEditorStatements::ConditionToken::LargerEqualsThan; - - if (token.value == "<") - return ConnectionEditorStatements::ConditionToken::SmallerThan; - - if (token.value == "<=") - return ConnectionEditorStatements::ConditionToken::SmallerEqualsThan; - - return ConnectionEditorStatements::ConditionToken::Unknown; -} - -ConnectionEditorStatements::ComparativeStatement ConditionListModel::toStatement( - const ConditionToken &token) -{ - if (token.type == Variable) { - QStringList list = token.value.split("."); - ConnectionEditorStatements::Variable variable; - - variable.nodeId = list.first(); - if (list.count() > 1) - variable.propertyName = list.last(); - return variable; - } else if (token.type == Literal) { - return parseTextArgumentComparativeStatement(token.value); - } - - return {}; + return model->signalHandlerPropertyForRow(currentRow()); } void QmlDesigner::ConnectionModel::modelAboutToBeDetached() diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h index 017cf676843..8767dac4134 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h @@ -3,15 +3,11 @@ #pragma once -#include -#include +#include #include -#include #include -#include - namespace QmlDesigner { class AbstractProperty; @@ -23,247 +19,35 @@ class VariantProperty; class ConnectionView; class ConnectionModel; -class ConditionListModel : public QAbstractListModel +class ConnectionModelBackendDelegate : public ScriptEditorBackend { Q_OBJECT - - Q_PROPERTY(bool valid READ valid NOTIFY validChanged) - Q_PROPERTY(bool empty READ empty NOTIFY emptyChanged) - Q_PROPERTY(QString error READ error NOTIFY errorChanged) - Q_PROPERTY(int errorIndex READ errorIndex NOTIFY errorIndexChanged) - -public: - enum ConditionType { Intermediate, Invalid, Operator, Literal, Variable, Shadow }; - Q_ENUM(ConditionType) - - struct ConditionToken - { - ConditionType type; - QString value; - }; - - ConditionListModel(ConnectionModel *model); - - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - - QHash roleNames() const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - - void setup(); - void setCondition(ConnectionEditorStatements::MatchedCondition &condition); - ConnectionEditorStatements::MatchedCondition &condition(); - - static ConditionToken tokenFromConditionToken(const ConnectionEditorStatements::ConditionToken &token); - - static ConditionToken tokenFromComparativeStatement( - const ConnectionEditorStatements::ComparativeStatement &token); - - Q_INVOKABLE void insertToken(int index, const QString &value); - Q_INVOKABLE void updateToken(int index, const QString &value); - Q_INVOKABLE void appendToken(const QString &value); - Q_INVOKABLE void removeToken(int index); - - Q_INVOKABLE void insertIntermediateToken(int index, const QString &value); - Q_INVOKABLE void insertShadowToken(int index, const QString &value); - Q_INVOKABLE void setShadowToken(int index, const QString &value); - - bool valid() const; - bool empty() const; - - //for debugging - Q_INVOKABLE void command(const QString &string); - - void setInvalid(const QString &errorMessage, int index = -1); - void setValid(); - - QString error() const; - int errorIndex() const; - - Q_INVOKABLE bool operatorAllowed(int cursorPosition); - -signals: - void validChanged(); - void emptyChanged(); - void conditionChanged(); - void errorChanged(); - void errorIndexChanged(); - -private: - void internalSetup(); - ConditionToken valueToToken(const QString &value); - void resetModel(); - int checkOrder() const; - void validateAndRebuildTokens(); - void rebuildTokens(); - - ConnectionEditorStatements::ConditionToken toOperatorStatement(const ConditionToken &token); - ConnectionEditorStatements::ComparativeStatement toStatement(const ConditionToken &token); - - ConnectionModel *m_connectionModel = nullptr; - ConnectionEditorStatements::MatchedCondition &m_condition; - QList m_tokens; - bool m_valid = false; - QString m_errorMessage; - int m_errorIndex = -1; -}; - -class ConnectionModelStatementDelegate : public QObject -{ - Q_OBJECT - -public: - explicit ConnectionModelStatementDelegate(ConnectionModel *model); - - enum ActionType { CallFunction, Assign, ChangeState, SetProperty, PrintMessage, Custom }; - - Q_ENUM(ActionType) - - Q_PROPERTY(ActionType actionType READ actionType NOTIFY actionTypeChanged) - - Q_PROPERTY(PropertyTreeModelDelegate *function READ function CONSTANT) - Q_PROPERTY(PropertyTreeModelDelegate *lhs READ lhs CONSTANT) - Q_PROPERTY(PropertyTreeModelDelegate *rhsAssignment READ rhsAssignment CONSTANT) - Q_PROPERTY(StudioQmlTextBackend *stringArgument READ stringArgument CONSTANT) - Q_PROPERTY(StudioQmlComboBoxBackend *states READ states CONSTANT) - Q_PROPERTY(StudioQmlComboBoxBackend *stateTargets READ stateTargets CONSTANT) - - void setActionType(ActionType type); - void setup(); - void setStatement(ConnectionEditorStatements::MatchedStatement &statement); - ConnectionEditorStatements::MatchedStatement &statement(); - -signals: - void actionTypeChanged(); - void statementChanged(); - -private: - ActionType actionType() const; - PropertyTreeModelDelegate *signal(); - PropertyTreeModelDelegate *function(); - PropertyTreeModelDelegate *lhs(); - PropertyTreeModelDelegate *rhsAssignment(); - StudioQmlTextBackend *stringArgument(); - StudioQmlComboBoxBackend *stateTargets(); - StudioQmlComboBoxBackend *states(); - - void handleFunctionChanged(); - void handleLhsChanged(); - void handleRhsAssignmentChanged(); - void handleStringArgumentChanged(); - void handleStateChanged(); - void handleStateTargetsChanged(); - - void setupAssignment(); - void setupSetProperty(); - void setupCallFunction(); - void setupChangeState(); - void setupStates(); - void setupPrintMessage(); - void setupPropertyType(); - QString baseStateName() const; - - ActionType m_actionType; - PropertyTreeModelDelegate m_functionDelegate; - PropertyTreeModelDelegate m_lhsDelegate; - PropertyTreeModelDelegate m_rhsAssignmentDelegate; - ConnectionEditorStatements::MatchedStatement &m_statement; - ConnectionModel *m_model = nullptr; - StudioQmlTextBackend m_stringArgument; - StudioQmlComboBoxBackend m_stateTargets; - StudioQmlComboBoxBackend m_states; -}; - -class ConnectionModelBackendDelegate : public QObject -{ - Q_OBJECT - Q_PROPERTY(int currentRow READ currentRow WRITE setCurrentRow NOTIFY currentRowChanged) - - Q_PROPERTY(ActionType actionType READ actionType NOTIFY actionTypeChanged) Q_PROPERTY(PropertyTreeModelDelegate *signal READ signal CONSTANT) - Q_PROPERTY(ConnectionModelStatementDelegate *okStatement READ okStatement CONSTANT) - Q_PROPERTY(ConnectionModelStatementDelegate *koStatement READ koStatement CONSTANT) - Q_PROPERTY(ConditionListModel *conditionListModel READ conditionListModel CONSTANT) - Q_PROPERTY(bool hasCondition READ hasCondition NOTIFY hasConditionChanged) - Q_PROPERTY(bool hasElse READ hasElse NOTIFY hasElseChanged) - Q_PROPERTY(QString source READ source NOTIFY sourceChanged) - Q_PROPERTY(QString indentedSource READ indentedSource NOTIFY sourceChanged) - - Q_PROPERTY(PropertyTreeModel *propertyTreeModel READ propertyTreeModel CONSTANT) - Q_PROPERTY(PropertyListProxyModel *propertyListProxyModel READ propertyListProxyModel CONSTANT) - public: explicit ConnectionModelBackendDelegate(ConnectionModel *model); - using ActionType = ConnectionModelStatementDelegate::ActionType; - - Q_INVOKABLE void changeActionType(QmlDesigner::ConnectionModelStatementDelegate::ActionType actionType); - - Q_INVOKABLE void addCondition(); - Q_INVOKABLE void removeCondition(); - - Q_INVOKABLE void addElse(); - Q_INVOKABLE void removeElse(); - - Q_INVOKABLE void setNewSource(const QString &newSource); + void update() override; void setCurrentRow(int i); - void update(); - Q_INVOKABLE void jumpToCode(); + PropertyTreeModelDelegate *signal(); signals: void currentRowChanged(); - void actionTypeChanged(); - void hasConditionChanged(); - void hasElseChanged(); - void sourceChanged(); - void popupShouldClose(); - void popupShouldOpen(); + +private slots: + void handleTargetChanged(); private: + AbstractProperty getSourceProperty() const override; + void setPropertySource(const QString &source) override; + + SignalHandlerProperty getSignalHandlerProperty() const; int currentRow() const; - void handleException(); - bool hasCondition() const; - bool hasElse() const; - void setHasCondition(bool b); - void setHasElse(bool b); - ActionType actionType() const; - PropertyTreeModelDelegate *signal(); - ConnectionModelStatementDelegate *okStatement(); - ConnectionModelStatementDelegate *koStatement(); - ConditionListModel *conditionListModel(); - QString indentedSource() const; - QString source() const; - void setSource(const QString &source); - PropertyTreeModel *propertyTreeModel(); - PropertyListProxyModel *propertyListProxyModel(); - - void setupCondition(); - void setupHandlerAndStatements(); - - void handleTargetChanged(); - void handleOkStatementChanged(); - void handleKOStatementChanged(); - void handleConditionChanged(); - - void commitNewSource(const QString &source); - - ActionType m_actionType; - QString m_exceptionError; int m_currentRow = -1; - ConnectionEditorStatements::Handler m_handler; PropertyTreeModelDelegate m_signalDelegate; - ConnectionModelStatementDelegate m_okStatementDelegate; - ConnectionModelStatementDelegate m_koStatementDelegate; - ConditionListModel m_conditionListModel; - bool m_hasCondition = false; - bool m_hasElse = false; - QString m_source; - PropertyTreeModel m_propertyTreeModel; - PropertyListProxyModel m_propertyListProxyModel; - bool m_blockReflection = false; QPointer m_model = nullptr; }; diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp index a60edca67b9..aa81e7c06de 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp @@ -6,7 +6,6 @@ #include "bindingmodel.h" #include "connectionmodel.h" #include "dynamicpropertiesmodel.h" -#include "propertytreemodel.h" #include "theme.h" #include @@ -32,13 +31,28 @@ namespace QmlDesigner { -static QString propertyEditorResourcesPath() +static QString resourcesPath(const QString &dir) { #ifdef SHARE_QML_PATH if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) - return QLatin1String(SHARE_QML_PATH) + "/propertyEditorQmlSources"; + return QLatin1String(SHARE_QML_PATH) + "/" + dir; #endif - return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toUrlishString(); + return Core::ICore::resourcePath("qmldesigner/" + dir).toUrlishString(); +} + +static QString propertyEditorResourcesPath() +{ + return resourcesPath("propertyEditorQmlSources"); +} + +static QString scriptsEditorResourcesPath() +{ + return resourcesPath("scriptseditor"); +} + +static QString connectionsEditorResourcesPath() +{ + return resourcesPath("connectionseditor"); } class ConnectionViewQuickWidget : public StudioQuickWidget @@ -53,9 +67,10 @@ public: : m_connectionEditorView(connectionEditorView) { - engine()->addImportPath(qmlSourcesPath()); + engine()->addImportPath(connectionsEditorResourcesPath()); engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); - engine()->addImportPath(qmlSourcesPath() + "/imports"); + engine()->addImportPath(scriptsEditorResourcesPath() + "/imports"); + engine()->addImportPath(connectionsEditorResourcesPath() + "/imports"); m_qmlSourceUpdateShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_F12), this); connect(m_qmlSourceUpdateShortcut, @@ -83,19 +98,6 @@ public: 0, "DynamicPropertiesModelBackendDelegate"); - qmlRegisterType("ConnectionsEditorEditorBackend", - 1, - 0, - "ConnectionModelStatementDelegate"); - - qmlRegisterType("ConnectionsEditorEditorBackend", 1, 0, "ConditionListModel"); - - qmlRegisterType("ConnectionsEditorEditorBackend", 1, 0, "PropertyTreeModel"); - qmlRegisterType("ConnectionsEditorEditorBackend", - 1, - 0, - "PropertyListProxyModel"); - Theme::setupTheme(engine()); setMinimumSize(QSize(195, 195)); @@ -105,19 +107,11 @@ public: } ~ConnectionViewQuickWidget() = default; - static QString qmlSourcesPath() - { -#ifdef SHARE_QML_PATH - if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) - return QLatin1String(SHARE_QML_PATH) + "/connectionseditor"; -#endif - return Core::ICore::resourcePath("qmldesigner/connectionseditor").toUrlishString(); - } - private: void reloadQmlSource() { - QString connectionEditorQmlFilePath = qmlSourcesPath() + QStringLiteral("/Main.qml"); + QString connectionEditorQmlFilePath = connectionsEditorResourcesPath() + + QStringLiteral("/Main.qml"); QTC_ASSERT(QFileInfo::exists(connectionEditorQmlFilePath), return ); setSource(QUrl::fromLocalFile(connectionEditorQmlFilePath)); @@ -129,7 +123,7 @@ private: Core::AsynchronousMessageBox::warning( Tr::tr("Cannot Create QtQuick View"), Tr::tr("ConnectionsEditorWidget: %1 cannot be created.%2") - .arg(qmlSourcesPath(), errorString)); + .arg(connectionsEditorResourcesPath(), errorString)); return; } } @@ -145,7 +139,6 @@ struct ConnectionView::ConnectionViewData : connectionModel{view} , bindingModel{view} , dynamicPropertiesModel{false, view} - , propertyTreeModel{view} , connectionViewQuickWidget{Utils::makeUniqueObjectPtr( view, &connectionModel, &bindingModel, &dynamicPropertiesModel)} {} @@ -153,7 +146,6 @@ struct ConnectionView::ConnectionViewData ConnectionModel connectionModel; BindingModel bindingModel; DynamicPropertiesModel dynamicPropertiesModel; - PropertyTreeModel propertyTreeModel; int currentIndex = 0; // Ensure that QML is deleted first to avoid calling back to C++. diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionview.h b/src/plugins/qmldesigner/components/connectioneditor/connectionview.h index debf78ae2e7..e06f1702892 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionview.h +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionview.h @@ -22,7 +22,6 @@ class BindingModel; class ConnectionModel; class DynamicPropertiesModel; class ConnectionViewQuickWidget; -class PropertyTreeModel; class PropertyListProxyModel; class ConnectionView : public AbstractView diff --git a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesitem.cpp b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesitem.cpp index a45b0e115d8..1694231630c 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesitem.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesitem.cpp @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "dynamicpropertiesitem.h" -#include "connectioneditorutils.h" +#include #include #include diff --git a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp index 687752b8c98..c4307a07182 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp @@ -2,22 +2,23 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "dynamicpropertiesmodel.h" +#include "connectioneditorlogging.h" #include "dynamicpropertiesitem.h" -#include "connectioneditorutils.h" #include #include #include -#include -#include -#include -#include -#include #include #include #include #include #include +#include +#include +#include +#include +#include +#include #include diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp index 83415587fdc..7ec0ff221bd 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp @@ -35,8 +35,6 @@ #include -#include - #ifndef QMLDESIGNER_TEST #include #include @@ -77,23 +75,7 @@ WidgetInfo ContentLibraryView::widgetInfo() m_bundleHelper = std::make_unique(this, m_widget); connect(m_widget, &ContentLibraryWidget::importQtQuick3D, this, [&] { - DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument(); - if (document && !document->inFileComponentModelActive() && model()) { -#ifdef QDS_USE_PROJECTSTORAGE - Import import = Import::createLibraryImport("QtQuick3D"); - model()->changeImports({import}, {}); - return; -#else - if (ModelUtils::addImportWithCheck( - "QtQuick3D", - [](const Import &import) { return !import.hasVersion() || import.majorVersion() >= 6; }, - model())) { - return; - } -#endif - } - Core::AsynchronousMessageBox::warning(tr("Failed to Add Import"), - tr("Could not add QtQuick3D import to project.")); + Utils3D::addQuick3DImportAndView3D(this); }); connect(m_widget, &ContentLibraryWidget::bundleMaterialDragStarted, this, [&] (QmlDesigner::ContentLibraryMaterial *mat) { diff --git a/src/plugins/qmldesigner/components/designsystemview/collectionmodel.cpp b/src/plugins/qmldesigner/components/designsystemview/collectionmodel.cpp index a9c4922caf4..08caa829935 100644 --- a/src/plugins/qmldesigner/components/designsystemview/collectionmodel.cpp +++ b/src/plugins/qmldesigner/components/designsystemview/collectionmodel.cpp @@ -116,6 +116,11 @@ QVariant CollectionModel::headerData(int section, Qt::Orientation orientation, i Qt::ItemFlags CollectionModel::flags(const QModelIndex &index) const { + // If group type is FLAGS and not binding block editable + if (data(index, Roles::GroupRole).value() == GroupType::Flags + && !data(index, Roles::BindingRole).toBool()) + return QAbstractItemModel::flags(index); + return Qt::ItemIsEditable | QAbstractItemModel::flags(index); } @@ -214,9 +219,9 @@ bool CollectionModel::setData(const QModelIndex &index, const QVariant &value, i p.name = propName; const ThemeId id = m_themeIdList[index.column()]; if (m_collection->updateProperty(id, groupType, p)) { - beginResetModel(); updateCache(); - endResetModel(); + + emit dataChanged(index, index); } } default: diff --git a/src/plugins/qmldesigner/components/designsystemview/collectionmodel.h b/src/plugins/qmldesigner/components/designsystemview/collectionmodel.h index c0b43500012..118f55ce152 100644 --- a/src/plugins/qmldesigner/components/designsystemview/collectionmodel.h +++ b/src/plugins/qmldesigner/components/designsystemview/collectionmodel.h @@ -25,6 +25,7 @@ public: ResolvedValueRole, PropertyValueRole }; + Q_ENUM(Roles) Q_PROPERTY(QStringList themeNames READ themeNameList NOTIFY themeNameChanged FINAL) diff --git a/src/plugins/qmldesigner/components/designsystemview/designsysteminterface.cpp b/src/plugins/qmldesigner/components/designsystemview/designsysteminterface.cpp index e57131e9b98..3066e09e46a 100644 --- a/src/plugins/qmldesigner/components/designsystemview/designsysteminterface.cpp +++ b/src/plugins/qmldesigner/components/designsystemview/designsysteminterface.cpp @@ -16,6 +16,7 @@ DesignSystemInterface::DesignSystemInterface(DSStore *store) { qmlRegisterUncreatableMetaObject( QmlDesigner::staticMetaObject, "QmlDesigner.DesignSystem", 1, 0, "GroupType", ""); + qmlRegisterUncreatableType("QmlDesigner.DesignSystem", 1, 0, "CollectionModel", ""); } DesignSystemInterface::~DesignSystemInterface() {} diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp index e23c926fec7..8887b8f176d 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -33,7 +32,6 @@ #include #include -#include #include @@ -481,7 +479,7 @@ void Edit3DView::nodeAtPosReady(const ModelNode &modelNode, const QVector3D &pos createdNode = QmlVisualNode::createQml3DNode( this, m_droppedEntry, edit3DWidget()->canvas()->activeScene(), pos3d).modelNode(); if (createdNode.metaInfo().isQtQuick3DModel()) - MaterialUtils::assignMaterialTo3dModel(this, createdNode); + Utils3D::assignMaterialTo3dModel(this, createdNode); }); if (createdNode.isValid()) setSelectedModelNode(createdNode); @@ -489,7 +487,7 @@ void Edit3DView::nodeAtPosReady(const ModelNode &modelNode, const QVector3D &pos bool isModel = modelNode.metaInfo().isQtQuick3DModel(); if (m_droppedModelNode.isValid() && isModel) { executeInTransaction(__FUNCTION__, [&] { - MaterialUtils::assignMaterialTo3dModel(this, modelNode, m_droppedModelNode); + Utils3D::assignMaterialTo3dModel(this, modelNode, m_droppedModelNode); }); } } else if (m_nodeAtPosReqType == NodeAtPosReqType::BundleMaterialDrop) { @@ -1400,27 +1398,6 @@ Edit3DBakeLightsAction *Edit3DView::bakeLightsAction() const return m_bakeLightsAction.get(); } -void Edit3DView::addQuick3DImport() -{ - DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument(); - if (document && !document->inFileComponentModelActive() && model()) { -#ifdef QDS_USE_PROJECTSTORAGE - Import import = Import::createLibraryImport("QtQuick3D"); - model()->changeImports({import}, {}); - return; -#else - if (ModelUtils::addImportWithCheck( - "QtQuick3D", - [](const Import &import) { return !import.hasVersion() || import.majorVersion() >= 6; }, - model())) { - return; - } -#endif - } - Core::AsynchronousMessageBox::warning(tr("Failed to Add Import"), - tr("Could not add QtQuick3D import to project.")); -} - // This method is called upon right-clicking the view to prepare for context-menu creation. The actual // context menu is created when nodeAtPosReady() is received from puppet void Edit3DView::startContextMenu(const QPoint &pos) diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.h b/src/plugins/qmldesigner/components/edit3d/edit3dview.h index 5e45ee80889..e6dd069b0cf 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.h @@ -93,7 +93,6 @@ public: Edit3DAction *edit3DAction(View3DActionType type) const; Edit3DBakeLightsAction *bakeLightsAction() const; - void addQuick3DImport(); void startContextMenu(const QPoint &pos); void showContextMenu(); void dropMaterial(const ModelNode &matNode, const QPointF &pos); diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index 9029001c5de..124a6ab252d 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -185,6 +184,7 @@ Edit3DWidget::Edit3DWidget(Edit3DView *view) // Onboarding label contains instructions for new users how to get 3D content into the project m_onboardingLabel = new QLabel(this); m_onboardingLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); + m_onboardingLabel->setWordWrap(true); connect(m_onboardingLabel, &QLabel::linkActivated, this, &Edit3DWidget::linkActivated); fillLayout->addWidget(m_onboardingLabel.data()); @@ -378,14 +378,14 @@ void Edit3DWidget::createContextMenu() m_importBundleAction = m_contextMenu->addAction( contextIcon(DesignerIcons::CreateIcon), // TODO: placeholder icon - tr("Import Component"), [&] { + tr("Import Bundle"), [&] { m_bundleHelper->importBundleToProject(); }); m_exportBundleAction = m_contextMenu->addAction( contextIcon(DesignerIcons::CreateIcon), // TODO: placeholder icon - tr("Export Component"), [&] { - m_bundleHelper->exportBundle(m_contextMenuTarget); + tr("Export Bundle"), [&] { + m_bundleHelper->exportBundle(m_view->selectedModelNodes()); }); m_contextMenu->addSeparator(); @@ -419,19 +419,11 @@ void Edit3DWidget::showOnboardingLabel() if (text.isEmpty()) { if (m_view->externalDependencies().isQt6Project()) { QString labelText = - tr("Your file does not import Qt Quick 3D.

" - "To create a 3D view, add the" - " QtQuick3D" - " module in the" - " Components" - " view or click" + tr("To use the 3D view, add the QtQuick3D module and the View3D" + " component in the Components view or click" " here" ".

" - "To import 3D assets, select" - " +" - " in the" - " Assets" - " view."); + "To import 3D assets, select + in the Assets view."); text = labelText.arg(Utils::creatorColor(Utils::Theme::TextColorLink).name()); } else { text = tr("3D view is not supported in Qt5 projects."); @@ -566,7 +558,7 @@ void Edit3DWidget::onCreateAction(QAction *action) // if added node is a Model, assign it a material if (modelNode.metaInfo().isQtQuick3DModel()) - MaterialUtils::assignMaterialTo3dModel(m_view, modelNode); + Utils3D::assignMaterialTo3dModel(m_view, modelNode); }); } @@ -732,8 +724,7 @@ void Edit3DWidget::showContextMenu(const QPoint &pos, const ModelNode &modelNode void Edit3DWidget::linkActivated([[maybe_unused]] const QString &link) { - if (m_view) - m_view->addQuick3DImport(); + Utils3D::addQuick3DImportAndView3D(m_view); } Edit3DCanvas *Edit3DWidget::canvas() const diff --git a/src/plugins/qmldesigner/components/formeditor/dragtool.cpp b/src/plugins/qmldesigner/components/formeditor/dragtool.cpp index ec9897aeeac..9ea9c9a8feb 100644 --- a/src/plugins/qmldesigner/components/formeditor/dragtool.cpp +++ b/src/plugins/qmldesigner/components/formeditor/dragtool.cpp @@ -7,13 +7,13 @@ #include "assetslibrarywidget.h" #include "formeditorscene.h" #include "formeditorview.h" -#include "materialutils.h" #include "qmldesignerconstants.h" #include #include #include #include #include +#include #include @@ -455,7 +455,7 @@ void DragTool::handleView3dDrop() const QList models = dragNode.modelNode().subModelNodesOfType( model->qtQuick3DModelMetaInfo()); QTC_ASSERT(models.size() == 1, return); - MaterialUtils::assignMaterialTo3dModel(view(), models.at(0)); + Utils3D::assignMaterialTo3dModel(view(), models.at(0)); } } } diff --git a/src/plugins/qmldesigner/components/import3d/import3ddialog.cpp b/src/plugins/qmldesigner/components/import3d/import3ddialog.cpp index 68e704fcb7d..b2b7adda751 100644 --- a/src/plugins/qmldesigner/components/import3d/import3ddialog.cpp +++ b/src/plugins/qmldesigner/components/import3d/import3ddialog.cpp @@ -92,7 +92,7 @@ constexpr QStringView expandValuesKey{u"expandValueComponents"}; } // namespace Import3dDialog::Import3dDialog( - const QStringList &importFiles, const QString &defaulTargetDirectory, + const QStringList &importFiles, const QVariantMap &supportedExts, const QVariantMap &supportedOpts, const QJsonObject &defaultOpts, const QSet &preselectedFilesForOverwrite, AbstractView *view, QWidget *parent) @@ -152,10 +152,6 @@ Import3dDialog::Import3dDialog( importPaths = model->importPaths(); } - QString targetDir = QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath().toUrlishString(); - if (targetDir.isEmpty()) - targetDir = defaulTargetDirectory; - m_quick3DImportPath = QmlDesignerPlugin::instance()->documentManager() .generatedComponentUtils().import3dBasePath().toUrlishString(); @@ -282,111 +278,114 @@ Import3dDialog::~Import3dDialog() } void Import3dDialog::updateImport(AbstractView *view, + const Utils::FilePath &import3dQml, const ModelNode &updateNode, const QVariantMap &supportedExts, const QVariantMap &supportedOpts) { QString errorMsg; - const ModelNode &node = updateNode; - if (node.hasMetaInfo()) { - QString compFileName = ModelUtils::componentFilePath(node); // absolute path - bool preselectNodeSource = false; + QString compFileName; // absolute path to imported 3D qml + bool preselectNodeSource = false; + if (import3dQml.exists()) { + compFileName = import3dQml.toFSPathString(); + } else if (updateNode.isValid()) { + compFileName = ModelUtils::componentFilePath(updateNode); if (compFileName.isEmpty()) { // Node is not a file component, so we have to check if the current doc itself is - compFileName = node.model()->fileUrl().toLocalFile(); + compFileName = updateNode.model()->fileUrl().toLocalFile(); preselectNodeSource = true; } - QFileInfo compFileInfo{compFileName}; + } - // Find to top asset folder - const QString oldAssetFolder = Constants::oldQuick3dAssetsFolder; - QString assetFolder = Constants::quick3DComponentsFolder; - const QStringList parts = compFileName.split('/'); - int i = parts.size() - 1; - int previousSize = 0; - for (; i >= 0; --i) { - if (parts[i] == oldAssetFolder) - assetFolder = oldAssetFolder; - if (parts[i] == assetFolder) - break; - previousSize = parts[i].size(); - } - if (i >= 0) { - const QString assetPath = compFileName.left(compFileName.lastIndexOf(assetFolder) - + assetFolder.size() + previousSize + 1); - const QDir assetDir(assetPath); + QFileInfo compFileInfo{compFileName}; - // Find import options and the original source scene - const QString jsonFileName = assetDir.absoluteFilePath( - Constants::QUICK_3D_ASSET_IMPORT_DATA_NAME); - QFile jsonFile{jsonFileName}; - if (jsonFile.open(QIODevice::ReadOnly)) { - QJsonParseError jsonError; - const QByteArray fileData = jsonFile.readAll(); - auto jsonDocument = QJsonDocument::fromJson(fileData, &jsonError); - jsonFile.close(); - if (jsonError.error == QJsonParseError::NoError) { - QJsonObject jsonObj = jsonDocument.object(); - const QJsonObject options = jsonObj.value( - Constants::QUICK_3D_ASSET_IMPORT_DATA_OPTIONS_KEY).toObject(); - QString sourcePath = jsonObj.value( - Constants::QUICK_3D_ASSET_IMPORT_DATA_SOURCE_KEY).toString(); - if (options.isEmpty() || sourcePath.isEmpty()) { - errorMsg = Tr::tr("Asset import data file \"%1\" is invalid.").arg(jsonFileName); - } else { - QFileInfo sourceInfo{sourcePath}; - if (!sourceInfo.exists()) { - // Unable to find original scene source, launch file dialog to locate it - QString initialPath; - ProjectExplorer::Project *currentProject - = ProjectExplorer::ProjectManager::projectForFile( - Utils::FilePath::fromString(compFileName)); - if (currentProject) - initialPath = currentProject->projectDirectory().toUrlishString(); - else - initialPath = compFileInfo.absolutePath(); - QStringList selectedFiles = QFileDialog::getOpenFileNames( - Core::ICore::dialogParent(), - tr("Locate 3D Asset \"%1\"").arg(sourceInfo.fileName()), - initialPath, sourceInfo.fileName()); - if (!selectedFiles.isEmpty() - && QFileInfo{selectedFiles[0]}.fileName() == sourceInfo.fileName()) { - sourcePath = selectedFiles[0]; - sourceInfo.setFile(sourcePath); - } - } - if (sourceInfo.exists()) { - // In case of a selected node inside an imported component, preselect - // any file pointed to by a "source" property of the node. - QSet preselectedFiles; - if (preselectNodeSource && updateNode.hasProperty("source")) { - QString source = updateNode.variantProperty("source").value().toString(); - if (QFileInfo{source}.isRelative()) - source = QDir{compFileInfo.absolutePath()}.absoluteFilePath(source); - preselectedFiles.insert(source); - } - auto importDlg = new Import3dDialog( - {sourceInfo.absoluteFilePath()}, - node.model()->fileUrl().toLocalFile(), - supportedExts, supportedOpts, options, - preselectedFiles, view, - Core::ICore::dialogParent()); - importDlg->show(); + // Find to top asset folder + const QString oldAssetFolder = Constants::oldQuick3dAssetsFolder; + QString assetFolder = Constants::quick3DComponentsFolder; + const QStringList parts = compFileName.split('/'); + int i = parts.size() - 1; + int previousSize = 0; + for (; i >= 0; --i) { + if (parts[i] == oldAssetFolder) + assetFolder = oldAssetFolder; + if (parts[i] == assetFolder) + break; + previousSize = parts[i].size(); + } + if (i >= 0) { + const QString assetPath = compFileName.left(compFileName.lastIndexOf(assetFolder) + + assetFolder.size() + previousSize + 1); + const QDir assetDir(assetPath); - } else { - errorMsg = Tr::tr("Unable to locate source scene \"%1\".") - .arg(sourceInfo.fileName()); + // Find import options and the original source scene + const QString jsonFileName = assetDir.absoluteFilePath( + Constants::QUICK_3D_ASSET_IMPORT_DATA_NAME); + QFile jsonFile{jsonFileName}; + if (jsonFile.open(QIODevice::ReadOnly)) { + QJsonParseError jsonError; + const QByteArray fileData = jsonFile.readAll(); + auto jsonDocument = QJsonDocument::fromJson(fileData, &jsonError); + jsonFile.close(); + if (jsonError.error == QJsonParseError::NoError) { + QJsonObject jsonObj = jsonDocument.object(); + const QJsonObject options = jsonObj.value( + Constants::QUICK_3D_ASSET_IMPORT_DATA_OPTIONS_KEY).toObject(); + QString sourcePath = jsonObj.value( + Constants::QUICK_3D_ASSET_IMPORT_DATA_SOURCE_KEY).toString(); + if (options.isEmpty() || sourcePath.isEmpty()) { + errorMsg = Tr::tr("Asset import data file \"%1\" is invalid.").arg(jsonFileName); + } else { + QFileInfo sourceInfo{sourcePath}; + if (!sourceInfo.exists()) { + // Unable to find original scene source, launch file dialog to locate it + QString initialPath; + ProjectExplorer::Project *currentProject + = ProjectExplorer::ProjectManager::projectForFile( + Utils::FilePath::fromString(compFileName)); + if (currentProject) + initialPath = currentProject->projectDirectory().toUrlishString(); + else + initialPath = compFileInfo.absolutePath(); + QStringList selectedFiles = QFileDialog::getOpenFileNames( + Core::ICore::dialogParent(), + tr("Locate 3D Asset \"%1\"").arg(sourceInfo.fileName()), + initialPath, sourceInfo.fileName()); + if (!selectedFiles.isEmpty() + && QFileInfo{selectedFiles[0]}.fileName() == sourceInfo.fileName()) { + sourcePath = selectedFiles[0]; + sourceInfo.setFile(sourcePath); } } - } else { - errorMsg = jsonError.errorString(); + if (sourceInfo.exists()) { + // In case of a selected node inside an imported component, preselect + // any file pointed to by a "source" property of the node. + QSet preselectedFiles; + if (preselectNodeSource && updateNode.hasProperty("source")) { + QString source = updateNode.variantProperty("source").value().toString(); + if (QFileInfo{source}.isRelative()) + source = QDir{compFileInfo.absolutePath()}.absoluteFilePath(source); + preselectedFiles.insert(source); + } + auto importDlg = new Import3dDialog( + {sourceInfo.absoluteFilePath()}, + supportedExts, supportedOpts, options, + preselectedFiles, view, + Core::ICore::dialogParent()); + importDlg->show(); + + } else { + errorMsg = Tr::tr("Unable to locate source scene \"%1\".") + .arg(sourceInfo.fileName()); + } } } else { - errorMsg = Tr::tr("Opening asset import data file \"%1\" failed.").arg(jsonFileName); + errorMsg = jsonError.errorString(); } } else { - errorMsg = Tr::tr("Unable to resolve asset import path."); + errorMsg = Tr::tr("Opening asset import data file \"%1\" failed.").arg(jsonFileName); } + } else { + errorMsg = Tr::tr("Unable to resolve asset import path."); } if (!errorMsg.isEmpty()) { diff --git a/src/plugins/qmldesigner/components/import3d/import3ddialog.h b/src/plugins/qmldesigner/components/import3d/import3ddialog.h index df7dd7c27d5..98cdf937cc5 100644 --- a/src/plugins/qmldesigner/components/import3d/import3ddialog.h +++ b/src/plugins/qmldesigner/components/import3d/import3ddialog.h @@ -41,7 +41,6 @@ class Import3dDialog : public QDialog public: explicit Import3dDialog(const QStringList &importFiles, - const QString &defaulTargetDirectory, const QVariantMap &supportedExts, const QVariantMap &supportedOpts, const QJsonObject &defaultOpts, @@ -51,6 +50,7 @@ public: ~Import3dDialog(); static void updateImport(AbstractView *view, + const Utils::FilePath &import3dQml, const ModelNode &updateNode, const QVariantMap &supportedExts, const QVariantMap &supportedOpts); diff --git a/src/plugins/qmldesigner/components/integration/componentview.cpp b/src/plugins/qmldesigner/components/integration/componentview.cpp index 0e3f0243724..806bdb4ee31 100644 --- a/src/plugins/qmldesigner/components/integration/componentview.cpp +++ b/src/plugins/qmldesigner/components/integration/componentview.cpp @@ -316,10 +316,18 @@ void ComponentView::nodeSourceChanged(const ModelNode &node, const QString &/*ne void ComponentView::customNotification(const AbstractView *, const QString &identifier, const QList &nodeList, - const QList &) + const QList &data) { - if (identifier == "UpdateImported3DAsset" && nodeList.size() > 0) { - Import3dDialog::updateImport(this, nodeList[0], + if (identifier == "UpdateImported3DAsset") { + Utils::FilePath import3dQml; + if (!data.isEmpty()) + import3dQml = Utils::FilePath::fromString(data[0].toString()); + + ModelNode node; + if (!nodeList.isEmpty()) + node = nodeList[0]; + + Import3dDialog::updateImport(this, import3dQml, node, m_importableExtensions3DMap, m_importOptions3DMap); } @@ -338,11 +346,11 @@ void ComponentView::updateImport3DSupport(const QVariantMap &supportMap) m_importableExtensions3DMap = extMap; AddResourceOperation import3DModelOperation = [this](const QStringList &fileNames, - const QString &defaultDir, + const QString &, bool showDialog) -> AddFilesResult { Q_UNUSED(showDialog) - auto importDlg = new Import3dDialog(fileNames, defaultDir, + auto importDlg = new Import3dDialog(fileNames, m_importableExtensions3DMap, m_importOptions3DMap, {}, {}, this, Core::ICore::dialogParent()); diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp index 64f2c384caa..3baacb2571f 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -325,11 +326,10 @@ void ItemLibraryModel::update(Model *model) // create import sections const Imports usedImports = model->usedImports(); QHash importHash; + const QString generatedPrefix = compUtils.generatedComponentTypePrefix(); for (const Import &import : model->imports()) { - if (excludedImports.contains(import.url()) - || import.url().startsWith(compUtils.generatedComponentTypePrefix())) { + if (excludedImports.contains(import.url()) || import.url().startsWith(generatedPrefix)) continue; - } bool addNew = true; QString importUrl = import.url(); @@ -362,6 +362,9 @@ void ItemLibraryModel::update(Model *model) DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument(); const bool blockNewImports = document->inFileComponentModelActive(); #endif + + TypeName currentFileType = QFileInfo(model->fileUrl().toLocalFile()).baseName().toUtf8(); + const QList itemLibEntries = model->itemLibraryEntries(); for (const ItemLibraryEntry &entry : itemLibEntries) { NodeMetaInfo metaInfo; @@ -398,6 +401,7 @@ void ItemLibraryModel::update(Model *model) // we need to exclude all items from unsupported imports but only if they are not user-defined modules if (!(entry.category() == ItemLibraryImport::userComponentsTitle()) + && !entry.requiredImport().isEmpty() && !mcuManager.allowedImports().contains(entry.requiredImport())) { blocked = true; } @@ -421,6 +425,8 @@ void ItemLibraryModel::update(Model *model) if (isUsable) { if (catName == ItemLibraryImport::userComponentsTitle()) { if (entry.requiredImport().isEmpty()) { // user components + if (currentFileType == entry.typeName()) + continue; importSection = importHash[ItemLibraryImport::userComponentsTitle()]; if (!importSection) { importSection = new ItemLibraryImport( @@ -444,6 +450,8 @@ void ItemLibraryModel::update(Model *model) : entry.requiredImport()]; } } else { + if (entry.requiredImport().startsWith(generatedPrefix)) + continue; catName = ItemLibraryImport::unimportedComponentsTitle(); importSection = importHash[catName]; if (!importSection) { diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp index 9ac95dba432..a8b29b52b41 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -382,10 +383,16 @@ void MaterialBrowserWidget::importMaterial() { m_bundleHelper->importBundleToProject(); } + void MaterialBrowserWidget::exportMaterial() { ModelNode mat = m_materialBrowserModel->selectedMaterial(); - m_bundleHelper->exportBundle(mat, m_previewImageProvider->getPixmap(mat)); + m_bundleHelper->exportBundle({mat}, m_previewImageProvider->getPixmap(mat)); +} + +void MaterialBrowserWidget::addQtQuick3D() +{ + Utils3D::addQuick3DImportAndView3D(m_materialBrowserView.data()); } QString MaterialBrowserWidget::qmlSourcesPath() diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h index 7ebec0b622c..647847cb276 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h @@ -66,6 +66,7 @@ public: Q_INVOKABLE void addMaterialToContentLibrary(); Q_INVOKABLE void importMaterial(); Q_INVOKABLE void exportMaterial(); + Q_INVOKABLE void addQtQuick3D(); StudioQuickWidget *quickWidget() const; diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialutils.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialutils.cpp deleted file mode 100644 index 1a1071de347..00000000000 --- a/src/plugins/qmldesigner/components/materialbrowser/materialutils.cpp +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 - -#include "materialutils.h" - -#include "abstractview.h" -#include "nodelistproperty.h" -#include "nodemetainfo.h" -#include "qmlobjectnode.h" -#include "variantproperty.h" -#include - -#include - -namespace QmlDesigner { - -// Assigns given material to a 3D model. -// The assigned material is also inserted into material library if not already there. -// If given material is not valid, first existing material from material library is used, -// or if material library is empty, a new material is created. -// This function should be called only from inside a transaction, as it potentially does many -// changes to model. -void MaterialUtils::assignMaterialTo3dModel(AbstractView *view, const ModelNode &modelNode, - const ModelNode &materialNode) -{ - QTC_ASSERT(modelNode.isValid() && modelNode.metaInfo().isQtQuick3DModel(), return); - - ModelNode matLib = Utils3D::materialLibraryNode(view); - - if (!matLib.isValid()) - return; - - ModelNode newMaterialNode; - - if (materialNode.isValid() && materialNode.metaInfo().isQtQuick3DMaterial()) { - newMaterialNode = materialNode; - } else { - const QList materials = matLib.directSubModelNodes(); - if (materials.size() > 0) { - for (const ModelNode &mat : materials) { - if (mat.metaInfo().isQtQuick3DMaterial()) { - newMaterialNode = mat; - break; - } - } - } - - // if no valid material, create a new default material - if (!newMaterialNode.isValid()) { -#ifdef QDS_USE_PROJECTSTORAGE - newMaterialNode = view->createModelNode("PrincipledMaterial"); -#else - NodeMetaInfo metaInfo = view->model()->qtQuick3DPrincipledMaterialMetaInfo(); - newMaterialNode = view->createModelNode("QtQuick3D.PrincipledMaterial", - metaInfo.majorVersion(), - metaInfo.minorVersion()); -#endif - newMaterialNode.ensureIdExists(); - } - } - - QTC_ASSERT(newMaterialNode.isValid(), return); - - VariantProperty matNameProp = newMaterialNode.variantProperty("objectName"); - if (matNameProp.value().isNull()) - matNameProp.setValue("New Material"); - - if (!newMaterialNode.hasParentProperty() - || newMaterialNode.parentProperty() != matLib.defaultNodeListProperty()) { - matLib.defaultNodeListProperty().reparentHere(newMaterialNode); - } - - QmlObjectNode(modelNode).setBindingProperty("materials", newMaterialNode.id()); -} - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialutils.h b/src/plugins/qmldesigner/components/materialbrowser/materialutils.h deleted file mode 100644 index e273ef74a44..00000000000 --- a/src/plugins/qmldesigner/components/materialbrowser/materialutils.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 -#pragma once - -#include "modelnode.h" - -namespace QmlDesigner { - -class AbstractView; - -class MaterialUtils -{ -public: - MaterialUtils(); - - static void assignMaterialTo3dModel(AbstractView *view, const ModelNode &modelNode, - const ModelNode &materialNode = {}); -}; - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp index 328e62dff0f..4d67afd7985 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp @@ -905,8 +905,9 @@ void MaterialEditorView::requestPreviewRender() static int requestId = 0; m_previewRequestId = QByteArray(MATERIAL_EDITOR_IMAGE_REQUEST_ID) + QByteArray::number(++requestId); - static_cast(model()->nodeInstanceView()) - ->previewImageDataForGenericNode(m_selectedMaterial, {}, m_previewSize, m_previewRequestId); + + model()->sendCustomNotificationToNodeInstanceView( + NodePreviewImage{m_selectedMaterial, {}, m_previewSize, m_previewRequestId}); } } diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp index 7787e0ef829..4e045375347 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -773,7 +772,7 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in newQmlObjectNode.destroy(); return; } - MaterialUtils::assignMaterialTo3dModel(m_view, targetNode, newModelNode); + Utils3D::assignMaterialTo3dModel(m_view, targetNode, newModelNode); } else { ChooseFromPropertyListDialog *dialog = ChooseFromPropertyListDialog::createIfNeeded( targetNode, newModelNode, Core::ICore::dialogParent()); @@ -814,9 +813,9 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in const QList models = newModelNode.subModelNodesOfType( m_view->model()->qtQuick3DModelMetaInfo()); QTC_ASSERT(models.size() == 1, return); - MaterialUtils::assignMaterialTo3dModel(m_view, models.at(0)); + Utils3D::assignMaterialTo3dModel(m_view, models.at(0)); } else if (newModelNode.metaInfo().isQtQuick3DModel()) { - MaterialUtils::assignMaterialTo3dModel(m_view, newModelNode); + Utils3D::assignMaterialTo3dModel(m_view, newModelNode); } if (!validContainer) { diff --git a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp index fa5efd65346..bf5a2dfc7e3 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp @@ -183,11 +183,11 @@ void NavigatorView::clearExplorerWarnings() QList allNodes; allNodes.append(rootModelNode()); allNodes.append(rootModelNode().allSubModelNodes()); - for (ModelNode node : allNodes) { + for (const ModelNode &node : std::as_const(allNodes)) { if (node.metaInfo().isFileComponent()) { - const ProjectExplorer::FileNode *fnode = fileNodeForModelNode(node); - if (fnode) - fnode->setHasError(false); + const ProjectExplorer::FileNode *fNode = fileNodeForModelNode(node); + if (fNode) + fNode->setHasError(false); } } } diff --git a/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp b/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp index 8ba6b22ac0f..8c6f272fa91 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp @@ -27,7 +27,7 @@ #include "bindingproperty.h" #include "propertyeditorvalue.h" -#include "connectioneditorutils.h" +#include #include diff --git a/src/plugins/qmldesigner/components/propertyeditor/instanceimageprovider.cpp b/src/plugins/qmldesigner/components/propertyeditor/instanceimageprovider.cpp new file mode 100644 index 00000000000..eba1d90b0d1 --- /dev/null +++ b/src/plugins/qmldesigner/components/propertyeditor/instanceimageprovider.cpp @@ -0,0 +1,150 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "instanceimageprovider.h" + +#include +#include + +static const char INSTANCE_IMAGE_REQUEST_ID[] = "PropertyEditor.InstanceImage"; + +namespace QmlDesigner { + +InstanceImageProvider::InstanceImageProvider() + : QQuickImageProvider(Pixmap) + , m_delayTimer(new QTimer(this)) +{ + m_delayTimer->setInterval(500); + m_delayTimer->setSingleShot(true); + m_delayTimer->callOnTimeout([this] { requestOne(); }); +} + +/*! + * \internal + * \brief If a fresh image for the node is available, it returns it. + * Otherwise, if the requested node matches the received node, it loads a rescaled image of the + * most recent received image. + * But since it's been rescaled, and probably doesn't have a good resolution, + * it requests one more to get a new image. + * \return The most recent image received for the node from NodeInstanceView. + */ +QPixmap InstanceImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) +{ + using namespace Qt::StringLiterals; + static const QPixmap defaultImage = QPixmap::fromImage( + QImage(":/materialeditor/images/defaultmaterialpreview.png")); + + if (id != "preview") + return defaultImage; + + if (!m_requestedNode) + return defaultImage; + + QPixmap result; + if (dataAvailable(m_requestedNode, requestedSize)) { + result = m_receivedImage; + } else { + result = (m_requestedNode == m_receivedNode) ? getScaledImage(requestedSize) : defaultImage; + + // Here we should request one more image since the dataAvailable was false, and it means + // that we've temporarily returned a scaled image. But we need another fresh image with + // the correct size. + + if (hasPendingRequest()) + postponeRequest(requestedSize); + else + requestOne(requestedSize); + } + + if (result.isNull()) + result = defaultImage; + + if (size) + *size = result.size(); + + return result; +} + +bool InstanceImageProvider::feedImage(const ModelNode &node, + const QPixmap &pixmap, + const QByteArray &requestId) +{ + if (!requestId.startsWith(INSTANCE_IMAGE_REQUEST_ID)) + return false; + + if (m_pendingRequest == requestId) + m_pendingRequest.clear(); + + m_receivedImage = pixmap; + m_receivedNode = node; + + return true; +} + +void InstanceImageProvider::setModelNode(const ModelNode &node) +{ + m_requestedNode = node; +} + +bool InstanceImageProvider::hasPendingRequest() const +{ + return !m_pendingRequest.isEmpty(); +} + +void InstanceImageProvider::requestOne() +{ + if (!m_requestedNode) + return; + + static int requestId = 0; + QByteArray previewRequestId = QByteArray(INSTANCE_IMAGE_REQUEST_ID) + + QByteArray::number(++requestId); + m_pendingRequest = previewRequestId; + + m_resetRequest = false; + + m_requestedNode.model()->sendCustomNotificationToNodeInstanceView( + NodePreviewImage{m_requestedNode, {}, m_requestedSize, previewRequestId}); +} + +void InstanceImageProvider::requestOne(QSize size) +{ + prepareRequest(size); + requestOne(); +} + +void InstanceImageProvider::postponeRequest(QSize size) +{ + prepareRequest(size); + QMetaObject::invokeMethod(m_delayTimer, static_cast(&QTimer::start)); +} + +void InstanceImageProvider::prepareRequest(QSize size) +{ + m_requestedSize = size; +} + +/*! + * \internal + * \return true if the node matches the most recent node received from NodeInstanceView, and the + * size is the same as the received one, and data is not invalidated after the image is received. + */ +bool InstanceImageProvider::dataAvailable(const ModelNode &node, QSize size) +{ + return !m_resetRequest && node == m_receivedNode && size == m_receivedImage.size(); +} + +void InstanceImageProvider::invalidate() +{ + m_resetRequest = true; +} + +QPixmap InstanceImageProvider::getScaledImage(QSize size) +{ + if (size == m_receivedImage.size()) + return m_receivedImage; + else + return m_receivedImage.scaled(size, Qt::KeepAspectRatioByExpanding); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/instanceimageprovider.h b/src/plugins/qmldesigner/components/propertyeditor/instanceimageprovider.h new file mode 100644 index 00000000000..c25b0b383fd --- /dev/null +++ b/src/plugins/qmldesigner/components/propertyeditor/instanceimageprovider.h @@ -0,0 +1,53 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include +#include + +#include +#include + +QT_FORWARD_DECLARE_CLASS(QTimer) + +namespace QmlDesigner { + +class InstanceImageProvider : public QQuickImageProvider +{ + Q_OBJECT + +public: + explicit InstanceImageProvider(); + + QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override; + + bool feedImage(const ModelNode &node, const QPixmap &pixmap, const QByteArray &requestId); + void setModelNode(const ModelNode &node); + + bool hasPendingRequest() const; + void invalidate(); + +protected: + void requestOne(); + void requestOne(QSize size); + void postponeRequest(QSize size); + void prepareRequest(QSize size); + bool dataAvailable(const ModelNode &node, QSize size); + + QPixmap getScaledImage(QSize size); + +private: + QByteArray m_pendingRequest; + bool m_resetRequest = false; + + ModelNode m_requestedNode; + ModelNode m_receivedNode; + QSize m_requestedSize; + + QPixmap m_receivedImage; + + QTimer *m_delayTimer = nullptr; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditordynamicpropertiesproxymodel.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditordynamicpropertiesproxymodel.cpp new file mode 100644 index 00000000000..29fb649143e --- /dev/null +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditordynamicpropertiesproxymodel.cpp @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2025 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 "propertyeditordynamicpropertiesproxymodel.h" + +#include + +#include + +namespace QmlDesigner { + +PropertyEditorDynamicPropertiesProxyModel::PropertyEditorDynamicPropertiesProxyModel(QObject *parent) + : DynamicPropertiesProxyModel(parent) +{ + if (PropertyEditorView::instance()) + initModel(PropertyEditorView::instance()->dynamicPropertiesModel()); +} + +void PropertyEditorDynamicPropertiesProxyModel::registerDeclarativeType() +{ + DynamicPropertiesProxyModel::registerDeclarativeType(); + qmlRegisterType("HelperWidgets", 2, 0, "PropertyEditorDynamicPropertiesModel"); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditordynamicpropertiesproxymodel.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditordynamicpropertiesproxymodel.h new file mode 100644 index 00000000000..18e93bda895 --- /dev/null +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditordynamicpropertiesproxymodel.h @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** Copyright (C) 2025 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" + +namespace QmlDesigner { + +class PropertyEditorDynamicPropertiesProxyModel : public DynamicPropertiesProxyModel +{ + Q_OBJECT +public: + explicit PropertyEditorDynamicPropertiesProxyModel(QObject *parent = nullptr); + + static void registerDeclarativeType(); +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp index 92992a2ad62..f97d9b6dfeb 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp @@ -3,6 +3,7 @@ #include "propertyeditorqmlbackend.h" +#include "instanceimageprovider.h" #include "propertyeditortransaction.h" #include "propertyeditorutils.h" #include "propertyeditorvalue.h" @@ -29,14 +30,15 @@ #include #include #include +#include #include #include -#include #include #include #include #include +#include #include #include #include @@ -89,6 +91,19 @@ namespace QmlDesigner { using namespace Qt::StringLiterals; +static bool isMaterialAuxiliaryKey(AuxiliaryDataKeyView key) +{ + static constexpr auto previewKeys = Utils::to_array( + materialPreviewEnvDocProperty, + materialPreviewEnvValueDocProperty, + materialPreviewModelDocProperty, + materialPreviewEnvProperty, + materialPreviewEnvValueProperty, + materialPreviewModelProperty); + + return std::ranges::find(previewKeys, key) != std::ranges::end(previewKeys); +} + PropertyEditorQmlBackend::PropertyEditorQmlBackend(PropertyEditorView *propertyEditor, AsynchronousImageCache &imageCache) : m_view(Utils::makeUniqueObjectPtr(imageCache)) @@ -100,6 +115,8 @@ PropertyEditorQmlBackend::PropertyEditorQmlBackend(PropertyEditorView *propertyE ->settings().value(DesignerSettingsKey::SHOW_PROPERTYEDITOR_WARNINGS).toBool()); m_view->engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); + m_view->engine()->addImportPath(scriptsEditorResourcesPath() + "/imports"); + m_dummyPropertyEditorValue->setValue(QLatin1String("#000000")); context()->setContextProperty(QLatin1String("dummyBackendValue"), m_dummyPropertyEditorValue.get()); @@ -304,19 +321,58 @@ void PropertyEditorQmlBackend::handleInstancePropertyChangedInModelNodeProxy( m_backendModelNode.handleInstancePropertyChanged(modelNode, propertyName); } +void PropertyEditorQmlBackend::handleAuxiliaryDataChanges(const QmlObjectNode &qmlObjectNode, + AuxiliaryDataKeyView key) +{ + if (qmlObjectNode.isRootModelNode() && isMaterialAuxiliaryKey(key)) { + m_backendMaterialNode.handleAuxiliaryPropertyChanges(); + m_view->instanceImageProvider()->invalidate(); + } +} + void PropertyEditorQmlBackend::handleVariantPropertyChangedInModelNodeProxy(const VariantProperty &property) { m_backendModelNode.handleVariantPropertyChanged(property); + updateInstanceImage(); } void PropertyEditorQmlBackend::handleBindingPropertyChangedInModelNodeProxy(const BindingProperty &property) { m_backendModelNode.handleBindingPropertyChanged(property); + m_backendTextureNode.handleBindingPropertyChanged(property); + updateInstanceImage(); +} + +void PropertyEditorQmlBackend::handleBindingPropertyInModelNodeProxyAboutToChange( + const BindingProperty &property) +{ + if (m_backendMaterialNode.materialNode()) { + ModelNode expressionNode = property.resolveToModelNode(); + if (expressionNode.metaInfo().isQtQuick3DTexture()) + updateInstanceImage(); + } + m_backendTextureNode.handleBindingPropertyChanged(property); } void PropertyEditorQmlBackend::handlePropertiesRemovedInModelNodeProxy(const AbstractProperty &property) { m_backendModelNode.handlePropertiesRemoved(property); + m_backendTextureNode.handlePropertiesRemoved(property); + updateInstanceImage(); +} + +void PropertyEditorQmlBackend::handleModelNodePreviewPixmapChanged(const ModelNode &node, + const QPixmap &pixmap, + const QByteArray &requestId) +{ + InstanceImageProvider *imageProvider = m_view->instanceImageProvider(); + + if (!imageProvider) + return; + + bool imageFed = imageProvider->feedImage(node, pixmap, requestId); + if (imageFed && !imageProvider->hasPendingRequest()) + refreshPreview(); } void PropertyEditorQmlBackend::createPropertyEditorValue(const QmlObjectNode &qmlObjectNode, @@ -479,10 +535,15 @@ void QmlDesigner::PropertyEditorQmlBackend::createPropertyEditorValues(const Qml #endif } +void PropertyEditorQmlBackend::updateInstanceImage() +{ + m_view->instanceImageProvider()->invalidate(); + refreshPreview(); +} + void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const QString &stateName, const QUrl &qmlSpecificsFile, PropertyEditorView *propertyEditor) { if (qmlObjectNode.isValid()) { - m_contextObject->setModel(propertyEditor->model()); qCInfo(propertyEditorBenchmark) << Q_FUNC_INFO; @@ -500,6 +561,9 @@ void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const Q m_backendModelNode.setup(qmlObjectNode.modelNode()); context()->setContextProperty("modelNodeBackend", &m_backendModelNode); + m_backendMaterialNode.setup(qmlObjectNode); + m_backendTextureNode.setup(qmlObjectNode); + // className auto valueObject = qobject_cast(variantToQObject( m_backendValuesPropertyMap.value(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY))); @@ -589,64 +653,23 @@ void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const Q contextObject()->setSelectedNode(qmlObjectNode); contextObject()->setHasQuick3DImport(propertyEditor->model()->hasImport("QtQuick3D")); + m_view->instanceImageProvider()->setModelNode(propertyEditor->firstSelectedModelNode()); + updateInstanceImage(); + qCInfo(propertyEditorBenchmark) << "final:" << time.elapsed(); } else { qWarning() << "PropertyEditor: invalid node for setup"; } } -void PropertyEditorQmlBackend::initialSetup(const TypeName &typeName, const QUrl &qmlSpecificsFile, PropertyEditorView *propertyEditor) -{ - NodeMetaInfo metaInfo = propertyEditor->model()->metaInfo(typeName); - - for (const auto &property : PropertyEditorUtils::filteredProperties(metaInfo)) { - setupPropertyEditorValue(property.name(), propertyEditor, property.propertyType()); - } - - auto valueObject = qobject_cast(variantToQObject( - m_backendValuesPropertyMap.value(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY))); - if (!valueObject) - valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap); - valueObject->setName(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY); - - valueObject->setValue(typeName); - QObject::connect(valueObject, - &PropertyEditorValue::valueChanged, - &backendValuesPropertyMap(), - &DesignerPropertyMap::valueChanged); - m_backendValuesPropertyMap.insert(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY, - QVariant::fromValue(valueObject)); - - // id - valueObject = qobject_cast(variantToQObject(m_backendValuesPropertyMap.value(QLatin1String("id")))); - if (!valueObject) - valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap); - valueObject->setName("id"); - valueObject->setValue("id"); - QObject::connect(valueObject, &PropertyEditorValue::valueChanged, &backendValuesPropertyMap(), &DesignerPropertyMap::valueChanged); - m_backendValuesPropertyMap.insert(QLatin1String("id"), QVariant::fromValue(valueObject)); - - context()->setContextProperties(QVector{ - {{"anchorBackend"}, QVariant::fromValue(&m_backendAnchorBinding)}, - {{"modelNodeBackend"}, QVariant::fromValue(&m_backendModelNode)}, - {{"transaction"}, QVariant::fromValue(m_propertyEditorTransaction.get())}}); - - contextObject()->setSpecificsUrl(qmlSpecificsFile); - - contextObject()->setStateName(QStringLiteral("basestate")); - - contextObject()->setIsBaseState(true); - - contextObject()->setSpecificQmlData(QStringLiteral("")); -} - QString PropertyEditorQmlBackend::propertyEditorResourcesPath() { -#ifdef SHARE_QML_PATH - if (Utils::qtcEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) - return QLatin1String(SHARE_QML_PATH) + "/propertyEditorQmlSources"; -#endif - return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toUrlishString(); + return resourcesPath("propertyEditorQmlSources"); +} + +QString PropertyEditorQmlBackend::scriptsEditorResourcesPath() +{ + return resourcesPath("scriptseditor"); } inline bool dotPropertyHeuristic(const QmlObjectNode &node, @@ -920,6 +943,15 @@ TypeName PropertyEditorQmlBackend::fixTypeNameForPanes(const TypeName &typeName) return fixedTypeName; } +QString PropertyEditorQmlBackend::resourcesPath(const QString &dir) +{ +#ifdef SHARE_QML_PATH + if (Utils::qtcEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) + return QLatin1String(SHARE_QML_PATH) + "/" + dir; +#endif + return Core::ICore::resourcePath("qmldesigner/" + dir).toUrlishString(); +} + static NodeMetaInfo findCommonSuperClass(const NodeMetaInfo &first, const NodeMetaInfo &second) { auto commonBase = first.commonBase(second); @@ -934,11 +966,14 @@ NodeMetaInfo PropertyEditorQmlBackend::findCommonAncestor(const ModelNode &node) AbstractView *view = node.view(); - if (view->selectedModelNodes().size() > 1) { + const QList &selectedNodes = view->selectedModelNodes(); + if (selectedNodes.size() > 1) { NodeMetaInfo commonClass = node.metaInfo(); - for (const ModelNode ¤tNode : view->selectedModelNodes()) { - if (currentNode.metaInfo().isValid() && !currentNode.metaInfo().isBasedOn(commonClass)) - commonClass = findCommonSuperClass(currentNode.metaInfo(), commonClass); + + for (const ModelNode &selectedNode : selectedNodes) { + const NodeMetaInfo &nodeMetaInfo = selectedNode.metaInfo(); + if (nodeMetaInfo.isValid() && !nodeMetaInfo.isBasedOn(commonClass)) + commonClass = findCommonSuperClass(nodeMetaInfo, commonClass); } return commonClass; } @@ -951,12 +986,24 @@ void PropertyEditorQmlBackend::refreshBackendModel() m_backendModelNode.refresh(); } +void PropertyEditorQmlBackend::refreshPreview() +{ + auto qmlPreview = widget()->rootObject(); + + if (qmlPreview && qmlPreview->metaObject()->indexOfMethod("refreshPreview()") > -1) + QMetaObject::invokeMethod(qmlPreview, "refreshPreview"); +} + void PropertyEditorQmlBackend::setupContextProperties() { - context()->setContextProperty("modelNodeBackend", &m_backendModelNode); - context()->setContextProperties(QVector{ - {{"anchorBackend"}, QVariant::fromValue(&m_backendAnchorBinding)}, - {{"transaction"}, QVariant::fromValue(m_propertyEditorTransaction.get())}}); + context()->setContextProperties({ + {"modelNodeBackend", QVariant::fromValue(&m_backendModelNode)}, + {"materialNodeBackend", QVariant::fromValue(&m_backendMaterialNode)}, + {"textureNodeBackend", QVariant::fromValue(&m_backendTextureNode)}, + {"anchorBackend", QVariant::fromValue(&m_backendAnchorBinding)}, + {"transaction", QVariant::fromValue(m_propertyEditorTransaction.get())}, + {"dummyBackendValue", QVariant::fromValue(m_dummyPropertyEditorValue.get())}, + }); } #ifndef QDS_USE_PROJECTSTORAGE @@ -1009,6 +1056,7 @@ bool PropertyEditorQmlBackend::checkIfUrlExists(const QUrl &url) void PropertyEditorQmlBackend::emitSelectionToBeChanged() { m_backendModelNode.emitSelectionToBeChanged(); + m_backendTextureNode.updateSelectionDetails(); } void PropertyEditorQmlBackend::emitSelectionChanged() @@ -1050,14 +1098,12 @@ void PropertyEditorQmlBackend::setValueforAuxiliaryProperties(const QmlObjectNod #ifndef QDS_USE_PROJECTSTORAGE std::tuple PropertyEditorQmlBackend::getQmlUrlForMetaInfo(const NodeMetaInfo &metaInfo) { - QString className; if (metaInfo.isValid()) { - const NodeMetaInfos hierarchy = metaInfo.selfAndPrototypes(); + const NodeMetaInfos &hierarchy = metaInfo.selfAndPrototypes(); for (const NodeMetaInfo &info : hierarchy) { QUrl fileUrl = fileToUrl(locateQmlFile(info, QString::fromUtf8(qmlFileName(info)))); - if (fileUrl.isValid()) { + if (fileUrl.isValid()) return {fileUrl, info}; - } } } @@ -1114,4 +1160,3 @@ QString PropertyEditorQmlBackend::locateQmlFile(const NodeMetaInfo &info, const #endif // QDS_USE_PROJECTSTORAGE } //QmlDesigner - diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h index 062153289da..e58822b076e 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h @@ -7,7 +7,9 @@ #include "propertyeditorcontextobject.h" #include "propertyeditorvalue.h" #include "qmlanchorbindingproxy.h" +#include "qmlmaterialnodeproxy.h" #include "qmlmodelnodeproxy.h" +#include "qmltexturenodeproxy.h" #include "quick2propertyeditorview.h" #include @@ -18,6 +20,8 @@ #include +QT_FORWARD_DECLARE_CLASS(QQuickImageProvider) + class PropertyEditorValue; namespace QmlDesigner { @@ -37,7 +41,6 @@ public: ~PropertyEditorQmlBackend(); void setup(const QmlObjectNode &fxObjectNode, const QString &stateName, const QUrl &qmlSpecificsFile, PropertyEditorView *propertyEditor); - void initialSetup(const TypeName &typeName, const QUrl &qmlSpecificsFile, PropertyEditorView *propertyEditor); void setValue(const QmlObjectNode &fxObjectNode, PropertyNameView name, const QVariant &value); void setExpression(PropertyNameView propName, const QString &exp); @@ -52,6 +55,7 @@ public: PropertyEditorValue *propertyValueForName(const QString &propertyName); static QString propertyEditorResourcesPath(); + static QString scriptsEditorResourcesPath(); static QUrl emptyPaneUrl(); #ifndef QDS_USE_PROJECTSTORAGE static QString templateGeneration(const NodeMetaInfo &type, @@ -82,13 +86,20 @@ public: void handleInstancePropertyChangedInModelNodeProxy(const ModelNode &modelNode, PropertyNameView propertyName); + void handleAuxiliaryDataChanges(const QmlObjectNode &qmlObjectNode, AuxiliaryDataKeyView key); void handleVariantPropertyChangedInModelNodeProxy(const VariantProperty &property); void handleBindingPropertyChangedInModelNodeProxy(const BindingProperty &property); + void handleBindingPropertyInModelNodeProxyAboutToChange(const BindingProperty &property); void handlePropertiesRemovedInModelNodeProxy(const AbstractProperty &property); + void handleModelNodePreviewPixmapChanged(const ModelNode &node, + const QPixmap &pixmap, + const QByteArray &requestId); static NodeMetaInfo findCommonAncestor(const ModelNode &node); void refreshBackendModel(); + void refreshPreview(); + void updateInstanceImage(); void setupContextProperties(); @@ -109,6 +120,7 @@ private: static QString locateQmlFile(const NodeMetaInfo &info, const QString &relativePath); #endif static TypeName fixTypeNameForPanes(const TypeName &typeName); + static QString resourcesPath(const QString &dir); private: // to avoid a crash while destructing DesignerPropertyMap in the QQmlData @@ -117,6 +129,8 @@ private: Utils::UniqueObjectPtr m_view = nullptr; QmlAnchorBindingProxy m_backendAnchorBinding; + QmlMaterialNodeProxy m_backendMaterialNode; + QmlTextureNodeProxy m_backendTextureNode; QmlModelNodeProxy m_backendModelNode; std::unique_ptr m_propertyEditorTransaction; std::unique_ptr m_dummyPropertyEditorValue; diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp index b4e7f9a6eac..1828e891727 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp @@ -160,6 +160,7 @@ void PropertyEditorValue::setExpressionWithEmit(const QString &expression) emit expressionChanged(nameAsQString()); emit expressionChangedQml();// Note that we set the name in this case } + emit isBoundChanged(); } void PropertyEditorValue::setExpression(const QString &expression) @@ -221,9 +222,15 @@ void PropertyEditorValue::setIsValid(bool valid) bool PropertyEditorValue::isTranslated() const { if (modelNode().isValid()) { - if (auto metaInfo = modelNode().metaInfo(); - metaInfo.isValid() && metaInfo.hasProperty(name()) - && metaInfo.property(name()).propertyType().isString()) { + auto metaInfo = modelNode().metaInfo(); + auto isString = metaInfo.isValid() && metaInfo.hasProperty(name()) + && metaInfo.property(name()).propertyType().isString(); + + auto property = modelNode().property(name()); + auto isDynamicString = property.isValid() && property.isDynamic() + && property.dynamicTypeName() == TypeNameView("string"); + + if (isString || isDynamicString) { const QmlObjectNode objectNode(modelNode()); if (objectNode.hasBindingProperty(name())) { const QRegularExpression rx( diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp index 2b4b348a978..09ddc373e7c 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp @@ -10,8 +10,10 @@ #include #include +#include #include #include +#include "qmldesignerplugin.h" #include #include @@ -67,10 +69,9 @@ PropertyEditorView::PropertyEditorView(AsynchronousImageCache &imageCache, , m_timerId(0) , m_stackedWidget(new PropertyEditorWidget()) , m_qmlBackEndForCurrentType(nullptr) - , m_propertyComponentGenerator{QmlDesigner::PropertyEditorQmlBackend::propertyEditorResourcesPath(), - model()} + , m_propertyComponentGenerator{PropertyEditorQmlBackend::propertyEditorResourcesPath(), model()} , m_locked(false) - + , m_dynamicPropertiesModel(new DynamicPropertiesModel(true, this)) { m_qmlDir = PropertyEditorQmlBackend::propertyEditorResourcesPath(); @@ -291,6 +292,11 @@ void PropertyEditorView::refreshMetaInfos(const TypeIds &deletedTypeIds) m_propertyComponentGenerator.refreshMetaInfos(deletedTypeIds); } +DynamicPropertiesModel *PropertyEditorView::dynamicPropertiesModel() const +{ + return m_dynamicPropertiesModel; +} + void PropertyEditorView::setExpressionOnObjectNode(const QmlObjectNode &constObjectNode, PropertyNameView name, const QString &newExpression) @@ -393,6 +399,24 @@ void PropertyEditorView::removeAliasForProperty(const ModelNode &modelNode, cons } } +PropertyEditorView *PropertyEditorView::instance() +{ + static PropertyEditorView *s_instance = nullptr; + + if (s_instance) + return s_instance; + + const QList views = QmlDesignerPlugin::instance()->viewManager().views(); + for (AbstractView *view : views) { + PropertyEditorView *propView = qobject_cast(view); + if (propView) + s_instance = propView; + } + + QTC_ASSERT(s_instance, return nullptr); + return s_instance; +} + void PropertyEditorView::updateSize() { if (!m_qmlBackEndForCurrentType) @@ -422,8 +446,10 @@ void PropertyEditorView::resetView() setupQmlBackend(); - if (m_qmlBackEndForCurrentType) + if (m_qmlBackEndForCurrentType) { m_qmlBackEndForCurrentType->emitSelectionChanged(); + QMetaObject::invokeMethod(m_qmlBackEndForCurrentType->widget()->rootObject(), "clearSearch"); + } m_locked = false; @@ -602,11 +628,16 @@ void PropertyEditorView::setupQmlBackend() #else const NodeMetaInfo commonAncestor = PropertyEditorQmlBackend::findCommonAncestor(m_selectedNode); + // qmlFileUrl is panel url. and specifics is its metainfo const auto [qmlFileUrl, specificsClassMetaInfo] = PropertyEditorQmlBackend::getQmlUrlForMetaInfo( commonAncestor); auto [diffClassMetaInfo, qmlSpecificsFile] = diffType(commonAncestor, specificsClassMetaInfo); + // Hack to fix Textures in property views in case obsolete specifics are loaded from module + if (qmlFileUrl.toLocalFile().endsWith("TexturePane.qml")) + qmlSpecificsFile = QUrl{}; + QString specificQmlData = getSpecificQmlData(commonAncestor, m_selectedNode, diffClassMetaInfo); PropertyEditorQmlBackend *currentQmlBackend = getQmlBackend(m_qmlBackendHash, @@ -628,6 +659,8 @@ void PropertyEditorView::setupQmlBackend() setupInsight(rootModelNode(), currentQmlBackend); #endif // QDS_USE_PROJECTSTORAGE + + m_dynamicPropertiesModel->setSelectedNode(m_selectedNode); } void PropertyEditorView::commitVariantValueToModel(PropertyNameView propertyName, const QVariant &value) @@ -746,6 +779,7 @@ void PropertyEditorView::modelAboutToBeDetached(Model *model) m_qmlBackEndForCurrentType->propertyEditorTransaction()->end(); resetView(); + m_dynamicPropertiesModel->reset(); } void PropertyEditorView::propertiesRemoved(const QList &propertyList) @@ -755,6 +789,7 @@ void PropertyEditorView::propertiesRemoved(const QList &proper QTC_ASSERT(m_qmlBackEndForCurrentType, return ); + bool changed = false; for (const AbstractProperty &property : propertyList) { m_qmlBackEndForCurrentType->handlePropertiesRemovedInModelNodeProxy(property); @@ -765,6 +800,7 @@ void PropertyEditorView::propertiesRemoved(const QList &proper if (node == m_selectedNode || QmlObjectNode(m_selectedNode).propertyChangeForCurrentState() == node) { m_locked = true; + changed = true; const PropertyName propertyName = property.name().toByteArray(); PropertyName convertedpropertyName = propertyName; @@ -812,8 +848,18 @@ void PropertyEditorView::propertiesRemoved(const QList &proper if (propertyName.contains("anchor")) m_qmlBackEndForCurrentType->backendAnchorBinding().invalidate(m_selectedNode); + + dynamicPropertiesModel()->dispatchPropertyChanges(property); } } + if (changed) + m_qmlBackEndForCurrentType->updateInstanceImage(); +} + +void PropertyEditorView::propertiesAboutToBeRemoved(const QList &propertyList) +{ + for (const auto &property : propertyList) + m_dynamicPropertiesModel->removeItem(property); } void PropertyEditorView::variantPropertiesChanged(const QList& propertyList, PropertyChangeFlags /*propertyChange*/) @@ -823,6 +869,11 @@ void PropertyEditorView::variantPropertiesChanged(const QList& QTC_ASSERT(m_qmlBackEndForCurrentType, return ); + bool changed = false; + + bool selectedNodeIsMaterial = m_selectedNode.metaInfo().isQtQuick3DMaterial(); + bool selectedNodeHasBindingProperties = !m_selectedNode.bindingProperties().isEmpty(); + for (const VariantProperty &property : propertyList) { m_qmlBackEndForCurrentType->handleVariantPropertyChangedInModelNodeProxy(property); @@ -837,21 +888,45 @@ void PropertyEditorView::variantPropertiesChanged(const QList& property.name()); if (node == m_selectedNode || QmlObjectNode(m_selectedNode).propertyChangeForCurrentState() == node) { + if (property.isDynamic()) + m_dynamicPropertiesModel->updateItem(property); if ( QmlObjectNode(m_selectedNode).modelNode().property(property.name()).isBindingProperty()) setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name())); else setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).modelValue(property.name())); + changed = true; } + + if (!changed) { + // Check if property changes affects the selected node preview + + if (selectedNodeIsMaterial && selectedNodeHasBindingProperties + && node.metaInfo().isQtQuick3DTexture()) { + changed = true; + } + } + m_dynamicPropertiesModel->dispatchPropertyChanges(property); } + + if (changed) + m_qmlBackEndForCurrentType->updateInstanceImage(); } -void PropertyEditorView::bindingPropertiesChanged(const QList &propertyList, PropertyChangeFlags /*propertyChange*/) +void PropertyEditorView::bindingPropertiesChanged(const QList &propertyList, + PropertyChangeFlags /*propertyChange*/) { - if (locked() || noValidSelection()) + if (noValidSelection()) return; - QTC_ASSERT(m_qmlBackEndForCurrentType, return ); + QTC_ASSERT(m_qmlBackEndForCurrentType, return); + if (locked()) { + for (const BindingProperty &property : propertyList) + m_qmlBackEndForCurrentType->handleBindingPropertyInModelNodeProxyAboutToChange(property); + return; + } + + bool changed = false; for (const BindingProperty &property : propertyList) { m_qmlBackEndForCurrentType->handleBindingPropertyChangedInModelNodeProxy(property); @@ -861,6 +936,8 @@ void PropertyEditorView::bindingPropertiesChanged(const QList & m_qmlBackEndForCurrentType->contextObject()->setHasAliasExport(QmlObjectNode(m_selectedNode).isAliasExported()); if (node == m_selectedNode || QmlObjectNode(m_selectedNode).propertyChangeForCurrentState() == node) { + if (property.isDynamic()) + m_dynamicPropertiesModel->updateItem(property); if (property.name().contains("anchor")) m_qmlBackEndForCurrentType->backendAnchorBinding().invalidate(m_selectedNode); @@ -868,8 +945,13 @@ void PropertyEditorView::bindingPropertiesChanged(const QList & QString exp = QmlObjectNode(m_selectedNode).bindingProperty(property.name()).expression(); m_qmlBackEndForCurrentType->setExpression(property.name(), exp); m_locked = false; + changed = true; } + m_dynamicPropertiesModel->dispatchPropertyChanges(property); } + + if (changed) + m_qmlBackEndForCurrentType->updateInstanceImage(); } void PropertyEditorView::auxiliaryDataChanged(const ModelNode &node, @@ -879,10 +961,21 @@ void PropertyEditorView::auxiliaryDataChanged(const ModelNode &node, if (noValidSelection()) return; + bool saved = false; + + QScopeGuard rootGuard([this, node, key, &saved] { + if (node.isRootNode()) { + if (!saved) + m_qmlBackEndForCurrentType->setValueforAuxiliaryProperties(m_selectedNode, key); + m_qmlBackEndForCurrentType->handleAuxiliaryDataChanges(node, key); + } + }); + if (!node.isSelected()) return; m_qmlBackEndForCurrentType->setValueforAuxiliaryProperties(m_selectedNode, key); + saved = true; if (key == insightEnabledProperty) m_qmlBackEndForCurrentType->contextObject()->setInsightEnabled(data.toBool()); @@ -912,6 +1005,8 @@ void PropertyEditorView::nodeIdChanged(const ModelNode &node, const QString &new if (!QmlObjectNode(m_selectedNode).isValid()) return; + m_dynamicPropertiesModel->reset(); + if (m_qmlBackEndForCurrentType) { if (newId == Constants::MATERIAL_LIB_ID) m_qmlBackEndForCurrentType->contextObject()->setHasMaterialLibrary(true); @@ -979,7 +1074,7 @@ void PropertyEditorView::instancePropertyChanged(const QList; for (const ModelNodePropertyPair &propertyPair : propertyList) { const ModelNode modelNode = propertyPair.first; @@ -989,20 +1084,21 @@ void PropertyEditorView::instancePropertyChanged(const QListhandleInstancePropertyChangedInModelNodeProxy(modelNode, propertyName); - if (qmlObjectNode.isValid() && m_qmlBackEndForCurrentType && modelNode == m_selectedNode + if (qmlObjectNode.isValid() && modelNode == m_selectedNode && qmlObjectNode.currentState().isValid()) { const AbstractProperty property = modelNode.property(propertyName); - if (modelNode == m_selectedNode || qmlObjectNode.propertyChangeForCurrentState() == qmlObjectNode) { - if ( !modelNode.hasProperty(propertyName) || modelNode.property(property.name()).isBindingProperty() ) - setValue(modelNode, property.name(), qmlObjectNode.instanceValue(property.name())); - else - setValue(modelNode, property.name(), qmlObjectNode.modelValue(property.name())); - } + if (!modelNode.hasProperty(propertyName) || property.isBindingProperty()) + setValue(modelNode, property.name(), qmlObjectNode.instanceValue(property.name())); + else + setValue(modelNode, property.name(), qmlObjectNode.modelValue(property.name())); + changed = true; } } - m_locked = false; + if (changed) + m_qmlBackEndForCurrentType->updateInstanceImage(); + m_locked = false; } void PropertyEditorView::rootNodeTypeChanged(const QString &/*type*/, int /*majorVersion*/, int /*minorVersion*/) @@ -1045,6 +1141,17 @@ void PropertyEditorView::importsChanged(const Imports &addedImports, const Impor m_qmlBackEndForCurrentType->contextObject()->setHasQuick3DImport(true); } +void PropertyEditorView::modelNodePreviewPixmapChanged(const ModelNode &node, + const QPixmap &pixmap, + const QByteArray &requestId) +{ + if (node != m_selectedNode) + return; + + if (m_qmlBackEndForCurrentType) + m_qmlBackEndForCurrentType->handleModelNodePreviewPixmapChanged(node, pixmap, requestId); +} + void PropertyEditorView::highlightTextureProperties(bool highlight) { NodeMetaInfo metaInfo = m_selectedNode.metaInfo(); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h index 7ac545b6a83..547a6d2aa7c 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h @@ -21,6 +21,7 @@ QT_END_NAMESPACE namespace QmlDesigner { class CollapseButton; +class DynamicPropertiesModel; class ModelNode; class PropertyEditorQmlBackend; class PropertyEditorView; @@ -46,6 +47,7 @@ public: const NodeAbstractProperty &parentProperty, PropertyChangeFlags propertyChange) override; void propertiesRemoved(const QList& propertyList) override; + void propertiesAboutToBeRemoved(const QList &propertyList) override; void modelAttached(Model *model) override; @@ -73,6 +75,10 @@ public: const NodeAbstractProperty &oldPropertyParent, AbstractView::PropertyChangeFlags propertyChange) override; + void modelNodePreviewPixmapChanged(const ModelNode &node, + const QPixmap &pixmap, + const QByteArray &requestId) override; + void importsChanged(const Imports &addedImports, const Imports &removedImports) override; void dragStarted(QMimeData *mimeData) override; @@ -89,6 +95,8 @@ public: void refreshMetaInfos(const TypeIds &deletedTypeIds) override; + DynamicPropertiesModel *dynamicPropertiesModel() const; + static void setExpressionOnObjectNode(const QmlObjectNode &objectNode, PropertyNameView name, const QString &expression); @@ -120,6 +128,8 @@ private: //functions bool noValidSelection() const; void highlightTextureProperties(bool highlight = true); + static PropertyEditorView *instance(); + private: //variables AsynchronousImageCache &m_imageCache; ModelNode m_selectedNode; @@ -133,6 +143,9 @@ private: //variables PropertyEditorComponentGenerator m_propertyEditorComponentGenerator{m_propertyComponentGenerator}; bool m_locked; bool m_textureAboutToBeRemoved = false; + DynamicPropertiesModel *m_dynamicPropertiesModel = nullptr; + + friend class PropertyEditorDynamicPropertiesProxyModel; }; } //QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/qmlmaterialnodeproxy.cpp b/src/plugins/qmldesigner/components/propertyeditor/qmlmaterialnodeproxy.cpp new file mode 100644 index 00000000000..6cac8b471c7 --- /dev/null +++ b/src/plugins/qmldesigner/components/propertyeditor/qmlmaterialnodeproxy.cpp @@ -0,0 +1,285 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qmlmaterialnodeproxy.h" + +#include +#include +#include +#include +#include +#include + +#include + +namespace QmlDesigner { + +using namespace Qt::StringLiterals; + +static void renameMaterial(ModelNode &material, const QString &newName) +{ + QTC_ASSERT(material.isValid(), return); + QmlObjectNode(material).setNameAndId(newName, "material"); +} + +QmlMaterialNodeProxy::QmlMaterialNodeProxy() + : QObject() + , m_previewUpdateTimer(this) +{ + m_previewUpdateTimer.setInterval(200); + m_previewUpdateTimer.setSingleShot(true); + m_previewUpdateTimer.callOnTimeout( + std::bind_front(&QmlMaterialNodeProxy::updatePreviewModel, this)); +} + +QmlMaterialNodeProxy::~QmlMaterialNodeProxy() = default; + +void QmlMaterialNodeProxy::setup(const QmlObjectNode &objectNode) +{ + const QmlObjectNode material = objectNode.metaInfo().isQtQuick3DMaterial() ? objectNode + : QmlObjectNode{}; + setMaterialNode(material); + updatePossibleTypes(); + updatePreviewModel(); +} + +ModelNode QmlMaterialNodeProxy::materialNode() const +{ + return m_materialNode; +} + +void QmlMaterialNodeProxy::setPossibleTypes(const QStringList &types) +{ + if (types == m_possibleTypes) + return; + + m_possibleTypes = types; + emit possibleTypesChanged(); + + updatePossibleTypeIndex(); +} + +void QmlMaterialNodeProxy::updatePossibleTypes() +{ + static const QStringList basicTypes{ + "CustomMaterial", + "DefaultMaterial", + "PrincipledMaterial", + "SpecularGlossyMaterial", + }; + + const QString &matType = materialNode().simplifiedTypeName(); + setPossibleTypes(basicTypes.contains(matType) ? basicTypes : QStringList{matType}); + setCurrentType(matType); +} + +void QmlMaterialNodeProxy::setCurrentType(const QString &type) +{ + m_currentType = type.split('.').last(); + updatePossibleTypeIndex(); +} + +void QmlMaterialNodeProxy::toolBarAction(int action) +{ + QTC_ASSERT(hasQuick3DImport(), return); + + switch (ToolBarAction(action)) { + case ToolBarAction::ApplyToSelected: { + Utils3D::applyMaterialToModels(materialView(), + materialNode(), + Utils3D::getSelectedModels(materialView())); + break; + } + + case ToolBarAction::ApplyToSelectedAdd: { + Utils3D::applyMaterialToModels(materialView(), + materialNode(), + Utils3D::getSelectedModels(materialView()), + true); + break; + } + + case ToolBarAction::AddNewMaterial: { + if (!materialNode()) + break; + + ModelNode newMatNode; + AbstractView *view = materialView(); + view->executeInTransaction(__FUNCTION__, [&] { + ModelNode matLib = Utils3D::materialLibraryNode(view); + if (!matLib.isValid()) + return; +#ifdef QDS_USE_PROJECTSTORAGE + ModelNode newMatNode = view->createModelNode("PrincipledMaterial"); +#else + NodeMetaInfo metaInfo = materialView()->model()->qtQuick3DPrincipledMaterialMetaInfo(); + newMatNode = materialView()->createModelNode("QtQuick3D.PrincipledMaterial", + metaInfo.majorVersion(), + metaInfo.minorVersion()); +#endif + renameMaterial(newMatNode, "New Material"); + Utils3D::materialLibraryNode(view).defaultNodeListProperty().reparentHere(newMatNode); + }); + QTimer::singleShot(0, this, [newMatNode]() { + newMatNode.model()->setSelectedModelNodes({newMatNode}); + }); + break; + } + + case ToolBarAction::DeleteCurrentMaterial: { + if (materialNode().isValid()) + materialView()->executeInTransaction(__FUNCTION__, [&] { materialNode().destroy(); }); + break; + } + + case ToolBarAction::OpenMaterialBrowser: { + QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("MaterialBrowser", true); + break; + } + } +} + +void QmlMaterialNodeProxy::setPreviewEnv(const QString &envAndValue) +{ + if (envAndValue.isEmpty()) + return; + + if (!hasQuick3DImport()) + return; + + AbstractView *view = m_materialNode.modelNode().view(); + ModelNode rootModelNode = view->rootModelNode(); + + QStringList parts = envAndValue.split('='); + QString env = parts[0]; + QString value; + if (parts.size() > 1) + value = parts[1]; + + if (env == "Color" && value.isEmpty()) + value = rootModelNode.auxiliaryDataWithDefault(materialPreviewColorDocProperty).toString(); + + auto renderPreviews = [rootModelNode](const QString &auxEnv, const QString &auxValue) { + if (!rootModelNode) + return; + rootModelNode.setAuxiliaryData(materialPreviewEnvDocProperty, auxEnv); + rootModelNode.setAuxiliaryData(materialPreviewEnvProperty, auxEnv); + rootModelNode.setAuxiliaryData(materialPreviewEnvValueDocProperty, auxValue); + rootModelNode.setAuxiliaryData(materialPreviewEnvValueProperty, auxValue); + + if (auxEnv == "Color" && !auxValue.isEmpty()) + rootModelNode.setAuxiliaryData(materialPreviewColorDocProperty, auxEnv); + + rootModelNode.view()->emitCustomNotification("refresh_material_browser", {}); + }; + + QMetaObject::invokeMethod(view, renderPreviews, env, value); +} + +void QmlMaterialNodeProxy::setPreviewModel(const QString &modelStr) +{ + if (modelStr.isEmpty()) + return; + + if (!hasQuick3DImport()) + return; + + AbstractView *view = m_materialNode.modelNode().view(); + ModelNode rootModelNode = view->rootModelNode(); + + auto renderPreviews = [rootModelNode](const QString &modelStr) { + if (!rootModelNode) + return; + + rootModelNode.setAuxiliaryData(materialPreviewModelDocProperty, modelStr); + rootModelNode.setAuxiliaryData(materialPreviewModelProperty, modelStr); + + rootModelNode.view()->emitCustomNotification("refresh_material_browser", {}); + }; + + QMetaObject::invokeMethod(view, renderPreviews, modelStr); +} + +void QmlMaterialNodeProxy::handleAuxiliaryPropertyChanges() +{ + if (!hasQuick3DImport()) + return; + + m_previewUpdateTimer.start(); +} + +void QmlMaterialNodeProxy::registerDeclarativeType() +{ + qmlRegisterType("HelperWidgets", 2, 0, "QmlMaterialNodeProxy"); +} + +void QmlMaterialNodeProxy::updatePossibleTypeIndex() +{ + int newIndex = -1; + if (!m_currentType.isEmpty()) + newIndex = m_possibleTypes.indexOf(m_currentType); + + // Emit valid possible type index change even if the index doesn't change, as currentIndex on + // QML side will change to default internally if model is updated + if (m_possibleTypeIndex != -1 || m_possibleTypeIndex != newIndex) { + m_possibleTypeIndex = newIndex; + emit possibleTypeIndexChanged(); + } +} + +void QmlMaterialNodeProxy::updatePreviewModel() +{ + if (!hasQuick3DImport()) + return; + + AbstractView *view = m_materialNode.modelNode().view(); + ModelNode rootModelNode = view->rootModelNode(); + + // Read auxiliary preview Data + QString env = rootModelNode.auxiliaryDataWithDefault(materialPreviewEnvDocProperty).toString(); + QString envValue = rootModelNode.auxiliaryDataWithDefault(materialPreviewEnvValueDocProperty) + .toString(); + QString modelStr = rootModelNode.auxiliaryDataWithDefault(materialPreviewModelDocProperty).toString(); + + if (!envValue.isEmpty() && env != "Basic") { + env += '='; + env += envValue; + } + if (env.isEmpty()) + env = "SkyBox=preview_studio"; + + if (modelStr.isEmpty()) + modelStr = "#Sphere"; + + // Set node proxy properties + if (m_previewModel != modelStr) { + m_previewModel = modelStr; + emit previewModelChanged(); + } + + if (m_previewEnv != env) { + m_previewEnv = env; + emit previewEnvChanged(); + } +} + +void QmlMaterialNodeProxy::setMaterialNode(const QmlObjectNode &material) +{ + if (material == m_materialNode) + return; + + m_materialNode = material; + emit materialNodeChanged(); +} + +bool QmlMaterialNodeProxy::hasQuick3DImport() const +{ + return materialNode().isValid() && materialNode().model()->hasImport("QtQuick3D"_L1); +} + +AbstractView *QmlMaterialNodeProxy::materialView() const +{ + return materialNode().view(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/qmlmaterialnodeproxy.h b/src/plugins/qmldesigner/components/propertyeditor/qmlmaterialnodeproxy.h new file mode 100644 index 00000000000..58bf8935970 --- /dev/null +++ b/src/plugins/qmldesigner/components/propertyeditor/qmlmaterialnodeproxy.h @@ -0,0 +1,89 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +#include +#include + +namespace QmlDesigner { + +class ModelNode; + +class QmlMaterialNodeProxy : public QObject +{ + Q_OBJECT + + Q_PROPERTY(ModelNode materialNode READ materialNode NOTIFY materialNodeChanged) + Q_PROPERTY(QStringList possibleTypes READ possibleTypes NOTIFY possibleTypesChanged) + Q_PROPERTY(int possibleTypeIndex READ possibleTypeIndex NOTIFY possibleTypeIndexChanged) + Q_PROPERTY(QString previewEnv MEMBER m_previewEnv WRITE setPreviewEnv NOTIFY previewEnvChanged) + Q_PROPERTY(QString previewModel MEMBER m_previewModel WRITE setPreviewModel NOTIFY previewModelChanged) + +public: + enum class ToolBarAction { + ApplyToSelected = 0, + ApplyToSelectedAdd, + AddNewMaterial, + DeleteCurrentMaterial, + OpenMaterialBrowser + }; + Q_ENUM(ToolBarAction) + + explicit QmlMaterialNodeProxy(); + ~QmlMaterialNodeProxy() override; + + void setup(const QmlObjectNode &objectNode); + + QStringList possibleTypes() const { return m_possibleTypes; } + + ModelNode materialNode() const; + + int possibleTypeIndex() const { return m_possibleTypeIndex; } + + void setCurrentType(const QString &type); + + Q_INVOKABLE void toolBarAction(int action); + + void setPreviewEnv(const QString &envAndValue); + void setPreviewModel(const QString &modelStr); + + void handleAuxiliaryPropertyChanges(); + + static void registerDeclarativeType(); + +signals: + void possibleTypesChanged(); + void possibleTypeIndexChanged(); + void materialNodeChanged(); + void previewEnvChanged(); + void previewModelChanged(); + +private: // Methods + void setPossibleTypes(const QStringList &types); + void updatePossibleTypes(); + void updatePossibleTypeIndex(); + void updatePreviewModel(); + void setMaterialNode(const QmlObjectNode &material); + + bool hasQuick3DImport() const; + + AbstractView *materialView() const; + +private: + bool m_has3DModelSelection = false; + + QmlObjectNode m_materialNode; + + QStringList m_possibleTypes; + int m_possibleTypeIndex = -1; + QString m_currentType; + + QString m_previewEnv; + QString m_previewModel; + QTimer m_previewUpdateTimer; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/qmltexturenodeproxy.cpp b/src/plugins/qmldesigner/components/propertyeditor/qmltexturenodeproxy.cpp new file mode 100644 index 00000000000..b030d08d4df --- /dev/null +++ b/src/plugins/qmldesigner/components/propertyeditor/qmltexturenodeproxy.cpp @@ -0,0 +1,170 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qmltexturenodeproxy.h" + +#include +#include +#include +#include +#include + +#include + +namespace QmlDesigner { + +using namespace Qt::StringLiterals; + +QmlTextureNodeProxy::QmlTextureNodeProxy() = default; + +QmlTextureNodeProxy::~QmlTextureNodeProxy() = default; + +void QmlTextureNodeProxy::setup(const QmlObjectNode &objectNode) +{ + const QmlObjectNode texture = objectNode.metaInfo().isQtQuick3DTexture() ? objectNode + : QmlObjectNode{}; + + setTextureNode(texture); + updateSelectionDetails(); +} + +void QmlTextureNodeProxy::updateSelectionDetails() +{ + QScopeGuard falseSetter{ + std::bind_front(&QmlTextureNodeProxy::setSelectedNodeAcceptsMaterial, this, false)}; + + if (!textureNode()) + return; + + QmlObjectNode selectedNode = textureNode().view()->singleSelectedModelNode(); + if (!selectedNode) + return; + + falseSetter.dismiss(); + setSelectedNodeAcceptsMaterial(selectedNode.hasBindingProperty("materials")); +} + +void QmlTextureNodeProxy::handlePropertyChanged(const AbstractProperty &property) +{ + if (!textureNode()) + return; + + QmlObjectNode node = property.parentModelNode(); + if (!node) + return; + + QmlObjectNode selectedNode = textureNode().view()->singleSelectedModelNode(); + if (!selectedNode) + return; + + if (property.name() == "materials"_L1 + && (selectedNode == node || selectedNode.propertyChangeForCurrentState() == node)) { + updateSelectionDetails(); + } +} + +void QmlTextureNodeProxy::handleBindingPropertyChanged(const BindingProperty &property) +{ + handlePropertyChanged(property); +} + +void QmlTextureNodeProxy::handlePropertiesRemoved(const AbstractProperty &property) +{ + handlePropertyChanged(property); +} + +QmlObjectNode QmlTextureNodeProxy::textureNode() const +{ + return m_textureNode; +} + +bool QmlTextureNodeProxy::hasTexture() const +{ + return textureNode().isValid(); +} + +bool QmlTextureNodeProxy::selectedNodeAcceptsMaterial() const +{ + return m_selectedNodeAcceptsMaterial; +} + +QString QmlTextureNodeProxy::resolveResourcePath(const QString &path) const +{ + if (Utils::FilePath::fromString(path).isAbsolutePath()) + return path; + + return QmlDesignerPlugin::instance() + ->documentManager() + .currentDesignDocument() + ->fileName() + .absolutePath() + .pathAppended(path) + .cleanPath() + .toUrlishString(); +} + +void QmlTextureNodeProxy::toolbarAction(int action) +{ + if (!hasQuick3DImport()) + return; + + switch (action) { + case ToolBarAction::ApplyToSelected: { + if (!textureNode()) + return; + AbstractView *view = textureNode().view(); + QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("MaterialBrowser"); + ModelNode targetNode = view->singleSelectedModelNode(); + view->emitCustomNotification("apply_texture_to_model3D", {targetNode, textureNode()}); + } break; + + case ToolBarAction::AddNewTexture: { + if (!textureNode()) + break; + ModelNode newTexture = CreateTexture(textureNode().view()).execute(); + QTimer::singleShot(0, this, [newTexture]() { + if (newTexture) + newTexture.model()->setSelectedModelNodes({newTexture}); + }); + } break; + + case ToolBarAction::DeleteCurrentTexture: { + if (textureNode()) { + AbstractView *view = textureNode().view(); + view->executeInTransaction(__FUNCTION__, [&] { textureNode().destroy(); }); + } + } break; + + case ToolBarAction::OpenMaterialBrowser: { + QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("MaterialBrowser", true); + } break; + } +} + +void QmlTextureNodeProxy::registerDeclarativeType() +{ + qmlRegisterType("HelperWidgets", 2, 0, "QmlTextureNodeProxy"); +} + +void QmlTextureNodeProxy::setTextureNode(const QmlObjectNode &node) +{ + if (m_textureNode == node) + return; + m_textureNode = node; + emit textureNodeChanged(); +} + +void QmlTextureNodeProxy::setSelectedNodeAcceptsMaterial(bool value) +{ + if (m_selectedNodeAcceptsMaterial == value) + return; + m_selectedNodeAcceptsMaterial = value; + emit selectedNodeAcceptsMaterialChanged(); +} + +bool QmlTextureNodeProxy::hasQuick3DImport() const +{ + return textureNode().isValid() && textureNode().model()->hasImport("QtQuick3D"_L1); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/qmltexturenodeproxy.h b/src/plugins/qmldesigner/components/propertyeditor/qmltexturenodeproxy.h new file mode 100644 index 00000000000..eee8f9b5f2f --- /dev/null +++ b/src/plugins/qmldesigner/components/propertyeditor/qmltexturenodeproxy.h @@ -0,0 +1,64 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +#include + +namespace QmlDesigner { + +class QmlTextureNodeProxy : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QmlObjectNode textureNode READ textureNode NOTIFY textureNodeChanged) + Q_PROPERTY(bool hasTexture READ hasTexture NOTIFY textureNodeChanged) + Q_PROPERTY( + bool selectedNodeAcceptsMaterial + READ selectedNodeAcceptsMaterial + NOTIFY selectedNodeAcceptsMaterialChanged) + +public: + enum ToolBarAction { + ApplyToSelected, + AddNewTexture, + DeleteCurrentTexture, + OpenMaterialBrowser + }; + Q_ENUM(ToolBarAction) + + explicit QmlTextureNodeProxy(); + ~QmlTextureNodeProxy() override; + + void setup(const QmlObjectNode &objectNode); + + void updateSelectionDetails(); + void handlePropertyChanged(const AbstractProperty &property); + void handleBindingPropertyChanged(const BindingProperty &property); + void handlePropertiesRemoved(const AbstractProperty &property); + + QmlObjectNode textureNode() const; + bool hasTexture() const; + bool selectedNodeAcceptsMaterial() const; + + Q_INVOKABLE QString resolveResourcePath(const QString &path) const; + Q_INVOKABLE void toolbarAction(int action); + + static void registerDeclarativeType(); + +signals: + void textureNodeChanged(); + void selectedNodeAcceptsMaterialChanged(); + +private: + void setTextureNode(const QmlObjectNode &node); + void setSelectedNodeAcceptsMaterial(bool value); + bool hasQuick3DImport() const; + + QmlObjectNode m_textureNode; + bool m_selectedNodeAcceptsMaterial = false; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp index 78514fc91a0..9e3a397fa90 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp @@ -4,6 +4,7 @@ #include "quick2propertyeditorview.h" #include +#include #include "aligndistribute.h" #include "annotationeditor/annotationeditor.h" @@ -15,6 +16,7 @@ #include "gradientmodel.h" #include "gradientpresetcustomlistmodel.h" #include "gradientpresetdefaultlistmodel.h" +#include "instanceimageprovider.h" #include "itemfiltermodel.h" #include "listvalidator.h" #include "propertychangesmodel.h" @@ -24,8 +26,10 @@ #include "propertymodel.h" #include "propertynamevalidator.h" #include "qmlanchorbindingproxy.h" +#include "qmlmaterialnodeproxy.h" +#include "qmltexturenodeproxy.h" #include "richtexteditor/richtexteditorproxy.h" -#include "selectiondynamicpropertiesproxymodel.h" +#include "propertyeditordynamicpropertiesproxymodel.h" #include "theme.h" #include "tooltip.h" @@ -39,6 +43,14 @@ Quick2PropertyEditorView::Quick2PropertyEditorView(AsynchronousImageCache &image Theme::setupTheme(engine()); engine()->addImageProvider("qmldesigner_thumbnails", new AssetImageProvider(imageCache)); + + m_instanceImageProvider = new InstanceImageProvider(); + engine()->addImageProvider("nodeInstance", m_instanceImageProvider); +} + +InstanceImageProvider *Quick2PropertyEditorView::instanceImageProvider() const +{ + return m_instanceImageProvider; } void Quick2PropertyEditorView::registerQmlTypes() @@ -55,6 +67,8 @@ void Quick2PropertyEditorView::registerQmlTypes() ListValidator::registerDeclarativeType(); ColorPaletteBackend::registerDeclarativeType(); QmlAnchorBindingProxy::registerDeclarativeType(); + QmlMaterialNodeProxy::registerDeclarativeType(); + QmlTextureNodeProxy::registerDeclarativeType(); BindingEditor::registerDeclarativeType(); ActionEditor::registerDeclarativeType(); AnnotationEditor::registerDeclarativeType(); @@ -62,11 +76,12 @@ void Quick2PropertyEditorView::registerQmlTypes() Tooltip::registerDeclarativeType(); EasingCurveEditor::registerDeclarativeType(); RichTextEditorProxy::registerDeclarativeType(); - SelectionDynamicPropertiesProxyModel::registerDeclarativeType(); + PropertyEditorDynamicPropertiesProxyModel::registerDeclarativeType(); DynamicPropertyRow::registerDeclarativeType(); PropertyChangesModel::registerDeclarativeType(); PropertyModel::registerDeclarativeType(); PropertyNameValidator::registerDeclarativeType(); + ScriptEditorBackend::registerDeclarativeType(); const QString resourcePath = PropertyEditorQmlBackend::propertyEditorResourcesPath(); diff --git a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.h b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.h index 3b0872df482..c3f4d3ffbaf 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.h +++ b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.h @@ -5,9 +5,10 @@ #include - namespace QmlDesigner { +class InstanceImageProvider; + class Quick2PropertyEditorView : public QQuickWidget { Q_OBJECT @@ -15,7 +16,12 @@ class Quick2PropertyEditorView : public QQuickWidget public: explicit Quick2PropertyEditorView(class AsynchronousImageCache &imageCache); + InstanceImageProvider *instanceImageProvider() const; + static void registerQmlTypes(); + +private: + InstanceImageProvider *m_instanceImageProvider = nullptr; }; } //QmlDesigner diff --git a/src/plugins/qmldesigner/components/runmanager/runmanager.cpp b/src/plugins/qmldesigner/components/runmanager/runmanager.cpp index 451990166b1..fd49d9a2f0f 100644 --- a/src/plugins/qmldesigner/components/runmanager/runmanager.cpp +++ b/src/plugins/qmldesigner/components/runmanager/runmanager.cpp @@ -175,6 +175,9 @@ RunManager::RunManager(DeviceShare::DeviceManager &deviceManager) [this](ProjectExplorer::RunControl *runControl) { qCDebug(runManagerLog) << "Run Control started."; + if (m_currentTargetId != runControl->runMode()) + selectRunTarget(runControl->runMode()); + m_runningTargets.append(QPointer(runControl)); setState(TargetState::Running); diff --git a/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.cpp b/src/plugins/qmldesigner/components/scripteditor/propertytreemodel.cpp similarity index 98% rename from src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.cpp rename to src/plugins/qmldesigner/components/scripteditor/propertytreemodel.cpp index 463286e6956..b1892c9b557 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.cpp +++ b/src/plugins/qmldesigner/components/scripteditor/propertytreemodel.cpp @@ -163,8 +163,8 @@ std::vector properityLists() return result; } -PropertyTreeModel::PropertyTreeModel(ConnectionView *view) - : m_connectionView(view) +PropertyTreeModel::PropertyTreeModel(AbstractView *view) + : m_view(view) {} void PropertyTreeModel::resetModel() @@ -257,7 +257,7 @@ Qt::ItemFlags PropertyTreeModel::flags(const QModelIndex &) const QModelIndex PropertyTreeModel::index(int row, int column, const QModelIndex &parent) const { auto internalId = parent.internalId(); - if (!m_connectionView->isAttached()) + if (!m_view->isAttached()) return {}; if (!parent.isValid()) @@ -337,7 +337,7 @@ QPersistentModelIndex PropertyTreeModel::indexForInternalIdAndRow(quintptr inter int PropertyTreeModel::rowCount(const QModelIndex &parent) const { - if (!m_connectionView->isAttached() || parent.column() > 0) + if (!m_view->isAttached() || parent.column() > 0) return 0; if (!parent.isValid()) @@ -404,10 +404,10 @@ const std::vector PropertyTreeModel::getProperties(const ModelNode ModelNode PropertyTreeModel::getModelNodeForId(const QString &id) const { - if (!m_connectionView->isAttached()) + if (!m_view->isAttached()) return {}; - return m_connectionView->modelNodeForId(id); + return m_view->modelNodeForId(id); } QModelIndex PropertyTreeModel::ensureModelIndex(const ModelNode &node, int row) const @@ -473,10 +473,10 @@ void PropertyTreeModel::testModel() const QList PropertyTreeModel::allModelNodesWithIdsSortedByDisplayName() const { - if (!m_connectionView->isAttached()) + if (!m_view->isAttached()) return {}; - return Utils::sorted(ModelUtils::allModelNodesWithId(m_connectionView), + return Utils::sorted(ModelUtils::allModelNodesWithId(m_view), [](const ModelNode &lhs, const ModelNode &rhs) { return lhs.displayName() < rhs.displayName(); }); @@ -882,7 +882,7 @@ QString PropertyListProxyModel::parentName() const return m_treeModel->data(m_parentIndex, PropertyTreeModel::UserRoles::PropertyNameRole).toString(); } -PropertyTreeModelDelegate::PropertyTreeModelDelegate(ConnectionView *view) +PropertyTreeModelDelegate::PropertyTreeModelDelegate(AbstractView *view) : m_model(view) { connect(&m_nameCombboBox, &StudioQmlComboBoxBackend::activated, this, [this] { diff --git a/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.h b/src/plugins/qmldesigner/components/scripteditor/propertytreemodel.h similarity index 97% rename from src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.h rename to src/plugins/qmldesigner/components/scripteditor/propertytreemodel.h index 39af4857654..bb7dab533db 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.h +++ b/src/plugins/qmldesigner/components/scripteditor/propertytreemodel.h @@ -49,7 +49,8 @@ public: BoolType }; - PropertyTreeModel(ConnectionView *view); + // PropertyTreeModel(ConnectionView *view); + PropertyTreeModel(AbstractView *view); void resetModel(); @@ -120,7 +121,7 @@ private: const PropertyMetaInfo &metaInfo, bool recursive) const; - ConnectionView *m_connectionView; + AbstractView *m_view; mutable std::set m_indexCache; mutable std::vector m_indexHash; @@ -187,7 +188,7 @@ class PropertyTreeModelDelegate : public QObject Q_PROPERTY(StudioQmlComboBoxBackend *id READ idCombboBox CONSTANT) public: - explicit PropertyTreeModelDelegate(ConnectionView *view); + explicit PropertyTreeModelDelegate(AbstractView *view); void setPropertyType(PropertyTreeModel::PropertyTypes type); void setup(const QString &id, const QString &name, bool *nameExists = nullptr); void setupNameComboBox(const QString &id, const QString &name, bool *nameExists); diff --git a/src/plugins/qmldesigner/components/scripteditor/scripteditorbackend.cpp b/src/plugins/qmldesigner/components/scripteditor/scripteditorbackend.cpp new file mode 100644 index 00000000000..3bf324336b6 --- /dev/null +++ b/src/plugins/qmldesigner/components/scripteditor/scripteditorbackend.cpp @@ -0,0 +1,1393 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "scripteditorbackend.h" +#include "scripteditorevaluator.h" +#include "scripteditorutils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace { +const char defaultCondition[] = "condition"; + +QString getSourceFromProperty(const QmlDesigner::AbstractProperty &property) +{ + QTC_ASSERT(property.isValid(), return {}); + + if (!property.exists()) + return {}; + + if (property.isSignalHandlerProperty()) + return property.toSignalHandlerProperty().source(); + if (property.isBindingProperty()) + return property.toBindingProperty().expression(); + + return {}; +} +} // namespace + +namespace QmlDesigner { +static ScriptEditorStatements::MatchedCondition emptyCondition; + +ConditionListModel::ConditionListModel(AbstractView *view) + : m_view(view) + , m_condition(emptyCondition) +{} + +int ConditionListModel::rowCount(const QModelIndex & /*parent*/) const +{ + return m_tokens.size(); +} + +QHash ConditionListModel::roleNames() const +{ + static QHash roleNames{{Qt::UserRole + 1, "type"}, {Qt::UserRole + 2, "value"}}; + return roleNames; +} + +QVariant ConditionListModel::data(const QModelIndex &index, int role) const +{ + if (index.isValid() && index.row() < rowCount()) { + if (role == Qt::UserRole + 1) { + return m_tokens.at(index.row()).type; + } else if (role == Qt::UserRole + 2) { + return m_tokens.at(index.row()).value; + } + + qCWarning(ScriptEditorLog) << __FUNCTION__ << "invalid role"; + } else { + qCWarning(ScriptEditorLog) << __FUNCTION__ << "invalid index"; + } + + return QVariant(); +} + +void ConditionListModel::setup() +{ + m_tokens.clear(); + + internalSetup(); + + emit validChanged(); + emit emptyChanged(); + + beginResetModel(); + endResetModel(); +} + +void ConditionListModel::setCondition(ScriptEditorStatements::MatchedCondition &condition) +{ + m_condition = condition; + setup(); +} + +ScriptEditorStatements::MatchedCondition &ConditionListModel::condition() +{ + return m_condition; +} + +ConditionListModel::ConditionToken ConditionListModel::tokenFromConditionToken( + const ScriptEditorStatements::ConditionToken &token) +{ + ConditionToken ret; + ret.type = Operator; + ret.value = ScriptEditorStatements::toJavascript(token); + + return ret; +} + +ConditionListModel::ConditionToken ConditionListModel::tokenFromComparativeStatement( + const ScriptEditorStatements::ComparativeStatement &token) +{ + ConditionToken ret; + + if (auto *variable = std::get_if(&token)) { + ret.type = Variable; + ret.value = variable->expression(); + return ret; + } else if (auto *literal = std::get_if(&token)) { + ret.type = Literal; + ret.value = "\"" + *literal + "\""; + return ret; + } else if (auto *literal = std::get_if(&token)) { + ret.type = Literal; + if (*literal) + ret.value = "true"; + else + ret.value = "false"; + return ret; + } else if (auto *literal = std::get_if(&token)) { + ret.type = Literal; + ret.value = QString::number(*literal); + return ret; + } + + ret.type = Invalid; + ret.value = "invalid"; + return {}; +} + +void ConditionListModel::insertToken(int index, const QString &value) +{ + beginInsertRows({}, index, index); + + m_tokens.insert(index, valueToToken(value)); + validateAndRebuildTokens(); + + endInsertRows(); +} + +void ConditionListModel::updateToken(int index, const QString &value) +{ + m_tokens[index] = valueToToken(value); + validateAndRebuildTokens(); + + emit dataChanged(createIndex(index, 0), createIndex(index, 0)); +} + +void ConditionListModel::appendToken(const QString &value) +{ + beginInsertRows({}, rowCount() - 1, rowCount() - 1); + + insertToken(rowCount(), value); + validateAndRebuildTokens(); + + endInsertRows(); +} + +void ConditionListModel::removeToken(int index) +{ + QTC_ASSERT(index < m_tokens.count(), return); + beginRemoveRows({}, index, index); + + m_tokens.remove(index, 1); + validateAndRebuildTokens(); + + endRemoveRows(); +} + +void ConditionListModel::insertIntermediateToken(int index, const QString &value) +{ + beginInsertRows({}, index, index); + + ConditionToken token; + token.type = Intermediate; + token.value = value; + + m_tokens.insert(index, token); + + endInsertRows(); +} + +void ConditionListModel::insertShadowToken(int index, const QString &value) +{ + beginInsertRows({}, index, index); + + ConditionToken token; + token.type = Shadow; + token.value = value; + + m_tokens.insert(index, token); + + endInsertRows(); +} + +void ConditionListModel::setShadowToken(int index, const QString &value) +{ + m_tokens[index].type = Shadow; + m_tokens[index].value = value; + + emit dataChanged(createIndex(index, 0), createIndex(index, 0)); +} + +bool ConditionListModel::valid() const +{ + return m_valid; +} + +bool ConditionListModel::empty() const +{ + return m_tokens.isEmpty(); +} + +void ConditionListModel::command(const QString &string) +{ + //TODO remove from prodcution code + QStringList list = string.split("%", Qt::SkipEmptyParts); + + if (list.size() < 2) + return; + + if (list.size() == 2) { + if (list.first() == "A") { + appendToken(list.last()); + } else if (list.first() == "R") { + bool ok = true; + int index = list.last().toInt(&ok); + + if (ok) + removeToken(index); + } + } + + if (list.size() == 3) { + if (list.first() == "U") { + bool ok = true; + int index = list.at(1).toInt(&ok); + + if (ok) + updateToken(index, list.last()); + } else if (list.first() == "I") { + bool ok = true; + int index = list.at(1).toInt(&ok); + + if (ok) + insertToken(index, list.last()); + } + } +} + +void ConditionListModel::setInvalid(const QString &errorMessage, int index) +{ + m_valid = false; + m_errorMessage = errorMessage; + + emit errorChanged(); + emit validChanged(); + + if (index != -1) { + m_errorIndex = index; + emit errorIndexChanged(); + } +} + +void ConditionListModel::setValid() +{ + m_valid = true; + m_errorMessage.clear(); + m_errorIndex = -1; + + emit errorChanged(); + emit validChanged(); + emit errorIndexChanged(); +} + +QString ConditionListModel::error() const +{ + return m_errorMessage; +} + +int ConditionListModel::errorIndex() const +{ + return m_errorIndex; +} + +bool ConditionListModel::operatorAllowed(int cursorPosition) +{ + if (m_tokens.empty()) + return false; + + int tokenIdx = cursorPosition - 1; + + if (tokenIdx >= 0 && tokenIdx < m_tokens.length() && m_tokens[tokenIdx].type != Operator) + return true; + + return false; +} + +void ConditionListModel::internalSetup() +{ + setInvalid(tr("No Valid Condition")); + if (!m_condition.statements.size() && !m_condition.tokens.size()) + return; + + if (m_condition.statements.size() != m_condition.tokens.size() + 1) + return; + + if (m_condition.statements.size() == 1 && m_condition.tokens.isEmpty()) { + auto token = tokenFromComparativeStatement(m_condition.statements.first()); + if (token.value == defaultCondition) + return; + } + + auto s_it = m_condition.statements.begin(); + auto o_it = m_condition.tokens.begin(); + + while (o_it != m_condition.tokens.end()) { + m_tokens.append(tokenFromComparativeStatement(*s_it)); + m_tokens.append(tokenFromConditionToken(*o_it)); + + s_it++; + o_it++; + } + m_tokens.append(tokenFromComparativeStatement(*s_it)); + + setValid(); +} + +ConditionListModel::ConditionToken ConditionListModel::valueToToken(const QString &value) +{ + const QStringList operators = {"&&", "||", "===", "!==", ">", ">=", "<", "<="}; + + if (operators.contains(value)) { + ConditionToken token; + token.type = Operator; + token.value = value; + return token; + } + + bool ok = false; + value.toDouble(&ok); + + if (value == "true" || value == "false" || ok || (value.startsWith("\"") && value.endsWith("\""))) { + ConditionToken token; + token.type = Literal; + token.value = value; + return token; + } + + static QRegularExpression regexp("^[a-z_]\\w*|^[A-Z]\\w*\\.{1}([a-z_]\\w*\\.?)+"); + QRegularExpressionMatch match = regexp.match(value); + + if (match.hasMatch()) { //variable + ConditionToken token; + token.type = Variable; + token.value = value; + return token; + } + + ConditionToken token; + token.type = Invalid; + token.value = value; + + return token; +} + +void ConditionListModel::resetModel() +{ + beginResetModel(); + endResetModel(); +} + +int ConditionListModel::checkOrder() const +{ + auto it = m_tokens.begin(); + + bool wasOperator = true; + + int ret = 0; + while (it != m_tokens.end()) { + if (wasOperator && it->type == Operator) + return ret; + if (!wasOperator && it->type == Literal) + return ret; + if (!wasOperator && it->type == Variable) + return ret; + wasOperator = it->type == Operator; + it++; + ret++; + } + + if (wasOperator) + return ret; + + return -1; +} + +void ConditionListModel::validateAndRebuildTokens() +{ + /// NEW + auto it = m_tokens.begin(); + + while (it != m_tokens.end()) { + if (it->type == Intermediate) + *it = valueToToken(it->value); + + it++; + } + // NEW + + QString invalidValue; + const bool invalidToken = Utils::contains(m_tokens, [&invalidValue](const ConditionToken &token) { + if (token.type == Invalid) + invalidValue = token.value; + return token.type == Invalid; + }); + + if (invalidToken) { + setInvalid(tr("Invalid token %1").arg(invalidValue)); + return; + } + + if (int firstError = checkOrder() != -1) { + setInvalid(tr("Invalid order at %1").arg(firstError), firstError); + return; + } + + setValid(); + + rebuildTokens(); +} + +ScriptEditorStatements::ConditionToken ConditionListModel::toOperatorStatement(const ConditionToken &token) +{ + if (token.value == "&&") + return ScriptEditorStatements::ConditionToken::And; + + if (token.value == "||") + return ScriptEditorStatements::ConditionToken::Or; + + if (token.value == "===") + return ScriptEditorStatements::ConditionToken::Equals; + + if (token.value == "!==") + return ScriptEditorStatements::ConditionToken::Not; + + if (token.value == ">") + return ScriptEditorStatements::ConditionToken::LargerThan; + + if (token.value == ">=") + return ScriptEditorStatements::ConditionToken::LargerEqualsThan; + + if (token.value == "<") + return ScriptEditorStatements::ConditionToken::SmallerThan; + + if (token.value == "<=") + return ScriptEditorStatements::ConditionToken::SmallerEqualsThan; + + return ScriptEditorStatements::ConditionToken::Unknown; +} + +static ScriptEditorStatements::ComparativeStatement parseTextArgumentComparativeStatement( + const QString &text) +{ + if (text.startsWith("\"") && text.endsWith("\"")) { + QString ret = text; + ret.remove(0, 1); + ret.chop(1); + return ret; + } + + if (text == "true") + return true; + + if (text == "false") + return false; + + bool ok = true; + double d = text.toDouble(&ok); + if (ok) + return d; + + return text; +} + +ScriptEditorStatements::ComparativeStatement ConditionListModel::toStatement(const ConditionToken &token) +{ + if (token.type == Variable) { + QStringList list = token.value.split("."); + ScriptEditorStatements::Variable variable; + + variable.nodeId = list.first(); + if (list.count() > 1) + variable.propertyName = list.last(); + return variable; + } else if (token.type == Literal) { + return parseTextArgumentComparativeStatement(token.value); + } + + return {}; +} + +void ConditionListModel::rebuildTokens() +{ + QTC_ASSERT(m_valid, return); + + m_condition.statements.clear(); + m_condition.tokens.clear(); + + auto it = m_tokens.begin(); + + while (it != m_tokens.end()) { + QTC_ASSERT(it->type != Invalid, return); + if (it->type == Operator) + m_condition.tokens.append(toOperatorStatement(*it)); + else if (it->type == Literal || it->type == Variable) + m_condition.statements.append(toStatement(*it)); + + it++; + } + + emit conditionChanged(); +} + +static ScriptEditorStatements::MatchedStatement emptyStatement; + +StatementDelegate::StatementDelegate(AbstractView *view) + : m_functionDelegate(view) + , m_lhsDelegate(view) + , m_rhsAssignmentDelegate(view) + , m_statement(emptyStatement) + , m_view(view) +{ + m_functionDelegate.setPropertyType(PropertyTreeModel::SlotType); + + connect(&m_functionDelegate, &PropertyTreeModelDelegate::commitData, this, [this] { + handleFunctionChanged(); + }); + + connect(&m_rhsAssignmentDelegate, &PropertyTreeModelDelegate::commitData, this, [this] { + handleRhsAssignmentChanged(); + }); + + connect(&m_lhsDelegate, &PropertyTreeModelDelegate::commitData, this, [this] { + handleLhsChanged(); + }); + + connect(&m_stringArgument, &StudioQmlTextBackend::activated, this, [this] { + handleStringArgumentChanged(); + }); + + connect(&m_states, &StudioQmlComboBoxBackend::activated, this, [this] { handleStateChanged(); }); + + connect(&m_stateTargets, &StudioQmlComboBoxBackend::activated, this, [this] { + handleStateTargetsChanged(); + }); +} + +void StatementDelegate::setActionType(ActionType type) +{ + if (m_actionType == type) + return; + + m_actionType = type; + emit actionTypeChanged(); + setup(); +} + +void StatementDelegate::setup() +{ + switch (m_actionType) { + case CallFunction: + setupCallFunction(); + break; + case Assign: + setupAssignment(); + break; + case ChangeState: + setupChangeState(); + break; + case SetProperty: + setupSetProperty(); + break; + case PrintMessage: + setupPrintMessage(); + break; + case Custom: + break; + }; +} + +void StatementDelegate::setStatement(ScriptEditorStatements::MatchedStatement &statement) +{ + m_statement = statement; + setup(); +} + +ScriptEditorStatements::MatchedStatement &StatementDelegate::statement() +{ + return m_statement; +} + +StatementDelegate::ActionType StatementDelegate::actionType() const +{ + return m_actionType; +} + +PropertyTreeModelDelegate *StatementDelegate::function() +{ + return &m_functionDelegate; +} + +PropertyTreeModelDelegate *StatementDelegate::lhs() +{ + return &m_lhsDelegate; +} + +PropertyTreeModelDelegate *StatementDelegate::rhsAssignment() +{ + return &m_rhsAssignmentDelegate; +} + +StudioQmlTextBackend *StatementDelegate::stringArgument() +{ + return &m_stringArgument; +} + +StudioQmlComboBoxBackend *StatementDelegate::stateTargets() +{ + return &m_stateTargets; +} + +StudioQmlComboBoxBackend *StatementDelegate::states() +{ + return &m_states; +} + +void StatementDelegate::handleFunctionChanged() +{ + QTC_ASSERT(std::holds_alternative(m_statement), return); + + ScriptEditorStatements::MatchedFunction &functionStatement = std::get( + m_statement); + + functionStatement.functionName = m_functionDelegate.name(); + functionStatement.nodeId = m_functionDelegate.id(); + + emit statementChanged(); +} + +void StatementDelegate::handleLhsChanged() +{ + if (m_actionType == Assign) { + QTC_ASSERT(std::holds_alternative(m_statement), return); + + ScriptEditorStatements::Assignment &assignmentStatement = std::get( + m_statement); + + assignmentStatement.lhs.nodeId = m_lhsDelegate.id(); + assignmentStatement.lhs.propertyName = m_lhsDelegate.name(); + + } else if (m_actionType == SetProperty) { + QTC_ASSERT(std::holds_alternative(m_statement), return); + + ScriptEditorStatements::PropertySet &setPropertyStatement = std::get( + m_statement); + + setPropertyStatement.lhs.nodeId = m_lhsDelegate.id(); + setPropertyStatement.lhs.propertyName = m_lhsDelegate.name(); + } else { + QTC_ASSERT(false, return); + } + + emit statementChanged(); +} + +void StatementDelegate::handleRhsAssignmentChanged() +{ + QTC_ASSERT(std::holds_alternative(m_statement), return); + + ScriptEditorStatements::Assignment &assignmentStatement = std::get( + m_statement); + + assignmentStatement.rhs.nodeId = m_rhsAssignmentDelegate.id(); + assignmentStatement.rhs.propertyName = m_rhsAssignmentDelegate.name(); + + setupPropertyType(); + + emit statementChanged(); +} + +static ScriptEditorStatements::Literal parseTextArgument(const QString &text) +{ + if (text.startsWith("\"") && text.endsWith("\"")) { + QString ret = text; + ret.remove(0, 1); + ret.chop(1); + return ret; + } + + if (text == "true") + return true; + + if (text == "false") + return false; + + bool ok = true; + double d = text.toDouble(&ok); + if (ok) + return d; + + return text; +} + +static ScriptEditorStatements::RightHandSide parseLogTextArgument(const QString &text) +{ + if (text.startsWith("\"") && text.endsWith("\"")) { + QString ret = text; + ret.remove(0, 1); + ret.chop(1); + return ret; + } + + if (text == "true") + return true; + + if (text == "false") + return true; + + bool ok = true; + double d = text.toDouble(&ok); + if (ok) + return d; + + //TODO variables and function calls + return text; +} + +void StatementDelegate::handleStringArgumentChanged() +{ + if (m_actionType == SetProperty) { + QTC_ASSERT(std::holds_alternative(m_statement), return); + + ScriptEditorStatements::PropertySet &propertySet = std::get( + m_statement); + + propertySet.rhs = parseTextArgument(m_stringArgument.text()); + + } else if (m_actionType == PrintMessage) { + QTC_ASSERT(std::holds_alternative(m_statement), return); + + ScriptEditorStatements::ConsoleLog &consoleLog = std::get( + m_statement); + + consoleLog.argument = parseLogTextArgument(m_stringArgument.text()); + } else { + QTC_ASSERT(false, return); + } + + emit statementChanged(); +} + +void StatementDelegate::handleStateChanged() +{ + QTC_ASSERT(std::holds_alternative(m_statement), return); + + ScriptEditorStatements::StateSet &stateSet = std::get( + m_statement); + + QString stateName = m_states.currentText(); + if (stateName == baseStateName()) + stateName = ""; + stateSet.stateName = "\"" + stateName + "\""; + + emit statementChanged(); +} + +void StatementDelegate::handleStateTargetsChanged() +{ + QTC_ASSERT(std::holds_alternative(m_statement), return); + + ScriptEditorStatements::StateSet &stateSet = std::get( + m_statement); + + stateSet.nodeId = m_stateTargets.currentText(); + stateSet.stateName = "\"\""; + + setupStates(); + + emit statementChanged(); +} + +void StatementDelegate::setupAssignment() +{ + QTC_ASSERT(std::holds_alternative(m_statement), return); + + const auto assignment = std::get(m_statement); + m_lhsDelegate.setup(assignment.lhs.nodeId, assignment.lhs.propertyName); + m_rhsAssignmentDelegate.setup(assignment.rhs.nodeId, assignment.rhs.propertyName); + setupPropertyType(); +} + +void StatementDelegate::setupSetProperty() +{ + QTC_ASSERT(std::holds_alternative(m_statement), return); + + const auto propertySet = std::get(m_statement); + m_lhsDelegate.setup(propertySet.lhs.nodeId, propertySet.lhs.propertyName); + m_stringArgument.setText(ScriptEditorStatements::toString(propertySet.rhs)); +} + +void StatementDelegate::setupCallFunction() +{ + QTC_ASSERT(std::holds_alternative(m_statement), return); + + const auto functionStatement = std::get(m_statement); + m_functionDelegate.setup(functionStatement.nodeId, functionStatement.functionName); +} + +void StatementDelegate::setupChangeState() +{ + QTC_ASSERT(std::holds_alternative(m_statement), return); + + QTC_ASSERT(m_view->isAttached(), return); + + auto model = m_view->model(); + const auto items = Utils::filtered(m_view->allModelNodesOfType(model->qtQuickItemMetaInfo()), + [](const ModelNode &node) { + QmlItemNode item(node); + return node.hasId() && item.isValid() + && !item.allStateNames().isEmpty(); + }); + + QStringList itemIds = Utils::transform(items, &ModelNode::id); + const auto groups = m_view->allModelNodesOfType(model->qtQuickStateGroupMetaInfo()); + + const auto rootId = m_view->rootModelNode().id(); + itemIds.removeAll(rootId); + + QStringList groupIds = Utils::transform(groups, &ModelNode::id); + + Utils::sort(itemIds); + Utils::sort(groupIds); + + if (!rootId.isEmpty()) + groupIds.prepend(rootId); + + const QStringList stateGroupModel = groupIds + itemIds; + m_stateTargets.setModel(stateGroupModel); + + const auto stateSet = std::get(m_statement); + + m_stateTargets.setCurrentText(stateSet.nodeId); + setupStates(); +} + +static QString stripQuotesFromState(const QString &input) +{ + if (input.startsWith("\"") && input.endsWith("\"")) { + QString ret = input; + ret.remove(0, 1); + ret.chop(1); + return ret; + } + return input; +} + +void StatementDelegate::setupStates() +{ + QTC_ASSERT(std::holds_alternative(m_statement), return); + QTC_ASSERT(m_view->isAttached(), return); + + const auto stateSet = std::get(m_statement); + + const QString nodeId = m_stateTargets.currentText(); + + const ModelNode node = m_view->modelNodeForId(nodeId); + + QStringList states; + if (node.metaInfo().isQtQuickItem()) { + QmlItemNode item(node); + QTC_ASSERT(item.isValid(), return); + if (item.isRootNode()) + states = item.states().names(); //model + else + states = item.allStateNames(); //instances + } else { + QmlModelStateGroup group(node); + states = group.names(); //model + } + + const QString stateName = stripQuotesFromState(stateSet.stateName); + + states.prepend(baseStateName()); + m_states.setModel(states); + if (stateName.isEmpty()) + m_states.setCurrentText(baseStateName()); + else + m_states.setCurrentText(stateName); +} + +void StatementDelegate::setupPrintMessage() +{ + QTC_ASSERT(std::holds_alternative(m_statement), return); + + const auto consoleLog = std::get(m_statement); + m_stringArgument.setText(ScriptEditorStatements::toString(consoleLog.argument)); +} + +void StatementDelegate::setupPropertyType() +{ + PropertyTreeModel::PropertyTypes type = PropertyTreeModel::AllTypes; + + const NodeMetaInfo metaInfo = m_rhsAssignmentDelegate.propertyMetaInfo(); + + if (metaInfo.isBool()) + type = PropertyTreeModel::BoolType; + else if (metaInfo.isNumber()) + type = PropertyTreeModel::NumberType; + else if (metaInfo.isColor()) + type = PropertyTreeModel::ColorType; + else if (metaInfo.isString()) + type = PropertyTreeModel::StringType; + else if (metaInfo.isUrl()) + type = PropertyTreeModel::UrlType; + + m_lhsDelegate.setPropertyType(type); +} + +QString StatementDelegate::baseStateName() const +{ + return tr("Base State"); +} + +ScriptEditorBackend::ScriptEditorBackend(AbstractView *view) + : m_okStatementDelegate(view) + , m_koStatementDelegate(view) + , m_conditionListModel(view) + , m_propertyTreeModel(view) + , m_propertyListProxyModel(&m_propertyTreeModel) + , m_view(view) +{ + connect(&m_okStatementDelegate, &StatementDelegate::statementChanged, this, [this] { + handleOkStatementChanged(); + }); + + connect(&m_koStatementDelegate, &StatementDelegate::statementChanged, this, [this] { + handleKOStatementChanged(); + }); + + connect(&m_conditionListModel, &ConditionListModel::conditionChanged, this, [this] { + handleConditionChanged(); + }); +} + +static QString generateDefaultStatement(ScriptEditorBackend::ActionType actionType, + const QString &rootId) +{ + switch (actionType) { + case StatementDelegate::CallFunction: + return "Qt.quit()"; + case StatementDelegate::Assign: + return QString("%1.visible = %1.visible").arg(rootId); + case StatementDelegate::ChangeState: + return QString("%1.state = \"\"").arg(rootId); + case StatementDelegate::SetProperty: + return QString("%1.visible = true").arg(rootId); + case StatementDelegate::PrintMessage: + return QString("console.log(\"test\")").arg(rootId); + case StatementDelegate::Custom: + return {}; + }; + + return {}; +} + +void ScriptEditorBackend::changeActionType(ActionType actionType) +{ + QTC_ASSERT(actionType != StatementDelegate::Custom, return); + + AbstractView *view = m_view; + + QTC_ASSERT(view, return); + QTC_ASSERT(view->isAttached(), return); + + view->executeInTransaction("ScriptEditorBackend::removeCondition", [&]() { + ScriptEditorStatements::MatchedStatement &okStatement = ScriptEditorStatements::okStatement( + m_handler); + + ScriptEditorStatements::MatchedStatement &koStatement = ScriptEditorStatements::koStatement( + m_handler); + + koStatement = ScriptEditorStatements::EmptyBlock(); + + //We expect a valid id on the root node + const QString validId = view->rootModelNode().validId(); + QString statementSource = generateDefaultStatement(actionType, validId); + + auto tempHandler = ScriptEditorEvaluator::parseStatement(statementSource); //what's that? + + auto newOkStatement = ScriptEditorStatements::okStatement(tempHandler); + + QTC_ASSERT(!ScriptEditorStatements::isEmptyStatement(newOkStatement), return); + + okStatement = newOkStatement; + + QString newSource = ScriptEditorStatements::toJavascript(m_handler); + + setPropertySource(newSource); + }); + + setSource(getSourceFromProperty(getSourceProperty())); + + setupHandlerAndStatements(); + setupCondition(); +} + +void ScriptEditorBackend::addCondition() +{ + ScriptEditorStatements::MatchedStatement okStatement = ScriptEditorStatements::okStatement( + m_handler); + + ScriptEditorStatements::MatchedCondition newCondition; + + ScriptEditorStatements::Variable variable; + variable.nodeId = defaultCondition; + newCondition.statements.append(variable); + + ScriptEditorStatements::ConditionalStatement conditionalStatement; + + conditionalStatement.ok = okStatement; + conditionalStatement.condition = newCondition; + + m_handler = conditionalStatement; + + QString newSource = ScriptEditorStatements::toJavascript(m_handler); + + commitNewSource(newSource); + + setupHandlerAndStatements(); + setupCondition(); +} + +void ScriptEditorBackend::removeCondition() +{ + ScriptEditorStatements::MatchedStatement okStatement = ScriptEditorStatements::okStatement( + m_handler); + + m_handler = okStatement; + + QString newSource = ScriptEditorStatements::toJavascript(m_handler); + + commitNewSource(newSource); + + setupHandlerAndStatements(); + setupCondition(); +} + +void ScriptEditorBackend::addElse() +{ + ScriptEditorStatements::MatchedStatement okStatement = ScriptEditorStatements::okStatement( + m_handler); + + auto &condition = ScriptEditorStatements::conditionalStatement(m_handler); + condition.ko = condition.ok; + + QString newSource = ScriptEditorStatements::toJavascript(m_handler); + + commitNewSource(newSource); + setupHandlerAndStatements(); +} + +void ScriptEditorBackend::removeElse() +{ + ScriptEditorStatements::MatchedStatement okStatement = ScriptEditorStatements::okStatement( + m_handler); + + auto &condition = ScriptEditorStatements::conditionalStatement(m_handler); + condition.ko = ScriptEditorStatements::EmptyBlock(); + + QString newSource = ScriptEditorStatements::toJavascript(m_handler); + + commitNewSource(newSource); + setupHandlerAndStatements(); +} + +void ScriptEditorBackend::setNewSource(const QString &newSource) +{ + setSource(newSource); + commitNewSource(newSource); + setupHandlerAndStatements(); + setupCondition(); +} + +void ScriptEditorBackend::update() +{ + if (m_blockReflection) + return; + + m_propertyTreeModel.resetModel(); + m_propertyListProxyModel.setRowAndInternalId(0, internalRootIndex); + + AbstractView *view = m_view; + + QTC_ASSERT(view, return); + if (!view->isAttached()) + return; + + // setup print message as default action if property does not exists + auto sourceProperty = getSourceProperty(); + if (sourceProperty.exists()) + setSource(getSourceFromProperty(sourceProperty)); + else + setSource(QStringLiteral("console.log(\"%1\")").arg(sourceProperty.parentModelNode().id())); + + setupHandlerAndStatements(); + + setupCondition(); +} + +void ScriptEditorBackend::jumpToCode() +{ + AbstractView *view = m_view; + + QTC_ASSERT(view, return); + QTC_ASSERT(view->isAttached(), return); + auto sourceProperty = getSourceProperty(); + + ModelNodeOperations::jumpToCode(sourceProperty.parentModelNode()); +} + +void ScriptEditorBackend::registerDeclarativeType() +{ + qmlRegisterType("ScriptEditorBackend", 1, 0, "StatementDelegate"); + qmlRegisterType("ScriptEditorBackend", 1, 0, "PropertyTreeModel"); + qmlRegisterType("ScriptEditorBackend", 1, 0, "ConditionListModel"); + qmlRegisterType("ScriptEditorBackend", 1, 0, "PropertyListProxyModel"); + + QTC_ASSERT(!QmlDesignerPlugin::viewManager().views().isEmpty(), return); + AbstractView *view = QmlDesignerPlugin::viewManager().views().first(); + + qmlRegisterSingletonType("ScriptEditorBackend", + 1, + 0, + "ScriptEditorBackend", + [view](QQmlEngine *, QJSEngine *) { + return new ScriptEditorBackend(view); + }); +} + +bool ScriptEditorBackend::blockReflection() const +{ + return m_blockReflection; +} + +BindingProperty ScriptEditorBackend::getBindingProperty() const +{ + AbstractView *view = m_view; + + QTC_ASSERT(view, return {}); + QTC_ASSERT(view->isAttached(), return {}); + + return SelectionContext{view}.currentSingleSelectedNode().bindingProperty("script"); +} + +void ScriptEditorBackend::handleException() +{ + QMessageBox::warning(nullptr, tr("Error"), m_exceptionError); +} + +bool ScriptEditorBackend::hasCondition() const +{ + return m_hasCondition; +} + +bool ScriptEditorBackend::hasElse() const +{ + return m_hasElse; +} + +void ScriptEditorBackend::setHasCondition(bool b) +{ + if (b == m_hasCondition) + return; + + m_hasCondition = b; + emit hasConditionChanged(); +} + +void ScriptEditorBackend::setHasElse(bool b) +{ + if (b == m_hasElse) + return; + + m_hasElse = b; + emit hasElseChanged(); +} + +ScriptEditorBackend::ActionType ScriptEditorBackend::actionType() const +{ + return m_actionType; +} + +AbstractProperty ScriptEditorBackend::getSourceProperty() const +{ + return getBindingProperty(); +} + +StatementDelegate *ScriptEditorBackend::okStatement() +{ + return &m_okStatementDelegate; +} + +StatementDelegate *ScriptEditorBackend::koStatement() +{ + return &m_koStatementDelegate; +} + +ConditionListModel *ScriptEditorBackend::conditionListModel() +{ + return &m_conditionListModel; +} + +QString ScriptEditorBackend::indentedSource() const +{ + if (m_source.isEmpty()) + return {}; + + QTextDocument doc(m_source); + IndentingTextEditModifier mod(&doc); + + mod.indent(0, m_source.length() - 1); + return mod.text(); +} + +QString ScriptEditorBackend::source() const +{ + return m_source; +} + +void ScriptEditorBackend::setSource(const QString &source) +{ + if (source == m_source) + return; + + m_source = source; + emit sourceChanged(); +} + +PropertyTreeModel *ScriptEditorBackend::propertyTreeModel() +{ + return &m_propertyTreeModel; +} + +PropertyListProxyModel *ScriptEditorBackend::propertyListProxyModel() +{ + return &m_propertyListProxyModel; +} + +void ScriptEditorBackend::setupCondition() +{ + auto &condition = ScriptEditorStatements::matchedCondition(m_handler); + m_conditionListModel.setCondition(ScriptEditorStatements::matchedCondition(m_handler)); + setHasCondition(!condition.statements.isEmpty()); +} + +void ScriptEditorBackend::setupHandlerAndStatements() +{ + AbstractView *view = m_view; + QTC_ASSERT(view, return); + + if (m_source.isEmpty()) { + m_actionType = StatementDelegate::Custom; + m_handler = ScriptEditorStatements::EmptyBlock(); + } else { + m_handler = ScriptEditorEvaluator::parseStatement(m_source); + const QString statementType = QmlDesigner::ScriptEditorStatements::toDisplayName(m_handler); + if (statementType == ScriptEditorStatements::EMPTY_DISPLAY_NAME) { + m_actionType = StatementDelegate::Custom; + } else if (statementType == ScriptEditorStatements::ASSIGNMENT_DISPLAY_NAME) { + m_actionType = StatementDelegate::Assign; + } else if (statementType == ScriptEditorStatements::SETPROPERTY_DISPLAY_NAME) { + m_actionType = StatementDelegate::SetProperty; + } else if (statementType == ScriptEditorStatements::FUNCTION_DISPLAY_NAME) { + m_actionType = StatementDelegate::CallFunction; + } else if (statementType == ScriptEditorStatements::SETSTATE_DISPLAY_NAME) { + m_actionType = StatementDelegate::ChangeState; + } else if (statementType == ScriptEditorStatements::LOG_DISPLAY_NAME) { + m_actionType = StatementDelegate::PrintMessage; + } else { + m_actionType = StatementDelegate::Custom; + } + } + + ScriptEditorStatements::MatchedStatement &okStatement = ScriptEditorStatements::okStatement( + m_handler); + m_okStatementDelegate.setStatement(okStatement); + m_okStatementDelegate.setActionType(m_actionType); + + ScriptEditorStatements::MatchedStatement &koStatement = ScriptEditorStatements::koStatement( + m_handler); + + if (!ScriptEditorStatements::isEmptyStatement(koStatement)) { + m_koStatementDelegate.setStatement(koStatement); + m_koStatementDelegate.setActionType(m_actionType); + } + + setHasElse(!ScriptEditorStatements::isEmptyStatement(koStatement)); + + emit actionTypeChanged(); +} + +void ScriptEditorBackend::handleOkStatementChanged() +{ + ScriptEditorStatements::MatchedStatement &okStatement = ScriptEditorStatements::okStatement( + m_handler); + + okStatement = m_okStatementDelegate.statement(); //TODO why? + + QString newSource = ScriptEditorStatements::toJavascript(m_handler); + + commitNewSource(newSource); +} + +void ScriptEditorBackend::handleKOStatementChanged() +{ + ScriptEditorStatements::MatchedStatement &koStatement = ScriptEditorStatements::koStatement( + m_handler); + + koStatement = m_koStatementDelegate.statement(); //TODO why? + + QString newSource = ScriptEditorStatements::toJavascript(m_handler); + + commitNewSource(newSource); +} + +void ScriptEditorBackend::handleConditionChanged() +{ + AbstractView *view = m_view; + + QTC_ASSERT(view, return); + QTC_ASSERT(view->isAttached(), return); + + ScriptEditorStatements::MatchedCondition &condition = ScriptEditorStatements::matchedCondition( + m_handler); + condition = m_conditionListModel.condition(); //why? + QString newSource = ScriptEditorStatements::toJavascript(m_handler); + + commitNewSource(newSource); +} + +void ScriptEditorBackend::setPropertySource(const QString &source) +{ + auto property = getSourceProperty(); + + QTC_ASSERT(property.isValid(), return); + + if (source.isEmpty()) + return; + + auto normalizedSource = QmlDesigner::SignalHandlerProperty::normalizedSourceWithBraces(source); + if (property.exists()) + property.toBindingProperty().setExpression(normalizedSource); + else + property.parentModelNode().bindingProperty(property.name()).setExpression(normalizedSource); +} + +void ScriptEditorBackend::commitNewSource(const QString &source) +{ + AbstractView *view = m_view; + + QTC_ASSERT(view, return); + QTC_ASSERT(view->isAttached(), return); + + m_blockReflection = true; + view->executeInTransaction("ScriptEditorBackend::commitNewSource", + [&]() { setPropertySource(source); }); + setSource(getSourceFromProperty(getSourceProperty())); + m_blockReflection = false; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/scripteditor/scripteditorbackend.h b/src/plugins/qmldesigner/components/scripteditor/scripteditorbackend.h new file mode 100644 index 00000000000..64012c195b4 --- /dev/null +++ b/src/plugins/qmldesigner/components/scripteditor/scripteditorbackend.h @@ -0,0 +1,252 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "propertytreemodel.h" +#include "scripteditorstatements.h" + +namespace QmlDesigner { + +class ConditionListModel : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(bool valid READ valid NOTIFY validChanged) + Q_PROPERTY(bool empty READ empty NOTIFY emptyChanged) + Q_PROPERTY(QString error READ error NOTIFY errorChanged) + Q_PROPERTY(int errorIndex READ errorIndex NOTIFY errorIndexChanged) + +public: + enum ConditionType { Intermediate, Invalid, Operator, Literal, Variable, Shadow }; + Q_ENUM(ConditionType) + + struct ConditionToken + { + ConditionType type; + QString value; + }; + + ConditionListModel(AbstractView *view); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + QHash roleNames() const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + void setup(); + void setCondition(ScriptEditorStatements::MatchedCondition &condition); + ScriptEditorStatements::MatchedCondition &condition(); + + static ConditionToken tokenFromConditionToken(const ScriptEditorStatements::ConditionToken &token); + + static ConditionToken tokenFromComparativeStatement( + const ScriptEditorStatements::ComparativeStatement &token); + + Q_INVOKABLE void insertToken(int index, const QString &value); + Q_INVOKABLE void updateToken(int index, const QString &value); + Q_INVOKABLE void appendToken(const QString &value); + Q_INVOKABLE void removeToken(int index); + + Q_INVOKABLE void insertIntermediateToken(int index, const QString &value); + Q_INVOKABLE void insertShadowToken(int index, const QString &value); + Q_INVOKABLE void setShadowToken(int index, const QString &value); + + bool valid() const; + bool empty() const; + + //for debugging + Q_INVOKABLE void command(const QString &string); + + void setInvalid(const QString &errorMessage, int index = -1); + void setValid(); + + QString error() const; + int errorIndex() const; + + Q_INVOKABLE bool operatorAllowed(int cursorPosition); + +signals: + void validChanged(); + void emptyChanged(); + void conditionChanged(); + void errorChanged(); + void errorIndexChanged(); + +private: + void internalSetup(); + ConditionToken valueToToken(const QString &value); + void resetModel(); + int checkOrder() const; + void validateAndRebuildTokens(); + void rebuildTokens(); + + ScriptEditorStatements::ConditionToken toOperatorStatement(const ConditionToken &token); + ScriptEditorStatements::ComparativeStatement toStatement(const ConditionToken &token); + + AbstractView *m_view = nullptr; + ScriptEditorStatements::MatchedCondition &m_condition; + QList m_tokens; + bool m_valid = false; + QString m_errorMessage; + int m_errorIndex = -1; +}; + +class StatementDelegate : public QObject +{ + Q_OBJECT + +public: + explicit StatementDelegate(AbstractView *view); + + enum ActionType { CallFunction, Assign, ChangeState, SetProperty, PrintMessage, Custom }; + + Q_ENUM(ActionType) + + Q_PROPERTY(ActionType actionType READ actionType NOTIFY actionTypeChanged) + + Q_PROPERTY(PropertyTreeModelDelegate *function READ function CONSTANT) + Q_PROPERTY(PropertyTreeModelDelegate *lhs READ lhs CONSTANT) + Q_PROPERTY(PropertyTreeModelDelegate *rhsAssignment READ rhsAssignment CONSTANT) + Q_PROPERTY(StudioQmlTextBackend *stringArgument READ stringArgument CONSTANT) + Q_PROPERTY(StudioQmlComboBoxBackend *states READ states CONSTANT) + Q_PROPERTY(StudioQmlComboBoxBackend *stateTargets READ stateTargets CONSTANT) + + void setActionType(ActionType type); + void setup(); + void setStatement(ScriptEditorStatements::MatchedStatement &statement); + ScriptEditorStatements::MatchedStatement &statement(); + +signals: + void actionTypeChanged(); + void statementChanged(); + +private: + ActionType actionType() const; + PropertyTreeModelDelegate *function(); + PropertyTreeModelDelegate *lhs(); + PropertyTreeModelDelegate *rhsAssignment(); + StudioQmlTextBackend *stringArgument(); + StudioQmlComboBoxBackend *stateTargets(); + StudioQmlComboBoxBackend *states(); + + void handleFunctionChanged(); + void handleLhsChanged(); + void handleRhsAssignmentChanged(); + void handleStringArgumentChanged(); + void handleStateChanged(); + void handleStateTargetsChanged(); + + void setupAssignment(); + void setupSetProperty(); + void setupCallFunction(); + void setupChangeState(); + void setupStates(); + void setupPrintMessage(); + void setupPropertyType(); + QString baseStateName() const; + + ActionType m_actionType; + PropertyTreeModelDelegate m_functionDelegate; + PropertyTreeModelDelegate m_lhsDelegate; + PropertyTreeModelDelegate m_rhsAssignmentDelegate; + ScriptEditorStatements::MatchedStatement &m_statement; + AbstractView *m_view = nullptr; + StudioQmlTextBackend m_stringArgument; + StudioQmlComboBoxBackend m_stateTargets; + StudioQmlComboBoxBackend m_states; +}; + +class ScriptEditorBackend : public QObject +{ + Q_OBJECT + Q_PROPERTY(ActionType actionType READ actionType NOTIFY actionTypeChanged) + Q_PROPERTY(StatementDelegate *okStatement READ okStatement CONSTANT) + Q_PROPERTY(StatementDelegate *koStatement READ koStatement CONSTANT) + Q_PROPERTY(ConditionListModel *conditionListModel READ conditionListModel CONSTANT) + Q_PROPERTY(bool hasCondition READ hasCondition NOTIFY hasConditionChanged) + Q_PROPERTY(bool hasElse READ hasElse NOTIFY hasElseChanged) + Q_PROPERTY(QString source READ source NOTIFY sourceChanged) + Q_PROPERTY(QString indentedSource READ indentedSource NOTIFY sourceChanged) + + Q_PROPERTY(PropertyTreeModel *propertyTreeModel READ propertyTreeModel CONSTANT) + Q_PROPERTY(PropertyListProxyModel *propertyListProxyModel READ propertyListProxyModel CONSTANT) + +public: + explicit ScriptEditorBackend(AbstractView *view); + + using ActionType = StatementDelegate::ActionType; + + Q_INVOKABLE void changeActionType(QmlDesigner::StatementDelegate::ActionType actionType); + + Q_INVOKABLE void addCondition(); + Q_INVOKABLE void removeCondition(); + + Q_INVOKABLE void addElse(); + Q_INVOKABLE void removeElse(); + + Q_INVOKABLE void setNewSource(const QString &newSource); + + Q_INVOKABLE virtual void update(); + + Q_INVOKABLE void jumpToCode(); + + static void registerDeclarativeType(); + +signals: + void actionTypeChanged(); + void hasConditionChanged(); + void hasElseChanged(); + void sourceChanged(); + void popupShouldClose(); + void popupShouldOpen(); + +protected: + bool blockReflection() const; + ; + +private: + virtual AbstractProperty getSourceProperty() const; + virtual void setPropertySource(const QString &source); + + BindingProperty getBindingProperty() const; + void handleException(); + bool hasCondition() const; + bool hasElse() const; + void setHasCondition(bool b); + void setHasElse(bool b); + ActionType actionType() const; + StatementDelegate *okStatement(); + StatementDelegate *koStatement(); + ConditionListModel *conditionListModel(); + QString indentedSource() const; + QString source() const; + void setSource(const QString &source); + + PropertyTreeModel *propertyTreeModel(); + PropertyListProxyModel *propertyListProxyModel(); + + void setupCondition(); + void setupHandlerAndStatements(); + + void handleOkStatementChanged(); + void handleKOStatementChanged(); + void handleConditionChanged(); + + void commitNewSource(const QString &source); + + ActionType m_actionType; + QString m_exceptionError; + ScriptEditorStatements::Handler m_handler; + StatementDelegate m_okStatementDelegate; + StatementDelegate m_koStatementDelegate; + ConditionListModel m_conditionListModel; + bool m_hasCondition = false; + bool m_hasElse = false; + QString m_source; + PropertyTreeModel m_propertyTreeModel; + PropertyListProxyModel m_propertyListProxyModel; + bool m_blockReflection = false; + QPointer m_view = nullptr; +}; +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorevaluator.cpp b/src/plugins/qmldesigner/components/scripteditor/scripteditorevaluator.cpp similarity index 80% rename from src/plugins/qmldesigner/components/connectioneditor/connectioneditorevaluator.cpp rename to src/plugins/qmldesigner/components/scripteditor/scripteditorevaluator.cpp index 7bea5cd2cdd..2f6d8ba6c2f 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorevaluator.cpp +++ b/src/plugins/qmldesigner/components/scripteditor/scripteditorevaluator.cpp @@ -1,10 +1,10 @@ // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "connectioneditorevaluator.h" -#include "connectioneditorutils.h" +#include "scripteditorevaluator.h" #include "qmljs/parser/qmljsast_p.h" #include "qmljs/qmljsdocument.h" +#include "scripteditorutils.h" #include @@ -14,10 +14,10 @@ using namespace QmlDesigner; using QmlJS::AST::Node; using Kind = Node::Kind; -using ConnectionEditorStatements::ConditionalStatement; -using ConnectionEditorStatements::ConditionToken; -using ConnectionEditorStatements::MatchedCondition; -using ConnectionEditorStatements::MatchedStatement; +using ScriptEditorStatements::ConditionalStatement; +using ScriptEditorStatements::ConditionToken; +using ScriptEditorStatements::MatchedCondition; +using ScriptEditorStatements::MatchedStatement; namespace { enum class TrackingArea { No, Condition, Ok, Ko }; @@ -255,14 +255,14 @@ protected: void throwRecursionDepthError() override { checkValidityAndReturn(false, "Recursion depth problem"); - qCWarning(ConnectionEditorLog) << __FUNCTION__ << "Recursion depth error"; + qCWarning(ScriptEditorLog) << __FUNCTION__ << "Recursion depth error"; } void checkAndResetVariable() { if (--m_depth == 0) { m_condition.statements.push_back( - ConnectionEditorStatements::Variable{m_identifier, m_fields.join(".")}); + ScriptEditorStatements::Variable{m_identifier, m_fields.join(".")}); m_identifier.clear(); m_fields.clear(); } @@ -300,7 +300,7 @@ private: class RightHandVisitor : public QmlJS::AST::Visitor { public: - ConnectionEditorStatements::RightHandSide rhs() const { return m_rhs; } + ScriptEditorStatements::RightHandSide rhs() const { return m_rhs; } void reset() { @@ -318,48 +318,48 @@ public: if (!isValid()) return false; - return ConnectionEditorStatements::isLiteralType(rhs()); + return ScriptEditorStatements::isLiteralType(rhs()); } bool couldBeLHS() const { if (!isValid()) return false; - return std::holds_alternative(m_rhs); + return std::holds_alternative(m_rhs); } inline bool couldBeVariable() const { return couldBeLHS(); } - ConnectionEditorStatements::Literal literal() const + ScriptEditorStatements::Literal literal() const { if (!isLiteralType()) return {}; return std::visit( - Overload{[](const bool &var) -> ConnectionEditorStatements::Literal { return var; }, - [](const double &var) -> ConnectionEditorStatements::Literal { return var; }, - [](const QString &var) -> ConnectionEditorStatements::Literal { return var; }, - [](const auto &) -> ConnectionEditorStatements::Literal { return false; }}, + Overload{[](const bool &var) -> ScriptEditorStatements::Literal { return var; }, + [](const double &var) -> ScriptEditorStatements::Literal { return var; }, + [](const QString &var) -> ScriptEditorStatements::Literal { return var; }, + [](const auto &) -> ScriptEditorStatements::Literal { return false; }}, m_rhs); } - ConnectionEditorStatements::Variable lhs() const + ScriptEditorStatements::Variable lhs() const { if (!isValid()) return {}; - if (auto rhs = std::get_if(&m_rhs)) + if (auto rhs = std::get_if(&m_rhs)) return *rhs; return {}; } - ConnectionEditorStatements::Variable variable() const + ScriptEditorStatements::Variable variable() const { if (!isValid()) return {}; - if (auto rhs = std::get_if(&m_rhs)) + if (auto rhs = std::get_if(&m_rhs)) return *rhs; return {}; @@ -478,13 +478,13 @@ protected: void throwRecursionDepthError() override { setFailed(); - qCWarning(ConnectionEditorLog) << __FUNCTION__ << "Recursion depth error"; + qCWarning(ScriptEditorLog) << __FUNCTION__ << "Recursion depth error"; } void checkAndResetCal() { if (--m_depth == 0) { - m_rhs = ConnectionEditorStatements::MatchedFunction{m_identifier, m_fields.join(".")}; + m_rhs = ScriptEditorStatements::MatchedFunction{m_identifier, m_fields.join(".")}; m_specified = true; m_identifier.clear(); @@ -495,7 +495,7 @@ protected: void checkAndResetNonCal() { if (--m_depth == 0) { - m_rhs = ConnectionEditorStatements::Variable{m_identifier, m_fields.join(".")}; + m_rhs = ScriptEditorStatements::Variable{m_identifier, m_fields.join(".")}; m_specified = true; m_identifier.clear(); @@ -513,22 +513,23 @@ private: int m_depth = 0; QString m_identifier; QStringList m_fields; - ConnectionEditorStatements::RightHandSide m_rhs; + ScriptEditorStatements::RightHandSide m_rhs; }; MatchedStatement checkForStateSet(const MatchedStatement ¤tState) { - using namespace ConnectionEditorStatements; - return std::visit( - Overload{[](const PropertySet &propertySet) -> MatchedStatement { - if (propertySet.lhs.nodeId.size() && propertySet.lhs.propertyName == u"state" - && std::holds_alternative(propertySet.rhs)) - return StateSet{propertySet.lhs.nodeId, - ConnectionEditorStatements::toString(propertySet.rhs)}; - return propertySet; - }, - [](const auto &pSet) -> MatchedStatement { return pSet; }}, - currentState); + using namespace ScriptEditorStatements; + return std::visit(Overload{[](const PropertySet &propertySet) -> MatchedStatement { + if (propertySet.lhs.nodeId.size() + && propertySet.lhs.propertyName == u"state" + && std::holds_alternative(propertySet.rhs)) + return StateSet{propertySet.lhs.nodeId, + ScriptEditorStatements::toString( + propertySet.rhs)}; + return propertySet; + }, + [](const auto &pSet) -> MatchedStatement { return pSet; }}, + currentState); } class ConsoleLogEvaluator : public QmlJS::AST::Visitor @@ -536,9 +537,9 @@ class ConsoleLogEvaluator : public QmlJS::AST::Visitor public: bool isValid() { return m_completed; } - ConnectionEditorStatements::ConsoleLog expression() + ScriptEditorStatements::ConsoleLog expression() { - return ConnectionEditorStatements::ConsoleLog{m_arg}; + return ScriptEditorStatements::ConsoleLog{m_arg}; } protected: @@ -618,14 +619,14 @@ protected: void throwRecursionDepthError() override { m_failed = true; - qCWarning(ConnectionEditorLog) << __FUNCTION__ << "Recursion depth error"; + qCWarning(ScriptEditorLog) << __FUNCTION__ << "Recursion depth error"; } private: bool m_failed = false; bool m_completed = false; int m_stepId = 0; - ConnectionEditorStatements::RightHandSide m_arg; + ScriptEditorStatements::RightHandSide m_arg; }; struct StatementReply @@ -650,10 +651,10 @@ struct StatementReply } // namespace -class QmlDesigner::ConnectionEditorEvaluatorPrivate +class QmlDesigner::ScriptEditorEvaluatorPrivate { - friend class ConnectionEditorEvaluator; - using Status = ConnectionEditorEvaluator::Status; + friend class ScriptEditorEvaluator; + using Status = ScriptEditorEvaluator::Status; public: bool checkValidityAndReturn(bool valid, const QString &parseError = {}); @@ -731,28 +732,28 @@ private: QString m_errorString; Status m_checkStatus = Status::UnStarted; QList m_nodeHierarchy; - ConnectionEditorStatements::Handler m_handler; + ScriptEditorStatements::Handler m_handler; }; -ConnectionEditorEvaluator::ConnectionEditorEvaluator() - : d(std::make_unique()) +ScriptEditorEvaluator::ScriptEditorEvaluator() + : d(std::make_unique()) {} -ConnectionEditorEvaluator::~ConnectionEditorEvaluator() {} +ScriptEditorEvaluator::~ScriptEditorEvaluator() {} -ConnectionEditorEvaluator::Status ConnectionEditorEvaluator::status() const +ScriptEditorEvaluator::Status ScriptEditorEvaluator::status() const { return d->m_checkStatus; } -ConnectionEditorStatements::Handler ConnectionEditorEvaluator::resultNode() const +ScriptEditorStatements::Handler ScriptEditorEvaluator::resultNode() const { - return (d->m_checkStatus == Succeeded) ? d->m_handler : ConnectionEditorStatements::EmptyBlock{}; + return (d->m_checkStatus == Succeeded) ? d->m_handler : ScriptEditorStatements::EmptyBlock{}; } -QString ConnectionEditorEvaluator::getDisplayStringForType(const QString &statement) +QString ScriptEditorEvaluator::getDisplayStringForType(const QString &statement) { - ConnectionEditorEvaluator evaluator; + ScriptEditorEvaluator evaluator; QmlJS::Document::MutablePtr newDoc = QmlJS::Document::create(Utils::FilePath::fromString( ""), QmlJS::Dialect::JavaScript); @@ -761,23 +762,23 @@ QString ConnectionEditorEvaluator::getDisplayStringForType(const QString &statem newDoc->parseJavaScript(); if (!newDoc->isParsedCorrectly()) - return ConnectionEditorStatements::CUSTOM_DISPLAY_NAME; + return ScriptEditorStatements::CUSTOM_DISPLAY_NAME; newDoc->ast()->accept(&evaluator); - const bool valid = evaluator.status() == ConnectionEditorEvaluator::Succeeded; + const bool valid = evaluator.status() == ScriptEditorEvaluator::Succeeded; if (!valid) - return ConnectionEditorStatements::CUSTOM_DISPLAY_NAME; + return ScriptEditorStatements::CUSTOM_DISPLAY_NAME; auto result = evaluator.resultNode(); - return QmlDesigner::ConnectionEditorStatements::toDisplayName(result); + return QmlDesigner::ScriptEditorStatements::toDisplayName(result); } -ConnectionEditorStatements::Handler ConnectionEditorEvaluator::parseStatement(const QString &statement) +ScriptEditorStatements::Handler ScriptEditorEvaluator::parseStatement(const QString &statement) { - ConnectionEditorEvaluator evaluator; + ScriptEditorEvaluator evaluator; QmlJS::Document::MutablePtr newDoc = QmlJS::Document::create(Utils::FilePath::fromString( ""), QmlJS::Dialect::JavaScript); @@ -786,19 +787,19 @@ ConnectionEditorStatements::Handler ConnectionEditorEvaluator::parseStatement(co newDoc->parseJavaScript(); if (!newDoc->isParsedCorrectly()) - return ConnectionEditorStatements::EmptyBlock{}; + return ScriptEditorStatements::EmptyBlock{}; newDoc->ast()->accept(&evaluator); - const bool valid = evaluator.status() == ConnectionEditorEvaluator::Succeeded; + const bool valid = evaluator.status() == ScriptEditorEvaluator::Succeeded; if (!valid) - return ConnectionEditorStatements::EmptyBlock{}; + return ScriptEditorStatements::EmptyBlock{}; return evaluator.resultNode(); } -bool ConnectionEditorEvaluator::preVisit(Node *node) +bool ScriptEditorEvaluator::preVisit(Node *node) { if (d->m_nodeHierarchy.size()) { NodeStatus &parentNode = d->m_nodeHierarchy.last(); @@ -832,7 +833,7 @@ bool ConnectionEditorEvaluator::preVisit(Node *node) } } -void ConnectionEditorEvaluator::postVisit(QmlJS::AST::Node *node) +void ScriptEditorEvaluator::postVisit(QmlJS::AST::Node *node) { if (d->m_nodeHierarchy.isEmpty()) { d->checkValidityAndReturn(false, "Unexpected post visiting"); @@ -859,18 +860,18 @@ void ConnectionEditorEvaluator::postVisit(QmlJS::AST::Node *node) } } -bool ConnectionEditorEvaluator::visit([[maybe_unused]] QmlJS::AST::Program *program) +bool ScriptEditorEvaluator::visit([[maybe_unused]] QmlJS::AST::Program *program) { d->setStatus(UnFinished); d->setTrackingArea(false, 0); d->m_ifStatement = 0; d->m_consoleLogCount = 0; d->m_consoleIdentifierCount = 0; - d->m_handler = ConnectionEditorStatements::EmptyBlock{}; + d->m_handler = ScriptEditorStatements::EmptyBlock{}; return true; } -bool ConnectionEditorEvaluator::visit([[maybe_unused]] QmlJS::AST::IfStatement *ifStatement) +bool ScriptEditorEvaluator::visit([[maybe_unused]] QmlJS::AST::IfStatement *ifStatement) { if (d->m_ifStatement++) return d->checkValidityAndReturn(false, "Nested if conditions are not supported"); @@ -885,7 +886,7 @@ bool ConnectionEditorEvaluator::visit([[maybe_unused]] QmlJS::AST::IfStatement * return d->checkValidityAndReturn(true); } -bool ConnectionEditorEvaluator::visit(QmlJS::AST::IdentifierExpression *identifier) +bool ScriptEditorEvaluator::visit(QmlJS::AST::IdentifierExpression *identifier) { if (d->parentNodeStatus() == Kind::Kind_FieldMemberExpression) if (d->m_consoleLogCount) @@ -896,7 +897,7 @@ bool ConnectionEditorEvaluator::visit(QmlJS::AST::IdentifierExpression *identifi return d->checkValidityAndReturn(true); } -bool ConnectionEditorEvaluator::visit(QmlJS::AST::BinaryExpression *binaryExpression) +bool ScriptEditorEvaluator::visit(QmlJS::AST::BinaryExpression *binaryExpression) { if (d->isInIfCondition()) { if (binaryExpression->op == QSOperator::Assign) @@ -923,7 +924,7 @@ bool ConnectionEditorEvaluator::visit(QmlJS::AST::BinaryExpression *binaryExpres return false; } else { MatchedStatement *currentStatement = d->currentStatement(); - if (currentStatement && ConnectionEditorStatements::isEmptyStatement(*currentStatement) + if (currentStatement && ScriptEditorStatements::isEmptyStatement(*currentStatement) && d->parentNodeStatus().childId() == 0) { if (binaryExpression->op == QSOperator::Assign) { RightHandVisitor variableVisitor; @@ -932,16 +933,16 @@ bool ConnectionEditorEvaluator::visit(QmlJS::AST::BinaryExpression *binaryExpres if (!variableVisitor.couldBeLHS()) return d->checkValidityAndReturn(false, "Invalid left hand."); - ConnectionEditorStatements::Variable lhs = variableVisitor.lhs(); + ScriptEditorStatements::Variable lhs = variableVisitor.lhs(); variableVisitor.reset(); binaryExpression->right->accept(&variableVisitor); if (variableVisitor.couldBeLHS()) { - ConnectionEditorStatements::Assignment assignment{lhs, variableVisitor.variable()}; + ScriptEditorStatements::Assignment assignment{lhs, variableVisitor.variable()}; *currentStatement = assignment; } else if (variableVisitor.isLiteralType()) { - ConnectionEditorStatements::PropertySet propSet{lhs, variableVisitor.literal()}; + ScriptEditorStatements::PropertySet propSet{lhs, variableVisitor.literal()}; *currentStatement = propSet; } else { return d->checkValidityAndReturn(false, "Invalid RHS"); @@ -955,7 +956,7 @@ bool ConnectionEditorEvaluator::visit(QmlJS::AST::BinaryExpression *binaryExpres return d->checkValidityAndReturn(true); } -bool ConnectionEditorEvaluator::visit(QmlJS::AST::FieldMemberExpression *fieldExpression) +bool ScriptEditorEvaluator::visit(QmlJS::AST::FieldMemberExpression *fieldExpression) { if (d->parentNodeStatus() == Kind::Kind_CallExpression && fieldExpression->name == u"log") d->m_consoleLogCount++; @@ -965,7 +966,7 @@ bool ConnectionEditorEvaluator::visit(QmlJS::AST::FieldMemberExpression *fieldEx return d->checkValidityAndReturn(true); } -bool ConnectionEditorEvaluator::visit(QmlJS::AST::CallExpression *callExpression) +bool ScriptEditorEvaluator::visit(QmlJS::AST::CallExpression *callExpression) { if (d->isInIfCondition()) return d->checkValidityAndReturn(false, "Functions are not allowd in the expressions"); @@ -974,7 +975,7 @@ bool ConnectionEditorEvaluator::visit(QmlJS::AST::CallExpression *callExpression if (!currentStatement) return d->checkValidityAndReturn(false, "Invalid place to call an expression"); - if (ConnectionEditorStatements::isEmptyStatement(*currentStatement)) { + if (ScriptEditorStatements::isEmptyStatement(*currentStatement)) { if (d->parentNodeStatus().childId() == 0) { ConsoleLogEvaluator logEvaluator; callExpression->accept(&logEvaluator); @@ -985,8 +986,8 @@ bool ConnectionEditorEvaluator::visit(QmlJS::AST::CallExpression *callExpression callExpression->accept(&callVisitor); if (callVisitor.isValid()) { - ConnectionEditorStatements::RightHandSide rhs = callVisitor.rhs(); - if (auto rhs_ = std::get_if(&rhs)) + ScriptEditorStatements::RightHandSide rhs = callVisitor.rhs(); + if (auto rhs_ = std::get_if(&rhs)) *currentStatement = *rhs_; else return d->checkValidityAndReturn(false, "Invalid Matched Function type."); @@ -999,7 +1000,7 @@ bool ConnectionEditorEvaluator::visit(QmlJS::AST::CallExpression *callExpression return d->checkValidityAndReturn(true); } -bool ConnectionEditorEvaluator::visit([[maybe_unused]] QmlJS::AST::Block *block) +bool ScriptEditorEvaluator::visit([[maybe_unused]] QmlJS::AST::Block *block) { Kind parentKind = d->parentNodeStatus(); @@ -1013,7 +1014,7 @@ bool ConnectionEditorEvaluator::visit([[maybe_unused]] QmlJS::AST::Block *block) return d->checkValidityAndReturn(false, "Block count ptoblem"); } -bool ConnectionEditorEvaluator::visit(QmlJS::AST::ArgumentList *arguments) +bool ScriptEditorEvaluator::visit(QmlJS::AST::ArgumentList *arguments) { if (d->trackingArea() == TrackingArea::Condition) return d->checkValidityAndReturn(false, "Arguments are not supported in if condition"); @@ -1022,7 +1023,7 @@ bool ConnectionEditorEvaluator::visit(QmlJS::AST::ArgumentList *arguments) if (!currentStatement) return d->checkValidityAndReturn(false, "No statement found for argument"); - if (!ConnectionEditorStatements::isConsoleLog(*currentStatement)) + if (!ScriptEditorStatements::isConsoleLog(*currentStatement)) return d->checkValidityAndReturn(false, "Arguments are only supported for console.log"); if (d->m_acceptLogArgument && !arguments->next) @@ -1031,13 +1032,13 @@ bool ConnectionEditorEvaluator::visit(QmlJS::AST::ArgumentList *arguments) return d->checkValidityAndReturn(false, "The only supported argument is in console.log"); } -void ConnectionEditorEvaluator::endVisit([[maybe_unused]] QmlJS::AST::Program *program) +void ScriptEditorEvaluator::endVisit([[maybe_unused]] QmlJS::AST::Program *program) { if (status() == UnFinished) d->setStatus(Succeeded); } -void ConnectionEditorEvaluator::endVisit(QmlJS::AST::FieldMemberExpression *fieldExpression) +void ScriptEditorEvaluator::endVisit(QmlJS::AST::FieldMemberExpression *fieldExpression) { if (status() != UnFinished) return; @@ -1053,12 +1054,12 @@ void ConnectionEditorEvaluator::endVisit(QmlJS::AST::FieldMemberExpression *fiel } } -void ConnectionEditorEvaluator::endVisit([[maybe_unused]] QmlJS::AST::CallExpression *callExpression) +void ScriptEditorEvaluator::endVisit([[maybe_unused]] QmlJS::AST::CallExpression *callExpression) { d->m_acceptLogArgument = false; } -void ConnectionEditorEvaluator::endVisit(QmlJS::AST::IfStatement * /*ifStatement*/) +void ScriptEditorEvaluator::endVisit(QmlJS::AST::IfStatement * /*ifStatement*/) { if (status() != UnFinished) return; @@ -1071,7 +1072,7 @@ void ConnectionEditorEvaluator::endVisit(QmlJS::AST::IfStatement * /*ifStatement } } -void ConnectionEditorEvaluator::endVisit(QmlJS::AST::StatementList * /*statementList*/) +void ScriptEditorEvaluator::endVisit(QmlJS::AST::StatementList * /*statementList*/) { if (status() != UnFinished) return; @@ -1080,26 +1081,26 @@ void ConnectionEditorEvaluator::endVisit(QmlJS::AST::StatementList * /*statement d->checkValidityAndReturn(false, "More than one statements are available."); } -void ConnectionEditorEvaluator::throwRecursionDepthError() +void ScriptEditorEvaluator::throwRecursionDepthError() { d->checkValidityAndReturn(false, "Recursion depth problem"); - qCWarning(ConnectionEditorLog) << __FUNCTION__ << "Recursion depth error"; + qCWarning(ScriptEditorLog) << __FUNCTION__ << "Recursion depth error"; } -bool ConnectionEditorEvaluatorPrivate::checkValidityAndReturn(bool valid, const QString &parseError) +bool ScriptEditorEvaluatorPrivate::checkValidityAndReturn(bool valid, const QString &parseError) { if (!valid) { if (m_checkStatus != Status::Failed) { setStatus(Status::Failed); m_errorString = parseError; - qCWarning(ConnectionEditorLog) << __FUNCTION__ << "Parse error" << parseError; + qCWarning(ScriptEditorLog) << __FUNCTION__ << "Parse error" << parseError; } } return m_checkStatus; } -NodeStatus ConnectionEditorEvaluatorPrivate::nodeStatus(int reverseLevel) const +NodeStatus ScriptEditorEvaluatorPrivate::nodeStatus(int reverseLevel) const { if (m_nodeHierarchy.size() > reverseLevel) return m_nodeHierarchy.at(m_nodeHierarchy.size() - reverseLevel - 1); diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorevaluator.h b/src/plugins/qmldesigner/components/scripteditor/scripteditorevaluator.h similarity index 77% rename from src/plugins/qmldesigner/components/connectioneditor/connectioneditorevaluator.h rename to src/plugins/qmldesigner/components/scripteditor/scripteditorevaluator.h index 2cc285ed6c5..33cae4b7e6d 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorevaluator.h +++ b/src/plugins/qmldesigner/components/scripteditor/scripteditorevaluator.h @@ -3,7 +3,7 @@ #pragma once -#include "connectioneditorstatements.h" +#include "scripteditorstatements.h" #include "qmldesigner_global.h" @@ -11,21 +11,21 @@ namespace QmlDesigner { -class ConnectionEditorEvaluatorPrivate; +class ScriptEditorEvaluatorPrivate; -class QMLDESIGNER_EXPORT ConnectionEditorEvaluator : public QmlJS::AST::Visitor +class QMLDESIGNER_EXPORT ScriptEditorEvaluator : public QmlJS::AST::Visitor { public: enum Status { UnStarted, UnFinished, Succeeded, Failed }; - ConnectionEditorEvaluator(); - virtual ~ConnectionEditorEvaluator(); + ScriptEditorEvaluator(); + virtual ~ScriptEditorEvaluator(); Status status() const; - ConnectionEditorStatements::Handler resultNode() const; + ScriptEditorStatements::Handler resultNode() const; static QString getDisplayStringForType(const QString &statement); - static ConnectionEditorStatements::Handler parseStatement(const QString &statement); + static ScriptEditorStatements::Handler parseStatement(const QString &statement); protected: bool preVisit(QmlJS::AST::Node *node) override; @@ -49,7 +49,7 @@ protected: void throwRecursionDepthError() override; private: - std::unique_ptr d; + std::unique_ptr d; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorstatements.cpp b/src/plugins/qmldesigner/components/scripteditor/scripteditorstatements.cpp similarity index 71% rename from src/plugins/qmldesigner/components/connectioneditor/connectioneditorstatements.cpp rename to src/plugins/qmldesigner/components/scripteditor/scripteditorstatements.cpp index 5fbad9acadb..cc8c2872b98 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorstatements.cpp +++ b/src/plugins/qmldesigner/components/scripteditor/scripteditorstatements.cpp @@ -1,11 +1,11 @@ // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "connectioneditorstatements.h" +#include "scripteditorstatements.h" #include using namespace QmlDesigner; -using namespace ConnectionEditorStatements; +using namespace ScriptEditorStatements; namespace { template @@ -39,31 +39,31 @@ struct StringVisitor return "Variable{" + var.nodeId + propertyName + "}"; } - QString operator()(const ConnectionEditorStatements::MatchedFunction &func) + QString operator()(const ScriptEditorStatements::MatchedFunction &func) { return "MatchedFunction{" + func.nodeId + "." + func.functionName + "}"; } - QString operator()(const ConnectionEditorStatements::Assignment &assignment) + QString operator()(const ScriptEditorStatements::Assignment &assignment) { return "Assignment{" + assignment.lhs.expression() + " = " + StringVisitor()(assignment.rhs) + "}"; } - QString operator()(const ConnectionEditorStatements::PropertySet &propertySet) + QString operator()(const ScriptEditorStatements::PropertySet &propertySet) { return "PropertySet{" + propertySet.lhs.expression() + " = " + std::visit(StringVisitor{}, propertySet.rhs) + "}"; } - QString operator()(const ConnectionEditorStatements::StateSet &stateSet) + QString operator()(const ScriptEditorStatements::StateSet &stateSet) { return "StateSet{" + stateSet.nodeId + ".state = " + stateSet.stateName + "}"; } - QString operator()(const ConnectionEditorStatements::EmptyBlock &) { return "EmptyBlock{}"; } + QString operator()(const ScriptEditorStatements::EmptyBlock &) { return "EmptyBlock{}"; } - QString operator()(const ConnectionEditorStatements::ConsoleLog &consoleLog) + QString operator()(const ScriptEditorStatements::ConsoleLog &consoleLog) { return "ConsoleLog{" + std::visit(StringVisitor{}, consoleLog.argument) + "}"; } @@ -92,7 +92,7 @@ struct StringVisitor } } - QString operator()(const ConnectionEditorStatements::MatchedCondition &matched) + QString operator()(const ScriptEditorStatements::MatchedCondition &matched) { if (!matched.statements.size() && !matched.tokens.size()) return "MatchedCondition{}"; @@ -113,7 +113,7 @@ struct StringVisitor return value; } - QString operator()(const ConnectionEditorStatements::ConditionalStatement &conditional) + QString operator()(const ScriptEditorStatements::ConditionalStatement &conditional) { QString value; value.reserve(200); @@ -130,7 +130,7 @@ struct StringVisitor return value; } - QString operator()(const ConnectionEditorStatements::MatchedStatement &conditional) + QString operator()(const ScriptEditorStatements::MatchedStatement &conditional) { return std::visit(StringVisitor{}, conditional); } @@ -156,7 +156,7 @@ struct JSOverload return var.nodeId + propertyName; } - QString operator()(const ConnectionEditorStatements::MatchedFunction &func) + QString operator()(const ScriptEditorStatements::MatchedFunction &func) { QString funcName; if (func.functionName.size()) @@ -165,31 +165,31 @@ struct JSOverload return func.nodeId + funcName + "()"; } - QString operator()(const ConnectionEditorStatements::Assignment &assignment) + QString operator()(const ScriptEditorStatements::Assignment &assignment) { return JSOverload()(assignment.lhs) + " = " + JSOverload()(assignment.rhs); } - QString operator()(const ConnectionEditorStatements::PropertySet &propertySet) + QString operator()(const ScriptEditorStatements::PropertySet &propertySet) { return JSOverload()(propertySet.lhs) + " = " + std::visit(JSOverload{}, propertySet.rhs); } - QString operator()(const ConnectionEditorStatements::StateSet &stateSet) + QString operator()(const ScriptEditorStatements::StateSet &stateSet) { return stateSet.nodeId + ".state = " + stateSet.stateName; } - QString operator()(const ConnectionEditorStatements::EmptyBlock &) { return "{}"; } + QString operator()(const ScriptEditorStatements::EmptyBlock &) { return "{}"; } - QString operator()(const ConnectionEditorStatements::ConsoleLog &consoleLog) + QString operator()(const ScriptEditorStatements::ConsoleLog &consoleLog) { return "console.log(" + std::visit(JSOverload{}, consoleLog.argument) + ")"; } QString operator()(const ConditionToken &token) { return toJavascript(token); } - QString operator()(const ConnectionEditorStatements::MatchedCondition &matched) + QString operator()(const ScriptEditorStatements::MatchedCondition &matched) { if (!matched.statements.size() && !matched.tokens.size()) return {}; @@ -209,7 +209,7 @@ struct JSOverload return value; } - QString operator()(const ConnectionEditorStatements::MatchedStatement &statement) + QString operator()(const ScriptEditorStatements::MatchedStatement &statement) { if (isEmptyStatement(statement)) return {}; @@ -217,7 +217,7 @@ struct JSOverload return std::visit(JSOverload{}, statement); } - QString operator()(const ConnectionEditorStatements::ConditionalStatement &conditional) + QString operator()(const ScriptEditorStatements::ConditionalStatement &conditional) { QString value; value.reserve(200); @@ -240,47 +240,47 @@ struct JSOverload } // namespace -bool ConnectionEditorStatements::isEmptyStatement(const MatchedStatement &stat) +bool ScriptEditorStatements::isEmptyStatement(const MatchedStatement &stat) { return std::holds_alternative(stat); } -QString ConnectionEditorStatements::toString(const ComparativeStatement &stat) +QString ScriptEditorStatements::toString(const ComparativeStatement &stat) { return std::visit(StringVisitor{}, stat); } -QString ConnectionEditorStatements::toString(const RightHandSide &rhs) +QString ScriptEditorStatements::toString(const RightHandSide &rhs) { return std::visit(StringVisitor{}, rhs); } -QString ConnectionEditorStatements::toString(const Literal &literal) +QString ScriptEditorStatements::toString(const Literal &literal) { return std::visit(StringVisitor{}, literal); } -QString ConnectionEditorStatements::toString(const MatchedStatement &statement) +QString ScriptEditorStatements::toString(const MatchedStatement &statement) { return std::visit(StringVisitor{}, statement); } -QString ConnectionEditorStatements::toString(const Handler &handler) +QString ScriptEditorStatements::toString(const Handler &handler) { return std::visit(StringVisitor{}, handler); } -QString ConnectionEditorStatements::toJavascript(const Handler &handler) +QString ScriptEditorStatements::toJavascript(const Handler &handler) { return std::visit(JSOverload{}, handler); } -bool ConnectionEditorStatements::isConsoleLog(const MatchedStatement &curState) +bool ScriptEditorStatements::isConsoleLog(const MatchedStatement &curState) { return std::holds_alternative(curState); } -bool ConnectionEditorStatements::isLiteralType(const RightHandSide &var) +bool ScriptEditorStatements::isLiteralType(const RightHandSide &var) { return std::visit(Overload{[](const double &) { return true; }, [](const bool &) { return true; }, @@ -289,7 +289,7 @@ bool ConnectionEditorStatements::isLiteralType(const RightHandSide &var) var); } -QString ConnectionEditorStatements::toDisplayName(const MatchedStatement &statement) +QString ScriptEditorStatements::toDisplayName(const MatchedStatement &statement) { const char *displayName = std::visit( Overload{[](const MatchedFunction &) { return FUNCTION_DISPLAY_NAME; }, @@ -303,7 +303,7 @@ QString ConnectionEditorStatements::toDisplayName(const MatchedStatement &statem return QString::fromLatin1(displayName); } -QString ConnectionEditorStatements::toDisplayName(const Handler &handler) +QString ScriptEditorStatements::toDisplayName(const Handler &handler) { const MatchedStatement &statement = std::visit( Overload{[](const MatchedStatement &statement) { return statement; }, @@ -312,51 +312,49 @@ QString ConnectionEditorStatements::toDisplayName(const Handler &handler) return toDisplayName(statement); } -MatchedStatement &ConnectionEditorStatements::okStatement( - ConnectionEditorStatements::Handler &handler) +MatchedStatement &ScriptEditorStatements::okStatement(ScriptEditorStatements::Handler &handler) { MatchedStatement statement; - return std::visit(Overload{[](ConnectionEditorStatements::MatchedStatement &var) - -> MatchedStatement & { return var; }, - [](ConnectionEditorStatements::ConditionalStatement &statement) + return std::visit(Overload{[](ScriptEditorStatements::MatchedStatement &var) -> MatchedStatement & { + return var; + }, + [](ScriptEditorStatements::ConditionalStatement &statement) -> MatchedStatement & { return statement.ok; }}, handler); } -MatchedStatement &ConnectionEditorStatements::koStatement( - ConnectionEditorStatements::Handler &handler) +MatchedStatement &ScriptEditorStatements::koStatement(ScriptEditorStatements::Handler &handler) { static MatchedStatement block; - if (auto *statement = std::get_if(&handler)) + if (auto *statement = std::get_if(&handler)) return statement->ko; return block; } -MatchedCondition &ConnectionEditorStatements::matchedCondition(Handler &handler) +MatchedCondition &ScriptEditorStatements::matchedCondition(Handler &handler) { static MatchedCondition block; - if (auto *statement = std::get_if(&handler)) + if (auto *statement = std::get_if(&handler)) return statement->condition; return block; } -ConditionalStatement &ConnectionEditorStatements::conditionalStatement( - ConnectionEditorStatements::Handler &handler) +ConditionalStatement &ScriptEditorStatements::conditionalStatement(ScriptEditorStatements::Handler &handler) { static ConditionalStatement block; - if (auto *statement = std::get_if(&handler)) + if (auto *statement = std::get_if(&handler)) return *statement; return block; } -QString ConnectionEditorStatements::toJavascript(const ConditionToken &token) +QString ScriptEditorStatements::toJavascript(const ConditionToken &token) { switch (token) { case ConditionToken::Not: diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorstatements.h b/src/plugins/qmldesigner/components/scripteditor/scripteditorstatements.h similarity index 77% rename from src/plugins/qmldesigner/components/connectioneditor/connectioneditorstatements.h rename to src/plugins/qmldesigner/components/scripteditor/scripteditorstatements.h index 06e6434b5ab..34f046b117f 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorstatements.h +++ b/src/plugins/qmldesigner/components/scripteditor/scripteditorstatements.h @@ -7,22 +7,22 @@ #include namespace QmlDesigner { -namespace ConnectionEditorStatements { +namespace ScriptEditorStatements { inline constexpr char FUNCTION_DISPLAY_NAME[] = QT_TRANSLATE_NOOP( - "QmlDesigner::ConnectionEditorStatements", "Function"); + "QmlDesigner::ScriptEditorStatements", "Function"); inline constexpr char ASSIGNMENT_DISPLAY_NAME[] = QT_TRANSLATE_NOOP( - "QmlDesigner::ConnectionEditorStatements", "Assignment"); + "QmlDesigner::ScriptEditorStatements", "Assignment"); inline constexpr char SETPROPERTY_DISPLAY_NAME[] = QT_TRANSLATE_NOOP( - "QmlDesigner::ConnectionEditorStatements", "Set Property"); + "QmlDesigner::ScriptEditorStatements", "Set Property"); inline constexpr char SETSTATE_DISPLAY_NAME[] = QT_TRANSLATE_NOOP( - "QmlDesigner::ConnectionEditorStatements", "Set State"); -inline constexpr char LOG_DISPLAY_NAME[] = QT_TRANSLATE_NOOP( - "QmlDesigner::ConnectionEditorStatements", "Print"); + "QmlDesigner::ScriptEditorStatements", "Set State"); +inline constexpr char LOG_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("QmlDesigner::ScriptEditorStatements", + "Print"); inline constexpr char EMPTY_DISPLAY_NAME[] = QT_TRANSLATE_NOOP( - "QmlDesigner::ConnectionEditorStatements", "Empty"); + "QmlDesigner::ScriptEditorStatements", "Empty"); inline constexpr char CUSTOM_DISPLAY_NAME[] = QT_TRANSLATE_NOOP( - "QmlDesigner::ConnectionEditorStatements", "Custom"); + "QmlDesigner::ScriptEditorStatements", "Custom"); struct Variable; struct MatchedFunction; @@ -122,13 +122,12 @@ QMLDESIGNER_EXPORT QString toDisplayName(const MatchedStatement &statement); QMLDESIGNER_EXPORT QString toDisplayName(const Handler &handler); QMLDESIGNER_EXPORT QString toJavascript(const ConditionToken &token); -QMLDESIGNER_EXPORT MatchedStatement &okStatement(ConnectionEditorStatements::Handler &handler); -QMLDESIGNER_EXPORT MatchedStatement &koStatement(ConnectionEditorStatements::Handler &handler); +QMLDESIGNER_EXPORT MatchedStatement &okStatement(ScriptEditorStatements::Handler &handler); +QMLDESIGNER_EXPORT MatchedStatement &koStatement(ScriptEditorStatements::Handler &handler); -QMLDESIGNER_EXPORT MatchedCondition &matchedCondition(ConnectionEditorStatements::Handler &handler); -QMLDESIGNER_EXPORT ConditionalStatement &conditionalStatement( - ConnectionEditorStatements::Handler &handler); +QMLDESIGNER_EXPORT MatchedCondition &matchedCondition(ScriptEditorStatements::Handler &handler); +QMLDESIGNER_EXPORT ConditionalStatement &conditionalStatement(ScriptEditorStatements::Handler &handler); -} // namespace ConnectionEditorStatements +} // namespace ScriptEditorStatements } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.cpp b/src/plugins/qmldesigner/components/scripteditor/scripteditorutils.cpp similarity index 92% rename from src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.cpp rename to src/plugins/qmldesigner/components/scripteditor/scripteditorutils.cpp index fcedbc9876c..36c5ed54d7c 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.cpp +++ b/src/plugins/qmldesigner/components/scripteditor/scripteditorutils.cpp @@ -1,6 +1,6 @@ // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "connectioneditorutils.h" +#include "scripteditorutils.h" #include #include @@ -8,11 +8,11 @@ #include #include #include +#include #include #include #include #include -#include #include #include @@ -24,7 +24,7 @@ namespace QmlDesigner { -Q_LOGGING_CATEGORY(ConnectionEditorLog, "qtc.qtquickdesigner.connectioneditor", QtWarningMsg) +Q_LOGGING_CATEGORY(ScriptEditorLog, "qtc.qtquickdesigner.scripteditor", QtWarningMsg) void callLater(const std::function &fun) { @@ -62,7 +62,7 @@ PropertyName uniquePropertyName(const PropertyName &suggestion, const ModelNode NodeMetaInfo dynamicTypeNameToNodeMetaInfo(const TypeName &typeName, Model *model) { // Note: Uses old mechanism to create the NodeMetaInfo and supports - // only types we care about in the connection editor. + // only types we care about in the script editor. // TODO: Support all possible AbstractProperty types and move to the // AbstractProperty class. if (typeName == "bool") @@ -80,7 +80,7 @@ NodeMetaInfo dynamicTypeNameToNodeMetaInfo(const TypeName &typeName, Model *mode else if (typeName == "var" || typeName == "variant") return model->metaInfo("QML.variant"); else - qCWarning(ConnectionEditorLog) << __FUNCTION__ << "type" << typeName << "not found"; + qCWarning(ScriptEditorLog) << __FUNCTION__ << "type" << typeName << "not found"; return {}; } @@ -355,7 +355,7 @@ QStringList availableTargetProperties(const BindingProperty &bindingProperty) { const ModelNode modelNode = bindingProperty.parentModelNode(); if (!modelNode.isValid()) { - qCWarning(ConnectionEditorLog) << __FUNCTION__ << "invalid model node"; + qCWarning(ScriptEditorLog) << __FUNCTION__ << "invalid model node"; return {}; } @@ -428,7 +428,7 @@ QStringList availableSourceProperties(const QString &id, } else if (auto metaInfo = targetProperty.parentModelNode().metaInfo(); metaInfo.isValid()) { targetType = metaInfo.property(targetProperty.name()).propertyType(); } else - qCWarning(ConnectionEditorLog) << __FUNCTION__ << "no meta info for target node"; + qCWarning(ScriptEditorLog) << __FUNCTION__ << "no meta info for target node"; QStringList possibleProperties; if (!modelNode.isValid()) { @@ -450,7 +450,7 @@ QStringList availableSourceProperties(const QString &id, return possibleProperties; } #endif - qCWarning(ConnectionEditorLog) << __FUNCTION__ << "invalid model node:" << id; + qCWarning(ScriptEditorLog) << __FUNCTION__ << "invalid model node:" << id; return {}; } @@ -476,10 +476,40 @@ QStringList availableSourceProperties(const QString &id, possibleProperties.push_back(QString::fromUtf8(property.name())); } } else { - qCWarning(ConnectionEditorLog) << __FUNCTION__ << "no meta info for source node"; + qCWarning(ScriptEditorLog) << __FUNCTION__ << "no meta info for source node"; } return possibleProperties; } +QString addOnToSignalName(const QString &signal) +{ + if (signal.isEmpty()) + return {}; + + static const QRegularExpression rx("^on[A-Z]"); + if (rx.match(signal).hasMatch()) + return signal; + + QString ret = signal; + ret[0] = ret.at(0).toUpper(); + ret.prepend("on"); + return ret; +} + +QString removeOnFromSignalName(const QString &signal) +{ + if (signal.isEmpty()) + return {}; + + static const QRegularExpression rx("^on[A-Z]"); + if (!rx.match(signal).hasMatch()) + return signal; + + QString ret = signal; + ret.remove(0, 2); + ret[0] = ret.at(0).toLower(); + return ret; +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.h b/src/plugins/qmldesigner/components/scripteditor/scripteditorutils.h similarity index 93% rename from src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.h rename to src/plugins/qmldesigner/components/scripteditor/scripteditorutils.h index 220fdc9e11d..9d5d170381c 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.h +++ b/src/plugins/qmldesigner/components/scripteditor/scripteditorutils.h @@ -13,7 +13,7 @@ namespace QmlDesigner { -Q_DECLARE_LOGGING_CATEGORY(ConnectionEditorLog) +Q_DECLARE_LOGGING_CATEGORY(ScriptEditorLog) class AbstractView; class AbstractProperty; @@ -26,6 +26,8 @@ void showErrorMessage(const QString &text); QString idOrTypeName(const ModelNode &modelNode); PropertyName uniquePropertyName(const PropertyName &suggestion, const ModelNode &modelNode); +QString addOnToSignalName(const QString &signal); +QString removeOnFromSignalName(const QString &signal); NodeMetaInfo dynamicTypeMetaInfo(const AbstractProperty &property); NodeMetaInfo dynamicTypeNameToNodeMetaInfo(const TypeName &typeName, Model *model); diff --git a/src/plugins/qmldesigner/documentmanager.cpp b/src/plugins/qmldesigner/documentmanager.cpp index c650c42713e..3ab5b8d937d 100644 --- a/src/plugins/qmldesigner/documentmanager.cpp +++ b/src/plugins/qmldesigner/documentmanager.cpp @@ -351,12 +351,16 @@ Utils::FilePath DocumentManager::currentProjectDirPath() { QTC_ASSERT(QmlDesignerPlugin::instance(), return {}); - if (!QmlDesignerPlugin::instance()->currentDesignDocument()) + if (!QmlDesignerPlugin::instance()->currentDesignDocument()) { + if (ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject()) + return project->projectDirectory(); return {}; + } Utils::FilePath qmlFileName = QmlDesignerPlugin::instance()->currentDesignDocument()->fileName(); ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::projectForFile(qmlFileName); + if (project) return project->projectDirectory(); diff --git a/src/plugins/qmldesigner/imagecachecollectors/imagecachecollector.cpp b/src/plugins/qmldesigner/imagecachecollectors/imagecachecollector.cpp index 0baf6cbef49..43e6cc78edc 100644 --- a/src/plugins/qmldesigner/imagecachecollectors/imagecachecollector.cpp +++ b/src/plugins/qmldesigner/imagecachecollectors/imagecachecollector.cpp @@ -157,6 +157,10 @@ bool ImageCacheCollector::runProcess(const QStringList &arguments) const QProcessUniquePointer puppetProcess{new QProcess}; + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.remove("QSG_RHI_BACKEND"); + puppetProcess->setProcessEnvironment(env); + QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, puppetProcess.get(), diff --git a/src/plugins/qmldesigner/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/instances/nodeinstanceview.cpp index 368872a8e99..6a16f65d83a 100644 --- a/src/plugins/qmldesigner/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/instances/nodeinstanceview.cpp @@ -679,21 +679,6 @@ void NodeInstanceView::auxiliaryDataChanged(const ModelNode &node, NodeInstance instance = instanceForModelNode(node); PropertyValueContainer container{instance.instanceId(), key.name, value, TypeName(), key.type}; m_nodeInstanceServer->changeAuxiliaryValues({{container}}); - const PropertyName name = key.name.toByteArray(); - if (node.hasVariantProperty(name)) { - PropertyValueContainer container(instance.instanceId(), - name, - node.variantProperty(name).value(), - TypeName()); - ChangeValuesCommand changeValueCommand({container}); - m_nodeInstanceServer->changePropertyValues(changeValueCommand); - } else if (node.hasBindingProperty(name)) { - PropertyBindingContainer container{instance.instanceId(), - name, - node.bindingProperty(name).expression(), - TypeName()}; - m_nodeInstanceServer->changePropertyBindings({{container}}); - } } break; @@ -727,10 +712,16 @@ void NodeInstanceView::customNotification(const AbstractView *view, void NodeInstanceView::customNotification(const CustomNotificationPackage &package) { - if (auto inputEvent = std::get_if(&package)) + if (auto inputEvent = std::get_if(&package)) { sendInputEvent(inputEvent->event); - else if (auto resize3DCanvas = std::get_if(&package)) + } else if (auto resize3DCanvas = std::get_if(&package)) { edit3DViewResized(resize3DCanvas->size); + } else if (auto preview = std::get_if(&package)) { + previewImageDataForGenericNode(preview->modelNode, + preview->renderNode, + preview->size, + preview->requestId); + } } void NodeInstanceView::nodeSourceChanged(const ModelNode &node, const QString & newNodeSource) diff --git a/src/plugins/qmldesigner/libs/designercore/CMakeLists.txt b/src/plugins/qmldesigner/libs/designercore/CMakeLists.txt index d362bc49377..ee1293830f6 100644 --- a/src/plugins/qmldesigner/libs/designercore/CMakeLists.txt +++ b/src/plugins/qmldesigner/libs/designercore/CMakeLists.txt @@ -402,7 +402,6 @@ extend_qtc_library(QmlDesignerCore projectstorageupdater.cpp projectstorageupdater.h projectstorage.cpp projectstorage.h projectstorageerrornotifierinterface.h - projectstorageerrornotifier.cpp projectstorageerrornotifier.h typeannotationreader.cpp typeannotationreader.h qmldocumentparserinterface.h qmltypesparserinterface.h diff --git a/src/plugins/qmldesigner/libs/designercore/include/auxiliarydata.h b/src/plugins/qmldesigner/libs/designercore/include/auxiliarydata.h index e5d9403d2cb..b61ec68a50b 100644 --- a/src/plugins/qmldesigner/libs/designercore/include/auxiliarydata.h +++ b/src/plugins/qmldesigner/libs/designercore/include/auxiliarydata.h @@ -34,29 +34,13 @@ public: operator BasicAuxiliaryDataKey() const { return {type, name}; } + constexpr auto operator<=>(const BasicAuxiliaryDataKey &) const = default; + public: AuxiliaryDataType type = AuxiliaryDataType::None; NameType name; }; -template -bool operator<(const BasicAuxiliaryDataKey &first, const BasicAuxiliaryDataKey &second) -{ - return std::tie(first.type, first.name) < std::tie(second.type, second.name); -} - -template -bool operator==(const BasicAuxiliaryDataKey &first, const BasicAuxiliaryDataKey &second) -{ - return first.type == second.type && first.name == second.name; -} - -template -bool operator!=(const BasicAuxiliaryDataKey &first, const BasicAuxiliaryDataKey &second) -{ - return !(first == second); -} - using AuxiliaryDataKey = BasicAuxiliaryDataKey; using AuxiliaryDataKeyView = BasicAuxiliaryDataKey; using AuxiliaryData = std::pair; diff --git a/src/plugins/qmldesigner/libs/designercore/include/customnotificationpackage.h b/src/plugins/qmldesigner/libs/designercore/include/customnotificationpackage.h index 07782f8014a..c0969d4d621 100644 --- a/src/plugins/qmldesigner/libs/designercore/include/customnotificationpackage.h +++ b/src/plugins/qmldesigner/libs/designercore/include/customnotificationpackage.h @@ -3,6 +3,8 @@ #pragma once +#include "modelnode.h" + #include #include @@ -18,6 +20,14 @@ struct Resize3DCanvas QSize size; }; -using CustomNotificationPackage = std::variant; +struct NodePreviewImage +{ + ModelNode modelNode; + ModelNode renderNode; + QSize size; + QByteArray requestId; +}; + +using CustomNotificationPackage = std::variant; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/libs/designercore/metainfo/nodemetainfo.cpp b/src/plugins/qmldesigner/libs/designercore/metainfo/nodemetainfo.cpp index 4c98763660d..f863cc1addf 100644 --- a/src/plugins/qmldesigner/libs/designercore/metainfo/nodemetainfo.cpp +++ b/src/plugins/qmldesigner/libs/designercore/metainfo/nodemetainfo.cpp @@ -4733,6 +4733,13 @@ void addSubProperties(CompoundPropertyMetaInfos &inflatedProperties, inflatedProperties.emplace_back(std::move(propertyMetaInfo)); } +bool isValueOrNonListReadOnlyReference(const NodeMetaInfo &propertyType, + const PropertyMetaInfo &property) +{ + return propertyType.type() == MetaInfoType::Value + || (property.isReadOnly() && !property.isListProperty()); +} + } // namespace CompoundPropertyMetaInfos MetaInfoUtils::inflateValueProperties(PropertyMetaInfos properties) @@ -4756,12 +4763,10 @@ CompoundPropertyMetaInfos MetaInfoUtils::inflateValueAndReadOnlyProperties(Prope inflatedProperties.reserve(properties.size() * 2); for (auto &property : properties) { - if (auto propertyType = property.propertyType(); - propertyType.type() == MetaInfoType::Value || property.isReadOnly()) { + if (auto propertyType = property.propertyType(); isValueOrNonListReadOnlyReference(propertyType, property)) addSubProperties(inflatedProperties, property, propertyType); - } else { + else inflatedProperties.emplace_back(std::move(property)); - } } return inflatedProperties; @@ -4773,8 +4778,7 @@ CompoundPropertyMetaInfos MetaInfoUtils::addInflatedValueAndReadOnlyProperties(P inflatedProperties.reserve(properties.size() * 2); for (auto &property : properties) { - if (auto propertyType = property.propertyType(); - propertyType.type() == MetaInfoType::Value || property.isReadOnly()) { + if (auto propertyType = property.propertyType(); isValueOrNonListReadOnlyReference(propertyType, property)) { addSubProperties(inflatedProperties, property, propertyType); if (!property.isReadOnly()) inflatedProperties.emplace_back(std::move(property)); diff --git a/src/plugins/qmldesigner/libs/designercore/model/internalnode_p.h b/src/plugins/qmldesigner/libs/designercore/model/internalnode_p.h index 8160e149e60..fee8341118c 100644 --- a/src/plugins/qmldesigner/libs/designercore/model/internalnode_p.h +++ b/src/plugins/qmldesigner/libs/designercore/model/internalnode_p.h @@ -11,8 +11,10 @@ #include "internalsignalhandlerproperty.h" #include "internalvariantproperty.h" +#include #include #include +#include #include #include @@ -146,46 +148,47 @@ public: auto nodeProperty(PropertyNameView name) const { return property(name); } template - Type *addProperty(PropertyNameView name) + std::tuple getProperty(PropertyNameView name) { auto [iter, inserted] = m_nameProperties.try_emplace( - name, std::make_shared(name, shared_from_this())); + name, makeLazySharedPtr(name, shared_from_this())); + auto flags = AbstractView::NoAdditionalChanges; if (inserted) - return static_cast(iter->second.get()); + flags = AbstractView::PropertiesAdded; - return nullptr; + return std::make_tuple(static_cast(iter->second.get()), flags); } - auto addBindingProperty(PropertyNameView name) + auto getBindingProperty(PropertyNameView name) { - return addProperty(name); + return getProperty(name); } - auto addSignalHandlerProperty(PropertyNameView name) + auto getSignalHandlerProperty(PropertyNameView name) { - return addProperty(name); + return getProperty(name); } - auto addSignalDeclarationProperty(PropertyNameView name) + auto getSignalDeclarationProperty(PropertyNameView name) { - return addProperty(name); + return getProperty(name); } - auto addNodeListProperty(PropertyNameView name) + auto getNodeListProperty(PropertyNameView name) { - return addProperty(name); + return getProperty(name); } - auto addVariantProperty(PropertyNameView name) + auto getVariantProperty(PropertyNameView name) { - return addProperty(name); + return getProperty(name); } - auto addNodeProperty(PropertyNameView name, const TypeName &dynamicTypeName) + auto getNodeProperty(PropertyNameView name, const TypeName &dynamicTypeName) { - auto property = addProperty(name); - property->setDynamicTypeName(dynamicTypeName); + auto property = getProperty(name); + std::get<0>(property)->setDynamicTypeName(dynamicTypeName); return property; } diff --git a/src/plugins/qmldesigner/libs/designercore/model/model.cpp b/src/plugins/qmldesigner/libs/designercore/model/model.cpp index c8956b45682..e3e98598364 100644 --- a/src/plugins/qmldesigner/libs/designercore/model/model.cpp +++ b/src/plugins/qmldesigner/libs/designercore/model/model.cpp @@ -341,7 +341,7 @@ InternalNodePointer ModelPrivate::createNode(TypeNameView typeName, using PropertyPair = QPair; for (const PropertyPair &propertyPair : propertyList) { - newNode->addVariantProperty(propertyPair.first); + newNode->getVariantProperty(propertyPair.first); newNode->variantProperty(propertyPair.first)->setValue(propertyPair.second); } @@ -1379,14 +1379,7 @@ void ModelPrivate::setBindingProperty(const InternalNodePointer &node, PropertyNameView name, const QString &expression) { - AbstractView::PropertyChangeFlags propertyChange = AbstractView::NoAdditionalChanges; - InternalBindingProperty *bindingProperty = nullptr; - if (auto property = node->property(name)) { - bindingProperty = property->to(); - } else { - bindingProperty = node->addBindingProperty(name); - propertyChange = AbstractView::PropertiesAdded; - } + auto [bindingProperty, propertyChange] = node->getBindingProperty(name); notifyBindingPropertiesAboutToBeChanged({bindingProperty}); bindingProperty->setExpression(expression); @@ -1415,14 +1408,7 @@ void ModelPrivate::setSignalHandlerProperty(const InternalNodePointer &node, PropertyNameView name, const QString &source) { - AbstractView::PropertyChangeFlags propertyChange = AbstractView::NoAdditionalChanges; - InternalSignalHandlerProperty *signalHandlerProperty = nullptr; - if (auto property = node->property(name)) { - signalHandlerProperty = property->to(); - } else { - signalHandlerProperty = node->addSignalHandlerProperty(name); - propertyChange = AbstractView::PropertiesAdded; - } + auto [signalHandlerProperty, propertyChange] = node->getSignalHandlerProperty(name); signalHandlerProperty->setSource(source); notifySignalHandlerPropertiesChanged({signalHandlerProperty}, propertyChange); @@ -1432,14 +1418,7 @@ void ModelPrivate::setSignalDeclarationProperty(const InternalNodePointer &node, PropertyNameView name, const QString &signature) { - AbstractView::PropertyChangeFlags propertyChange = AbstractView::NoAdditionalChanges; - InternalSignalDeclarationProperty *signalDeclarationProperty = nullptr; - if (auto property = node->property(name)) { - signalDeclarationProperty = property->to(); - } else { - signalDeclarationProperty = node->addSignalDeclarationProperty(name); - propertyChange = AbstractView::PropertiesAdded; - } + auto [signalDeclarationProperty, propertyChange] = node->getSignalDeclarationProperty(name); signalDeclarationProperty->setSignature(signature); notifySignalDeclarationPropertiesChanged({signalDeclarationProperty}, propertyChange); @@ -1449,14 +1428,7 @@ void ModelPrivate::setVariantProperty(const InternalNodePointer &node, PropertyNameView name, const QVariant &value) { - AbstractView::PropertyChangeFlags propertyChange = AbstractView::NoAdditionalChanges; - InternalVariantProperty *variantProperty = nullptr; - if (auto property = node->property(name)) { - variantProperty = property->to(); - } else { - variantProperty = node->addVariantProperty(name); - propertyChange = AbstractView::PropertiesAdded; - } + auto [variantProperty, propertyChange] = node->getVariantProperty(name); variantProperty->setValue(value); variantProperty->resetDynamicTypeName(); @@ -1468,14 +1440,7 @@ void ModelPrivate::setDynamicVariantProperty(const InternalNodePointer &node, const TypeName &dynamicPropertyType, const QVariant &value) { - AbstractView::PropertyChangeFlags propertyChange = AbstractView::NoAdditionalChanges; - InternalVariantProperty *variantProperty = nullptr; - if (auto property = node->property(name)) { - variantProperty = property->to(); - } else { - variantProperty = node->addVariantProperty(name); - propertyChange = AbstractView::PropertiesAdded; - } + auto [variantProperty, propertyChange] = node->getVariantProperty(name); variantProperty->setDynamicValue(dynamicPropertyType, value); notifyVariantPropertiesChanged(node, PropertyNameViews({name}), propertyChange); @@ -1486,14 +1451,7 @@ void ModelPrivate::setDynamicBindingProperty(const InternalNodePointer &node, const TypeName &dynamicPropertyType, const QString &expression) { - AbstractView::PropertyChangeFlags propertyChange = AbstractView::NoAdditionalChanges; - InternalBindingProperty *bindingProperty = nullptr; - if (auto property = node->property(name)) { - bindingProperty = property->to(); - } else { - bindingProperty = node->addBindingProperty(name); - propertyChange = AbstractView::PropertiesAdded; - } + auto [bindingProperty, propertyChange] = node->getBindingProperty(name); notifyBindingPropertiesAboutToBeChanged({bindingProperty}); bindingProperty->setDynamicExpression(dynamicPropertyType, expression); @@ -1524,16 +1482,14 @@ void ModelPrivate::reparentNode(const InternalNodePointer &parentNode, propertyChange); InternalNodeAbstractProperty *newParentProperty = nullptr; - if (auto property = parentNode->property(name)) { - newParentProperty = property->to(); - } else { - if (list) - newParentProperty = parentNode->addNodeListProperty(name); - else - newParentProperty = parentNode->addNodeProperty(name, dynamicTypeName); + AbstractView::PropertyChangeFlags newPropertyChange = AbstractView::NoAdditionalChanges; - propertyChange |= AbstractView::PropertiesAdded; - } + if (list) + std::tie(newParentProperty, newPropertyChange) = parentNode->getNodeListProperty(name); + else + std::tie(newParentProperty, newPropertyChange) = parentNode->getNodeProperty(name, + dynamicTypeName); + propertyChange |= newPropertyChange; Q_ASSERT(newParentProperty); diff --git a/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorage.h b/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorage.h index f7a0ad43af1..f11921330f3 100644 --- a/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorage.h +++ b/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorage.h @@ -4,7 +4,7 @@ #pragma once #include "commontypecache.h" -#include "projectstorageerrornotifier.h" +#include "projectstorageerrornotifierinterface.h" #include "projectstorageexceptions.h" #include "projectstorageinterface.h" #include "projectstoragetypes.h" diff --git a/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageerrornotifier.cpp b/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageerrornotifier.cpp deleted file mode 100644 index b722e90dd4f..00000000000 --- a/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageerrornotifier.cpp +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "projectstorageerrornotifier.h" - -#include "sourcepathstorage/sourcepathcache.h" - -namespace QmlDesigner { - -void ProjectStorageErrorNotifier::typeNameCannotBeResolved(Utils::SmallStringView typeName, - SourceId sourceId) -{ - qDebug() << "Missing type name: " << typeName - << " in file: " << m_pathCache.sourcePath(sourceId).toStringView(); -} - -void ProjectStorageErrorNotifier::missingDefaultProperty(Utils::SmallStringView typeName, - Utils::SmallStringView propertyName, - SourceId sourceId) - -{ - qDebug() << "Missing default property: " << propertyName << " in type: " << typeName - << " in file: " << m_pathCache.sourcePath(sourceId).toStringView(); -} - -void ProjectStorageErrorNotifier::propertyNameDoesNotExists(Utils::SmallStringView propertyName, - SourceId sourceId) -{ - qDebug() << "Missing property: " << propertyName - << " in file: " << m_pathCache.sourcePath(sourceId).toStringView(); -} - -void ProjectStorageErrorNotifier::qmlDocumentDoesNotExistsForQmldirEntry(Utils::SmallStringView typeName, - Storage::Version, - SourceId qmlDocumentSourceId, - SourceId qmldirSourceId) -{ - qDebug() << "Not existing Qml Document " - << m_pathCache.sourcePath(qmlDocumentSourceId).toStringView() << " for type " - << typeName << " in file: " << m_pathCache.sourcePath(qmldirSourceId).toStringView(); -} - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstoragetypes.h b/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstoragetypes.h index 5c776313479..71279f57412 100644 --- a/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstoragetypes.h +++ b/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstoragetypes.h @@ -1255,8 +1255,6 @@ public: using NanotraceHR::keyValue; auto dict = dictonary(keyValue("type name", typeAnnotation.typeName), keyValue("icon path", typeAnnotation.iconPath), - keyValue("item library json", typeAnnotation.itemLibraryJson), - keyValue("hints json", typeAnnotation.hintsJson), keyValue("type id", typeAnnotation.typeId), keyValue("source id", typeAnnotation.sourceId), keyValue("module id", typeAnnotation.moduleId), diff --git a/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageupdater.h b/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageupdater.h index 364f2c36f86..de042968930 100644 --- a/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageupdater.h +++ b/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageupdater.h @@ -4,7 +4,7 @@ #pragma once #include "filestatus.h" -#include "projectstorageerrornotifier.h" +#include "projectstorageerrornotifierinterface.h" #include "projectstorageids.h" #include "projectstoragepathwatchernotifierinterface.h" #include "projectstoragepathwatchertypes.h" diff --git a/src/plugins/qmldesigner/libs/designercore/sourcepathstorage/sourcepathcache.h b/src/plugins/qmldesigner/libs/designercore/sourcepathstorage/sourcepathcache.h index 47877803e3b..c7d3184f78a 100644 --- a/src/plugins/qmldesigner/libs/designercore/sourcepathstorage/sourcepathcache.h +++ b/src/plugins/qmldesigner/libs/designercore/sourcepathstorage/sourcepathcache.h @@ -79,7 +79,7 @@ public: SourceContextId sourceContextId(Utils::SmallStringView sourceContextPath) const override { Utils::SmallStringView path = sourceContextPath.back() == '/' - ? sourceContextPath.mid(0, sourceContextPath.size() - 1) + ? sourceContextPath.substr(0, sourceContextPath.size() - 1) : sourceContextPath; return m_sourceContextPathCache.id(path); diff --git a/src/plugins/qmldesigner/libs/designercore/sourcepathstorage/sourcepathview.h b/src/plugins/qmldesigner/libs/designercore/sourcepathstorage/sourcepathview.h index 8523c70a928..9100154d7e2 100644 --- a/src/plugins/qmldesigner/libs/designercore/sourcepathstorage/sourcepathview.h +++ b/src/plugins/qmldesigner/libs/designercore/sourcepathstorage/sourcepathview.h @@ -51,13 +51,13 @@ public: Utils::SmallStringView directory() const noexcept { - return mid(0, std::size_t(std::max(std::ptrdiff_t(0), m_slashIndex))); + return substr(0, std::size_t(std::max(std::ptrdiff_t(0), m_slashIndex))); } Utils::SmallStringView name() const noexcept { - return mid(std::size_t(m_slashIndex + 1), - std::size_t(std::ptrdiff_t(size()) - m_slashIndex - std::ptrdiff_t(1))); + return substr(std::size_t(m_slashIndex + 1), + std::size_t(std::ptrdiff_t(size()) - m_slashIndex - std::ptrdiff_t(1))); } static diff --git a/src/plugins/qmldesigner/libs/designsystem/dsstore.cpp b/src/plugins/qmldesigner/libs/designsystem/dsstore.cpp index 42110e31dfc..b5de54cf2ab 100644 --- a/src/plugins/qmldesigner/libs/designsystem/dsstore.cpp +++ b/src/plugins/qmldesigner/libs/designsystem/dsstore.cpp @@ -72,11 +72,7 @@ std::optional modelSerializeHelper( view.setTextModifier(&modifier); model->attachView(&view); - try { - callback(model.get()); - } catch (const QmlDesigner::RewritingException &e) { - return e.description(); - } + view.executeInTransaction("DSStore::modelSerializeHelper", [&] { callback(model.get()); }); Utils::FileSaver saver(targetDir / (typeName + ".qml"), QIODevice::Text); saver.write(reformatQml(modifier.text())); diff --git a/src/plugins/qmldesigner/libs/qmldesignerutils/memory.h b/src/plugins/qmldesigner/libs/qmldesignerutils/memory.h index 17b8874d3bb..dd64c4a0a07 100644 --- a/src/plugins/qmldesigner/libs/qmldesignerutils/memory.h +++ b/src/plugins/qmldesigner/libs/qmldesignerutils/memory.h @@ -8,21 +8,20 @@ namespace QmlDesigner { -template -class LazyPtr +template +class LazySharedPtr { - using ResultType = Pointer::element_type; public: - LazyPtr(Arguments &&...arguments) + LazySharedPtr(Arguments &&...arguments) : m_arguments{std::forward_as_tuple(std::forward(arguments)...)} {} - operator Pointer() const + operator std::shared_ptr() const { return std::apply( [](auto &&...arguments) { - return Pointer(new ResultType(std::forward(arguments)...)); + return std::make_shared(std::forward(arguments)...); }, m_arguments); } @@ -31,22 +30,10 @@ private: std::tuple m_arguments; }; -template -using LazyUniquePtr = LazyPtr, Arguments...>; - -template -LazyUniquePtr makeLazyUniquePtr(Arguments &&...arguments) +template +LazySharedPtr makeLazySharedPtr(Arguments &&...arguments) { - return LazyUniquePtr(std::forward(arguments)...); -} - -template -using LazySharedPtr = LazyPtr, Arguments...>; - -template -LazySharedPtr makeLazySharedPtr(Arguments &&...arguments) -{ - return LazySharedPtr(std::forward(arguments)...); + return LazySharedPtr(std::forward(arguments)...); } } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/project/projectstorageerrornotifier.cpp b/src/plugins/qmldesigner/project/projectstorageerrornotifier.cpp new file mode 100644 index 00000000000..f6fefe2b297 --- /dev/null +++ b/src/plugins/qmldesigner/project/projectstorageerrornotifier.cpp @@ -0,0 +1,74 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "projectstorageerrornotifier.h" + +#include + +#include +#include +#include + +#include + +namespace QmlDesigner { + +namespace { + +void logIssue(ProjectExplorer::Task::TaskType type, const QString &message, const SourcePath &sourcePath) +{ + const Utils::Id category = ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM; + + Utils::FilePath filePath = Utils::FilePath::fromUserInput(sourcePath.toQString()); + ProjectExplorer::Task task(type, message, filePath, -1, category); + ProjectExplorer::TaskHub::addTask(task); + ProjectExplorer::TaskHub::requestPopup(); +} +} // namespace + +void ProjectStorageErrorNotifier::typeNameCannotBeResolved(Utils::SmallStringView typeName, + SourceId sourceId) +{ + const QString typeNameString{typeName}; + + logIssue(ProjectExplorer::Task::Warning, + Tr::tr("Missing type %1 name.").arg(typeNameString), + m_pathCache.sourcePath(sourceId)); +} + +void ProjectStorageErrorNotifier::missingDefaultProperty(Utils::SmallStringView typeName, + Utils::SmallStringView propertyName, + SourceId sourceId) + +{ + const QString typeNameString{typeName}; + const QString propertyNameString{propertyName}; + + logIssue(ProjectExplorer::Task::Warning, + Tr::tr("Missing default property: %1 in type %2.").arg(propertyNameString).arg(typeNameString), + m_pathCache.sourcePath(sourceId)); +} + +void ProjectStorageErrorNotifier::propertyNameDoesNotExists(Utils::SmallStringView propertyName, + SourceId sourceId) +{ + const QString propertyNameString{propertyName}; + + logIssue(ProjectExplorer::Task::Warning, + Tr::tr("Missing property %1 in type %2.").arg(propertyNameString), + m_pathCache.sourcePath(sourceId)); +} + +void ProjectStorageErrorNotifier::qmlDocumentDoesNotExistsForQmldirEntry(Utils::SmallStringView typeName, + Storage::Version, + SourceId qmlDocumentSourceId, + SourceId qmldirSourceId) +{ + const QString typeNameString{typeName}; + const QString missingPath = m_pathCache.sourcePath(qmlDocumentSourceId).toQString(); + logIssue(ProjectExplorer::Task::Warning, + Tr::tr("Not existing Qml Document %1 for type %2.").arg(missingPath).arg(typeNameString), + m_pathCache.sourcePath(qmldirSourceId)); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageerrornotifier.h b/src/plugins/qmldesigner/project/projectstorageerrornotifier.h similarity index 84% rename from src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageerrornotifier.h rename to src/plugins/qmldesigner/project/projectstorageerrornotifier.h index bea20ce394c..ae720bac693 100644 --- a/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageerrornotifier.h +++ b/src/plugins/qmldesigner/project/projectstorageerrornotifier.h @@ -3,15 +3,15 @@ #pragma once -#include "projectstorageerrornotifierinterface.h" +#include + +#include #include -#include namespace QmlDesigner { -class QMLDESIGNERCORE_EXPORT ProjectStorageErrorNotifier final - : public ProjectStorageErrorNotifierInterface +class QMLDESIGNER_EXPORT ProjectStorageErrorNotifier final : public ProjectStorageErrorNotifierInterface { public: ProjectStorageErrorNotifier(PathCacheType &pathCache) diff --git a/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp b/src/plugins/qmldesigner/project/qmldesignerprojectmanager.cpp similarity index 99% rename from src/plugins/qmldesigner/qmldesignerprojectmanager.cpp rename to src/plugins/qmldesigner/project/qmldesignerprojectmanager.cpp index 13fa82b4c72..e450576b9a9 100644 --- a/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp +++ b/src/plugins/qmldesigner/project/qmldesignerprojectmanager.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qmldesignerprojectmanager.h" +#include "projectstorageerrornotifier.h" #include #include @@ -12,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -100,13 +100,13 @@ auto makeCollectorDispatcherChain(ImageCacheCollector &nodeInstanceCollector, std::make_pair([](Utils::SmallStringView filePath, [[maybe_unused]] Utils::SmallStringView state, [[maybe_unused]] const QmlDesigner::ImageCache::AuxiliaryData - &auxiliaryData) { return filePath.endsWith(".qml"); }, + &auxiliaryData) { return filePath.ends_with(".qml"); }, &nodeInstanceCollector), std::make_pair( [](Utils::SmallStringView filePath, [[maybe_unused]] Utils::SmallStringView state, [[maybe_unused]] const QmlDesigner::ImageCache::AuxiliaryData &auxiliaryData) { - return filePath.endsWith(".mesh") || filePath.startsWith("#"); + return filePath.ends_with(".mesh") || filePath.starts_with("#"); }, &meshImageCollector), std::make_pair( diff --git a/src/plugins/qmldesigner/qmldesignerprojectmanager.h b/src/plugins/qmldesigner/project/qmldesignerprojectmanager.h similarity index 100% rename from src/plugins/qmldesigner/qmldesignerprojectmanager.h rename to src/plugins/qmldesigner/project/qmldesignerprojectmanager.h diff --git a/src/plugins/qmldesigner/qmldesignerplugin.cpp b/src/plugins/qmldesigner/qmldesignerplugin.cpp index 5026b91fa24..e0ab88fc453 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.cpp +++ b/src/plugins/qmldesigner/qmldesignerplugin.cpp @@ -16,6 +16,7 @@ #include "settingspage.h" #include "shortcutmanager.h" #include "toolbar.h" +#include "utils/checkablemessagebox.h" #include #include @@ -271,14 +272,17 @@ bool QmlDesignerPlugin::initialize(const QStringList & /*arguments*/, QString * Sqlite::LibraryInitializer::initialize(); QDir{}.mkpath(Core::ICore::cacheResourcePath().toUrlishString()); - QAction *action = new QAction(tr("Give Feedback..."), this); - Core::Command *cmd = Core::ActionManager::registerAction(action, "Help.GiveFeedback"); - Core::ActionManager::actionContainer(Core::Constants::M_HELP) - ->addAction(cmd, Core::Constants::G_HELP_SUPPORT); + if (Core::ICore::isQtDesignStudio()) { + QAction *action = new QAction(tr("Give Feedback..."), this); + action->setVisible(false); // keep hidden unless UsageStatistic plugin activates it + Core::Command *cmd = Core::ActionManager::registerAction(action, "Help.GiveFeedback"); + Core::ActionManager::actionContainer(Core::Constants::M_HELP) + ->addAction(cmd, Core::Constants::G_HELP_SUPPORT); - connect(action, &QAction::triggered, this, [this] { - launchFeedbackPopupInternal(QGuiApplication::applicationDisplayName()); - }); + connect(action, &QAction::triggered, this, [this] { + launchFeedbackPopupInternal(QGuiApplication::applicationDisplayName()); + }); + } d = new QmlDesignerPluginPrivate; d->timer.start(); @@ -309,18 +313,8 @@ bool QmlDesignerPlugin::initialize(const QStringList & /*arguments*/, QString * if (Core::ICore::isQtDesignStudio()) { d->toolBar = ToolBar::create(); d->statusBar = ToolBar::createStatusBar(); - - // uses simplified Telemetry settings page in case of Qt Design Studio - ExtensionSystem::PluginSpec *usageStatistic = Utils::findOrDefault(ExtensionSystem::PluginManager::plugins(), [](ExtensionSystem::PluginSpec *p) { - return p->id() == "usagestatistic"; - }); - - if (usageStatistic && usageStatistic->plugin()) - QMetaObject::invokeMethod(usageStatistic->plugin(), "useSimpleUi", true); } - initializeShutdownSettings(); - return true; } @@ -355,33 +349,26 @@ void QmlDesignerPlugin::extensionsInitialized() Core::IWizardFactory::registerFeatureProvider(new FullQDSFeatureProvider); } -void QmlDesignerPlugin::initializeShutdownSettings() -{ - auto settings = Core::ICore::settings(); - - if (!settings->contains("ShutdownCount")) - settings->setValue("ShutdownCount", 0); - - m_lastShutdownType = settings->value("LastShutdownType", "UserQuit").toString(); - settings->setValue("LastShutdownType", "Crash"); // value will persist unless changed in aboutToShutdown() -} - ExtensionSystem::IPlugin::ShutdownFlag QmlDesignerPlugin::aboutToShutdown() { Utils::QtcSettings *settings = Core::ICore::settings(); - int shutdownCount = settings->value("ShutdownCount", 0).toInt(); - if (m_lastShutdownType == "UserQuit") - settings->setValue("ShutdownCount", ++shutdownCount); - - settings->setValue("LastShutdownType", "UserQuit"); - if (shutdownCount != 5) // feedback popup should be displayed on the 5th shutdown + if (!Utils::CheckableDecider("FeedbackPopup").shouldAskAgain()) return SynchronousShutdown; - m_shutdownPending = true; - launchFeedbackPopupInternal(QGuiApplication::applicationDisplayName()); + int shutdownCount = settings->value("ShutdownCount", 0).toInt(); + settings->setValue("ShutdownCount", ++shutdownCount); - return AsynchronousShutdown; + if (!settings->value("UsageStatistic/TrackingEnabled").toBool()) + return SynchronousShutdown; + + if (shutdownCount >= 5) { + m_shutdownPending = true; + launchFeedbackPopupInternal(QGuiApplication::applicationDisplayName()); + return AsynchronousShutdown; + } + + return SynchronousShutdown; } static QStringList allUiQmlFilesforCurrentProject(const Utils::FilePath &fileName) @@ -886,8 +873,10 @@ void QmlDesignerPlugin::closeFeedbackPopup() m_feedbackWidget = nullptr; } - if (m_shutdownPending) + if (m_shutdownPending) { + Utils::CheckableDecider("FeedbackPopup").doNotAskAgain(); emit asynchronousShutdownFinished(); + } } void QmlDesignerPlugin::emitUsageStatisticsTime(const QString &identifier, int elapsed) diff --git a/src/plugins/qmldesigner/qmldesignerplugin.h b/src/plugins/qmldesigner/qmldesignerplugin.h index d9713fa4538..4952e96d76b 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.h +++ b/src/plugins/qmldesigner/qmldesignerplugin.h @@ -135,7 +135,6 @@ private: // variables QElapsedTimer m_usageTimer; bool m_delayedInitialized = false; bool m_shutdownPending = false; - QString m_lastShutdownType; }; } // namespace QmlDesigner diff --git a/src/plugins/qmlprojectmanager/qmlprojectexporter/boilerplate.qrc b/src/plugins/qmlprojectmanager/qmlprojectexporter/boilerplate.qrc index 4960324ab74..50e9909a17c 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectexporter/boilerplate.qrc +++ b/src/plugins/qmlprojectmanager/qmlprojectexporter/boilerplate.qrc @@ -11,5 +11,8 @@ templates/import_qml_components_h.tpl templates/qtquickcontrols2_conf.tpl templates/cmakelists_txt_shared.tpl + templates/python_generator_main.tpl + templates/python_generator_settings.tpl + templates/python_pyproject_toml.tpl diff --git a/src/plugins/qmlprojectmanager/qmlprojectexporter/exporter.cpp b/src/plugins/qmlprojectmanager/qmlprojectexporter/exporter.cpp index e228b53703a..40de901c4fe 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectexporter/exporter.cpp +++ b/src/plugins/qmlprojectmanager/qmlprojectexporter/exporter.cpp @@ -30,6 +30,7 @@ void Exporter::updateProject(QmlProject *project) void Exporter::updateProjectItem(QmlProjectItem *item, bool updateEnabled) { connect(item, &QmlProjectItem::filesChanged, m_cmakeGen, &CMakeGenerator::update); + connect(item, &QmlProjectItem::filesChanged, m_pythonGen, &PythonGenerator::update); connect(item, &QmlProjectItem::fileModified, m_cmakeGen, &CMakeGenerator::updateModifiedFile); if (updateEnabled) { diff --git a/src/plugins/qmlprojectmanager/qmlprojectexporter/pythongenerator.cpp b/src/plugins/qmlprojectmanager/qmlprojectexporter/pythongenerator.cpp index 11ae25fd2ce..636f47fa7d9 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectexporter/pythongenerator.cpp +++ b/src/plugins/qmlprojectmanager/qmlprojectexporter/pythongenerator.cpp @@ -3,41 +3,14 @@ #include "pythongenerator.h" #include "cmakewriter.h" +#include "resourcegenerator.h" #include "projectexplorer/projectmanager.h" #include "qmlprojectmanager/qmlproject.h" #include -namespace QmlProjectManager { - -namespace QmlProjectExporter { - -const char *PYTHON_MAIN_FILE_TEMPLATE = R"( -import os -import sys -from pathlib import Path - -from PySide6.QtGui import QGuiApplication -from PySide6.QtQml import QQmlApplicationEngine - -from autogen.settings import url, import_paths - -if __name__ == '__main__': - app = QGuiApplication(sys.argv) - engine = QQmlApplicationEngine() - - app_dir = Path(__file__).parent.parent - - engine.addImportPath(os.fspath(app_dir)) - for path in import_paths: - engine.addImportPath(os.fspath(app_dir / path)) - - engine.load(os.fspath(app_dir/url)) - if not engine.rootObjects(): - sys.exit(-1) - sys.exit(app.exec()) -)"; +namespace QmlProjectManager::QmlProjectExporter { void PythonGenerator::createMenuAction(QObject *parent) { @@ -66,9 +39,8 @@ PythonGenerator::PythonGenerator(QmlBuildSystem *bs) void PythonGenerator::updateMenuAction() { - FileGenerator::updateMenuAction( - "QmlProject.EnablePythonGenerator", - [this]() { return buildSystem()->enablePythonGeneration(); }); + FileGenerator::updateMenuAction("QmlProject.EnablePythonGenerator", + [this]() { return buildSystem()->enablePythonGeneration(); }); } void PythonGenerator::updateProject(QmlProject *project) @@ -77,38 +49,47 @@ void PythonGenerator::updateProject(QmlProject *project) return; Utils::FilePath projectPath = project->rootProjectDirectory(); - Utils::FilePath pythonPath = projectPath.pathAppended("Python"); - if (!pythonPath.exists()) - pythonPath.createDir(); + Utils::FilePath pythonFolderPath = projectPath.pathAppended("Python"); + if (!pythonFolderPath.exists()) + pythonFolderPath.createDir(); - Utils::FilePath mainFile = pythonPath.pathAppended("main.py"); - if (!mainFile.exists()) { - const QString mainContent = QString::fromUtf8(PYTHON_MAIN_FILE_TEMPLATE); - CMakeWriter::writeFile(mainFile, mainContent); + Utils::FilePath mainFilePath = pythonFolderPath.pathAppended("main.py"); + if (!mainFilePath.exists()) { + const QString mainFileTemplate = CMakeWriter::readTemplate( + ":/templates/python_generator_main"); + CMakeWriter::writeFile(mainFilePath, mainFileTemplate); } - Utils::FilePath autogenPath = pythonPath.pathAppended("autogen"); - if (!autogenPath.exists()) - autogenPath.createDir(); + Utils::FilePath pyprojectFilePath = pythonFolderPath.pathAppended("pyproject.toml"); + if (!pyprojectFilePath.exists()) { + const QString pyprojectFileTemplate = CMakeWriter::readTemplate( + ":/templates/python_pyproject_toml"); + const QString pyprojectFileContent = pyprojectFileTemplate.arg(project->displayName()); + CMakeWriter::writeFile(pyprojectFilePath, pyprojectFileContent); + } - Utils::FilePath settingsPath = autogenPath.pathAppended("settings.py"); - CMakeWriter::writeFile(settingsPath, settingsFileContent()); + Utils::FilePath autogenFolderPath = pythonFolderPath.pathAppended("autogen"); + if (!autogenFolderPath.exists()) + autogenFolderPath.createDir(); + + Utils::FilePath settingsFilePath = autogenFolderPath.pathAppended("settings.py"); + const QString settingsFileTemplate = CMakeWriter::readTemplate( + ":/templates/python_generator_settings"); + const QString settingsFileContent = settingsFileTemplate.arg(buildSystem()->mainFile()); + CMakeWriter::writeFile(settingsFilePath, settingsFileContent); + + // Python code uses the Qt resources collection file (.qrc) + ResourceGenerator::createQrc(project); } -QString PythonGenerator::settingsFileContent() const -{ - QTC_ASSERT(buildSystem(), return {}); +/*! + Regenerates the .qrc resources file +*/ +void PythonGenerator::update(const QSet &added, const QSet &removed) { + Q_UNUSED(added); + Q_UNUSED(removed); + ResourceGenerator::createQrc(qmlProject()); + // Generated Python code does not need to be updated +}; - QString content("\n"); - content.append("url = \"" + buildSystem()->mainFile() + "\"\n"); - - content.append("import_paths = [\n"); - for (const QString &path : buildSystem()->importPaths()) - content.append("\t\"" + path + "\",\n"); - content.append("]\n"); - - return content; -} - -} // namespace QmlProjectExporter. -} // namespace QmlProjectManager. +} // namespace QmlProjectExporter::QmlProjectManager. diff --git a/src/plugins/qmlprojectmanager/qmlprojectexporter/pythongenerator.h b/src/plugins/qmlprojectmanager/qmlprojectexporter/pythongenerator.h index 7c0a5810df5..34f8c2be38d 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectexporter/pythongenerator.h +++ b/src/plugins/qmlprojectmanager/qmlprojectexporter/pythongenerator.h @@ -21,11 +21,10 @@ public: static void createMenuAction(QObject *parent); PythonGenerator(QmlBuildSystem *bs); + + void update(const QSet &added, const QSet &removed); void updateMenuAction() override; void updateProject(QmlProject *project) override; - -private: - QString settingsFileContent() const; }; } // namespace QmlProjectExporter. diff --git a/src/plugins/qmlprojectmanager/qmlprojectexporter/templates/python_generator_main.tpl b/src/plugins/qmlprojectmanager/qmlprojectexporter/templates/python_generator_main.tpl new file mode 100644 index 00000000000..c768aa799bf --- /dev/null +++ b/src/plugins/qmlprojectmanager/qmlprojectexporter/templates/python_generator_main.tpl @@ -0,0 +1,26 @@ +import sys + +from PySide6.QtGui import QGuiApplication +from PySide6.QtQml import QQmlApplicationEngine + +from autogen.settings import setup_qt_environment + +# Import here the Python files that define QML elements + + +def main(): + app = QGuiApplication(sys.argv) + engine = QQmlApplicationEngine() + + setup_qt_environment(engine) + + if not engine.rootObjects(): + sys.exit(-1) + + ex = app.exec() + del engine + return ex + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/plugins/qmlprojectmanager/qmlprojectexporter/templates/python_generator_settings.tpl b/src/plugins/qmlprojectmanager/qmlprojectexporter/templates/python_generator_settings.tpl new file mode 100644 index 00000000000..435ee55aac4 --- /dev/null +++ b/src/plugins/qmlprojectmanager/qmlprojectexporter/templates/python_generator_settings.tpl @@ -0,0 +1,36 @@ +# This file is automatically generated by Qt Design Studio. +import os +import sys +from pathlib import Path + +from PySide6.QtQml import QQmlApplicationEngine + +project_root = Path(__file__).parent.parent.parent + + +def setup_qt_environment(qml_engine: QQmlApplicationEngine): + """ + Load the QML application. Import the compiled resources when the application is deployed. + """ + qml_app_url = "%1" + + if "__compiled__" in globals(): + # Application has been deployed using pyside6-deploy + try: + import autogen.resources # noqa: F401 + except ImportError: + resource_file = Path(__file__).parent / "resources.py" + print( + f"Error: No compiled resources found in {resource_file.absolute()}\n" + f"Please compile the resources using pyside6-rcc or pyside6-project build", + file=sys.stderr, + ) + sys.exit(1) + + qml_engine.addImportPath(":/") + qml_engine.load(f":/{qml_app_url}") + return + + qml_engine.addImportPath(str(project_root.absolute())) + os.environ["QT_QUICK_CONTROLS_CONF"] = str(project_root / "qtquickcontrols2.conf") + qml_engine.load(str(project_root / qml_app_url)) diff --git a/src/plugins/qmlprojectmanager/qmlprojectexporter/templates/python_pyproject_toml.tpl b/src/plugins/qmlprojectmanager/qmlprojectexporter/templates/python_pyproject_toml.tpl new file mode 100644 index 00000000000..21c00189295 --- /dev/null +++ b/src/plugins/qmlprojectmanager/qmlprojectexporter/templates/python_pyproject_toml.tpl @@ -0,0 +1,5 @@ +[project] +name = "%1" + +[tool.pyside6-project] +files = ["main.py", "autogen/settings.py"] diff --git a/src/plugins/studiowelcome/wizardhandler.cpp b/src/plugins/studiowelcome/wizardhandler.cpp index bcba47c01ab..9417f66803c 100644 --- a/src/plugins/studiowelcome/wizardhandler.cpp +++ b/src/plugins/studiowelcome/wizardhandler.cpp @@ -39,8 +39,10 @@ void WizardHandler::destroyWizard() emit deletingWizard(); m_selectedPreset = -1; - m_wizard->deleteLater(); - m_wizard = nullptr; + if (m_wizard) { + m_wizard->deleteLater(); + m_wizard = nullptr; + } m_detailsPage = nullptr; } diff --git a/tests/unit/tests/testdesignercore/CMakeLists.txt b/tests/unit/tests/testdesignercore/CMakeLists.txt index 5cdb2935e6e..240c6b36390 100644 --- a/tests/unit/tests/testdesignercore/CMakeLists.txt +++ b/tests/unit/tests/testdesignercore/CMakeLists.txt @@ -351,7 +351,6 @@ extend_qtc_library(TestDesignerCore projectstorageupdater.cpp projectstorageupdater.h projectstorage.cpp projectstorage.h projectstorageerrornotifierinterface.h - projectstorageerrornotifier.cpp projectstorageerrornotifier.h typeannotationreader.cpp typeannotationreader.h qmldocumentparserinterface.h qmltypesparserinterface.h diff --git a/tests/unit/tests/unittests/metainfo/nodemetainfo-test.cpp b/tests/unit/tests/unittests/metainfo/nodemetainfo-test.cpp index 0ace7a2c578..5b957f15b8d 100644 --- a/tests/unit/tests/unittests/metainfo/nodemetainfo-test.cpp +++ b/tests/unit/tests/unittests/metainfo/nodemetainfo-test.cpp @@ -487,6 +487,11 @@ TEST_F(NodeMetaInfo, inflate_value_and_readonly_properties) PropertyDeclarationTraits::IsReadOnly, inputDeviceId); auto seatNamePropertyId = projectStorageMock.propertyDeclarationId(inputDeviceId, "seatName"); + auto listPropertyId = projectStorageMock.createProperty(metaInfo.id(), + "transform", + PropertyDeclarationTraits::IsList + | PropertyDeclarationTraits::IsReadOnly, + inputDeviceId); auto properties = QmlDesigner::MetaInfoUtils::inflateValueAndReadOnlyProperties( metaInfo.properties()); @@ -497,7 +502,8 @@ TEST_F(NodeMetaInfo, inflate_value_and_readonly_properties) Not(Contains(CompoundPropertyIds(fontPropertyId, IsFalse(), _))), Contains(CompoundPropertyIds(familyPropertyId, fontPropertyId, "font.family")), Contains(CompoundPropertyIds(pixelSizePropertyId, fontPropertyId, "font.pixelSize")), - Contains(CompoundPropertyIds(seatNamePropertyId, devicePropertyId, "device.seatName")))); + Contains(CompoundPropertyIds(seatNamePropertyId, devicePropertyId, "device.seatName")), + Not(Contains(CompoundPropertyIds(seatNamePropertyId, listPropertyId, _))))); } TEST_F(NodeMetaInfo, inflate_value_and_readonly_properties_handles_invalid) @@ -525,6 +531,11 @@ TEST_F(NodeMetaInfo, add_inflated_value_and_readonly_properties) PropertyDeclarationTraits::IsReadOnly, inputDeviceId); auto seatNamePropertyId = projectStorageMock.propertyDeclarationId(inputDeviceId, "seatName"); + auto listPropertyId = projectStorageMock.createProperty(metaInfo.id(), + "transform", + PropertyDeclarationTraits::IsList + | PropertyDeclarationTraits::IsReadOnly, + inputDeviceId); auto properties = QmlDesigner::MetaInfoUtils::addInflatedValueAndReadOnlyProperties( metaInfo.properties()); @@ -536,7 +547,8 @@ TEST_F(NodeMetaInfo, add_inflated_value_and_readonly_properties) Contains(CompoundPropertyIds(familyPropertyId, fontPropertyId, "font.family")), Contains(CompoundPropertyIds(pixelSizePropertyId, fontPropertyId, "font.pixelSize")), Not(Contains(CompoundPropertyIds(devicePropertyId, IsFalse(), _))), - Contains(CompoundPropertyIds(seatNamePropertyId, devicePropertyId, "device.seatName")))); + Contains(CompoundPropertyIds(seatNamePropertyId, devicePropertyId, "device.seatName")), + Not(Contains(CompoundPropertyIds(seatNamePropertyId, listPropertyId, _))))); } TEST_F(NodeMetaInfo, add_inflated_value_and_readonly_properties_handles_invalid) diff --git a/tests/unit/tests/unittests/utils/smallstring-test.cpp b/tests/unit/tests/unittests/utils/smallstring-test.cpp index 6b5af085ba7..99775cbedb1 100644 --- a/tests/unit/tests/unittests/utils/smallstring-test.cpp +++ b/tests/unit/tests/unittests/utils/smallstring-test.cpp @@ -726,44 +726,6 @@ TYPED_TEST(SmallString, from_q_byte_array) ASSERT_THAT(text, SmallString("short string")); } -TYPED_TEST(SmallString, mid_one_parameter) -{ - using SmallString = typename TestFixture::String; - SmallString text("some text"); - - auto midString = text.mid(5); - - ASSERT_THAT(midString, Eq(SmallString("text"))); -} - -TYPED_TEST(SmallString, mid_two_parameter) -{ - using SmallString = typename TestFixture::String; - SmallString text("some text and more"); - - auto midString = text.mid(5, 4); - - ASSERT_THAT(midString, Eq(SmallString("text"))); -} - -TYPED_TEST(SmallString, small_string_view_mid_one_parameter) -{ - SmallStringView text("some text"); - - auto midString = text.mid(5); - - ASSERT_THAT(midString, Eq(SmallStringView("text"))); -} - -TYPED_TEST(SmallString, small_string_view_mid_two_parameter) -{ - SmallStringView text("some text and more"); - - auto midString = text.mid(5, 4); - - ASSERT_THAT(midString, Eq(SmallStringView("text"))); -} - TYPED_TEST(SmallString, size_of_empty_stringl) { using SmallString = typename TestFixture::String; @@ -1442,79 +1404,6 @@ TYPED_TEST(SmallString, dont_reserve_if_nothing_is_replaced_for_shorter_replacem ASSERT_TRUE(text.isReadOnlyReference()); } -TYPED_TEST(SmallString, starts_with) -{ - using SmallString = typename TestFixture::String; - - SmallString text("$column"); - - ASSERT_FALSE(text.startsWith("$columnxxx")); - ASSERT_TRUE(text.startsWith("$column")); - ASSERT_TRUE(text.startsWith("$col")); - ASSERT_FALSE(text.startsWith("col")); - ASSERT_TRUE(text.startsWith('$')); - ASSERT_FALSE(text.startsWith('@')); -} - -TYPED_TEST(SmallString, starts_with_string_view) -{ - SmallStringView text("$column"); - - ASSERT_FALSE(text.startsWith("$columnxxx")); - ASSERT_TRUE(text.startsWith("$column")); - ASSERT_TRUE(text.startsWith("$col")); - ASSERT_FALSE(text.startsWith("col")); - ASSERT_TRUE(text.startsWith('$')); - ASSERT_FALSE(text.startsWith('@')); -} - -TYPED_TEST(SmallString, starts_with_qstringview) -{ - using SmallString = typename TestFixture::String; - using namespace Qt::StringLiterals; - - SmallString text("$column"); - - ASSERT_FALSE(text.startsWith(u"$columnxxx"_s)); - ASSERT_TRUE(text.startsWith(u"$column"_s)); - ASSERT_TRUE(text.startsWith(u"$col"_s)); - ASSERT_FALSE(text.startsWith(u"col"_s)); - ASSERT_TRUE(text.startsWith(u"$"_s)); - ASSERT_FALSE(text.startsWith(u"@"_s)); -} - -TYPED_TEST(SmallString, ends_with) -{ - using SmallString = typename TestFixture::String; - - SmallString text("/my/path"); - - ASSERT_TRUE(text.endsWith("/my/path")); - ASSERT_TRUE(text.endsWith("path")); - ASSERT_FALSE(text.endsWith("paths")); - ASSERT_TRUE(text.endsWith('h')); - ASSERT_FALSE(text.endsWith('x')); -} - -TYPED_TEST(SmallString, ends_with_string_view) -{ - SmallStringView text("/my/path"); - - ASSERT_TRUE(text.endsWith("/my/path")); - ASSERT_TRUE(text.endsWith("path")); - ASSERT_FALSE(text.endsWith("paths")); -} - -TYPED_TEST(SmallString, ends_with_small_string) -{ - using SmallString = typename TestFixture::String; - - SmallString text("/my/path"); - - ASSERT_TRUE(text.endsWith(SmallString("path"))); - ASSERT_TRUE(text.endsWith('h')); -} - TYPED_TEST(SmallString, reserve_smaller_than_short_string_capacity) { using SmallString = typename TestFixture::String;