diff --git a/cmake/Utils.cmake b/cmake/Utils.cmake index b1be1ccf105..3dbe0f1e810 100644 --- a/cmake/Utils.cmake +++ b/cmake/Utils.cmake @@ -22,6 +22,9 @@ function(setup_dependencies_component) set(_elfutils_arg "--elfutils \"${_elfutils_path}\"") endif() install(CODE " + if (CMAKE_VERSION GREATER_EQUAL 3.19) + set(QTC_COMMAND_ERROR_IS_FATAL COMMAND_ERROR_IS_FATAL ANY) + endif() # DESTDIR is set for e.g. the cpack DEB generator, but is empty in other situations if(DEFINED ENV{DESTDIR}) set(DESTDIR_WITH_SEP \"\$ENV{DESTDIR}/\") @@ -48,7 +51,7 @@ function(setup_dependencies_component) \"\${_ide_app_target}\" \"${_qmake_binary}\" COMMAND_ECHO STDOUT - \${QTC_COMMAND_ERROR_IS_FATAL} + ${QTC_COMMAND_ERROR_IS_FATAL} ) " COMPONENT Dependencies diff --git a/doc/qtcreator/src/qtquick/qtquick-live-preview-devices.qdoc b/doc/qtcreator/src/qtquick/qtquick-live-preview-devices.qdoc index 8e527264d5d..f84285718f0 100644 --- a/doc/qtcreator/src/qtquick/qtquick-live-preview-devices.qdoc +++ b/doc/qtcreator/src/qtquick/qtquick-live-preview-devices.qdoc @@ -23,17 +23,24 @@ necessary files to a location in a device where you want to run the executable at. + \note To preview on a wirelessly connected device, select \preferences > \uicontrol Devices + and connect the device. + To preview a UI on a device: \list 1 + \if defined(qtcreator) \li In \uicontrol Projects > \uicontrol {Build & Run}, enable the kit predefined for the device type (1). \li Select the kit for the device in the kit selector (2). - \if defined(qtcreator) \image qtcreator-live-preview-kit.png - \else - \image studio-kit-selector.png "Kit selector" - \endif + \else + \li In the bottom toolbar, select \inlineimage icons/settings.png + and enable the kit predefined for the device type. + \image design-studio-kit-selection.webp. + \li Select the kit for the device in the bottom toolbar. + \image design-studio-select-kit.webp + \endif \li Select \uicontrol Build > \uicontrol {QML Preview} or press \key {Alt+P}. \endlist diff --git a/doc/qtdesignstudio/images/design-studio-kit-selection.webp b/doc/qtdesignstudio/images/design-studio-kit-selection.webp new file mode 100644 index 00000000000..b38c85fb792 Binary files /dev/null and b/doc/qtdesignstudio/images/design-studio-kit-selection.webp differ diff --git a/doc/qtdesignstudio/images/design-studio-select-kit.webp b/doc/qtdesignstudio/images/design-studio-select-kit.webp new file mode 100644 index 00000000000..16c4146cc1f Binary files /dev/null and b/doc/qtdesignstudio/images/design-studio-select-kit.webp differ diff --git a/doc/qtdesignstudio/images/icons/3d-background-color.png b/doc/qtdesignstudio/images/icons/3d-background-color.png index 37c68e632ca..5d4aaf93cd2 100644 Binary files a/doc/qtdesignstudio/images/icons/3d-background-color.png and b/doc/qtdesignstudio/images/icons/3d-background-color.png differ diff --git a/doc/qtdesignstudio/images/icons/bakelights.png b/doc/qtdesignstudio/images/icons/bakelights.png new file mode 100644 index 00000000000..21ca6137566 Binary files /dev/null and b/doc/qtdesignstudio/images/icons/bakelights.png differ diff --git a/doc/qtdesignstudio/images/icons/snapping-3d-conf.png b/doc/qtdesignstudio/images/icons/snapping-3d-conf.png index bf57f857be1..79d55117b95 100644 Binary files a/doc/qtdesignstudio/images/icons/snapping-3d-conf.png and b/doc/qtdesignstudio/images/icons/snapping-3d-conf.png differ diff --git a/doc/qtdesignstudio/images/icons/snapping-3d.png b/doc/qtdesignstudio/images/icons/snapping-3d.png index a434305e257..b10aae11347 100644 Binary files a/doc/qtdesignstudio/images/icons/snapping-3d.png and b/doc/qtdesignstudio/images/icons/snapping-3d.png differ diff --git a/doc/qtdesignstudio/images/icons/split-view.png b/doc/qtdesignstudio/images/icons/split-view.png new file mode 100644 index 00000000000..f07ab82478f Binary files /dev/null and b/doc/qtdesignstudio/images/icons/split-view.png differ diff --git a/doc/qtdesignstudio/images/studio-3d-split-view.webp b/doc/qtdesignstudio/images/studio-3d-split-view.webp new file mode 100644 index 00000000000..630fdb48fe4 Binary files /dev/null and b/doc/qtdesignstudio/images/studio-3d-split-view.webp differ diff --git a/doc/qtdesignstudio/src/mcus/qtdesignstudio-creating-uis-for-mcus.qdoc b/doc/qtdesignstudio/src/mcus/qtdesignstudio-creating-uis-for-mcus.qdoc index dbcb90269ba..2a02f879ee8 100644 --- a/doc/qtdesignstudio/src/mcus/qtdesignstudio-creating-uis-for-mcus.qdoc +++ b/doc/qtdesignstudio/src/mcus/qtdesignstudio-creating-uis-for-mcus.qdoc @@ -9,12 +9,12 @@ \title Creating UIs for MCUs As a technical artist or a designer, you can use specialized UI design tools, - such as Adobe Photoshop, Sketch, Figma, Blender, or Maya, to create the - original UI design files for your MCU application. After the initial design - work, export your design from the design tools, and import your 2D and 3D UI - design assets into \QDS, which can convert them into code for developers. - For more information on managing the original assets created with - specialized UI design tools, see \l {Asset Creation with Other Tools}. + such as Adobe Photoshop, Sketch, or Figma, to create the original UI design + files for your MCU application. After the initial design work, export your + design from the design tools, and import your 2D UI design assets into \QDS, + which can convert them into code for developers. For more information on + managing the original assets created with specialized UI design tools, see + \l {Asset Creation with Other Tools}. Once your UI design assets are in \QDS, use it to \l {Wireframing} {wireframe} your MCU application, to visualize its structure. To modify the look and feel diff --git a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc index 39b0b05b048..7111d2db90e 100644 --- a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc +++ b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc @@ -104,6 +104,16 @@ selected, the camera is pointed at the world origin. This does not affect the camera zoom level. + To view the scene in a split view of four different point of views, select + \inlineimage icons/split-view.png. + + \image studio-3d-split-view.webp "Split view in the 3D view" + + To select one of the four panes, click on it. The selected pane is marked with + a blue frame. Use the world axis helper to change the point of view for each pane + independently. Navigate each split by panning, rotating, and zooming, as + described above. + \image studio-3d-editor-axis-helper.webp "Axis helper in the 3D view" You can use scene cameras (2) to view the \uicontrol View3D component from a @@ -143,6 +153,13 @@ For more information about the available scene light types and their properties, see \l{Lights}. + \section1 Baking Lights + + Bake lights to light static elements in your scene. To bake lights, + select \inlineimage icons/bakelights.png to open the + \uicontrol {Lights Baking Setup} dialog. For more information, see + \l {Baking Lightmaps}. + \section1 Selecting Components To move, rotate, or scale components in the scene, you need to select them @@ -358,6 +375,54 @@ \inlineimage icons/particles-seek.png to manually seek forward or backward in the particle animation. + \section1 Using Viewport Shading + + Use \uicontrol {Viewport Shading} to change the rendering of the materials to only + reflect a particular aspect of the overall rendering process. Use shading also as a + debugging tool to understand why a material looks the way it does. In split view, + view the scene using different shading properties in each split. + + To use the \uicontrol {Viewport Shading}, right-click the \uicontrol 3D view to open the + context menu, select \uicontrol {Viewport Shading} and then select \uicontrol Wireframe, + one of the material properties, or \uicontrol{Reset All Viewports}. + + Select \uicontrol Wireframe to only show the edges of the objects in the scene. + + Select one of the material properties available for shading: + \table + \header + \li Property + \li Description + \row + \li Base Color + \li Shows only the base color of a material passed through without any lighting. + \row + \li Roughness + \li Shows only the roughness of a material passed through as an unlit greyscale + value. + \row + \li Metalness + \li Shows only the metalness of a material passed through as an unlit greyscale + value. + \row + \li Normals + \li Shows only the interpolated world space normal value of the material mapped + to an RGB color. + \row + \li Ambient Occlusion + \li Shows only the ambient occlusion of the material. + \row + \li Diffuse + \li Shows only the diffuse contribution of the material after all lighting. + \row + \li Specular + \li Shows only the specular contribution of the material after all + lighting. + \endtable + + Select \uicontrol{Reset All Viewports} to reset the shading of the scene in all of the + splits. + \section1 Summary of the 3D View Toolbar Buttons The \uicontrol{3D} view toolbar contains the following buttons: @@ -441,6 +506,11 @@ \li Background Color Actions \li \li \l{Changing Colors} + \row + \li \inlineimage icons/split-view.png + \li Toggle Split View On/Off + \li \key Ctrl + \key Alt + \key Q + \li \l{Using Split View} \row \li \inlineimage icons/particles-seek.png \li Seek Particle System Time diff --git a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-lights.qdoc b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-lights.qdoc index ec1de943f2b..af994d58774 100644 --- a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-lights.qdoc +++ b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-lights.qdoc @@ -213,10 +213,11 @@ \note Lightmaps baking is released as technical preview in \QDS 4.1. - Baked lightmaps allow pre-generating the direct lighting from lights such as DirectionalLight, - PointLight, and SpotLight, including the shadows cast by the lights. At run time, instead of - performing the appropriate calculations in the fragment shader, and, in case of shadows, - generating the potentially costly shadow maps in real time, the pre-generated light map is + Baked lightmaps allow pre-generating the direct lighting from lights, + such as DirectionalLight, PointLight, and SpotLight, including the shadows + cast by the lights. At run time, instead of performing the appropriate + calculations in the fragment shader, and, in case of shadows, generating + the potentially costly shadow maps in real time, the pre-generated light map is sampled instead. \section2 Baking Lightmaps @@ -224,47 +225,56 @@ To bake lightmaps for models in your 3D scene: \list 1 - \li Right-click anywhere in the \uicontrol 3D view and select \uicontrol {Bake Lights}. - \li In the \uicontrol {Lights Baking Setup} dialog: + \li Select \inlineimage icons/bakelights.png in the \uicontrol 3D view + toolbar or right-click anywhere in the \uicontrol 3D view and select + \uicontrol {Bake Lights} to open the \uicontrol {Lights Baking Setup} + dialog to define settings for baking lights. + \li Set \uicontrol {Bake Mode} for each light source: \list - \li For every light you want to use to bake lightmaps, set \uicontrol {Bake Mode} to BakeModeAll. - \li For every 3D model you want to bake lightmaps, select \uicontrol {In Use} and - \uicontrol {Enabled}, and set the desired \uicontrol {Resolution}. + \li To bake both direct (diffuse and shadow) and indirect lighting, + select \uicontrol {Bake All}. + \li To bake only indirect lighting, select \uicontrol {Bake Indirect}. + \li To not include a light source in the baking of lights, select + \uicontrol {Baking Disabled}. \endlist - \li Optional. If you have components with unexposed models or lights (for example, imported - 3D models created in other software), select \uicontrol {Expose models and lights} to add the - models and light of that component to the \uicontrol Models and \uicontrol Lights sections of - the dialog. + \li For every 3D model you want to bake into a lightmap, select + \uicontrol {In Use} and \uicontrol {Enabled}, and set the desired + \uicontrol {Resolution}. + \li Optional. If you have components with unexposed models or lights + (for example, imported 3D models created in other software), select + \uicontrol {Expose models and lights} to add the models and light of + that component to the \uicontrol Models and \uicontrol Lights sections + of the dialog. \li Select \uicontrol Bake. \endlist \image bake-lights-dialog.png \section2 Manually Baking Lightmaps for a 3D Model - Baked lightmap components are not visible in the \uicontrol Navigator view by default. To make - them visible, select \inlineimage icons/visibilityon.png + \note Baked lightmap components are not visible in the \uicontrol Navigator view by + default. To make them visible, select \inlineimage icons/visibilityon.png in the \uicontrol Navigator view. To bake lightmaps for a 3D model: \list 1 - \li From \uicontrol Components, drag a \uicontrol {Baked Lightmap} component to - the 3D model in the \uicontrol Navigator view. + \li From \uicontrol Components, drag a \uicontrol {Baked Lightmap} component + to the 3D model in the \uicontrol Navigator view. \image baked-lightmaps-navigator.png - \li In the \uicontrol Navigator view, select \e bakedLightmap and in the \uicontrol Properties - view: + \li In the \uicontrol Navigator view, select \e bakedLightmap, and in the + \uicontrol Properties view: \list \li Select \uicontrol Enabled. - \li In \uicontrol Key, set the filename base for the generated light maps. This must be - a unique name. - \li In \uicontrol {Load Prefix}, set the relative path to the folder where the generated - light map files are saved. + \li In \uicontrol Key, set the filename base for the generated light maps. + This must be a unique name. + \li In \uicontrol {Load Prefix}, set the relative path to the folder where + the generated light map files are saved. \endlist \li In the \uicontrol Navigator view, select the 3D model and in the \uicontrol Properties - view, select \uicontrol {Used in Baked Lighting}. + view, select \uicontrol {Used in Baked Lighting}. \li Optional. Adjust \uicontrol Resolution to set the light map resoution. This effects how - accurate and time-consuming the lightmap baking is. + accurate and time-consuming the lightmap baking is. \li In the \uicontrol Navigator view, select the light component that you want to bake - lightmaps for, and in the \uicontrol Properties view, set \uicontrol {Bake Mode} to BakeModeAll. + lightmaps for, and in the \uicontrol Properties view, set \uicontrol {Bake Mode} to BakeModeAll. \li Right-click anywhere in the \uicontrol 3D view and select \uicontrol {Bake Lights}. \li Select \uicontrol {Setup baking manually}, and then select \uicontrol Bake. \endlist diff --git a/scripts/deploy.py b/scripts/deploy.py index 5dd6198eca6..20ade283d2a 100755 --- a/scripts/deploy.py +++ b/scripts/deploy.py @@ -147,20 +147,20 @@ def copy_qt_libs(target_qt_prefix_path, qt_bin_dir, qt_libs_dir): shutil.copy(library, lib_dest) -def deploy_qtdiag(qtc_binary_path, qt_install): - print("Copying qtdiag") - qtdiag_src = os.path.join(qt_install.bin, with_exe_ext('qtdiag')) +def deploy_binary(binary_name, qtc_binary_path, qt_install): + print(f"Copying {binary_name}") + binary_src = os.path.join(qt_install.bin, with_exe_ext(binary_name)) destdir = (qtc_binary_path if common.is_windows_platform() else os.path.join(qtc_binary_path, 'Contents', 'MacOS') if common.is_mac_platform() else os.path.join(qtc_binary_path, '..', 'lib', 'Qt', 'bin')) if not os.path.exists(destdir): os.makedirs(destdir) - shutil.copy(qtdiag_src, destdir) + shutil.copy(binary_src, destdir) if common.is_mac_platform(): # fix RPATHs - qtdiag_dest = os.path.join(destdir, 'qtdiag') - subprocess.check_call(['xcrun', 'install_name_tool', '-add_rpath', '@loader_path/../Frameworks', qtdiag_dest]) - subprocess.check_call(['xcrun', 'install_name_tool', '-delete_rpath', '@loader_path/../lib', qtdiag_dest]) + binary_dest = os.path.join(destdir, binary_name) + subprocess.check_call(['xcrun', 'install_name_tool', '-add_rpath', '@loader_path/../Frameworks', binary_dest]) + subprocess.check_call(['xcrun', 'install_name_tool', '-delete_rpath', '@loader_path/../lib', binary_dest]) def deploy_plugins(qtc_binary_path, qt_install): @@ -469,7 +469,8 @@ def main(): qtcreator_binary_path = (args.qtcreator_binary if common.is_mac_platform() else os.path.dirname(args.qtcreator_binary)) - deploy_qtdiag(qtcreator_binary_path, qt_install) + deploy_binary('qtdiag', qtcreator_binary_path, qt_install) + deploy_binary('qsb', qtcreator_binary_path, qt_install) deploy_plugins(qtcreator_binary_path, qt_install) deploy_imports(qtcreator_binary_path, qt_install) deploy_translations(qtcreator_binary_path, qt_install) diff --git a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionDetailsToolbar.qml b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionDetailsToolbar.qml index 93839872ba2..3e268d10f48 100644 --- a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionDetailsToolbar.qml +++ b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionDetailsToolbar.qml @@ -99,45 +99,17 @@ Item { Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter IconButton { - icon: StudioTheme.Constants.updateContent_medium - tooltip: qsTr("Update existing file with changes") + icon: StudioTheme.Constants.save_medium + tooltip: qsTr("Save changes") enabled: root.model.collectionName !== "" - onClicked: - { - if (root.backend.selectedSourceAddress().indexOf("json") !== -1) - root.model.exportCollection(root.backend.selectedSourceAddress(), root.model.collectionName, "JSON") - else - root.model.exportCollection(root.backend.selectedSourceAddress(), root.model.collectionName, "CSV") - } + onClicked: root.model.saveCurrentCollection() } IconButton { icon: StudioTheme.Constants.export_medium - tooltip: qsTr("Export the model to a new file") + tooltip: qsTr("Export model") enabled: root.model.collectionName !== "" - onClicked: exportMenu.popup() - } - - StudioControls.Menu { - id: exportMenu - - StudioControls.MenuItem { - text: qsTr("Export as JSON") - onTriggered: - { - fileDialog.defaultSuffix = "json" - fileDialog.open() - } - } - - StudioControls.MenuItem { - text: qsTr("Export as CSV") - onTriggered: - { - fileDialog.defaultSuffix = "csv" - fileDialog.open() - } - } + onClicked: fileDialog.open() } } } @@ -145,17 +117,18 @@ Item { PlatformWidgets.FileDialog { id: fileDialog + fileMode: PlatformWidgets.FileDialog.SaveFile - onAccepted: - { - var fileAddress = file.toString() - if (fileAddress.indexOf("json") !== -1) - root.model.exportCollection(fileAddress, root.model.collectionName, "JSON") - else if (fileAddress.indexOf("csv") !== -1) - root.model.exportCollection(fileAddress, root.model.collectionName, "CSV") + nameFilters: ["JSON Files (*.json)", + "Comma-Separated Values (*.csv)" + ] - fileDialog.reject() + selectedNameFilter.index: 0 + + onAccepted: { + let filePath = fileDialog.file.toString() + root.model.exportCollection(filePath) } } diff --git a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionDetailsView.qml b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionDetailsView.qml index 5b88ebc2619..e208553a4c5 100644 --- a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionDetailsView.qml +++ b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionDetailsView.qml @@ -4,6 +4,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import CollectionDetails 1.0 as CollectionDetails import HelperWidgets 2.0 as HelperWidgets import StudioTheme 1.0 as StudioTheme import StudioControls 1.0 as StudioControls @@ -109,15 +110,9 @@ Rectangle { clip: true delegate: HeaderDelegate { - id: horizontalHeaderItem - selectedItem: tableView.model.selectedColumn color: StudioTheme.Values.themeControlBackgroundInteraction - function getGlobalBottomLeft() { - return mapToGlobal(0, horizontalHeaderItem.height) - } - MouseArea { anchors.fill: parent anchors.margins: 5 @@ -125,47 +120,51 @@ Rectangle { onClicked: (mouse) => { tableView.model.selectColumn(index) - if (mouse.button === Qt.RightButton) - headerMenu.popIndex(index, horizontalHeaderItem.getGlobalBottomLeft()) + if (mouse.button === Qt.RightButton) { + let posX = index === root.model.columnCount() - 1 ? parent.width - editProperyDialog.width : 0 + + headerMenu.clickedHeaderIndex = index + headerMenu.dialogPos = parent.mapToGlobal(posX, parent.height) + headerMenu.popup() + } } } + + HelperWidgets.ToolTipArea { + anchors.fill: parent + text: root.model.propertyType(index) + } } StudioControls.Menu { id: headerMenu - property int clickedHeader: -1 - property point initialPosition - - function popIndex(clickedIndex, clickedRect) - { - headerMenu.clickedHeader = clickedIndex - headerMenu.initialPosition = clickedRect - headerMenu.popup() - } + property int clickedHeaderIndex: -1 + property point dialogPos onClosed: { - headerMenu.clickedHeader = -1 + headerMenu.clickedHeaderIndex = -1 } StudioControls.MenuItem { text: qsTr("Edit") - onTriggered: editProperyDialog.editProperty(headerMenu.clickedHeader, headerMenu.initialPosition) + onTriggered: editProperyDialog.openDialog(headerMenu.clickedHeaderIndex, + headerMenu.dialogPos) } StudioControls.MenuItem { text: qsTr("Delete") - onTriggered: deleteColumnDialog.popUp(headerMenu.clickedHeader) + onTriggered: deleteColumnDialog.popUp(headerMenu.clickedHeaderIndex) } StudioControls.MenuItem { text: qsTr("Sort Ascending") - onTriggered: sortedModel.sort(headerMenu.clickedHeader, Qt.AscendingOrder) + onTriggered: sortedModel.sort(headerMenu.clickedHeaderIndex, Qt.AscendingOrder) } StudioControls.MenuItem { text: qsTr("Sort Descending") - onTriggered: sortedModel.sort(headerMenu.clickedHeader, Qt.DescendingOrder) + onTriggered: sortedModel.sort(headerMenu.clickedHeaderIndex, Qt.DescendingOrder) } } } @@ -209,13 +208,23 @@ Rectangle { id: itemCell implicitWidth: 100 implicitHeight: itemText.height + border.color: dataTypeWarning !== CollectionDetails.Warning.None ? + StudioTheme.Values.themeWarning : StudioTheme.Values.themeControlBackgroundInteraction border.width: 1 + HelperWidgets.ToolTipArea { + anchors.fill: parent + text: root.model.warningToString(dataTypeWarning) + enabled: dataTypeWarning !== CollectionDetails.Warning.None && text !== "" + hoverEnabled: true + acceptedButtons: Qt.NoButton + } + Text { id: itemText - text: display ? display : "" - + text: display + color: StudioTheme.Values.themePlaceholderTextColorInteraction width: parent.width leftPadding: 5 topPadding: 3 @@ -241,7 +250,6 @@ Rectangle { PropertyChanges { target: itemCell color: StudioTheme.Values.themeControlBackground - border.color: StudioTheme.Values.themeControlBackgroundInteraction } PropertyChanges { @@ -306,14 +314,11 @@ Rectangle { } Text { - anchors.fill: parent + anchors.centerIn: parent text: qsTr("Select a model to continue") visible: !topRow.visible - textFormat: Text.RichText color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.mediumFontSize - horizontalAlignment: Text.AlignHCenter - wrapMode: Text.WordWrap } TextMetrics { diff --git a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionItem.qml b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionItem.qml index 589a1433965..e5ba5a96e5e 100644 --- a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionItem.qml +++ b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionItem.qml @@ -12,10 +12,11 @@ Item { id: root implicitWidth: 300 - implicitHeight: innerRect.height + 3 + implicitHeight: boundingRect.height + 3 property color textColor property string sourceType + property bool hasSelectedTarget signal selectItem(int itemIndex) signal deleteItem() @@ -23,9 +24,8 @@ Item { Item { id: boundingRect - anchors.centerIn: root width: parent.width - height: nameHolder.height + height: itemLayout.height clip: true MouseArea { @@ -49,39 +49,24 @@ Item { } RowLayout { + id: itemLayout + width: parent.width - Text { - id: moveTool - - property StudioTheme.ControlStyle style: StudioTheme.Values.viewBarButtonStyle - - Layout.preferredWidth: moveTool.style.squareControlSize.width - Layout.preferredHeight: nameHolder.height - Layout.leftMargin: 12 - Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter - - text: StudioTheme.Constants.dragmarks - font.family: StudioTheme.Constants.iconFont.family - font.pixelSize: moveTool.style.baseIconFontSize - color: root.textColor - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - Text { id: nameHolder Layout.fillWidth: true Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + Layout.leftMargin: StudioTheme.Values.collectionItemTextSideMargin + Layout.topMargin: StudioTheme.Values.collectionItemTextMargin + Layout.bottomMargin: StudioTheme.Values.collectionItemTextMargin text: collectionName font.pixelSize: StudioTheme.Values.baseFontSize color: root.textColor - leftPadding: 5 - topPadding: 8 - rightPadding: 8 - bottomPadding: 8 + topPadding: StudioTheme.Values.collectionItemTextPadding + bottomPadding: StudioTheme.Values.collectionItemTextPadding elide: Text.ElideMiddle verticalAlignment: Text.AlignVCenter } @@ -90,13 +75,16 @@ Item { id: threeDots Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + Layout.topMargin: StudioTheme.Values.collectionItemTextMargin + Layout.bottomMargin: StudioTheme.Values.collectionItemTextMargin + Layout.rightMargin: StudioTheme.Values.collectionItemTextSideMargin + text: StudioTheme.Constants.more_medium font.family: StudioTheme.Constants.iconFont.family font.pixelSize: StudioTheme.Values.baseIconFontSize color: root.textColor - rightPadding: 12 - topPadding: nameHolder.topPadding - bottomPadding: nameHolder.bottomPadding + padding: StudioTheme.Values.collectionItemTextPadding + horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter @@ -125,6 +113,12 @@ Item { shortcut: StandardKey.Replace onTriggered: renameDialog.open() } + + StudioControls.MenuItem { + text: qsTr("Assign to the selected node") + enabled: root.hasSelectedTarget + onTriggered: rootView.assignCollectionToSelectedNode(collectionName) + } } component Spacer: Item { @@ -137,6 +131,7 @@ Item { title: qsTr("Deleting the model") clip: true + implicitWidth: 300 contentItem: ColumnLayout { spacing: 2 diff --git a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionView.qml b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionView.qml index 70eb32c9c86..32f71cc231b 100644 --- a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionView.qml +++ b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionView.qml @@ -24,15 +24,8 @@ Item { warningDialog.open() } - JsonImport { - id: jsonImporter - - backendValue: root.rootView - anchors.centerIn: parent - } - - CsvImport { - id: csvImporter + ImportDialog { + id: importDialog backendValue: root.rootView anchors.centerIn: parent @@ -62,46 +55,37 @@ Item { ColumnLayout { id: collectionsSideBar + spacing: 0 Layout.alignment: Qt.AlignTop | Qt.AlignLeft Layout.minimumWidth: 300 Layout.fillWidth: !grid.isHorizontal - RowLayout { - spacing: StudioTheme.Values.sectionRowSpacing + Rectangle { + color: StudioTheme.Values.themeToolbarBackground + Layout.preferredHeight: StudioTheme.Values.toolbarHeight Layout.fillWidth: true - Layout.preferredHeight: 50 Text { - Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter - Layout.fillWidth: true + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: StudioTheme.Values.toolbarHorizontalMargin text: qsTr("Data Models") font.pixelSize: StudioTheme.Values.baseFontSize color: StudioTheme.Values.themeTextColor - leftPadding: 15 } - IconTextButton { - Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + HelperWidgets.AbstractButton { + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: StudioTheme.Values.toolbarHorizontalMargin - icon: StudioTheme.Constants.import_medium - text: qsTr("JSON") - tooltip: qsTr("Import JSON") - radius: StudioTheme.Values.smallRadius + style: StudioTheme.Values.viewBarButtonStyle + buttonIcon: StudioTheme.Constants.import_medium + tooltip: qsTr("Import a model") - onClicked: jsonImporter.open() - } - - IconTextButton { - Layout.alignment: Qt.AlignRight | Qt.AlignVCenter - - icon: StudioTheme.Constants.import_medium - text: qsTr("CSV") - tooltip: qsTr("Import CSV") - radius: StudioTheme.Values.smallRadius - - onClicked: csvImporter.open() + onClicked: importDialog.open() } } @@ -128,9 +112,7 @@ Item { delegate: ModelSourceItem { implicitWidth: sourceListView.width - onDeleteItem: root.model.removeRow(index) hasSelectedTarget: root.rootView.targetNodeSelected - onAssignToSelected: root.rootView.assignSourceNodeToSelectedItem(sourceNode) } } } @@ -138,7 +120,7 @@ Item { HelperWidgets.IconButton { id: addCollectionButton - iconSize:16 + iconSize: 16 Layout.fillWidth: true Layout.minimumWidth: 24 Layout.alignment: Qt.AlignTop | Qt.AlignHCenter diff --git a/share/qtcreator/qmldesigner/collectionEditorQmlSource/EditPropertyDialog.qml b/share/qtcreator/qmldesigner/collectionEditorQmlSource/EditPropertyDialog.qml index 545896cc278..37fd92cdd61 100644 --- a/share/qtcreator/qmldesigner/collectionEditorQmlSource/EditPropertyDialog.qml +++ b/share/qtcreator/qmldesigner/collectionEditorQmlSource/EditPropertyDialog.qml @@ -12,26 +12,22 @@ StudioControls.Dialog { required property var model property int __propertyIndex: -1 - property string __oldName + property string __currentName - title: qsTr("Edit Property") - clip: true + title: qsTr("Edit Column") - function editProperty(index, initialPosition) { + function openDialog(index, initialPosition) { root.__propertyIndex = index if (root.__propertyIndex < 0) return - let previousName = root.model.propertyName(root.__propertyIndex) - let previousType = root.model.propertyType(root.__propertyIndex) + root.__currentName = root.model.propertyName(root.__propertyIndex) + nameTextField.text = root.__currentName + nameTextField.selectAll() + nameTextField.forceActiveFocus() - root.__oldName = previousName - newNameField.text = previousName - - propertyType.initialType = previousType - - forceChangeType.checked = false + typeComboBox.initialType = root.model.propertyType(root.__propertyIndex) let newPoint = mapFromGlobal(initialPosition.x, initialPosition.y) x = newPoint.x @@ -41,144 +37,116 @@ StudioControls.Dialog { } onAccepted: { - if (newNameField.text !== "" && newNameField.text !== root.__oldName) - root.model.renameColumn(root.__propertyIndex, newNameField.text) + if (nameTextField.text !== "" && nameTextField.text !== root.__currentName) + root.model.renameColumn(root.__propertyIndex, nameTextField.text) - if (propertyType.changed || forceChangeType.checked) - root.model.setPropertyType(root.__propertyIndex, propertyType.currentText, forceChangeType.checked) + if (typeComboBox.initialType !== typeComboBox.currentText) + root.model.setPropertyType(root.__propertyIndex, typeComboBox.currentText) } - onRejected: { - let currentDatatype = propertyType.initialType - propertyType.currentIndex = propertyType.find(currentDatatype) - } + contentItem: Column { + spacing: 5 - component Spacer: Item { - implicitWidth: 1 - implicitHeight: StudioTheme.Values.columnGap - } + Grid { + columns: 2 + rows: 2 + spacing: 2 + verticalItemAlignment: Grid.AlignVCenter - contentItem: ColumnLayout { - spacing: 2 - - Text { - text: qsTr("Name") - color: StudioTheme.Values.themeTextColor - } - - StudioControls.TextField { - id: newNameField - - Layout.fillWidth: true - - actionIndicator.visible: false - translationIndicator.visible: false - - Keys.onEnterPressed: root.accept() - Keys.onReturnPressed: root.accept() - Keys.onEscapePressed: root.reject() - - validator: RegularExpressionValidator { - regularExpression: /^\w+$/ + Text { + text: qsTr("Name") + color: StudioTheme.Values.themeTextColor + width: 50 + verticalAlignment: Text.AlignVCenter } - onTextChanged: { - editButton.enabled = newNameField.text !== "" - } - } + StudioControls.TextField { + id: nameTextField - Spacer {} + actionIndicator.visible: false + translationIndicator.visible: false - Text { - text: qsTr("Type") - color: StudioTheme.Values.themeTextColor - } + Keys.onEnterPressed: root.accept() + Keys.onReturnPressed: root.accept() + Keys.onEscapePressed: root.reject() - StudioControls.ComboBox { - id: propertyType - - Layout.fillWidth: true - - property string initialType - readonly property bool changed: propertyType.initialType !== propertyType.currentText - - model: root.model.typesList() - actionIndicatorVisible: false - - onInitialTypeChanged: propertyType.currentIndex = propertyType.find(initialType) - } - - Spacer {} - - RowLayout { - spacing: StudioTheme.Values.sectionRowSpacing - - StudioControls.CheckBox { - id: forceChangeType - actionIndicatorVisible: false + validator: RegularExpressionValidator { + regularExpression: /^\w+$/ + } } Text { - text: qsTr("Force update values") + text: qsTr("Type") color: StudioTheme.Values.themeTextColor - Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter } - } - Spacer { - visible: warningBox.visible - implicitHeight: StudioTheme.Values.controlLabelGap + StudioControls.ComboBox { + id: typeComboBox + + property string initialType + + model: root.model.typesList() + actionIndicatorVisible: false + + onInitialTypeChanged: typeComboBox.currentIndex = typeComboBox.find(initialType) + } } Rectangle { id: warningBox - Layout.fillWidth: true - Layout.preferredHeight: warning.height - - visible: propertyType.initialType !== propertyType.currentText + visible: typeComboBox.initialType !== typeComboBox.currentText color: "transparent" clip: true border.color: StudioTheme.Values.themeWarning + width: parent.width + height: warning.height - RowLayout { + Row { id: warning - width: parent.width + padding: 5 + spacing: 5 HelperWidgets.IconLabel { icon: StudioTheme.Constants.warning_medium - Layout.leftMargin: 10 + anchors.verticalCenter: parent.verticalCenter } Text { - text: qsTr("Conversion from %1 to %2 may lead to irreversible data loss") - .arg(propertyType.initialType) - .arg(propertyType.currentText) + text: qsTr("Conversion from %1 to %2 may lead to data loss") + .arg(typeComboBox.initialType) + .arg(typeComboBox.currentText) + + width: warningBox.width - 20 color: StudioTheme.Values.themeTextColor wrapMode: Text.WordWrap - Layout.fillWidth: true - Layout.margins: 8 } } } - Spacer {} - - RowLayout { - Layout.alignment: Qt.AlignRight | Qt.AlignVCenter - spacing: StudioTheme.Values.sectionRowSpacing + Row { + height: 40 + spacing: 5 + anchors.right: parent.right HelperWidgets.Button { id: editButton - text: qsTr("Edit") + text: qsTr("Apply") + enabled: nameTextField.text !== "" + width: 70 + anchors.bottom: parent.bottom + onClicked: root.accept() } HelperWidgets.Button { text: qsTr("Cancel") + anchors.bottom: parent.bottom + width: 70 + onClicked: root.reject() } } diff --git a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CsvImport.qml b/share/qtcreator/qmldesigner/collectionEditorQmlSource/ImportDialog.qml similarity index 83% rename from share/qtcreator/qmldesigner/collectionEditorQmlSource/CsvImport.qml rename to share/qtcreator/qmldesigner/collectionEditorQmlSource/ImportDialog.qml index 6bcd6e97a3a..bf28695ec9b 100644 --- a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CsvImport.qml +++ b/share/qtcreator/qmldesigner/collectionEditorQmlSource/ImportDialog.qml @@ -13,7 +13,7 @@ import StudioTheme as StudioTheme StudioControls.Dialog { id: root - title: qsTr("Import A CSV File") + title: qsTr("Import a model") anchors.centerIn: parent closePolicy: Popup.CloseOnEscape modal: true @@ -23,8 +23,8 @@ StudioControls.Dialog { property bool fileExists: false onOpened: { - collectionName.text = "Collection_" - fileName.text = qsTr("New CSV File") + collectionName.text = "Model" + fileName.text = qsTr("Model path") fileName.selectAll() fileName.forceActiveFocus() } @@ -40,6 +40,14 @@ StudioControls.Dialog { PlatformWidgets.FileDialog { id: fileDialog + nameFilters : ["All Model Files (*.json *.csv)", + "JSON Files (*.json)", + "Comma-Separated Values (*.csv)"] + + title: qsTr("Select a model file") + fileMode: PlatformWidgets.FileDialog.OpenFile + acceptLabel: qsTr("Open") + onAccepted: fileName.text = fileDialog.file } @@ -61,7 +69,7 @@ StudioControls.Dialog { spacing: 2 Text { - text: qsTr("File name: ") + text: qsTr("File name") color: StudioTheme.Values.themeTextColor } @@ -80,11 +88,11 @@ StudioControls.Dialog { translationIndicator.visible: false validator: fileNameValidator - Keys.onEnterPressed: btnCreate.onClicked() - Keys.onReturnPressed: btnCreate.onClicked() + Keys.onEnterPressed: btnImport.onClicked() + Keys.onReturnPressed: btnImport.onClicked() Keys.onEscapePressed: root.reject() - onTextChanged: root.fileExists = root.backendValue.isCsvFile(fileName.text) + onTextChanged: root.fileExists = root.backendValue.isValidUrlToImport(fileName.text) } HelperWidgets.Button { @@ -100,7 +108,7 @@ StudioControls.Dialog { Spacer {} Text { - text: qsTr("The model name: ") + text: qsTr("The model name") color: StudioTheme.Values.themeTextColor } @@ -115,8 +123,8 @@ StudioControls.Dialog { regularExpression: /^\w+$/ } - Keys.onEnterPressed: btnCreate.onClicked() - Keys.onReturnPressed: btnCreate.onClicked() + Keys.onEnterPressed: btnImport.onClicked() + Keys.onReturnPressed: btnImport.onClicked() Keys.onEscapePressed: root.reject() } @@ -179,15 +187,17 @@ StudioControls.Dialog { Layout.alignment: Qt.AlignRight | Qt.AlignVCenter HelperWidgets.Button { - id: btnCreate + id: btnImport text: qsTr("Import") enabled: root.fileExists && collectionName.text !== "" onClicked: { - let csvLoaded = root.backendValue.loadCsvFile(fileName.text, collectionName.text) + let collectionImported = root.backendValue.importCollectionToDataStore( + collectionName.text, + fileName.text) - if (csvLoaded) + if (collectionImported) root.accept() else creationFailedDialog.open() diff --git a/share/qtcreator/qmldesigner/collectionEditorQmlSource/JsonImport.qml b/share/qtcreator/qmldesigner/collectionEditorQmlSource/JsonImport.qml deleted file mode 100644 index fec1155927b..00000000000 --- a/share/qtcreator/qmldesigner/collectionEditorQmlSource/JsonImport.qml +++ /dev/null @@ -1,145 +0,0 @@ -// 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 QtQuick.Layouts -import QtQuickDesignerTheme 1.0 -import Qt.labs.platform as PlatformWidgets -import HelperWidgets 2.0 as HelperWidgets -import StudioControls 1.0 as StudioControls -import StudioTheme as StudioTheme - -StudioControls.Dialog { - id: root - - title: qsTr("Import Models") - anchors.centerIn: parent - closePolicy: Popup.CloseOnEscape - modal: true - - required property var backendValue - property bool fileExists: false - - onOpened: { - fileName.text = qsTr("New JSON File") - fileName.selectAll() - fileName.forceActiveFocus() - } - - onRejected: { - fileName.text = "" - } - - RegularExpressionValidator { - id: fileNameValidator - regularExpression: /^(\w[^*> 0) - root.expanded = !root.expanded || sourceIsSelected; - } - - ColumnLayout { - id: wholeColumn width: parent.width - spacing: 0 + implicitHeight: contentHeight + leftMargin: 6 - Item { - id: boundingRect + model: internalModels + clip: true - Layout.fillWidth: true - Layout.preferredHeight: nameHolder.height - Layout.leftMargin: 6 - clip: true - - MouseArea { - id: itemMouse - - anchors.fill: parent - acceptedButtons: Qt.LeftButton - propagateComposedEvents: true - hoverEnabled: true - - onClicked: (event) => { - if (!sourceIsSelected) { - sourceIsSelected = true - event.accepted = true - } - } - - onDoubleClicked: (event) => { - root.toggleExpanded() - } - } - - Rectangle { - id: innerRect - anchors.fill: parent - } - - RowLayout { - width: parent.width - - Text { - id: expandButton - - property StudioTheme.ControlStyle style: StudioTheme.Values.viewBarButtonStyle - - Layout.preferredWidth: expandButton.style.squareControlSize.width - Layout.preferredHeight: nameHolder.height - Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter - - text: StudioTheme.Constants.startNode - font.family: StudioTheme.Constants.iconFont.family - font.pixelSize: 6 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - color: textColor - - rotation: root.expanded ? 90 : 0 - visible: collectionListView.count > 0 - - Behavior on rotation { - SpringAnimation { spring: 2; damping: 0.2 } - } - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.RightButton | Qt.LeftButton - onClicked: root.toggleExpanded() - } - } - - Text { - id: nameHolder - - Layout.fillWidth: true - Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter - - text: sourceName - font.pixelSize: StudioTheme.Values.baseFontSize - color: textColor - leftPadding: 5 - topPadding: 8 - rightPadding: 8 - bottomPadding: 8 - elide: Text.ElideMiddle - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - } - - Text { - id: threeDots - - Layout.alignment: Qt.AlignRight | Qt.AlignVCenter - - text: StudioTheme.Constants.more_medium - font.family: StudioTheme.Constants.iconFont.family - font.pixelSize: StudioTheme.Values.baseIconFontSize - color: textColor - rightPadding: 12 - topPadding: nameHolder.topPadding - bottomPadding: nameHolder.bottomPadding - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.RightButton | Qt.LeftButton - onClicked: collectionMenu.popup() - } - } - } - } - - ListView { - id: collectionListView - - Layout.fillWidth: true - Layout.preferredHeight: root.expanded ? contentHeight : 0 - Layout.leftMargin: 6 - model: internalModels - clip: true - - Behavior on Layout.preferredHeight { - NumberAnimation {duration: 500} - } - - delegate: CollectionItem { - width: collectionListView.width - sourceType: collectionListView.model.sourceType - onDeleteItem: collectionListView.model.removeRow(index) - } + delegate: CollectionItem { + width: collectionListView.width + sourceType: collectionListView.model.sourceType + hasSelectedTarget: root.hasSelectedTarget + onDeleteItem: collectionListView.model.removeRow(index) } } - - StudioControls.Menu { - id: collectionMenu - - closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside - - StudioControls.MenuItem { - text: qsTr("Delete") - shortcut: StandardKey.Delete - onTriggered: deleteDialog.open() - } - - StudioControls.MenuItem { - text: qsTr("Rename") - shortcut: StandardKey.Replace - onTriggered: renameDialog.open() - } - - StudioControls.MenuItem { - text: qsTr("Assign to the selected node") - enabled: root.hasSelectedTarget - onTriggered: root.assignToSelected() - } - } - - component Spacer: Item { - implicitWidth: 1 - implicitHeight: StudioTheme.Values.sectionColumnSpacing - } - - StudioControls.Dialog { - id: deleteDialog - - title: qsTr("Deleting source") - - contentItem: ColumnLayout { - spacing: StudioTheme.Values.sectionColumnSpacing - - Text { - text: qsTr("Are you sure that you want to delete source \"" + sourceName + "\"?") - color: StudioTheme.Values.themeTextColor - } - - RowLayout { - Layout.alignment: Qt.AlignRight | Qt.AlignVCenter - spacing: StudioTheme.Values.sectionRowSpacing - - HelperWidgets.Button { - id: btnDelete - - text: qsTr("Delete") - onClicked: root.deleteItem(index) - } - - HelperWidgets.Button { - text: qsTr("Cancel") - onClicked: deleteDialog.reject() - } - } - } - } - - StudioControls.Dialog { - id: renameDialog - - title: qsTr("Rename source") - - onAccepted: { - if (newNameField.text !== "") - sourceName = newNameField.text - } - - onOpened: { - newNameField.text = sourceName - } - - contentItem: ColumnLayout { - spacing: 2 - - Text { - text: qsTr("Previous name: " + sourceName) - color: StudioTheme.Values.themeTextColor - } - - Spacer {} - - Text { - text: qsTr("New name:") - color: StudioTheme.Values.themeTextColor - } - - StudioControls.TextField { - id: newNameField - - Layout.fillWidth: true - actionIndicator.visible: false - translationIndicator.visible: false - validator: newNameValidator - - Keys.onEnterPressed: renameDialog.accept() - Keys.onReturnPressed: renameDialog.accept() - Keys.onEscapePressed: renameDialog.reject() - - onTextChanged: { - btnRename.enabled = newNameField.text !== "" - } - } - - Spacer {} - - RowLayout { - Layout.alignment: Qt.AlignRight | Qt.AlignVCenter - spacing: StudioTheme.Values.sectionRowSpacing - - HelperWidgets.Button { - id: btnRename - - text: qsTr("Rename") - onClicked: renameDialog.accept() - } - - HelperWidgets.Button { - text: qsTr("Cancel") - onClicked: renameDialog.reject() - } - } - } - } - - RegularExpressionValidator { - id: newNameValidator - regularExpression: /^\w+$/ - } - - states: [ - State { - name: "default" - when: !sourceIsSelected && !itemMouse.containsMouse - - PropertyChanges { - target: innerRect - opacity: 0.4 - color: StudioTheme.Values.themeControlBackground - } - - PropertyChanges { - target: root - textColor: StudioTheme.Values.themeTextColor - } - }, - State { - name: "hovered" - when: !sourceIsSelected && itemMouse.containsMouse - - PropertyChanges { - target: innerRect - opacity: 0.5 - color: StudioTheme.Values.themeControlBackgroundHover - } - - PropertyChanges { - target: root - textColor: StudioTheme.Values.themeTextColor - } - }, - State { - name: "selected" - when: sourceIsSelected - - PropertyChanges { - target: innerRect - opacity: 0.6 - color: StudioTheme.Values.themeControlBackgroundInteraction - } - - PropertyChanges { - target: root - textColor: StudioTheme.Values.themeIconColorSelected - expanded: true - } - - PropertyChanges { - target: expandButton - enabled: false - } - } - ] } diff --git a/share/qtcreator/qmldesigner/collectionEditorQmlSource/NewCollectionDialog.qml b/share/qtcreator/qmldesigner/collectionEditorQmlSource/NewCollectionDialog.qml index ee5dba02706..50ae55ab722 100644 --- a/share/qtcreator/qmldesigner/collectionEditorQmlSource/NewCollectionDialog.qml +++ b/share/qtcreator/qmldesigner/collectionEditorQmlSource/NewCollectionDialog.qml @@ -14,15 +14,12 @@ import CollectionEditor 1.0 StudioControls.Dialog { id: root - enum SourceType { NewJson, NewCsv, ExistingCollection, NewCollectionToJson } + enum SourceType { NewJson, NewCsv, ExistingCollection } required property var backendValue required property var sourceModel - readonly property alias collectionType: typeMode.collectionType readonly property bool isValid: collectionName.isValid - && jsonCollections.isValid - && newCollectionPath.isValid title: qsTr("Add a new Model") anchors.centerIn: parent @@ -31,8 +28,6 @@ StudioControls.Dialog { onOpened: { collectionName.text = qsTr("Model") - updateType() - updateJsonSourceIndex() updateCollectionExists() } @@ -41,57 +36,12 @@ StudioControls.Dialog { } onAccepted: { - if (root.isValid) { - root.backendValue.addCollection(collectionName.text, - root.collectionType, - newCollectionPath.text, - jsonCollections.currentValue) - - } - } - - function updateType() { - newCollectionPath.text = "" - if (typeMode.currentValue === NewCollectionDialog.SourceType.NewJson) { - newCollectionFileDialog.nameFilters = ["JSON Files (*.json)"] - newCollectionFileDialog.fileMode = PlatformWidgets.FileDialog.SaveFile - newCollectionPath.enabled = true - jsonCollections.enabled = false - typeMode.collectionType = "json" - } else if (typeMode.currentValue === NewCollectionDialog.SourceType.NewCsv) { - newCollectionFileDialog.nameFilters = ["Comma-Separated Values (*.csv)"] - newCollectionFileDialog.fileMode = PlatformWidgets.FileDialog.SaveFile - newCollectionPath.enabled = true - jsonCollections.enabled = false - typeMode.collectionType = "csv" - } else if (typeMode.currentValue === NewCollectionDialog.SourceType.ExistingCollection) { - newCollectionFileDialog.nameFilters = ["All Model Group Files (*.json *.csv)", - "JSON Files (*.json)", - "Comma-Separated Values (*.csv)"] - newCollectionFileDialog.fileMode = PlatformWidgets.FileDialog.OpenFile - newCollectionPath.enabled = true - jsonCollections.enabled = false - typeMode.collectionType = "existing" - } else if (typeMode.currentValue === NewCollectionDialog.SourceType.NewCollectionToJson) { - newCollectionFileDialog.nameFilters = [""] - newCollectionPath.enabled = false - jsonCollections.enabled = true - typeMode.collectionType = "json" - } - } - - function updateJsonSourceIndex() { - if (!jsonCollections.enabled) { - jsonCollections.currentIndex = -1 - return - } - - if (jsonCollections.currentIndex === -1 && jsonCollections.model.rowCount()) - jsonCollections.currentIndex = 0 + if (root.isValid) + root.backendValue.addCollectionToDataStore(collectionName.text); } function updateCollectionExists() { - collectionName.alreadyExists = sourceModel.collectionExists(jsonCollections.currentValue, + collectionName.alreadyExists = sourceModel.collectionExists(backendValue.dataStoreNode(), collectionName.text) } @@ -119,118 +69,6 @@ StudioControls.Dialog { contentItem: ColumnLayout { spacing: 5 - NameField { - text: qsTr("Type") - } - - StudioControls.ComboBox { - id: typeMode - - property string collectionType - - Layout.minimumWidth: 300 - Layout.fillWidth: true - - model: ListModel { - ListElement { text: qsTr("New JSON model group"); value: NewCollectionDialog.SourceType.NewJson} - ListElement { text: qsTr("New CSV model"); value: NewCollectionDialog.SourceType.NewCsv} - ListElement { text: qsTr("Import an existing model group"); value: NewCollectionDialog.SourceType.ExistingCollection} - ListElement { text: qsTr("Add a model to an available JSON model group"); value: NewCollectionDialog.SourceType.NewCollectionToJson} - } - - textRole: "text" - valueRole: "value" - actionIndicatorVisible: false - - onCurrentValueChanged: root.updateType() - } - - Spacer {} - - RowLayout { - visible: newCollectionPath.enabled - - NameField { - text: qsTr("File location") - visible: newCollectionPath.enabled - } - - Text { - id: newCollectionPath - - readonly property bool isValid: !newCollectionPath.enabled || newCollectionPath.text !== "" - - Layout.fillWidth: true - Layout.alignment: Qt.AlignRight | Qt.AlignVCenter - elide: Text.ElideRight - font.family: StudioTheme.Constants.font.family - font.pixelSize: StudioTheme.Values.baseIconFontSize - color: StudioTheme.Values.themePlaceholderTextColor - } - - HelperWidgets.Button { - Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter - text: qsTr("Select") - - onClicked: newCollectionFileDialog.open() - - PlatformWidgets.FileDialog { - id: newCollectionFileDialog - - title: qsTr("Select source file") - fileMode: PlatformWidgets.FileDialog.OpenFile - acceptLabel: newCollectionFileDialog.fileMode === PlatformWidgets.FileDialog.OpenFile - ? qsTr("Open") - : qsTr("Add") - - onAccepted: newCollectionPath.text = newCollectionFileDialog.currentFile - } - } - } - - ErrorField { - visible: !newCollectionPath.isValid - text: qsTr("Select a file to continue") - } - - Spacer { visible: newCollectionPath.enabled } - - NameField { - text: qsTr("JSON model group") - visible: jsonCollections.enabled - } - - StudioControls.ComboBox { - id: jsonCollections - - readonly property bool isValid: !jsonCollections.enabled || jsonCollections.currentIndex !== -1 - - Layout.fillWidth: true - - implicitWidth: 300 - textRole: "sourceName" - valueRole: "sourceNode" - visible: jsonCollections.enabled - actionIndicatorVisible: false - - model: CollectionJsonSourceFilterModel { - sourceModel: root.sourceModel - onRowsInserted: root.updateJsonSourceIndex() - onModelReset: root.updateJsonSourceIndex() - onRowsRemoved: root.updateJsonSourceIndex() - } - - onEnabledChanged: root.updateJsonSourceIndex() - onCurrentValueChanged: root.updateCollectionExists() - } - - ErrorField { - visible: !jsonCollections.isValid - text: qsTr("Add a JSON resource to continue") - } - - Spacer {visible: jsonCollections.visible } - NameField { text: qsTr("The model name") visible: collectionName.enabled diff --git a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml index 85a847ba5e0..965f31d21ab 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml @@ -49,7 +49,7 @@ StudioControls.PopupDialog { root.close() } function onPopupShouldOpen() { - root.showGlobal() + Qt.callLater(root.showGlobal) } } } diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/BlurHelper.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/BlurHelper.qml new file mode 100644 index 00000000000..e68a0bc8a25 --- /dev/null +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/BlurHelper.qml @@ -0,0 +1,68 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: BSD-3-Clause + +// This file should match the BlurHelper.qml in qtquickdesigner repository, except for shader paths + +import QtQuick + +Item { + id: rootItem + property alias blurSrc1: blurredItemSource1 + property alias blurSrc2: blurredItemSource2 + property alias blurSrc3: blurredItemSource3 + property alias blurSrc4: blurredItemSource4 + property alias blurSrc5: blurredItemSource5 + + component BlurItem: ShaderEffect { + property vector2d offset: Qt.vector2d((1.0 + rootItem.blurMultiplier) / width, + (1.0 + rootItem.blurMultiplier) / height) + visible: false + layer.enabled: true + layer.smooth: true + vertexShader: g_propertyData.blur_vs_path + fragmentShader: g_propertyData.blur_fs_path + } + + QtObject { + id: priv + property bool useBlurItem1: true + property bool useBlurItem2: rootItem.blurMax > 2 + property bool useBlurItem3: rootItem.blurMax > 8 + property bool useBlurItem4: rootItem.blurMax > 16 + property bool useBlurItem5: rootItem.blurMax > 32 + } + + BlurItem { + id: blurredItemSource1 + property Item src: priv.useBlurItem1 ? source : null + // Size of the first blurred item is by default half of the source. + // Increase for quality and decrease for performance & more blur. + readonly property int blurItemSize: 8 + width: src ? Math.ceil(src.width / 16) * blurItemSize : 0 + height: src ? Math.ceil(src.height / 16) * blurItemSize : 0 + } + BlurItem { + id: blurredItemSource2 + property Item src: priv.useBlurItem2 ? blurredItemSource1 : null + width: blurredItemSource1.width * 0.5 + height: blurredItemSource1.height * 0.5 + } + BlurItem { + id: blurredItemSource3 + property Item src: priv.useBlurItem3 ? blurredItemSource2 : null + width: blurredItemSource2.width * 0.5 + height: blurredItemSource2.height * 0.5 + } + BlurItem { + id: blurredItemSource4 + property Item src: priv.useBlurItem4 ? blurredItemSource3 : null + width: blurredItemSource3.width * 0.5 + height: blurredItemSource3.height * 0.5 + } + BlurItem { + id: blurredItemSource5 + property Item src: priv.useBlurItem5 ? blurredItemSource4 : null + width: blurredItemSource4.width * 0.5 + height: blurredItemSource4.height * 0.5 + } +} diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectCompositionNode.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectCompositionNode.qml index 56a508377ec..2ccaeaf36c3 100644 --- a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectCompositionNode.qml +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectCompositionNode.qml @@ -12,19 +12,22 @@ import EffectMakerBackend HelperWidgets.Section { id: root + property int modelIndex: 0 + caption: nodeName category: "EffectMaker" - draggable: true + draggable: !isDependency fillBackground: true - showCloseButton: true + showCloseButton: !isDependency closeButtonToolTip: qsTr("Remove") + visible: repeater.count > 0 || !isDependency onCloseButtonClicked: { - EffectMakerBackend.effectMakerModel.removeNode(root.index) + EffectMakerBackend.effectMakerModel.removeNode(root.modelIndex) } - showEyeButton: true + showEyeButton: !isDependency eyeEnabled: nodeEnabled eyeButtonToolTip: qsTr("Enable/Disable Node") @@ -36,6 +39,7 @@ HelperWidgets.Section { spacing: 10 Repeater { + id: repeater model: nodeUniformsModel EffectCompositionNodeUniform { diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectCompositionNodeUniform.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectCompositionNodeUniform.qml index 6aedc798f59..d696fccc1eb 100644 --- a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectCompositionNodeUniform.qml +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectCompositionNodeUniform.qml @@ -15,6 +15,8 @@ Item { height: layout.implicitHeight + visible: !uniformUseCustomValue + Component.onCompleted: { if (uniformType === "int") valueLoader.source = "ValueInt.qml" diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMaker.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMaker.qml index a60b41acf31..d3ccb36a749 100644 --- a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMaker.qml +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMaker.qml @@ -16,14 +16,45 @@ Item { property int moveToIdx: 0 property bool previewAnimationRunning: false - SaveDialog { - id: saveDialog - compositionName: EffectMakerBackend.effectMakerModel.currentComposition + // Invoked after save changes is done + property var onSaveChangesCallback: () => {} + + // Invoked from C++ side when open composition is requested and there are unsaved changes + function promptToSaveBeforeOpen() { + root.onSaveChangesCallback = () => { EffectMakerBackend.rootView.doOpenComposition() } + + saveChangesDialog.open() + } + + Connections { + target: EffectMakerBackend.effectMakerModel + function onIsEmptyChanged() { + if (EffectMakerBackend.effectMakerModel.isEmpty) + saveAsDialog.close() + } + } + + SaveAsDialog { + id: saveAsDialog anchors.centerIn: parent - onAccepted: { - let name = saveDialog.compositionName - EffectMakerBackend.effectMakerModel.exportComposition(name) - EffectMakerBackend.effectMakerModel.exportResources(name) + } + + SaveChangesDialog { + id: saveChangesDialog + anchors.centerIn: parent + + onSave: { + if (EffectMakerBackend.effectMakerModel.currentComposition === "") { + // if current composition is unsaved, show save as dialog and clear afterwards + saveAsDialog.clearOnClose = true + saveAsDialog.open() + } else { + root.onSaveChangesCallback() + } + } + + onDiscard: { + root.onSaveChangesCallback() } } @@ -33,7 +64,29 @@ Item { spacing: 1 EffectMakerTopBar { - onSaveClicked: saveDialog.open() + onAddClicked: { + root.onSaveChangesCallback = () => { EffectMakerBackend.effectMakerModel.clear() } + + if (EffectMakerBackend.effectMakerModel.hasUnsavedChanges) + saveChangesDialog.open() + else + EffectMakerBackend.effectMakerModel.clear() + } + + onSaveClicked: { + let name = EffectMakerBackend.effectMakerModel.currentComposition + + if (name === "") + saveAsDialog.open() + else + EffectMakerBackend.effectMakerModel.saveComposition(name) + } + + onSaveAsClicked: saveAsDialog.open() + + onAssignToSelectedClicked: { + EffectMakerBackend.effectMakerModel.assignToSelected() + } } EffectMakerPreview { @@ -55,6 +108,21 @@ Item { mainRoot: root anchors.verticalCenter: parent.verticalCenter + x: 5 + width: parent.width - 50 + } + + HelperWidgets.AbstractButton { + anchors.right: parent.right + anchors.rightMargin: 5 + anchors.verticalCenter: parent.verticalCenter + + style: StudioTheme.Values.viewBarButtonStyle + buttonIcon: StudioTheme.Constants.clearList_medium + tooltip: qsTr("Remove all effect nodes.") + enabled: !EffectMakerBackend.effectMakerModel.isEmpty + + onClicked: EffectMakerBackend.effectMakerModel.clear() } HelperWidgets.AbstractButton { @@ -64,7 +132,7 @@ Item { style: StudioTheme.Values.viewBarButtonStyle buttonIcon: StudioTheme.Constants.code - tooltip: qsTr("Open Shader in Code Editor") + tooltip: qsTr("Open Shader in Code Editor.") visible: false // TODO: to be implemented onClicked: {} // TODO @@ -96,6 +164,7 @@ Item { delegate: EffectCompositionNode { width: root.width + modelIndex: index Behavior on y { PropertyAnimation { @@ -144,7 +213,8 @@ Item { currItem.y = root.secsY[i] } } else if (i < root.moveFromIdx) { - if (root.draggedSec.y < currItem.y + (currItem.height - root.draggedSec.height) * .5) { + if (!repeater.model.isDependencyNode(i) + && root.draggedSec.y < currItem.y + (currItem.height - root.draggedSec.height) * .5) { currItem.y = root.secsY[i] + root.draggedSec.height root.moveToIdx = Math.min(root.moveToIdx, i) } else { diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerPreview.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerPreview.qml index 9b58cd56bd2..ea0b2d12954 100644 --- a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerPreview.qml +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerPreview.qml @@ -84,17 +84,6 @@ Column { anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter - HelperWidgets.AbstractButton { - enabled: sourceImage.scale > .4 - style: StudioTheme.Values.viewBarButtonStyle - buttonIcon: StudioTheme.Constants.zoomOut_medium - tooltip: qsTr("Zoom out") - - onClicked: { - sourceImage.scale -= .2 - } - } - HelperWidgets.AbstractButton { enabled: sourceImage.scale < 2 style: StudioTheme.Values.viewBarButtonStyle @@ -106,6 +95,17 @@ Column { } } + HelperWidgets.AbstractButton { + enabled: sourceImage.scale > .4 + style: StudioTheme.Values.viewBarButtonStyle + buttonIcon: StudioTheme.Constants.zoomOut_medium + tooltip: qsTr("Zoom out") + + onClicked: { + sourceImage.scale -= .2 + } + } + HelperWidgets.AbstractButton { enabled: sourceImage.scale !== 1 style: StudioTheme.Values.viewBarButtonStyle @@ -194,6 +194,13 @@ Column { } } + BlurHelper { + id: blurHelper + anchors.fill: parent + property int blurMax: g_propertyData.blur_helper_max_level ? g_propertyData.blur_helper_max_level : 64 + property real blurMultiplier: g_propertyData.blurMultiplier ? g_propertyData.blurMultiplier : 0 + } + Item { id: componentParent width: source.width diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerTopBar.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerTopBar.qml index c8758ff2ebb..9c962655e3a 100644 --- a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerTopBar.qml +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerTopBar.qml @@ -15,15 +15,59 @@ Rectangle { height: StudioTheme.Values.toolbarHeight color: StudioTheme.Values.themeToolbarBackground + signal addClicked signal saveClicked + signal saveAsClicked + signal assignToSelectedClicked - HelperWidgets.Button { + Row { + spacing: 5 anchors.verticalCenter: parent.verticalCenter - x: 5 - text: qsTr("Save in Library") + HelperWidgets.AbstractButton { + style: StudioTheme.Values.viewBarButtonStyle + buttonIcon: StudioTheme.Constants.add_medium + tooltip: qsTr("Add new composition") - onClicked: root.saveClicked() + onClicked: root.addClicked() + } + + HelperWidgets.AbstractButton { + style: StudioTheme.Values.viewBarButtonStyle + buttonIcon: StudioTheme.Constants.save_medium + tooltip: qsTr("Save current composition") + enabled: EffectMakerBackend.effectMakerModel.hasUnsavedChanges + || EffectMakerBackend.effectMakerModel.currentComposition === "" + + onClicked: root.saveClicked() + } + + HelperWidgets.AbstractButton { + style: StudioTheme.Values.viewBarButtonStyle + buttonIcon: StudioTheme.Constants.saveAs_medium + tooltip: qsTr("Save current composition with a new name") + enabled: !EffectMakerBackend.effectMakerModel.isEmpty + + onClicked: root.saveAsClicked() + } + + HelperWidgets.AbstractButton { + style: StudioTheme.Values.viewBarButtonStyle + buttonIcon: StudioTheme.Constants.assignTo_medium + tooltip: qsTr("Assign current composition to selected item") + enabled: EffectMakerBackend.effectMakerModel.currentComposition !== "" + + onClicked: root.assignToSelectedClicked() + } + } + + + Text { + readonly property string compName: EffectMakerBackend.effectMakerModel.currentComposition + + text: compName !== "" ? compName : qsTr("Untitled") + anchors.centerIn: parent + color: StudioTheme.Values.themeTextColor } HelperWidgets.AbstractButton { diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectNode.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectNode.qml index 8ae703adb7e..f3fca1ccec7 100644 --- a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectNode.qml +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectNode.qml @@ -15,20 +15,22 @@ Rectangle { width: 140 height: 32 - color: mouseArea.containsMouse ? StudioTheme.Values.themeControlBackgroundInteraction - : "transparent" + color: mouseArea.containsMouse && modelData.canBeAdded + ? StudioTheme.Values.themeControlBackgroundInteraction : "transparent" signal addEffectNode(var nodeQenPath) - MouseArea { + ToolTipArea { id: mouseArea anchors.fill: parent - hoverEnabled: true acceptedButtons: Qt.LeftButton + tooltip: modelData.canBeAdded ? "" : qsTr("Existing effect has conflicting properties, this effect cannot be added.") + onClicked: { - root.addEffectNode(modelData.nodeQenPath) + if (modelData.canBeAdded) + root.addEffectNode(modelData.nodeQenPath) } } @@ -41,13 +43,15 @@ Rectangle { width: 32 height: 32 - color: StudioTheme.Values.themeTextColor + color: modelData.canBeAdded ? StudioTheme.Values.themeTextColor + : StudioTheme.Values.themeTextColorDisabled source: modelData.nodeIcon } Text { text: modelData.nodeName - color: StudioTheme.Values.themeTextColor + color: modelData.canBeAdded ? StudioTheme.Values.themeTextColor + : StudioTheme.Values.themeTextColorDisabled font.pointSize: StudioTheme.Values.smallFontSize anchors.verticalCenter: nodeIcon.verticalCenter } diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectNodesComboBox.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectNodesComboBox.qml index 0827b20c1e3..a2187d4aba7 100644 --- a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectNodesComboBox.qml +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectNodesComboBox.qml @@ -12,8 +12,6 @@ StudioControls.ComboBox { id: root actionIndicatorVisible: false - x: 5 - width: parent.width - 50 model: [qsTr("+ Add Effect")] @@ -23,18 +21,48 @@ StudioControls.ComboBox { required property Item mainRoot + readonly property int popupHeight: Math.min(800, row.height + 2) + + function calculateWindowGeometry() { + var globalPos = EffectMakerBackend.rootView.globalPos(mainRoot.mapFromItem(root, 0, 0)) + var screenRect = EffectMakerBackend.rootView.screenRect(); + + window.width = row.width + 2 // 2: scrollView left and right 1px margins + + var newX = globalPos.x + root.width - window.width + if (newX < screenRect.x) + newX = globalPos.x + + var newY = Math.min(screenRect.y + screenRect.height, + Math.max(screenRect.y, globalPos.y + root.height - 1)) + + // Check if we have more space above or below the control, and put control on that side, + // unless we have enough room for maximum size popup under the control + var newHeight + var screenY = newY - screenRect.y + if (screenRect.height - screenY > screenY || screenRect.height - screenY > root.popupHeight) { + newHeight = Math.min(root.popupHeight, screenRect.height - screenY) + } else { + newHeight = Math.min(root.popupHeight, screenY - root.height) + newY = newY - newHeight - root.height + 1 + } + + window.height = newHeight + window.x = newX + window.y = newY + } + Connections { target: root.popup function onAboutToShow() { - var a = mainRoot.mapToGlobal(0, 0) - var b = root.mapToItem(mainRoot, 0, 0) - - window.x = a.x + b.x + root.width - window.width - window.y = a.y + b.y + root.height - 1 + root.calculateWindowGeometry() window.show() window.requestActivate() + + // Geometry can get corrupted by first show after screen change, so recalc it + root.calculateWindowGeometry() } function onAboutToHide() { @@ -45,12 +73,10 @@ StudioControls.ComboBox { Window { id: window - width: row.width + 2 // 2: scrollView left and right 1px margins - height: Math.min(800, Math.min(row.height + 2, Screen.height - y - 40)) // 40: some bottom margin to cover OS bottom toolbar flags: Qt.Dialog | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint onActiveFocusItemChanged: { - if (!window.activeFocusItem && !root.indicator.hover && root.popup.opened) + if (!window.activeFocusItem && !root.hovered && root.popup.opened) root.popup.close() } @@ -59,6 +85,7 @@ StudioControls.ComboBox { color: StudioTheme.Values.themePanelBackground border.color: StudioTheme.Values.themeInteraction border.width: 1 + focus: true HelperWidgets.ScrollView { anchors.fill: parent @@ -67,16 +94,6 @@ StudioControls.ComboBox { Row { id: row - onWidthChanged: { - // Needed to update on first window showing, as row.width only gets - // correct value after the window is shown, so first showing is off - - var a = mainRoot.mapToGlobal(0, 0) - var b = root.mapToItem(mainRoot, 0, 0) - - window.x = a.x + b.x + root.width - row.width - } - padding: 10 spacing: 10 @@ -108,6 +125,11 @@ StudioControls.ComboBox { } } } + + Keys.onPressed: function(event) { + if (event.key === Qt.Key_Escape && root.popup.opened) + root.popup.close() + } } } } diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/PreviewImagesComboBox.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/PreviewImagesComboBox.qml index bcf4efbd4d9..feadaa1d940 100644 --- a/share/qtcreator/qmldesigner/effectMakerQmlSources/PreviewImagesComboBox.qml +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/PreviewImagesComboBox.qml @@ -30,18 +30,48 @@ StudioControls.ComboBox { "images/preview4.png"] property string selectedImage: images[0] + readonly property int popupHeight: Math.min(800, col.height + 2) + + function calculateWindowGeometry() { + var globalPos = EffectMakerBackend.rootView.globalPos(mainRoot.mapFromItem(root, 0, 0)) + var screenRect = EffectMakerBackend.rootView.screenRect(); + + window.width = col.width + 2 // 2: scrollView left and right 1px margins + + var newX = globalPos.x + root.width - window.width + if (newX < screenRect.x) + newX = globalPos.x + + var newY = Math.min(screenRect.y + screenRect.height, + Math.max(screenRect.y, globalPos.y + root.height - 1)) + + // Check if we have more space above or below the control, and put control on that side, + // unless we have enough room for maximum size popup under the control + var newHeight + var screenY = newY - screenRect.y + if (screenRect.height - screenY > screenY || screenRect.height - screenY > root.popupHeight) { + newHeight = Math.min(root.popupHeight, screenRect.height - screenY) + } else { + newHeight = Math.min(root.popupHeight, screenY - root.height) + newY = newY - newHeight - root.height + 1 + } + + window.height = newHeight + window.x = newX + window.y = newY + } + Connections { target: root.popup function onAboutToShow() { - var a = mainRoot.mapToGlobal(0, 0) - var b = root.mapToItem(mainRoot, 0, 0) - - window.x = a.x + b.x + root.width - window.width - window.y = a.y + b.y + root.height - 1 + root.calculateWindowGeometry() window.show() window.requestActivate() + + // Geometry can get corrupted by first show after screen change, so recalc it + root.calculateWindowGeometry() } function onAboutToHide() { @@ -60,17 +90,29 @@ StudioControls.ComboBox { anchors.fill: parent anchors.margins: 1 } + + MouseArea { + anchors.fill: parent + enabled: true + acceptedButtons: Qt.LeftButton + cursorShape: Qt.PointingHandCursor + onPressed: (mouse) => { + if (root.popup.opened) + root.popup.close() + else + root.popup.open() + mouse.accepted = true + } + } } Window { id: window - width: col.width + 2 // 2: scrollView left and right 1px margins - height: Math.min(800, Math.min(col.height + 2, Screen.height - y - 40)) // 40: some bottom margin to cover OS bottom toolbar flags: Qt.Dialog | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint onActiveFocusItemChanged: { - if (!window.activeFocusItem && !root.indicator.hover && root.popup.opened) + if (!window.activeFocusItem && !root.hovered && root.popup.opened) root.popup.close() } @@ -79,6 +121,7 @@ StudioControls.ComboBox { color: StudioTheme.Values.themePanelBackground border.color: StudioTheme.Values.themeInteraction border.width: 1 + focus: true HelperWidgets.ScrollView { anchors.fill: parent @@ -88,16 +131,6 @@ StudioControls.ComboBox { Column { id: col - onWidthChanged: { - // Needed to update on first window showing, as row.width only gets - // correct value after the window is shown, so first showing is off - - var a = mainRoot.mapToGlobal(0, 0) - var b = root.mapToItem(mainRoot, 0, 0) - - window.x = a.x + b.x + root.width - col.width - } - padding: 10 spacing: 10 @@ -135,6 +168,11 @@ StudioControls.ComboBox { } } } + + Keys.onPressed: function(event) { + if (event.key === Qt.Key_Escape && root.popup.opened) + root.popup.close() + } } } } diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/SaveDialog.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/SaveAsDialog.qml similarity index 75% rename from share/qtcreator/qmldesigner/effectMakerQmlSources/SaveDialog.qml rename to share/qtcreator/qmldesigner/effectMakerQmlSources/SaveAsDialog.qml index 8bd48e1d6c4..8e762cebbd8 100644 --- a/share/qtcreator/qmldesigner/effectMakerQmlSources/SaveDialog.qml +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/SaveAsDialog.qml @@ -6,7 +6,7 @@ import QtQuick.Controls import HelperWidgets as HelperWidgets import StudioControls as StudioControls import StudioTheme as StudioTheme -import AssetsLibraryBackend +import EffectMakerBackend StudioControls.Dialog { id: root @@ -18,12 +18,13 @@ StudioControls.Dialog { implicitWidth: 250 implicitHeight: 160 - property string compositionName: "" + property bool clearOnClose: false // clear the effect maker after saving onOpened: { - nameText.text = compositionName //TODO: Generate unique name - emptyText.text = "" + nameText.text = EffectMakerBackend.effectMakerModel.getUniqueEffectName() + nameText.selectAll() nameText.forceActiveFocus() + emptyText.text = "" } contentItem: Item { @@ -83,14 +84,28 @@ StudioControls.Dialog { text: qsTr("Save") enabled: nameText.text !== "" onClicked: { - root.compositionName = nameText.text - root.accept() //TODO: Check if name is unique + EffectMakerBackend.effectMakerModel.saveComposition(nameText.text) + + if (root.clearOnClose) { + EffectMakerBackend.effectMakerModel.clear() + root.clearOnClose = false + } + + root.accept() // TODO: confirm before overriding effect with same name } } HelperWidgets.Button { text: qsTr("Cancel") - onClicked: root.reject() + + onClicked: { + if (root.clearOnClose) { + EffectMakerBackend.effectMakerModel.clear() + root.clearOnClose = false + } + + root.reject() + } } } } diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/SaveChangesDialog.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/SaveChangesDialog.qml new file mode 100644 index 00000000000..c048c645d84 --- /dev/null +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/SaveChangesDialog.qml @@ -0,0 +1,65 @@ +// 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 StudioControls as StudioControls +import StudioTheme as StudioTheme +import EffectMakerBackend + +StudioControls.Dialog { + id: root + + title: qsTr("Save Changes") + + closePolicy: Popup.CloseOnEscape + modal: true + implicitWidth: 300 + implicitHeight: 130 + + signal save() + signal discard() + + contentItem: Item { + Text { + text: qsTr("Current composition has unsaved changes.") + color: StudioTheme.Values.themeTextColor + } + + HelperWidgets.Button { + width: 60 + anchors.bottom: parent.bottom + text: qsTr("Cancel") + onClicked: root.reject() + } + + Row { + anchors.right: parent.right + anchors.bottom: parent.bottom + spacing: 2 + + HelperWidgets.Button { + width: 50 + text: qsTr("Save") + onClicked: { + let name = EffectMakerBackend.effectMakerModel.currentComposition + if (name !== "") + EffectMakerBackend.effectMakerModel.saveComposition(name) + + root.save() + root.accept() + } + } + + HelperWidgets.Button { + width: 110 + text: qsTr("Discard Changes") + onClicked: { + root.discard() + root.accept() + } + } + } + } +} diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/ValueImage.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/ValueImage.qml index 571fac50002..832350ef177 100644 --- a/share/qtcreator/qmldesigner/effectMakerQmlSources/ValueImage.qml +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/ValueImage.qml @@ -19,5 +19,19 @@ Row { actionIndicatorVisible: false onAbsoluteFilePathChanged: uniformValue = absoluteFilePath + + function defaultAsString() { + let urlStr = uniformDefaultValue.toString() + urlStr = urlStr.replace(/^(file:\/{3})/, "") + + // Prepend slash if there is no drive letter + if (urlStr.length > 1 && urlStr[1] !== ':') + urlStr = '/' + urlStr; + + return urlStr + } + + defaultItems: [uniformDefaultValue.split('/').pop()] + defaultPaths: [defaultAsString(uniformDefaultValue)] } } diff --git a/share/qtcreator/qmldesigner/insight/Main.qml b/share/qtcreator/qmldesigner/insight/Main.qml index 1abad0050f5..14a69ce80f4 100644 --- a/share/qtcreator/qmldesigner/insight/Main.qml +++ b/share/qtcreator/qmldesigner/insight/Main.qml @@ -166,7 +166,7 @@ Rectangle { } } } - +/* HelperWidgets.Section { id: predefinedSection caption: qsTr("Predefined Categories") @@ -412,6 +412,7 @@ Rectangle { } } } +*/ } } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditor.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditor.qml index 4ff7c30c2bf..3262b03065d 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditor.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditor.qml @@ -183,8 +183,9 @@ SecondColumnLayout { property QtObject loaderItem: loader.item property string gradientPropertyName + keepOpen: loader.item?.eyeDropperActive ?? false + width: 260 - maximumHeight: Screen.desktopAvailableHeight * 0.7 function commitToGradient() { if (!loader.active) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditorPopup.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditorPopup.qml index c5e0ef63ee0..fcf2d341b89 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditorPopup.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditorPopup.qml @@ -13,6 +13,8 @@ import QtQuickDesignerColorPalette Column { id: root + property bool eyeDropperActive: ColorPaletteBackend.eyeDropperActive + property bool supportGradient: false property bool shapeGradients: false property alias gradientLine: gradientLine @@ -552,9 +554,9 @@ Column { textRole: "text" valueRole: "value" model: [ - { value: ColorPicker.Mode.HSVA, text: "HSVA" }, - { value: ColorPicker.Mode.RGBA, text: "RGBA" }, - { value: ColorPicker.Mode.HSLA, text: "HSLA" } + { value: StudioControls.ColorPicker.Mode.HSVA, text: "HSVA" }, + { value: StudioControls.ColorPicker.Mode.RGBA, text: "RGBA" }, + { value: StudioControls.ColorPicker.Mode.HSLA, text: "HSLA" } ] onActivated: colorPicker.mode = colorMode.currentValue @@ -597,7 +599,7 @@ Column { Row { id: rgbaRow - visible: colorPicker.mode === ColorPicker.Mode.RGBA + visible: colorPicker.mode === StudioControls.ColorPicker.Mode.RGBA spacing: StudioTheme.Values.controlGap DoubleSpinBox { @@ -683,7 +685,7 @@ Column { Row { id: hslaRow - visible: colorPicker.mode === ColorPicker.Mode.HSLA + visible: colorPicker.mode === StudioControls.ColorPicker.Mode.HSLA spacing: StudioTheme.Values.controlGap DoubleSpinBox { @@ -749,7 +751,7 @@ Column { Row { id: hsvaRow - visible: colorPicker.mode === ColorPicker.Mode.HSVA + visible: colorPicker.mode === StudioControls.ColorPicker.Mode.HSVA spacing: StudioTheme.Values.controlGap DoubleSpinBox { diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/HorizontalScrollBar.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/HorizontalScrollBar.qml index 7ce0324425f..9041dc17f0d 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/HorizontalScrollBar.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/HorizontalScrollBar.qml @@ -1,9 +1,9 @@ // Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -import QtQuick 2.15 -import QtQuick.Controls 2.15 -import StudioTheme 1.0 as StudioTheme +import QtQuick +import QtQuick.Controls.Basic +import StudioTheme as StudioTheme ScrollBar { id: scrollBar @@ -13,9 +13,10 @@ ScrollBar { implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding) - property bool scrollBarVisible: parent.childrenRect.width > parent.width + minimumSize: orientation == Qt.Horizontal ? height / width : width / height + orientation: Qt.Horizontal policy: scrollBar.scrollBarVisible ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff x: 0 @@ -24,8 +25,6 @@ ScrollBar { - (parent.bothVisible ? parent.verticalThickness : 0) padding: 0 - minimumSize: orientation == Qt.Horizontal ? height / width : width / height - background: Rectangle { color: StudioTheme.Values.themeScrollBarTrack } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/InsightSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/InsightSection.qml index b23f8ff95d3..77482bd8488 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/InsightSection.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/InsightSection.qml @@ -23,6 +23,7 @@ Section { } SectionLayout { +/* PropertyLabel { text: qsTr("Category") } SecondColumnLayout { @@ -84,7 +85,7 @@ Section { ExpandingSpacer {} } - +*/ PropertyLabel { text: qsTr("Object name") tooltip: qsTr("Sets the object name of the component.") diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml index 485640bcf84..3b00a30a1fc 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml @@ -1,13 +1,13 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -import QtQuick 2.15 -import QtQuick.Layouts 1.15 -import QtQuick.Controls 2.15 -import HelperWidgets 2.0 -import StudioControls 1.0 as StudioControls -import StudioTheme 1.0 as StudioTheme -import QtQuickDesignerTheme 1.0 +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme +import QtQuickDesignerTheme Row { id: root @@ -22,6 +22,10 @@ Row { // by QtQuick3D to add built-in primitives to the model. property var defaultItems + // These paths will be used for default items if they are defined. Otherwise, default item + // itself is used as the path. + property var defaultPaths + // Current item property string absoluteFilePath: "" @@ -40,6 +44,96 @@ Row { backendValue: root.backendValue } + component ThumbnailToolTip: ToolTip { + id: toolTip + + property alias checkerVisible: checker.visible + property alias thumbnailSource: thumbnail.source + + property alias titleText: title.text + property alias descriptionText: description.text + + property int maximumWidth: 420 + + delay: StudioTheme.Values.toolTipDelay + + background: Rectangle { + color: StudioTheme.Values.themeToolTipBackground + border.color: StudioTheme.Values.themeToolTipOutline + border.width: StudioTheme.Values.border + } + + contentItem: Row { + id: row + + readonly property real __epsilon: 2 + + height: Math.max(wrapper.visible ? wrapper.height : 0, column.height) + spacing: 10 + + Item { + id: wrapper + visible: thumbnail.status === Image.Ready + width: 96 + height: 96 + + Image { + id: checker + anchors.fill: parent + fillMode: Image.Tile + source: "images/checkers.png" + } + + Image { + id: thumbnail + anchors.fill: parent + sourceSize.width: wrapper.width + sourceSize.height: wrapper.height + asynchronous: true + fillMode: Image.PreserveAspectFit + } + } + + Column { + id: column + + property int thumbnailSize: wrapper.visible ? wrapper.width + row.spacing : 0 + + spacing: 10 + anchors.verticalCenter: parent.verticalCenter + width: Math.min(toolTip.maximumWidth - column.thumbnailSize, + Math.max(titleTextMetrics.width + row.__epsilon, + descriptionTextMetrics.width + row.__epsilon)) + + Text { + id: title + font: toolTip.font + color: StudioTheme.Values.themeToolTipText + + TextMetrics { + id: titleTextMetrics + text: title.text + font: title.font + } + } + + Text { + id: description + width: column.width + font: toolTip.font + color: StudioTheme.Values.themeToolTipText + wrapMode: Text.Wrap + + TextMetrics { + id: descriptionTextMetrics + text: description.text + font: description.font + } + } + } + } + } + StudioControls.FilterComboBox { id: comboBox @@ -86,71 +180,28 @@ Row { } } - ToolTip { - id: toolTip - visible: comboBox.hover && toolTip.text !== "" + ThumbnailToolTip { + id: rootToolTip + + visible: comboBox.hover && rootToolTip.text !== "" text: root.backendValue?.valueToString ?? "" - delay: StudioTheme.Values.toolTipDelay - background: Rectangle { - color: StudioTheme.Values.themeToolTipBackground - border.color: StudioTheme.Values.themeToolTipOutline - border.width: StudioTheme.Values.border - } - - contentItem: RowLayout { - spacing: 10 - - Item { - visible: thumbnail.status === Image.Ready - Layout.preferredWidth: 96 - Layout.preferredHeight: 96 - - Image { - id: checker - visible: !root.isMesh(root.absoluteFilePath) - anchors.fill: parent - fillMode: Image.Tile - source: "images/checkers.png" - } - - Image { - id: thumbnail - asynchronous: true - height: 96 - width: 96 - fillMode: Image.PreserveAspectFit - source: { - if (root.isBuiltInPrimitive(root.absoluteFilePath)) - return "image://qmldesigner_thumbnails/" - + root.absoluteFilePath.substring(1, root.absoluteFilePath.length) - + ".builtin" - - if (fileModel.isLocal(root.absoluteFilePath)) - return "image://qmldesigner_thumbnails/" + root.absoluteFilePath - - return root.absoluteFilePath - } - } - } - - ColumnLayout { - Text { - text: root.fileName(toolTip.text) - color: StudioTheme.Values.themeToolTipText - font: toolTip.font - } - - Text { - Layout.fillWidth: true - text: root.isBuiltInPrimitive(toolTip.text) ? qsTr("Built-in primitive") - : toolTip.text - font: toolTip.font - color: StudioTheme.Values.themeToolTipText - wrapMode: Text.WordWrap - } - } + checkerVisible: !root.isMesh(root.absoluteFilePath) + thumbnailSource: { + if (root.isBuiltInPrimitive(root.absoluteFilePath)) + return "image://qmldesigner_thumbnails/" + + root.absoluteFilePath.substring(1, root.absoluteFilePath.length) + + ".builtin" + + if (fileModel.isLocal(root.absoluteFilePath)) + return "image://qmldesigner_thumbnails/" + root.absoluteFilePath + + return root.absoluteFilePath } + titleText: root.fileName(rootToolTip.text) + descriptionText: root.isBuiltInPrimitive(rootToolTip.text) + ? qsTr("Built-in primitive") + : rootToolTip.text } delegate: ItemDelegate { @@ -213,71 +264,25 @@ Row { } } - ToolTip { + ThumbnailToolTip { id: delegateToolTip + visible: delegateRoot.hovered text: delegateRoot.relativeFilePath - delay: StudioTheme.Values.toolTipDelay - background: Rectangle { - color: StudioTheme.Values.themeToolTipBackground - border.color: StudioTheme.Values.themeToolTipOutline - border.width: StudioTheme.Values.border - } - - contentItem: RowLayout { - spacing: 10 - - Item { - visible: delegateThumbnail.status === Image.Ready - Layout.preferredWidth: 96 - Layout.preferredHeight: 96 - - Image { - id: delegateChecker - visible: !root.isMesh(delegateRoot.absoluteFilePath) - anchors.fill: parent - fillMode: Image.Tile - source: "images/checkers.png" - } - - Image { - id: delegateThumbnail - asynchronous: true - sourceSize.height: 96 - sourceSize.width: 96 - height: 96 - width: 96 - fillMode: Image.PreserveAspectFit - source: { - if (root.isBuiltInPrimitive(delegateRoot.name)) - return "image://qmldesigner_thumbnails/" - + delegateRoot.name.substring(1, delegateRoot.name.length) - + ".builtin" - - return "image://qmldesigner_thumbnails/" + delegateRoot.absoluteFilePath - } - } - } - - ColumnLayout { - Text { - text: delegateRoot.name - color: StudioTheme.Values.themeToolTipText - font: delegateToolTip.font - } - - Text { - Layout.fillWidth: true - text: root.isBuiltInPrimitive(delegateToolTip.text) - ? qsTr("Built-in primitive") - : delegateToolTip.text - font: delegateToolTip.font - color: StudioTheme.Values.themeToolTipText - wrapMode: Text.WordWrap - } - } + checkerVisible: !root.isMesh(delegateRoot.absoluteFilePath) + thumbnailSource: { + if (root.isBuiltInPrimitive(delegateRoot.name)) + return "image://qmldesigner_thumbnails/" + + delegateRoot.name.substring(1, delegateRoot.name.length) + + ".builtin" + + return "image://qmldesigner_thumbnails/" + delegateRoot.absoluteFilePath } + titleText: delegateRoot.name + descriptionText: root.isBuiltInPrimitive(delegateToolTip.text) + ? qsTr("Built-in primitive") + : delegateToolTip.text } } @@ -422,8 +427,10 @@ Row { if (root.defaultItems !== undefined) { for (var i = 0; i < root.defaultItems.length; ++i) { comboBox.listModel.append({ - absoluteFilePath: "", - relativeFilePath: root.defaultItems[i], + absoluteFilePath: root.defaultPaths ? root.defaultPaths[i] + : "", + relativeFilePath: root.defaultPaths ? root.defaultPaths[i] + : root.defaultItems[i], name: root.defaultItems[i], group: 0 }) @@ -454,6 +461,7 @@ Row { } onDefaultItemsChanged: root.createModel() + onDefaultPathsChanged: root.createModel() Component.onCompleted: { root.createModel() diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/VerticalScrollBar.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/VerticalScrollBar.qml index ed19826e656..520f401116f 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/VerticalScrollBar.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/VerticalScrollBar.qml @@ -1,9 +1,9 @@ // Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -import QtQuick 2.15 -import QtQuick.Controls 2.15 -import StudioTheme 1.0 as StudioTheme +import QtQuick +import QtQuick.Controls.Basic +import StudioTheme as StudioTheme ScrollBar { id: scrollBar diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/PopupDialog.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/PopupDialog.qml index ae821804a5d..594e115639b 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/PopupDialog.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/PopupDialog.qml @@ -25,19 +25,21 @@ QtObject { property alias flags: window.flags property alias visible: window.visible + property int anchorGap: 10 property int edge: Qt.LeftEdge property int actualEdge: root.edge - property alias chevronVisible: chevron.visible + //property alias chevronVisible: chevron.visible property rect __itemGlobal: Qt.rect(0, 0, 100, 100) + property bool keepOpen: false + signal closing(close: var) - function showGlobal() - { - var pos = WindowManager.globalCursorPosition(); + function showGlobal() { + var pos = WindowManager.globalCursorPosition() root.__itemGlobal = Qt.rect(pos.x, pos.y, 300, 20) - root.chevronVisible = false + //root.chevronVisible = false root.layout() window.show() window.raise() @@ -46,7 +48,7 @@ QtObject { function show(target: Item) { var originGlobal = target.mapToGlobal(0, 0) root.__itemGlobal = Qt.rect(originGlobal.x, originGlobal.y, target.width, target.height) - root.chevronVisible = true + //root.chevronVisible = true root.layout() window.show() window.raise() @@ -57,11 +59,8 @@ QtObject { } function layout() { - // Setup - var screen = Qt.rect(0, //Screen.virtualX, // TODO - 0, //Screen.virtualY, // TODO - Screen.desktopAvailableWidth, - Screen.desktopAvailableHeight) + let position = Qt.point(root.__itemGlobal.x, root.__itemGlobal.y) + var screen = WindowManager.getScreenGeometry(position) // Collect region information let edges = window.getRegions(screen, root.__itemGlobal) @@ -78,8 +77,8 @@ QtObject { let anchor = edges[edge].anchor let popoverRect = window.popoverGeometry(edge, anchor, edges[edge].region) - if (chevron.visible) - chevron.layout(edge, popoverRect, anchor) + //if (chevron.visible) + // chevron.layout(edge, popoverRect, anchor) window.x = popoverRect.x window.y = popoverRect.y @@ -88,7 +87,7 @@ QtObject { property Window window: Window { id: window - property int margin: 20 + property int margin: 0 //20 width: root.width + (2 * window.margin) height: root.height + (2 * window.margin) @@ -139,7 +138,7 @@ QtObject { return Qt.LeftEdge // Default } - function contains(a: rect, b: rect): boolean { + function contains(a: rect, b: rect): bool { let halfSizeA = Qt.size(a.width * 0.5, a.height * 0.5) let halfSizeB = Qt.size(b.width * 0.5, b.height * 0.5) @@ -165,9 +164,18 @@ QtObject { var targetCenter = Qt.point(target.x + (target.width * 0.5), target.y + (target.height * 0.5)) + // Just as a reminder why calculating custom right and bottom: + // > Note that for historical reasons this function returns top() + height() - 1; + // > use y() + height() to retrieve the true y-coordinate. + let sourceRight = source.x + source.width + let sourceBottom = source.y + source.height + // TOP - let topAnchor = Qt.point(targetCenter.x, target.y) - let topRegion = Qt.rect(source.x, source.y, source.width, Math.max(0, topAnchor.y)) + let topAnchor = Qt.point(targetCenter.x, target.y - root.anchorGap) + let topRegion = Qt.rect(source.x, + source.y, + source.width, + (topAnchor.y < source.top) ? 0 : Math.abs(topAnchor.y - source.top)) edges[Qt.TopEdge] = { anchor: topAnchor, @@ -177,10 +185,10 @@ QtObject { } // RIGHT - let rightAnchor = Qt.point(target.x + target.width, targetCenter.y) + let rightAnchor = Qt.point(target.x + target.width + root.anchorGap, targetCenter.y) let rightRegion = Qt.rect(rightAnchor.x, source.y, - Math.max(0, source.width - rightAnchor.x), + (rightAnchor.x > sourceRight) ? 0 : Math.abs(sourceRight - rightAnchor.x), source.height) edges[Qt.RightEdge] = { @@ -191,11 +199,11 @@ QtObject { } // BOTTOM - let bottomAnchor = Qt.point(targetCenter.x, target.y + target.height) + let bottomAnchor = Qt.point(targetCenter.x, target.y + target.height + root.anchorGap) let bottomRegion = Qt.rect(source.x, bottomAnchor.y, source.width, - Math.max(0, source.height - bottomAnchor.y)) + (bottomAnchor.y > sourceBottom) ? 0 : Math.abs(sourceBottom - bottomAnchor.y)) edges[Qt.BottomEdge] = { anchor: bottomAnchor, @@ -205,8 +213,11 @@ QtObject { } // LEFT - let leftAnchor = Qt.point(target.x, targetCenter.y) - let leftRegion = Qt.rect(source.x, source.y, Math.max(0, leftAnchor.x), source.height) + let leftAnchor = Qt.point(target.x - root.anchorGap, targetCenter.y) + let leftRegion = Qt.rect(source.x, + source.y, + (leftAnchor.x < source.left) ? 0 : Math.abs(leftAnchor.x - source.left), + source.height) edges[Qt.LeftEdge] = { anchor: leftAnchor, @@ -221,7 +232,9 @@ QtObject { function popoverGeometry(edge: int, anchor: point, region: rect) { if (edge === Qt.TopEdge) { let height = Math.min(window.height, region.height) - return Qt.rect(Math.max(0, Math.min(anchor.x - (window.width * 0.5), region.width - window.width)), + return Qt.rect(Math.max(region.x, + Math.min(anchor.x - (window.width * 0.5), + region.x + region.width - window.width)), anchor.y - height, Math.min(window.width, region.width), height) @@ -230,14 +243,18 @@ QtObject { if (edge === Qt.RightEdge) { let width = Math.min(window.width, region.width) return Qt.rect(anchor.x, - Math.max(0, Math.min(anchor.y - (window.height * 0.5), region.height - window.height)), + Math.max(region.y, + Math.min(anchor.y - (window.height * 0.5), + region.y + region.height - window.height)), width, Math.min(window.height, region.height)) } if (edge === Qt.BottomEdge) { let height = Math.min(window.height, region.height) - return Qt.rect(Math.max(0, Math.min(anchor.x - (window.width * 0.5), region.width - window.width)), + return Qt.rect(Math.max(region.x, + Math.min(anchor.x - (window.width * 0.5), + region.x + region.width - window.width)), anchor.y, Math.min(window.width, region.width), height) @@ -246,7 +263,9 @@ QtObject { if (edge === Qt.LeftEdge) { let width = Math.min(window.width, region.width) return Qt.rect(anchor.x - width, - Math.max(0, Math.min(anchor.y - (window.height * 0.5), region.height - window.height)), + Math.max(region.y, + Math.min(anchor.y - (window.height * 0.5), + region.y + region.height - window.height)), width, Math.min(window.height, region.height)) } @@ -270,6 +289,9 @@ QtObject { if (!focusWindow) return + if (root.keepOpen) + return + if (focusWindow !== window && focusWindow.transientParent !== window) root.close() } @@ -304,7 +326,10 @@ QtObject { } } } - +/* + // The chevron will be reactivated when we fixed all the issues that where found during testing. + // * Potential Qt bug: black background instead of transparent border due to GPU selection on Windows + // * Ghost chevron on macOS after dragging the window Shape { id: chevron @@ -381,7 +406,7 @@ QtObject { PathLine { id: end; x: 0; y: 0 } } } - +*/ Column { id: column anchors.fill: parent @@ -392,6 +417,17 @@ QtObject { width: parent.width height: StudioTheme.Values.titleBarHeight + DragHandler { + id: dragHandler + + target: null + grabPermissions: PointerHandler.CanTakeOverFromAnything + onActiveChanged: { + if (dragHandler.active) + window.startSystemMove() // QTBUG-102488 + } + } + Row { id: row anchors.fill: parent diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/Section.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/Section.qml index d937055dff2..18732305ce7 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/Section.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/Section.qml @@ -34,7 +34,7 @@ Item { SectionLabel { id: arrow - style: control.style + controlStyle: control.style width: control.style.smallIconSize.width height: control.style.smallIconSize.height text: StudioTheme.Constants.sectionToggle @@ -56,7 +56,7 @@ Item { SectionLabel { id: label - style: control.style + controlStyle: control.style anchors.verticalCenter: parent.verticalCenter color: control.style.text.idle x: 22 diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/SectionLabel.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/SectionLabel.qml index b8586d96213..4e52000e986 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/SectionLabel.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/SectionLabel.qml @@ -9,11 +9,11 @@ import StudioTheme 1.0 as StudioTheme T.Label { id: control - property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle + property StudioTheme.ControlStyle controlStyle: StudioTheme.Values.controlStyle width: Math.max(Math.min(240, parent.width - 220), 90) - color: control.style.text.idle - font.pixelSize: control.style.baseFontSize + color: control.controlStyle.text.idle + font.pixelSize: control.controlStyle.baseFontSize elide: Text.ElideRight Layout.preferredWidth: width diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml index 1f7351a8a00..92d0d771a3c 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml @@ -79,300 +79,304 @@ QtObject { readonly property string centerHorizontal: "\u0060" readonly property string centerVertical: "\u0061" readonly property string cleanLogs_medium: "\u0062" - readonly property string closeCross: "\u0063" - readonly property string closeFile_large: "\u0064" - readonly property string closeLink: "\u0065" - readonly property string close_small: "\u0066" - readonly property string code: "\u0067" - readonly property string codeEditor_medium: "\u0068" - readonly property string codeview_medium: "\u0069" - readonly property string colorPopupClose: "\u006A" - readonly property string colorSelection_medium: "\u006B" - readonly property string columnsAndRows: "\u006C" - readonly property string comboBox_medium: "\u006D" - readonly property string cone_medium: "\u006E" - readonly property string cone_small: "\u006F" - readonly property string connection_small: "\u0070" - readonly property string connections_medium: "\u0071" - readonly property string copyLink: "\u0072" - readonly property string copyStyle: "\u0073" - readonly property string copy_small: "\u0074" - readonly property string cornerA: "\u0075" - readonly property string cornerB: "\u0076" - readonly property string cornersAll: "\u0077" - readonly property string createComponent_large: "\u0078" - readonly property string createComponent_small: "\u0079" - readonly property string createObject_medium: "\u007A" - readonly property string create_medium: "\u007B" - readonly property string create_small: "\u007C" - readonly property string cube_medium: "\u007D" - readonly property string cube_small: "\u007E" - readonly property string curveDesigner: "\u007F" - readonly property string curveDesigner_medium: "\u0080" - readonly property string curveEditor: "\u0081" - readonly property string customMaterialEditor: "\u0082" - readonly property string cylinder_medium: "\u0083" - readonly property string cylinder_small: "\u0084" - readonly property string decisionNode: "\u0085" - readonly property string deleteColumn: "\u0086" - readonly property string deleteMaterial: "\u0087" - readonly property string deleteRow: "\u0088" - readonly property string deleteTable: "\u0089" - readonly property string delete_medium: "\u008A" - readonly property string delete_small: "\u008B" - readonly property string deletecolumn_medium: "\u008C" - readonly property string deletepermanently_medium: "\u008D" - readonly property string deleterow_medium: "\u008E" - readonly property string designMode_large: "\u008F" - readonly property string detach: "\u0090" - readonly property string directionalLight_small: "\u0091" - readonly property string distributeBottom: "\u0092" - readonly property string distributeCenterHorizontal: "\u0093" - readonly property string distributeCenterVertical: "\u0094" - readonly property string distributeLeft: "\u0095" - readonly property string distributeOriginBottomRight: "\u0096" - readonly property string distributeOriginCenter: "\u0097" - readonly property string distributeOriginNone: "\u0098" - readonly property string distributeOriginTopLeft: "\u0099" - readonly property string distributeRight: "\u009A" - readonly property string distributeSpacingHorizontal: "\u009B" - readonly property string distributeSpacingVertical: "\u009D" - readonly property string distributeTop: "\u009E" - readonly property string download: "\u009F" - readonly property string downloadUnavailable: "\u00A0" - readonly property string downloadUpdate: "\u00A1" - readonly property string downloaded: "\u00A2" - readonly property string dragmarks: "\u00A3" - readonly property string duplicate_small: "\u00A4" - readonly property string edit: "\u00A5" - readonly property string editComponent_large: "\u00A6" - readonly property string editComponent_small: "\u00A7" - readonly property string editLightOff_medium: "\u00A8" - readonly property string editLightOn_medium: "\u00A9" - readonly property string edit_medium: "\u00AA" - readonly property string edit_small: "\u00AB" - readonly property string effects: "\u00AC" - readonly property string events_small: "\u00AE" - readonly property string export_medium: "\u00AF" - readonly property string eyeDropper: "\u00B0" - readonly property string favorite: "\u00B1" - readonly property string fitAll_medium: "\u00B2" - readonly property string fitSelected_small: "\u00B3" - readonly property string fitSelection_medium: "\u00B4" - readonly property string fitToView_medium: "\u00B5" - readonly property string flowAction: "\u00B6" - readonly property string flowTransition: "\u00B7" - readonly property string fontStyleBold: "\u00B8" - readonly property string fontStyleItalic: "\u00B9" - readonly property string fontStyleStrikethrough: "\u00BA" - readonly property string fontStyleUnderline: "\u00BB" - readonly property string forward_medium: "\u00BC" - readonly property string globalOrient_medium: "\u00BD" - readonly property string gradient: "\u00BE" - readonly property string gridView: "\u00BF" - readonly property string grid_medium: "\u00C0" - readonly property string group_small: "\u00C1" - readonly property string help: "\u00C2" - readonly property string home_large: "\u00C3" - readonly property string idAliasOff: "\u00C4" - readonly property string idAliasOn: "\u00C5" - readonly property string import_medium: "\u00C6" - readonly property string imported: "\u00C7" - readonly property string importedModels_small: "\u00C8" - readonly property string infinity: "\u00C9" - readonly property string invisible_medium: "\u00CA" - readonly property string invisible_small: "\u00CB" - readonly property string jumpToCode_medium: "\u00CC" - readonly property string jumpToCode_small: "\u00CD" - readonly property string keyframe: "\u00CE" - readonly property string languageList_medium: "\u00CF" - readonly property string layouts_small: "\u00D0" - readonly property string lights_small: "\u00D1" - readonly property string linear_medium: "\u00D2" - readonly property string linkTriangle: "\u00D3" - readonly property string linked: "\u00D4" - readonly property string listView: "\u00D5" - readonly property string listView_medium: "\u00D6" - readonly property string list_medium: "\u00D7" - readonly property string localOrient_medium: "\u00D8" - readonly property string lockOff: "\u00D9" - readonly property string lockOn: "\u00DA" - readonly property string loopPlayback_medium: "\u00DB" - readonly property string materialBrowser_medium: "\u00DC" - readonly property string materialPreviewEnvironment: "\u00DD" - readonly property string materialPreviewModel: "\u00DE" - readonly property string material_medium: "\u00DF" - readonly property string maxBar_small: "\u00E0" - readonly property string mergeCells: "\u00E1" - readonly property string merge_small: "\u00E2" - readonly property string minus: "\u00E3" - readonly property string mirror: "\u00E4" - readonly property string more_medium: "\u00E5" - readonly property string mouseArea_small: "\u00E6" - readonly property string moveDown_medium: "\u00E7" - readonly property string moveInwards_medium: "\u00E8" - readonly property string moveUp_medium: "\u00E9" - readonly property string moveUpwards_medium: "\u00EA" - readonly property string move_medium: "\u00EB" - readonly property string newMaterial: "\u00EC" - readonly property string nextFile_large: "\u00ED" - readonly property string normalBar_small: "\u00EE" - readonly property string openLink: "\u00EF" - readonly property string openMaterialBrowser: "\u00F0" - readonly property string orientation: "\u00F1" - readonly property string orthCam_medium: "\u00F2" - readonly property string orthCam_small: "\u00F3" - readonly property string paddingEdge: "\u00F4" - readonly property string paddingFrame: "\u00F5" - readonly property string particleAnimation_medium: "\u00F6" - readonly property string pasteStyle: "\u00F7" - readonly property string paste_small: "\u00F8" - readonly property string pause: "\u00F9" - readonly property string pause_medium: "\u00FA" - readonly property string perspectiveCam_medium: "\u00FB" - readonly property string perspectiveCam_small: "\u00FC" - readonly property string pin: "\u00FD" - readonly property string plane_medium: "\u00FE" - readonly property string plane_small: "\u00FF" - readonly property string play: "\u0100" - readonly property string playFill_medium: "\u0101" - readonly property string playOutline_medium: "\u0102" - readonly property string plus: "\u0103" - readonly property string pointLight_small: "\u0104" - readonly property string positioners_small: "\u0105" - readonly property string previewEnv_medium: "\u0106" - readonly property string previousFile_large: "\u0107" - readonly property string promote: "\u0108" - readonly property string properties_medium: "\u0109" - readonly property string readOnly: "\u010A" - readonly property string recent_medium: "\u010B" - readonly property string recordFill_medium: "\u010C" - readonly property string recordOutline_medium: "\u010D" - readonly property string redo: "\u010E" - readonly property string reload_medium: "\u010F" - readonly property string remove_medium: "\u0110" - readonly property string remove_small: "\u0111" - readonly property string rename_small: "\u0112" - readonly property string replace_small: "\u0113" - readonly property string resetView_small: "\u0114" - readonly property string restartParticles_medium: "\u0115" - readonly property string reverseOrder_medium: "\u0116" - readonly property string roatate_medium: "\u0117" - readonly property string rotationFill: "\u0118" - readonly property string rotationOutline: "\u0119" - readonly property string runProjFill_large: "\u011A" - readonly property string runProjOutline_large: "\u011B" - readonly property string s_anchors: "\u011C" - readonly property string s_annotations: "\u011D" - readonly property string s_arrange: "\u011E" - readonly property string s_boundingBox: "\u011F" - readonly property string s_component: "\u0120" - readonly property string s_connections: "\u0121" - readonly property string s_edit: "\u0122" - readonly property string s_enterComponent: "\u0123" - readonly property string s_eventList: "\u0124" - readonly property string s_group: "\u0125" - readonly property string s_layouts: "\u0126" - readonly property string s_merging: "\u0127" - readonly property string s_mouseArea: "\u0128" - readonly property string s_positioners: "\u0129" - readonly property string s_selection: "\u012A" - readonly property string s_snapping: "\u012B" - readonly property string s_timeline: "\u012C" - readonly property string s_visibility: "\u012D" - readonly property string saveLogs_medium: "\u012E" - readonly property string scale_medium: "\u012F" - readonly property string search: "\u0130" - readonly property string search_small: "\u0131" - readonly property string sectionToggle: "\u0132" - readonly property string selectFill_medium: "\u0133" - readonly property string selectOutline_medium: "\u0134" - readonly property string selectParent_small: "\u0135" - readonly property string selection_small: "\u0136" - readonly property string settings_medium: "\u0137" - readonly property string signal_small: "\u0138" - readonly property string snapping_conf_medium: "\u0139" - readonly property string snapping_medium: "\u013A" - readonly property string snapping_small: "\u013B" - readonly property string sortascending_medium: "\u013C" - readonly property string sortdescending_medium: "\u013D" - readonly property string sphere_medium: "\u013E" - readonly property string sphere_small: "\u013F" - readonly property string splitColumns: "\u0140" - readonly property string splitRows: "\u0141" - readonly property string splitScreen_medium: "\u0142" - readonly property string spotLight_small: "\u0143" - readonly property string stackedContainer_small: "\u0144" - readonly property string startNode: "\u0145" - readonly property string step_medium: "\u0146" - readonly property string stop_medium: "\u0147" - readonly property string tableView_medium: "\u0148" - readonly property string testIcon: "\u0149" - readonly property string textAlignBottom: "\u014A" - readonly property string textAlignCenter: "\u014B" - readonly property string textAlignJustified: "\u014C" - readonly property string textAlignLeft: "\u014D" - readonly property string textAlignMiddle: "\u014E" - readonly property string textAlignRight: "\u014F" - readonly property string textAlignTop: "\u0150" - readonly property string textBulletList: "\u0151" - readonly property string textFullJustification: "\u0152" - readonly property string textNumberedList: "\u0153" - readonly property string textures_medium: "\u0154" - readonly property string tickIcon: "\u0155" - readonly property string tickMark_small: "\u0156" - readonly property string timeline_small: "\u0157" - readonly property string toEndFrame_medium: "\u0158" - readonly property string toNextFrame_medium: "\u0159" - readonly property string toPrevFrame_medium: "\u015A" - readonly property string toStartFrame_medium: "\u015B" - readonly property string topToolbar_annotations: "\u015C" - readonly property string topToolbar_closeFile: "\u015D" - readonly property string topToolbar_designMode: "\u015E" - readonly property string topToolbar_enterComponent: "\u015F" - readonly property string topToolbar_home: "\u0160" - readonly property string topToolbar_makeComponent: "\u0161" - readonly property string topToolbar_navFile: "\u0162" - readonly property string topToolbar_runProject: "\u0163" - readonly property string translationCreateFiles: "\u0164" - readonly property string translationCreateReport: "\u0165" - readonly property string translationExport: "\u0166" - readonly property string translationImport: "\u0167" - readonly property string translationSelectLanguages: "\u0168" - readonly property string translationTest: "\u0169" - readonly property string transparent: "\u016A" - readonly property string triState: "\u016B" - readonly property string triangleArcA: "\u016C" - readonly property string triangleArcB: "\u016D" - readonly property string triangleCornerA: "\u016E" - readonly property string triangleCornerB: "\u016F" - readonly property string unLinked: "\u0170" - readonly property string undo: "\u0171" - readonly property string unify_medium: "\u0172" - readonly property string unpin: "\u0173" - readonly property string upDownIcon: "\u0174" - readonly property string upDownSquare2: "\u0175" - readonly property string updateAvailable_medium: "\u0176" - readonly property string updateContent_medium: "\u0177" - readonly property string visibilityOff: "\u0178" - readonly property string visibilityOn: "\u0179" - readonly property string visible_medium: "\u017A" - readonly property string visible_small: "\u017B" - readonly property string warning_medium: "\u017C" - readonly property string wildcard: "\u017D" - readonly property string wizardsAutomotive: "\u017E" - readonly property string wizardsDesktop: "\u017F" - readonly property string wizardsGeneric: "\u0180" - readonly property string wizardsMcuEmpty: "\u0181" - readonly property string wizardsMcuGraph: "\u0182" - readonly property string wizardsMobile: "\u0183" - readonly property string wizardsUnknown: "\u0184" - readonly property string zoomAll: "\u0185" - readonly property string zoomIn: "\u0186" - readonly property string zoomIn_medium: "\u0187" - readonly property string zoomOut: "\u0188" - readonly property string zoomOut_medium: "\u0189" - readonly property string zoomSelection: "\u018A" + readonly property string clearList_large: "\u0063" + readonly property string clearList_medium: "\u0064" + readonly property string closeCross: "\u0065" + readonly property string closeFile_large: "\u0066" + readonly property string closeLink: "\u0067" + readonly property string close_small: "\u0068" + readonly property string code: "\u0069" + readonly property string codeEditor_medium: "\u006A" + readonly property string codeview_medium: "\u006B" + readonly property string colorPopupClose: "\u006C" + readonly property string colorSelection_medium: "\u006D" + readonly property string columnsAndRows: "\u006E" + readonly property string comboBox_medium: "\u006F" + readonly property string cone_medium: "\u0070" + readonly property string cone_small: "\u0071" + readonly property string connection_small: "\u0072" + readonly property string connections_medium: "\u0073" + readonly property string copyLink: "\u0074" + readonly property string copyStyle: "\u0075" + readonly property string copy_small: "\u0076" + readonly property string cornerA: "\u0077" + readonly property string cornerB: "\u0078" + readonly property string cornersAll: "\u0079" + readonly property string createComponent_large: "\u007A" + readonly property string createComponent_small: "\u007B" + readonly property string createObject_medium: "\u007C" + readonly property string create_medium: "\u007D" + readonly property string create_small: "\u007E" + readonly property string cube_medium: "\u007F" + readonly property string cube_small: "\u0080" + readonly property string curveDesigner: "\u0081" + readonly property string curveDesigner_medium: "\u0082" + readonly property string curveEditor: "\u0083" + readonly property string customMaterialEditor: "\u0084" + readonly property string cylinder_medium: "\u0085" + readonly property string cylinder_small: "\u0086" + readonly property string decisionNode: "\u0087" + readonly property string deleteColumn: "\u0088" + readonly property string deleteMaterial: "\u0089" + readonly property string deleteRow: "\u008A" + readonly property string deleteTable: "\u008B" + readonly property string delete_medium: "\u008C" + readonly property string delete_small: "\u008D" + readonly property string deletecolumn_medium: "\u008E" + readonly property string deletepermanently_medium: "\u008F" + readonly property string deleterow_medium: "\u0090" + readonly property string designMode_large: "\u0091" + readonly property string detach: "\u0092" + readonly property string directionalLight_small: "\u0093" + readonly property string distributeBottom: "\u0094" + readonly property string distributeCenterHorizontal: "\u0095" + readonly property string distributeCenterVertical: "\u0096" + readonly property string distributeLeft: "\u0097" + readonly property string distributeOriginBottomRight: "\u0098" + readonly property string distributeOriginCenter: "\u0099" + readonly property string distributeOriginNone: "\u009A" + readonly property string distributeOriginTopLeft: "\u009B" + readonly property string distributeRight: "\u009D" + readonly property string distributeSpacingHorizontal: "\u009E" + readonly property string distributeSpacingVertical: "\u009F" + readonly property string distributeTop: "\u00A0" + readonly property string download: "\u00A1" + readonly property string downloadUnavailable: "\u00A2" + readonly property string downloadUpdate: "\u00A3" + readonly property string downloaded: "\u00A4" + readonly property string dragmarks: "\u00A5" + readonly property string duplicate_small: "\u00A6" + readonly property string edit: "\u00A7" + readonly property string editComponent_large: "\u00A8" + readonly property string editComponent_small: "\u00A9" + readonly property string editLightOff_medium: "\u00AA" + readonly property string editLightOn_medium: "\u00AB" + readonly property string edit_medium: "\u00AC" + readonly property string edit_small: "\u00AE" + readonly property string effects: "\u00AF" + readonly property string events_small: "\u00B0" + readonly property string export_medium: "\u00B1" + readonly property string eyeDropper: "\u00B2" + readonly property string favorite: "\u00B3" + readonly property string fitAll_medium: "\u00B4" + readonly property string fitSelected_small: "\u00B5" + readonly property string fitSelection_medium: "\u00B6" + readonly property string fitToView_medium: "\u00B7" + readonly property string flowAction: "\u00B8" + readonly property string flowTransition: "\u00B9" + readonly property string fontStyleBold: "\u00BA" + readonly property string fontStyleItalic: "\u00BB" + readonly property string fontStyleStrikethrough: "\u00BC" + readonly property string fontStyleUnderline: "\u00BD" + readonly property string forward_medium: "\u00BE" + readonly property string globalOrient_medium: "\u00BF" + readonly property string gradient: "\u00C0" + readonly property string gridView: "\u00C1" + readonly property string grid_medium: "\u00C2" + readonly property string group_small: "\u00C3" + readonly property string help: "\u00C4" + readonly property string home_large: "\u00C5" + readonly property string idAliasOff: "\u00C6" + readonly property string idAliasOn: "\u00C7" + readonly property string import_medium: "\u00C8" + readonly property string imported: "\u00C9" + readonly property string importedModels_small: "\u00CA" + readonly property string infinity: "\u00CB" + readonly property string invisible_medium: "\u00CC" + readonly property string invisible_small: "\u00CD" + readonly property string jumpToCode_medium: "\u00CE" + readonly property string jumpToCode_small: "\u00CF" + readonly property string keyframe: "\u00D0" + readonly property string languageList_medium: "\u00D1" + readonly property string layouts_small: "\u00D2" + readonly property string lights_small: "\u00D3" + readonly property string linear_medium: "\u00D4" + readonly property string linkTriangle: "\u00D5" + readonly property string linked: "\u00D6" + readonly property string listView: "\u00D7" + readonly property string listView_medium: "\u00D8" + readonly property string list_medium: "\u00D9" + readonly property string localOrient_medium: "\u00DA" + readonly property string lockOff: "\u00DB" + readonly property string lockOn: "\u00DC" + readonly property string loopPlayback_medium: "\u00DD" + readonly property string materialBrowser_medium: "\u00DE" + readonly property string materialPreviewEnvironment: "\u00DF" + readonly property string materialPreviewModel: "\u00E0" + readonly property string material_medium: "\u00E1" + readonly property string maxBar_small: "\u00E2" + readonly property string mergeCells: "\u00E3" + readonly property string merge_small: "\u00E4" + readonly property string minus: "\u00E5" + readonly property string mirror: "\u00E6" + readonly property string more_medium: "\u00E7" + readonly property string mouseArea_small: "\u00E8" + readonly property string moveDown_medium: "\u00E9" + readonly property string moveInwards_medium: "\u00EA" + readonly property string moveUp_medium: "\u00EB" + readonly property string moveUpwards_medium: "\u00EC" + readonly property string move_medium: "\u00ED" + readonly property string newMaterial: "\u00EE" + readonly property string nextFile_large: "\u00EF" + readonly property string normalBar_small: "\u00F0" + readonly property string openLink: "\u00F1" + readonly property string openMaterialBrowser: "\u00F2" + readonly property string orientation: "\u00F3" + readonly property string orthCam_medium: "\u00F4" + readonly property string orthCam_small: "\u00F5" + readonly property string paddingEdge: "\u00F6" + readonly property string paddingFrame: "\u00F7" + readonly property string particleAnimation_medium: "\u00F8" + readonly property string pasteStyle: "\u00F9" + readonly property string paste_small: "\u00FA" + readonly property string pause: "\u00FB" + readonly property string pause_medium: "\u00FC" + readonly property string perspectiveCam_medium: "\u00FD" + readonly property string perspectiveCam_small: "\u00FE" + readonly property string pin: "\u00FF" + readonly property string plane_medium: "\u0100" + readonly property string plane_small: "\u0101" + readonly property string play: "\u0102" + readonly property string playFill_medium: "\u0103" + readonly property string playOutline_medium: "\u0104" + readonly property string plus: "\u0105" + readonly property string pointLight_small: "\u0106" + readonly property string positioners_small: "\u0107" + readonly property string previewEnv_medium: "\u0108" + readonly property string previousFile_large: "\u0109" + readonly property string promote: "\u010A" + readonly property string properties_medium: "\u010B" + readonly property string readOnly: "\u010C" + readonly property string recent_medium: "\u010D" + readonly property string recordFill_medium: "\u010E" + readonly property string recordOutline_medium: "\u010F" + readonly property string redo: "\u0110" + readonly property string reload_medium: "\u0111" + readonly property string remove_medium: "\u0112" + readonly property string remove_small: "\u0113" + readonly property string rename_small: "\u0114" + readonly property string replace_small: "\u0115" + readonly property string resetView_small: "\u0116" + readonly property string restartParticles_medium: "\u0117" + readonly property string reverseOrder_medium: "\u0118" + readonly property string roatate_medium: "\u0119" + readonly property string rotationFill: "\u011A" + readonly property string rotationOutline: "\u011B" + readonly property string runProjFill_large: "\u011C" + readonly property string runProjOutline_large: "\u011D" + readonly property string s_anchors: "\u011E" + readonly property string s_annotations: "\u011F" + readonly property string s_arrange: "\u0120" + readonly property string s_boundingBox: "\u0121" + readonly property string s_component: "\u0122" + readonly property string s_connections: "\u0123" + readonly property string s_edit: "\u0124" + readonly property string s_enterComponent: "\u0125" + readonly property string s_eventList: "\u0126" + readonly property string s_group: "\u0127" + readonly property string s_layouts: "\u0128" + readonly property string s_merging: "\u0129" + readonly property string s_mouseArea: "\u012A" + readonly property string s_positioners: "\u012B" + readonly property string s_selection: "\u012C" + readonly property string s_snapping: "\u012D" + readonly property string s_timeline: "\u012E" + readonly property string s_visibility: "\u012F" + readonly property string saveAs_medium: "\u0130" + readonly property string saveLogs_medium: "\u0131" + readonly property string save_medium: "\u0132" + readonly property string scale_medium: "\u0133" + readonly property string search: "\u0134" + readonly property string search_small: "\u0135" + readonly property string sectionToggle: "\u0136" + readonly property string selectFill_medium: "\u0137" + readonly property string selectOutline_medium: "\u0138" + readonly property string selectParent_small: "\u0139" + readonly property string selection_small: "\u013A" + readonly property string settings_medium: "\u013B" + readonly property string signal_small: "\u013C" + readonly property string snapping_conf_medium: "\u013D" + readonly property string snapping_medium: "\u013E" + readonly property string snapping_small: "\u013F" + readonly property string sortascending_medium: "\u0140" + readonly property string sortdescending_medium: "\u0141" + readonly property string sphere_medium: "\u0142" + readonly property string sphere_small: "\u0143" + readonly property string splitColumns: "\u0144" + readonly property string splitRows: "\u0145" + readonly property string splitScreen_medium: "\u0146" + readonly property string spotLight_small: "\u0147" + readonly property string stackedContainer_small: "\u0148" + readonly property string startNode: "\u0149" + readonly property string step_medium: "\u014A" + readonly property string stop_medium: "\u014B" + readonly property string tableView_medium: "\u014C" + readonly property string testIcon: "\u014D" + readonly property string textAlignBottom: "\u014E" + readonly property string textAlignCenter: "\u014F" + readonly property string textAlignJustified: "\u0150" + readonly property string textAlignLeft: "\u0151" + readonly property string textAlignMiddle: "\u0152" + readonly property string textAlignRight: "\u0153" + readonly property string textAlignTop: "\u0154" + readonly property string textBulletList: "\u0155" + readonly property string textFullJustification: "\u0156" + readonly property string textNumberedList: "\u0157" + readonly property string textures_medium: "\u0158" + readonly property string tickIcon: "\u0159" + readonly property string tickMark_small: "\u015A" + readonly property string timeline_small: "\u015B" + readonly property string toEndFrame_medium: "\u015C" + readonly property string toNextFrame_medium: "\u015D" + readonly property string toPrevFrame_medium: "\u015E" + readonly property string toStartFrame_medium: "\u015F" + readonly property string topToolbar_annotations: "\u0160" + readonly property string topToolbar_closeFile: "\u0161" + readonly property string topToolbar_designMode: "\u0162" + readonly property string topToolbar_enterComponent: "\u0163" + readonly property string topToolbar_home: "\u0164" + readonly property string topToolbar_makeComponent: "\u0165" + readonly property string topToolbar_navFile: "\u0166" + readonly property string topToolbar_runProject: "\u0167" + readonly property string translationCreateFiles: "\u0168" + readonly property string translationCreateReport: "\u0169" + readonly property string translationExport: "\u016A" + readonly property string translationImport: "\u016B" + readonly property string translationSelectLanguages: "\u016C" + readonly property string translationTest: "\u016D" + readonly property string transparent: "\u016E" + readonly property string triState: "\u016F" + readonly property string triangleArcA: "\u0170" + readonly property string triangleArcB: "\u0171" + readonly property string triangleCornerA: "\u0172" + readonly property string triangleCornerB: "\u0173" + readonly property string unLinked: "\u0174" + readonly property string undo: "\u0175" + readonly property string unify_medium: "\u0176" + readonly property string unpin: "\u0177" + readonly property string upDownIcon: "\u0178" + readonly property string upDownSquare2: "\u0179" + readonly property string updateAvailable_medium: "\u017A" + readonly property string updateContent_medium: "\u017B" + readonly property string visibilityOff: "\u017C" + readonly property string visibilityOn: "\u017D" + readonly property string visible_medium: "\u017E" + readonly property string visible_small: "\u017F" + readonly property string warning_medium: "\u0180" + readonly property string wildcard: "\u0181" + readonly property string wizardsAutomotive: "\u0182" + readonly property string wizardsDesktop: "\u0183" + readonly property string wizardsGeneric: "\u0184" + readonly property string wizardsMcuEmpty: "\u0185" + readonly property string wizardsMcuGraph: "\u0186" + readonly property string wizardsMobile: "\u0187" + readonly property string wizardsUnknown: "\u0188" + readonly property string zoomAll: "\u0189" + readonly property string zoomIn: "\u018A" + readonly property string zoomIn_medium: "\u018B" + readonly property string zoomOut: "\u018C" + readonly property string zoomOut_medium: "\u018D" + readonly property string zoomSelection: "\u018E" readonly property font iconFont: Qt.font({ "family": controlIcons.name, diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml index 82dbf132b19..6b179e42298 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml @@ -242,6 +242,11 @@ QtObject { property real dialogButtonSpacing: 10 property real dialogButtonPadding: 4 + // Collection Editor + property real collectionItemTextSideMargin: 10 + property real collectionItemTextMargin: 5 + property real collectionItemTextPadding: 5 + // NEW NEW NEW readonly property int flowMargin: 7 readonly property int flowSpacing: 7 // Odd so cursor has a center location diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf index c622c1950af..c650a651fe1 100644 Binary files a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf and b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf differ diff --git a/share/qtcreator/qmldesigner/statusbar/Main.qml b/share/qtcreator/qmldesigner/statusbar/Main.qml index 1eb33b82360..da18cf67938 100644 --- a/share/qtcreator/qmldesigner/statusbar/Main.qml +++ b/share/qtcreator/qmldesigner/statusbar/Main.qml @@ -63,7 +63,8 @@ Item { model: backend.kits onActivated: backend.setCurrentKit(kits.currentIndex) openUpwards: true - enabled: (backend.isInDesignMode || (backend.isInEditMode && backend.projectOpened)) && backend.isQt6 + enabled: (backend.isInDesignMode || (backend.isInEditMode && backend.projectOpened)) + && backend.isQt6 && !backend.isMCUs property int kitIndex: backend.currentKit onKitIndexChanged: kits.currentIndex = backend.currentKit } diff --git a/share/qtcreator/qmldesigner/studio_templates/files/javascript/wizard.json b/share/qtcreator/qmldesigner/studio_templates/files/javascript/wizard.json index fb11c65b344..7ba90f75f6f 100644 --- a/share/qtcreator/qmldesigner/studio_templates/files/javascript/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/files/javascript/wizard.json @@ -4,8 +4,8 @@ "id": "Z.QtStudio.JavaScript.2", "category": "R.StudioJSFiles", "trDescription": "Creates a JavaScript file.", - "trDisplayName": "Java Script File", - "trDisplayCategory": "Java Script", + "trDisplayName": "JavaScript File", + "trDisplayCategory": "JavaScript", "icon": "file_javascript.png", "platformIndependent": true, diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/wizard.json b/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/wizard.json index d5b1fef1e45..2a2e1bd53d0 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/wizard.json @@ -382,6 +382,22 @@ { "source": "../shared-plugin/name/designer/plugin.metainfo", "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/designer/plugin.metainfo" + }, + { + "source": "../shared-plugin/name/JsonData.qml.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/JsonData.qml" + }, + { + "source": "../shared-plugin/name/DataStore.qml.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/DataStore.qml" + }, + { + "source": "../shared-plugin/name/models.json.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/models.json" + }, + { + "source": "../shared-plugin/name/data.json.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/data.json" } ] } diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/application/wizard.json b/share/qtcreator/qmldesigner/studio_templates/projects/application/wizard.json index 0708838a5ae..24434e00762 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/application/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/application/wizard.json @@ -395,6 +395,22 @@ { "source": "../shared-plugin/name/designer/plugin.metainfo", "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/designer/plugin.metainfo" + }, + { + "source": "../shared-plugin/name/JsonData.qml.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/JsonData.qml" + }, + { + "source": "../shared-plugin/name/DataStore.qml.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/DataStore.qml" + }, + { + "source": "../shared-plugin/name/models.json.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/models.json" + }, + { + "source": "../shared-plugin/name/data.json.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/data.json" } ] } diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/common/app.qmlproject.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/common/app.qmlproject.tpl index 6169688f16b..95975c197d1 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/common/app.qmlproject.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/common/app.qmlproject.tpl @@ -63,6 +63,10 @@ Project { filter: "*.qsb" } + Files { + filter: "*.json" + } + Files { filter: "*.mesh" directory: "asset_imports" @@ -110,7 +114,7 @@ Project { /* Required for deployment */ targetDirectory: "/opt/%{ProjectName}" - qdsVersion: "4.4" + qdsVersion: "4.3" quickVersion: "%{QtQuickVersion}" diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/desktop-launcher/wizard.json b/share/qtcreator/qmldesigner/studio_templates/projects/desktop-launcher/wizard.json index 913eb0bf28b..2f590855690 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/desktop-launcher/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/desktop-launcher/wizard.json @@ -382,6 +382,22 @@ { "source": "../shared-plugin/name/designer/plugin.metainfo", "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/designer/plugin.metainfo" + }, + { + "source": "../shared-plugin/name/JsonData.qml.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/JsonData.qml" + }, + { + "source": "../shared-plugin/name/DataStore.qml.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/DataStore.qml" + }, + { + "source": "../shared-plugin/name/models.json.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/models.json" + }, + { + "source": "../shared-plugin/name/data.json.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/data.json" } ] } diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-scroll/wizard.json b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-scroll/wizard.json index 944b6b6289c..008e3eb9952 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-scroll/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-scroll/wizard.json @@ -341,6 +341,22 @@ { "source": "../shared-plugin/name/designer/plugin.metainfo", "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/designer/plugin.metainfo" + }, + { + "source": "../shared-plugin/name/JsonData.qml.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/JsonData.qml" + }, + { + "source": "../shared-plugin/name/DataStore.qml.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/DataStore.qml" + }, + { + "source": "../shared-plugin/name/models.json.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/models.json" + }, + { + "source": "../shared-plugin/name/data.json.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/data.json" } ] } diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-stack/wizard.json b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-stack/wizard.json index c8733770e07..e385b58ff8f 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-stack/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-stack/wizard.json @@ -343,6 +343,22 @@ { "source": "../shared-plugin/name/designer/plugin.metainfo", "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/designer/plugin.metainfo" + }, + { + "source": "../shared-plugin/name/JsonData.qml.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/JsonData.qml" + }, + { + "source": "../shared-plugin/name/DataStore.qml.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/DataStore.qml" + }, + { + "source": "../shared-plugin/name/models.json.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/models.json" + }, + { + "source": "../shared-plugin/name/data.json.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/data.json" } ] } diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-swipe/wizard.json b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-swipe/wizard.json index 295c85aa63a..47fe3bd4e3d 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-swipe/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-swipe/wizard.json @@ -343,6 +343,22 @@ { "source": "../shared-plugin/name/designer/plugin.metainfo", "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/designer/plugin.metainfo" + }, + { + "source": "../shared-plugin/name/JsonData.qml.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/JsonData.qml" + }, + { + "source": "../shared-plugin/name/DataStore.qml.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/DataStore.qml" + }, + { + "source": "../shared-plugin/name/models.json.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/models.json" + }, + { + "source": "../shared-plugin/name/data.json.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/data.json" } ] } diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/CMakeLists.importmodule.txt.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/CMakeLists.importmodule.txt.tpl index 517b91355a7..3b74123f7b0 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/CMakeLists.importmodule.txt.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/CMakeLists.importmodule.txt.tpl @@ -11,9 +11,14 @@ qt6_add_qml_module(%{ImportModuleName} URI "%{ImportModuleName}" VERSION 1.0 RESOURCE_PREFIX "/qt/qml" - QML_FILES + QML_FILES Constants.qml + DataStore.qml DirectoryFontLoader.qml EventListModel.qml EventListSimulator.qml + JsonData.qml + RESOURCES + data.json + models.json ) diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/DataStore.qml.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/DataStore.qml.tpl new file mode 100644 index 00000000000..ca8b45ede08 --- /dev/null +++ b/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/DataStore.qml.tpl @@ -0,0 +1,17 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +pragma Singleton +import QtQuick 6.5 +import QtQuick.Studio.Utils 1.0 + +JsonListModel { + id: models + source: Qt.resolvedUrl("models.json") + + property ChildListModel book: ChildListModel { + modelName: "book" + } + + property JsonData backend: JsonData {} +} diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/JsonData.qml.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/JsonData.qml.tpl new file mode 100644 index 00000000000..a49600e2704 --- /dev/null +++ b/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/JsonData.qml.tpl @@ -0,0 +1,9 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Studio.Utils 1.0 + +JsonBackend { + property string name: "someName" + property int number: 1 + source: Qt.resolvedUrl("data.json") +} diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/data.json.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/data.json.tpl new file mode 100644 index 00000000000..71208c18089 --- /dev/null +++ b/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/data.json.tpl @@ -0,0 +1,4 @@ +{ + "name": "Christen Anderson", + "number": "+3455641" +} diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/importmodule.qmldir.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/importmodule.qmldir.tpl index d374931eab8..a0ec2f17cfc 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/importmodule.qmldir.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/importmodule.qmldir.tpl @@ -1,6 +1,6 @@ Module %{ImportModuleName} +singleton DataStore 1.0 DataStore.qml singleton Constants 1.0 Constants.qml EventListSimulator 1.0 EventListSimulator.qml EventListModel 1.0 EventListModel.qml DirectoryFontLoader 1.0 DirectoryFontLoader.qml - diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/models.json.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/models.json.tpl new file mode 100644 index 00000000000..8ebda6fb7e6 --- /dev/null +++ b/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/models.json.tpl @@ -0,0 +1,30 @@ +{ + "book": [ + { + "author": "Nigel Rees", + "category": "reference", + "price": 8.95, + "title": "Sayings of the Century" + }, + { + "author": "Evelyn Waugh", + "category": "fiction", + "price": 12.99, + "title": "Sword of Honor" + }, + { + "author": "Herman Melville", + "category": "fiction", + "isbn": "0-553-21311-3", + "price": 8.99, + "title": "Moby Dick" + }, + { + "author": "J. R. R. Tolkien", + "category": "fiction", + "isbn": "0-395-19395-8", + "price": 22.99, + "title": "The Lord of the Rings" + } + ] +} diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/qmldir b/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/qmldir index 3ba5adcc643..b5924a433cc 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/qmldir +++ b/share/qtcreator/qmldesigner/studio_templates/projects/shared-plugin/name/qmldir @@ -1,4 +1,5 @@ singleton Constants 1.0 Constants.qml +singleton DataStore 1.0 DataStore.qml EventListModel 1.0 EventListModel.qml EventListSimulator 1.0 EventListSimulator.qml DirectoryFontLoader 1.0 DirectoryFontLoader.qml diff --git a/share/qtcreator/templates/wizards/projects/qtquickapplication/wizard.json b/share/qtcreator/templates/wizards/projects/qtquickapplication/wizard.json index 2e885c2bb63..ec2164d4f26 100644 --- a/share/qtcreator/templates/wizards/projects/qtquickapplication/wizard.json +++ b/share/qtcreator/templates/wizards/projects/qtquickapplication/wizard.json @@ -261,6 +261,22 @@ "source": "%{QdsWizardPath}/shared-plugin/name/designer/plugin.metainfo", "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/designer/plugin.metainfo", "condition": "%{QdsProjectStyle}" + }, + { + "source": "../shared-plugin/name/JsonData.qml.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/JsonData.qml" + }, + { + "source": "../shared-plugin/name/DataStore.qml.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/DataStore.qml" + }, + { + "source": "../shared-plugin/name/models.json.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/models.json" + }, + { + "source": "../shared-plugin/name/data.json.tpl", + "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/data.json" } ] } diff --git a/src/libs/advanceddockingsystem/ads_globals.h b/src/libs/advanceddockingsystem/ads_globals.h index 8541e3d9da7..364f49e01e2 100644 --- a/src/libs/advanceddockingsystem/ads_globals.h +++ b/src/libs/advanceddockingsystem/ads_globals.h @@ -66,7 +66,8 @@ enum eTitleBarButton { TitleBarButtonTabsMenu, TitleBarButtonUndock, TitleBarButtonClose, - TitleBarButtonAutoHide + TitleBarButtonAutoHide, + TitleBarButtonMinimize }; /** @@ -83,16 +84,17 @@ enum eDragState { * The different icons used in the UI */ enum eIcon { - TabCloseIcon, //!< TabCloseIcon - AutoHideIcon, //!< AutoHideIcon - DockAreaMenuIcon, //!< DockAreaMenuIcon - DockAreaUndockIcon, //!< DockAreaUndockIcon - DockAreaCloseIcon, //!< DockAreaCloseIcon + TabCloseIcon, //!< TabCloseIcon + AutoHideIcon, //!< AutoHideIcon + DockAreaMenuIcon, //!< DockAreaMenuIcon + DockAreaUndockIcon, //!< DockAreaUndockIcon + DockAreaCloseIcon, //!< DockAreaCloseIcon + DockAreaMinimizeIcon, FloatingWidgetCloseIcon, //!< FloatingWidgetCloseIcon FloatingWidgetNormalIcon, //!< FloatingWidgetNormalIcon FloatingWidgetMaximizeIcon, //!< FloatingWidgetMaximizeIcon - IconCount, //!< just a delimiter for range checks + IconCount, //!< just a delimiter for range checks }; /** @@ -161,8 +163,8 @@ bool isSideBarArea(DockWidgetArea area); /** * Searches for the parent widget of the given type. Returns the parent widget of the given - * widget or 0 if the widget is not child of any widget of type T. - * It is not safe to use this function in in DockWidget because only the current dock widget has a + * widget or nullptr if the widget is not child of any widget of type T. + * It is not safe to use this function in DockWidget, because only the current dock widget has a * parent. All dock widgets that are not the current dock widget in a dock area have no parent. */ template @@ -171,12 +173,12 @@ T findParent(const QWidget *widget) QWidget *parentWidget = widget->parentWidget(); while (parentWidget) { T parentImpl = qobject_cast(parentWidget); - if (parentImpl) { + if (parentImpl) return parentImpl; - } + parentWidget = parentWidget->parentWidget(); } - return 0; + return nullptr; } /** diff --git a/src/libs/advanceddockingsystem/dockareatitlebar.cpp b/src/libs/advanceddockingsystem/dockareatitlebar.cpp index 00627ef19fb..e8d83fe69d2 100644 --- a/src/libs/advanceddockingsystem/dockareatitlebar.cpp +++ b/src/libs/advanceddockingsystem/dockareatitlebar.cpp @@ -46,10 +46,11 @@ public: QPointer m_autoHideButton; QPointer m_undockButton; QPointer m_closeButton; + QPointer m_minimizeButton; QBoxLayout *m_layout = nullptr; DockAreaWidget *m_dockArea = nullptr; DockAreaTabBar *m_tabBar = nullptr; - ElidingLabel *m_autoHideTitleLabel; + ElidingLabel *m_autoHideTitleLabel = nullptr; bool m_menuOutdated = true; QMenu *m_tabsMenu; QList m_dockWidgetActionsButtons; @@ -198,6 +199,23 @@ void DockAreaTitleBarPrivate::createButtons() q, &DockAreaTitleBar::onAutoHideButtonClicked); + // Minimize button + m_minimizeButton = new TitleBarButton( + testAutoHideConfigFlag(DockManager::AutoHideHasMinimizeButton)); + m_minimizeButton->setObjectName("dockAreaMinimizeButton"); + //m_minimizeButton->setAutoRaise(true); + m_minimizeButton->setVisible(false); + internal::setButtonIcon(m_minimizeButton, + QStyle::SP_TitleBarMinButton, + ADS::DockAreaMinimizeIcon); + internal::setToolTip(m_minimizeButton, QObject::tr("Minimize")); + m_minimizeButton->setSizePolicy(sizePolicy); + m_layout->addWidget(m_minimizeButton, 0); + QObject::connect(m_minimizeButton, + &QToolButton::clicked, + q, + &DockAreaTitleBar::minimizeAutoHideContainer); + // Close button m_closeButton = new TitleBarButton(testConfigFlag(DockManager::DockAreaHasCloseButton)); m_closeButton->setObjectName("dockAreaCloseButton"); @@ -224,7 +242,10 @@ void DockAreaTitleBarPrivate::createAutoHideTitleLabel() { m_autoHideTitleLabel = new ElidingLabel(""); m_autoHideTitleLabel->setObjectName("autoHideTitleLabel"); - m_layout->addWidget(m_autoHideTitleLabel); + // At position 0 is the tab bar - insert behind tab bar + m_layout->insertWidget(1, m_autoHideTitleLabel); + m_autoHideTitleLabel->setVisible(false); // Default hidden + m_layout->insertWidget(2, new SpacerWidget(q)); } void DockAreaTitleBarPrivate::createTabBar() { @@ -366,10 +387,8 @@ DockAreaTitleBar::DockAreaTitleBar(DockAreaWidget *parent) setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); d->createTabBar(); - d->createAutoHideTitleLabel(); - d->m_autoHideTitleLabel->setVisible(false); // Default hidden - d->m_layout->addWidget(new SpacerWidget(this)); d->createButtons(); + d->createAutoHideTitleLabel(); setFocusPolicy(Qt::NoFocus); } @@ -448,6 +467,18 @@ void DockAreaTitleBar::onCloseButtonClicked() d->m_dockArea->closeArea(); } +void DockAreaTitleBar::onAutoHideCloseActionTriggered() +{ + d->m_dockArea->closeArea(); +} + +void DockAreaTitleBar::minimizeAutoHideContainer() +{ + auto autoHideContainer = d->m_dockArea->autoHideDockContainer(); + if (autoHideContainer) + autoHideContainer->collapseView(true); +} + void DockAreaTitleBar::onUndockButtonClicked() { if (d->m_dockArea->features().testFlag(DockWidget::DockWidgetFloatable)) @@ -533,6 +564,8 @@ TitleBarButton *DockAreaTitleBar::button(eTitleBarButton which) const return d->m_closeButton; case TitleBarButtonAutoHide: return d->m_autoHideButton; + case TitleBarButtonMinimize: + return d->m_minimizeButton; } return nullptr; } @@ -676,12 +709,28 @@ void DockAreaTitleBar::contextMenuEvent(QContextMenuEvent *event) } menu.addSeparator(); } - QAction *closeAction = menu.addAction(isAutoHide ? Tr::tr("Close") : Tr::tr("Close Group")); - closeAction->connect(closeAction, - &QAction::triggered, - this, - &DockAreaTitleBar::onCloseButtonClicked); - closeAction->setEnabled(d->m_dockArea->features().testFlag(DockWidget::DockWidgetClosable)); + + if (isAutoHide) { + QAction *minimizeAction = menu.addAction(Tr::tr("Minimize")); + minimizeAction->connect(minimizeAction, + &QAction::triggered, + this, + &DockAreaTitleBar::minimizeAutoHideContainer); + + QAction *closeAction = menu.addAction(Tr::tr("Close")); + closeAction->connect(closeAction, + &QAction::triggered, + this, + &DockAreaTitleBar::onAutoHideCloseActionTriggered); + closeAction->setEnabled(d->m_dockArea->features().testFlag(DockWidget::DockWidgetClosable)); + } else { + QAction *closeAction = menu.addAction(Tr::tr("Close Group")); + closeAction->connect(closeAction, + &QAction::triggered, + this, + &DockAreaTitleBar::onCloseButtonClicked); + closeAction->setEnabled(d->m_dockArea->features().testFlag(DockWidget::DockWidgetClosable)); + } if (!isAutoHide && !isTopLevelArea) { QAction *closeOthersAction = menu.addAction(Tr::tr("Close Other Groups")); @@ -718,8 +767,11 @@ QString DockAreaTitleBar::titleBarButtonToolTip(eTitleBarButton button) const break; case TitleBarButtonClose: - if (d->m_dockArea->isAutoHide()) - return Tr::tr("Close"); + if (d->m_dockArea->isAutoHide()) { + bool minimize = DockManager::testAutoHideConfigFlag( + DockManager::AutoHideCloseButtonCollapsesDock); + return minimize ? Tr::tr("Minimize") : Tr::tr("Close"); + } if (DockManager::testConfigFlag(DockManager::DockAreaCloseButtonClosesTab)) return Tr::tr("Close Active Tab"); @@ -748,4 +800,11 @@ void DockAreaTitleBar::setAreaFloating() d->makeAreaFloating(mapFromGlobal(QCursor::pos()), DraggingInactive); } +void DockAreaTitleBar::showAutoHideControls(bool show) +{ + d->m_tabBar->setVisible(!show); // Auto hide toolbar never has tabs + d->m_minimizeButton->setVisible(show); + d->m_autoHideTitleLabel->setVisible(show); +} + } // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockareatitlebar.h b/src/libs/advanceddockingsystem/dockareatitlebar.h index ac130bf197a..1364b383900 100644 --- a/src/libs/advanceddockingsystem/dockareatitlebar.h +++ b/src/libs/advanceddockingsystem/dockareatitlebar.h @@ -84,6 +84,8 @@ private: void onTabsMenuAboutToShow(); void onCloseButtonClicked(); + void onAutoHideCloseActionTriggered(); + void minimizeAutoHideContainer(); void onUndockButtonClicked(); void onTabsMenuActionTriggered(QAction *action); void onCurrentTabChanged(int index); @@ -190,6 +192,11 @@ public: */ void setAreaFloating(); + /** + * Call this function, to create all the required auto hide controls + */ + void showAutoHideControls(bool show); + signals: /** * This signal is emitted if a tab in the tab bar is clicked by the user diff --git a/src/libs/advanceddockingsystem/dockareawidget.cpp b/src/libs/advanceddockingsystem/dockareawidget.cpp index cc263895af6..5cf12d70509 100644 --- a/src/libs/advanceddockingsystem/dockareawidget.cpp +++ b/src/libs/advanceddockingsystem/dockareawidget.cpp @@ -295,13 +295,20 @@ void DockAreaWidgetPrivate::updateTitleBarButtonVisibility(bool isTopLevel) if (!container) return; - if (isTopLevel) { + bool isAutoHide = q->isAutoHide(); + if (isAutoHide) { + bool showCloseButton = DockManager::autoHideConfigFlags().testFlag( + DockManager::AutoHideHasCloseButton); + m_titleBar->button(TitleBarButtonClose)->setVisible(showCloseButton); + m_titleBar->button(TitleBarButtonAutoHide)->setVisible(true); + m_titleBar->button(TitleBarButtonUndock)->setVisible(false); + m_titleBar->button(TitleBarButtonTabsMenu)->setVisible(false); + } else if (isTopLevel) { m_titleBar->button(TitleBarButtonClose)->setVisible(!container->isFloating()); m_titleBar->button(TitleBarButtonAutoHide)->setVisible(!container->isFloating()); // Undock and tabs should never show when auto hidden - m_titleBar->button(TitleBarButtonUndock) - ->setVisible(!container->isFloating() && !q->isAutoHide()); - m_titleBar->button(TitleBarButtonTabsMenu)->setVisible(!q->isAutoHide()); + m_titleBar->button(TitleBarButtonUndock)->setVisible(!container->isFloating()); + m_titleBar->button(TitleBarButtonTabsMenu)->setVisible(true); } else { m_titleBar->button(TitleBarButtonClose)->setVisible(true); m_titleBar->button(TitleBarButtonAutoHide)->setVisible(true); @@ -650,10 +657,7 @@ void DockAreaWidget::updateTitleBarVisibility() } if (isAutoHideFeatureEnabled()) { - auto tabBar = d->m_titleBar->tabBar(); - tabBar->setVisible(!autoHide); // Never show tab bar when auto hidden - // Always show when auto hidden, never otherwise - d->m_titleBar->autoHideTitleLabel()->setVisible(autoHide); + d->m_titleBar->showAutoHideControls(autoHide); updateTitleBarButtonVisibility(container->topLevelDockArea() == this); } } diff --git a/src/libs/advanceddockingsystem/dockcontainerwidget.cpp b/src/libs/advanceddockingsystem/dockcontainerwidget.cpp index 606afa38128..f2e2b6b2063 100644 --- a/src/libs/advanceddockingsystem/dockcontainerwidget.cpp +++ b/src/libs/advanceddockingsystem/dockcontainerwidget.cpp @@ -484,9 +484,10 @@ void DockContainerWidgetPrivate::dropIntoSection(FloatingDockContainer *floating if (!targetAreaSplitter) { auto splitter = createSplitter(insertParam.orientation()); - m_layout->replaceWidget(targetArea, splitter); + QLayoutItem *layoutItem = m_layout->replaceWidget(targetArea, splitter); splitter->addWidget(targetArea); targetAreaSplitter = splitter; + delete layoutItem; } int areaIndex = targetAreaSplitter->indexOf(targetArea); auto floatingSplitter = floatingContainer->rootSplitter(); @@ -1532,10 +1533,11 @@ bool DockContainerWidget::restoreState(DockingStateReader &stateReader, bool tes if (!newRootSplitter) newRootSplitter = d->createSplitter(Qt::Horizontal); - d->m_layout->replaceWidget(d->m_rootSplitter, newRootSplitter); + QLayoutItem *layoutItem = d->m_layout->replaceWidget(d->m_rootSplitter, newRootSplitter); auto oldRoot = d->m_rootSplitter; d->m_rootSplitter = qobject_cast(newRootSplitter); oldRoot->deleteLater(); + delete layoutItem; return true; } diff --git a/src/libs/advanceddockingsystem/dockmanager.cpp b/src/libs/advanceddockingsystem/dockmanager.cpp index 2fd9d9a3d6d..5cae131facf 100644 --- a/src/libs/advanceddockingsystem/dockmanager.cpp +++ b/src/libs/advanceddockingsystem/dockmanager.cpp @@ -87,6 +87,11 @@ public: DockWidget *m_centralWidget = nullptr; bool m_isLeavingMinimized = false; + Qt::ToolButtonStyle m_toolBarStyleDocked = Qt::ToolButtonIconOnly; + Qt::ToolButtonStyle m_toolBarStyleFloating = Qt::ToolButtonTextUnderIcon; + QSize m_toolBarIconSizeDocked = QSize(16, 16); + QSize m_toolBarIconSizeFloating = QSize(24, 24); + QString m_workspacePresetsPath; QList m_workspaces; Workspace m_workspace; @@ -94,6 +99,7 @@ public: QtcSettings *m_settings = nullptr; bool m_modeChangeState = false; + bool m_wasShown = false; bool m_workspaceOrderDirty = false; /** @@ -364,8 +370,10 @@ DockManager::DockManager(QWidget *parent) DockManager::~DockManager() { - emit aboutToUnloadWorkspace(d->m_workspace.fileName()); - save(); + if (d->m_wasShown) { + emit aboutToUnloadWorkspace(d->m_workspace.fileName()); + save(); + } saveStartupWorkspace(); saveLockWorkspace(); @@ -766,6 +774,38 @@ QString DockManager::floatingContainersTitle() return g_floatingContainersTitle; } +void DockManager::setDockWidgetToolBarStyle(Qt::ToolButtonStyle style, DockWidget::eState state) +{ + if (DockWidget::StateFloating == state) + d->m_toolBarStyleFloating = style; + else + d->m_toolBarStyleDocked = style; +} + +Qt::ToolButtonStyle DockManager::dockWidgetToolBarStyle(DockWidget::eState state) const +{ + if (DockWidget::StateFloating == state) + return d->m_toolBarStyleFloating; + else + return d->m_toolBarStyleDocked; +} + +void DockManager::setDockWidgetToolBarIconSize(const QSize &iconSize, DockWidget::eState state) +{ + if (DockWidget::StateFloating == state) + d->m_toolBarIconSizeFloating = iconSize; + else + d->m_toolBarIconSizeDocked = iconSize; +} + +QSize DockManager::dockWidgetToolBarIconSize(DockWidget::eState state) const +{ + if (DockWidget::StateFloating == state) + return d->m_toolBarIconSizeFloating; + else + return d->m_toolBarIconSizeDocked; +} + DockWidget *DockManager::centralWidget() const { return d->m_centralWidget; @@ -1324,6 +1364,11 @@ bool DockManager::isModeChangeState() const return d->m_modeChangeState; } +void DockManager::aboutToShow() +{ + d->m_wasShown = true; +} + expected_str DockManager::importWorkspace(const QString &filePath) { qCInfo(adsLog) << "Import workspace" << filePath; diff --git a/src/libs/advanceddockingsystem/dockmanager.h b/src/libs/advanceddockingsystem/dockmanager.h index c11bd6ebb16..53117b93e6b 100644 --- a/src/libs/advanceddockingsystem/dockmanager.h +++ b/src/libs/advanceddockingsystem/dockmanager.h @@ -186,10 +186,13 @@ public: = 0x20, ///< show the auto hide window on mouse over tab and hide it if mouse leaves auto hide container AutoHideCloseButtonCollapsesDock = 0x40, ///< Close button of an auto hide container collapses the dock instead of hiding it completely + AutoHideHasCloseButton + = 0x80, //< If the flag is set an auto hide title bar has a close button + AutoHideHasMinimizeButton + = 0x100, ///< if this flag is set, the auto hide title bar has a minimize button to collapse the dock widget - DefaultAutoHideConfig - = AutoHideFeatureEnabled | DockAreaHasAutoHideButton - | AutoHideCloseButtonCollapsesDock ///< the default configuration for left and right side bars + DefaultAutoHideConfig = AutoHideFeatureEnabled | DockAreaHasAutoHideButton + | AutoHideCloseButtonCollapsesDock | AutoHideHasCloseButton }; Q_DECLARE_FLAGS(AutoHideFlags, eAutoHideFlag) @@ -452,6 +455,31 @@ public: */ static QString floatingContainersTitle(); + /** + * This function sets the tool button style for the given dock widget state. It is possible to + * switch the tool button style depending on the state. If a dock widget is floating, then here + * are more space and it is possible to select a style that requires more space like + * Qt::ToolButtonTextUnderIcon. For the docked state Qt::ToolButtonIconOnly might be better. + */ + void setDockWidgetToolBarStyle(Qt::ToolButtonStyle style, DockWidget::eState state); + + /** + * Returns the tool button style for the given docking state. \see setToolBarStyle() + */ + Qt::ToolButtonStyle dockWidgetToolBarStyle(DockWidget::eState state) const; + + /** + * This function sets the tool button icon size for the given state. If a dock widget is + * floating, there is more space and increasing the icon size is possible. For docked widgets, + * small icon sizes, eg. 16 x 16 might be better. + */ + void setDockWidgetToolBarIconSize(const QSize &iconSize, DockWidget::eState state); + + /** + * Returns the icon size for a given docking state. \see setToolBarIconSize() + */ + QSize dockWidgetToolBarIconSize(DockWidget::eState state) const; + /** * This function returns managers central widget or nullptr if no central widget is set. */ @@ -733,6 +761,13 @@ public: static QString readDisplayName(const Utils::FilePath &filePath); static bool writeDisplayName(const Utils::FilePath &filePath, const QString &displayName); + /** + * This is used to limit saving of workspaces to only when they were actually presented ones, + * otherwise it could lead to distorted workspace due to the correct windows sizes not being + * set when never presented/rendered. + */ + void aboutToShow(); + signals: void aboutToUnloadWorkspace(QString fileName); void aboutToLoadWorkspace(QString fileName); diff --git a/src/libs/advanceddockingsystem/docksplitter.cpp b/src/libs/advanceddockingsystem/docksplitter.cpp index a9207e0c08d..4446170e2ec 100644 --- a/src/libs/advanceddockingsystem/docksplitter.cpp +++ b/src/libs/advanceddockingsystem/docksplitter.cpp @@ -50,9 +50,8 @@ bool DockSplitter::hasVisibleContent() const { // TODO Cache or precalculate this to speed up for (int i = 0; i < count(); ++i) { - if (!widget(i)->isHidden()) { + if (!widget(i)->isHidden()) return true; - } } return false; diff --git a/src/libs/advanceddockingsystem/dockwidget.cpp b/src/libs/advanceddockingsystem/dockwidget.cpp index 6c89c92c66f..a46fae58bef 100644 --- a/src/libs/advanceddockingsystem/dockwidget.cpp +++ b/src/libs/advanceddockingsystem/dockwidget.cpp @@ -64,6 +64,7 @@ public: = DockWidget::MinimumSizeHintFromDockWidget; WidgetFactory *m_factory = nullptr; QPointer m_sideTabWidget; + DockWidget::eToolBarStyleSource m_toolBarStyleSource = DockWidget::ToolBarStyleFromDockManager; /** * Private data constructor @@ -106,6 +107,11 @@ public: * Creates the content widget with the registered widget factory and returns true on success. */ bool createWidgetFromFactory(); + + /** + * Use the dock manager toolbar style and icon size for the different states + */ + void setToolBarStyleFromDockManager(); }; // class DockWidgetPrivate DockWidgetPrivate::DockWidgetPrivate(DockWidget *parent) @@ -246,6 +252,19 @@ bool DockWidgetPrivate::createWidgetFromFactory() return true; } +void DockWidgetPrivate::setToolBarStyleFromDockManager() +{ + if (!m_dockManager) + return; + + auto state = DockWidget::StateDocked; + q->setToolBarIconSize(m_dockManager->dockWidgetToolBarIconSize(state), state); + q->setToolBarStyle(m_dockManager->dockWidgetToolBarStyle(state), state); + state = DockWidget::StateFloating; + q->setToolBarIconSize(m_dockManager->dockWidgetToolBarIconSize(state), state); + q->setToolBarStyle(m_dockManager->dockWidgetToolBarStyle(state), state); +} + DockWidget::DockWidget(const QString &uniqueId, QWidget *parent) : QFrame(parent) , d(new DockWidgetPrivate(this)) @@ -290,8 +309,11 @@ void DockWidget::setWidget(QWidget *widget, eInsertMode insertMode) auto scrollAreaWidget = qobject_cast(widget); if (scrollAreaWidget || ForceNoScrollArea == insertMode) { d->m_layout->addWidget(widget); - if (scrollAreaWidget && scrollAreaWidget->viewport()) - scrollAreaWidget->viewport()->setProperty("dockWidgetContent", true); + if (scrollAreaWidget) { + if (scrollAreaWidget->viewport()) + scrollAreaWidget->viewport()->setProperty("dockWidgetContent", true); + scrollAreaWidget->setProperty("focused", isFocused()); + } } else { d->setupScrollArea(); d->m_scrollArea->setWidget(widget); @@ -381,6 +403,12 @@ DockManager *DockWidget::dockManager() const void DockWidget::setDockManager(DockManager *dockManager) { d->m_dockManager = dockManager; + + if (!dockManager) + return; + + if (ToolBarStyleFromDockManager == d->m_toolBarStyleSource) + d->setToolBarStyleFromDockManager(); } DockContainerWidget *DockWidget::dockContainer() const @@ -457,6 +485,11 @@ void DockWidget::setFocused(bool focused) if (d->m_scrollArea) d->m_scrollArea->setProperty("focused", focused); + QList scrollAreas = d->m_widget->findChildren(QString(), + Qt::FindDirectChildrenOnly); + for (QAbstractScrollArea *scrollArea : scrollAreas) + scrollArea->setProperty("focused", focused); + const QString customObjectName = QString("__mainSrollView"); QList quickWidgets = d->m_widget->findChildren(); @@ -491,6 +524,18 @@ QAction *DockWidget::toggleViewAction() const return d->m_toggleViewAction; } +void DockWidget::setToggleViewAction(QAction *action) +{ + if (!action) + return; + + d->m_toggleViewAction->setParent(nullptr); + delete d->m_toggleViewAction; + d->m_toggleViewAction = action; + d->m_toggleViewAction->setParent(this); + connect(d->m_toggleViewAction, &QAction::triggered, this, &DockWidget::toggleView); +} + void DockWidget::setToggleViewActionMode(eToggleViewActionMode mode) { if (ActionModeToggle == mode) { @@ -693,6 +738,18 @@ void DockWidget::setToolBar(QToolBar *toolBar) setToolbarFloatingStyle(isFloating()); } +void DockWidget::setToolBarStyleSource(eToolBarStyleSource source) +{ + d->m_toolBarStyleSource = source; + if (ToolBarStyleFromDockManager == d->m_toolBarStyleSource) + d->setToolBarStyleFromDockManager(); +} + +DockWidget::eToolBarStyleSource DockWidget::toolBarStyleSource() const +{ + return d->m_toolBarStyleSource; +} + void DockWidget::setToolBarStyle(Qt::ToolButtonStyle style, eState state) { if (StateFloating == state) diff --git a/src/libs/advanceddockingsystem/dockwidget.h b/src/libs/advanceddockingsystem/dockwidget.h index 65495cc04d6..03c92227664 100644 --- a/src/libs/advanceddockingsystem/dockwidget.h +++ b/src/libs/advanceddockingsystem/dockwidget.h @@ -154,6 +154,8 @@ public: enum eState { StateHidden, StateDocked, StateFloating }; + enum eToolBarStyleSource { ToolBarStyleFromDockManager, ToolBarStyleFromDockWidget }; + /** * Sets the widget for the dock widget to widget. * The InsertMode defines how the widget is inserted into the dock widget. @@ -385,6 +387,12 @@ public: */ QAction *toggleViewAction() const; + /** + * Use provided action to be the default toggle view action for this dock widget. + * This dock widget now owns the action. + */ + void setToggleViewAction(QAction *action); + /** * Configures the behavior of the toggle view action. * \see eToggleViewActionMode for a detailed description @@ -443,6 +451,17 @@ public: */ void setToolBar(QToolBar *toolBar); + /** + * Configures, if the dock widget uses the global tool bar styles from + * dock manager or if it uses its own tool bar style + */ + void setToolBarStyleSource(eToolBarStyleSource source); + + /** + * Returns the configured tool bar style source + */ + eToolBarStyleSource toolBarStyleSource() const; + /** * This function sets the tool button style for the given dock widget state. * It is possible to switch the tool button style depending on the state. diff --git a/src/libs/advanceddockingsystem/dockwidgettab.cpp b/src/libs/advanceddockingsystem/dockwidgettab.cpp index f743db17fd6..3d1ef7d4905 100644 --- a/src/libs/advanceddockingsystem/dockwidgettab.cpp +++ b/src/libs/advanceddockingsystem/dockwidgettab.cpp @@ -374,10 +374,12 @@ void DockWidgetTab::mouseReleaseEvent(QMouseEvent *event) break; default: - if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) - d->focusController()->setDockWidgetTabPressed(false); break; } + + if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) + d->focusController()->setDockWidgetTabPressed(false); + } else if (event->button() == Qt::MiddleButton) { if (DockManager::testConfigFlag(DockManager::MiddleMouseButtonClosesTab) && d->m_dockWidget->features().testFlag(DockWidget::DockWidgetClosable)) { diff --git a/src/libs/advanceddockingsystem/workspace.cpp b/src/libs/advanceddockingsystem/workspace.cpp index 760e2c7dd1c..d3b0785d90b 100644 --- a/src/libs/advanceddockingsystem/workspace.cpp +++ b/src/libs/advanceddockingsystem/workspace.cpp @@ -41,16 +41,6 @@ const QString &Workspace::name() const return m_name; } -void Workspace::setLocked(bool value) -{ - m_locked = value; -} - -bool Workspace::isLocked() const -{ - return m_locked; -} - const Utils::FilePath &Workspace::filePath() const { return m_filePath; diff --git a/src/libs/advanceddockingsystem/workspace.h b/src/libs/advanceddockingsystem/workspace.h index 5e96e5a9815..c23db55d6b6 100644 --- a/src/libs/advanceddockingsystem/workspace.h +++ b/src/libs/advanceddockingsystem/workspace.h @@ -18,9 +18,6 @@ public: void setName(const QString &name); const QString &name() const; - void setLocked(bool value); - bool isLocked() const; - const Utils::FilePath &filePath() const; QString fileName() const; @@ -53,7 +50,6 @@ private: QString m_name; Utils::FilePath m_filePath; bool m_preset = false; - bool m_locked = false; }; } // namespace ADS diff --git a/src/libs/qmljs/qmljsinterpreter.cpp b/src/libs/qmljs/qmljsinterpreter.cpp index 63e009607b9..9448ef39b01 100644 --- a/src/libs/qmljs/qmljsinterpreter.cpp +++ b/src/libs/qmljs/qmljsinterpreter.cpp @@ -1485,8 +1485,10 @@ QList CppQmlTypes::createObjectsForImport(const QStri // if it already exists, skip const QString key = qualifiedName(package, fmo->className(), version); - if (m_objectsByQualifiedName.contains(key)) + if (m_objectsByQualifiedName.contains(key)) { + exportedObjects.insert(key, m_objectsByQualifiedName.value(key)); continue; + } ComponentVersion cppVersion; for (const FakeMetaObject::Export &bestExport : std::as_const(bestExports)) { diff --git a/src/plugins/cmakeprojectmanager/cmakesettingspage.cpp b/src/plugins/cmakeprojectmanager/cmakesettingspage.cpp index b82ffc4e3c8..34f12da7448 100644 --- a/src/plugins/cmakeprojectmanager/cmakesettingspage.cpp +++ b/src/plugins/cmakeprojectmanager/cmakesettingspage.cpp @@ -9,6 +9,7 @@ #include "cmaketoolmanager.h" #include +#include #include #include @@ -457,6 +458,9 @@ void CMakeToolItemConfigWidget::onBinaryPathEditingFinished() void CMakeToolItemConfigWidget::updateQchFilePath() { + // QDS does not want automatic detection of cmake help file + if (Core::ICore::isQtDesignStudio()) + return; if (m_qchFileChooser->filePath().isEmpty()) m_qchFileChooser->setFilePath(CMakeTool::searchQchFile(m_binaryChooser->filePath())); } diff --git a/src/plugins/effectmakernew/compositionnode.cpp b/src/plugins/effectmakernew/compositionnode.cpp index 8cd5bda971e..9520e50d050 100644 --- a/src/plugins/effectmakernew/compositionnode.cpp +++ b/src/plugins/effectmakernew/compositionnode.cpp @@ -14,7 +14,8 @@ namespace EffectMaker { -CompositionNode::CompositionNode(const QString &effectName, const QString &qenPath, const QJsonObject &jsonObject) +CompositionNode::CompositionNode(const QString &effectName, const QString &qenPath, + const QJsonObject &jsonObject) { QJsonObject json; if (jsonObject.isEmpty()) { @@ -58,6 +59,11 @@ QString CompositionNode::description() const return m_description; } +QString CompositionNode::id() const +{ + return m_id; +} + QObject *CompositionNode::uniformsModel() { return &m_unifomrsModel; @@ -81,6 +87,11 @@ void CompositionNode::setIsEnabled(bool newIsEnabled) } } +bool CompositionNode::isDependency() const +{ + return m_refCount > 0; +} + CompositionNode::NodeType CompositionNode::type() const { return m_type; @@ -102,6 +113,13 @@ void CompositionNode::parse(const QString &effectName, const QString &qenPath, c m_fragmentCode = EffectUtils::codeFromJsonArray(json.value("fragmentCode").toArray()); m_vertexCode = EffectUtils::codeFromJsonArray(json.value("vertexCode").toArray()); + m_id = json.value("id").toString(); + if (m_id.isEmpty() && !qenPath.isEmpty()) { + QString fileName = qenPath.split('/').last(); + fileName.chop(4); // remove ".qen" + m_id = fileName; + } + // parse properties QJsonArray jsonProps = json.value("properties").toArray(); for (const auto /*QJsonValueRef*/ &prop : jsonProps) { @@ -118,8 +136,7 @@ void CompositionNode::parse(const QString &effectName, const QString &qenPath, c for (const QString &codeLine : std::as_const(shaderCodeLines)) { QString trimmedLine = codeLine.trimmed(); if (trimmedLine.startsWith("@requires")) { - // Get the required node, remove "@requires" - QString l = trimmedLine.sliced(9).trimmed(); + // Get the required node, remove "@requires " QString nodeName = trimmedLine.sliced(10); if (!nodeName.isEmpty() && !m_requiredNodes.contains(nodeName)) m_requiredNodes << nodeName; @@ -132,6 +149,36 @@ QList CompositionNode::uniforms() const return m_uniforms; } +int CompositionNode::incRefCount() +{ + ++m_refCount; + + if (m_refCount == 1) + emit isDepencyChanged(); + + return m_refCount; +} + +int CompositionNode::decRefCount() +{ + --m_refCount; + + if (m_refCount == 0) + emit isDepencyChanged(); + + return m_refCount; +} + +void CompositionNode::setRefCount(int count) +{ + bool notifyChange = (m_refCount > 0 && count == 0) || (m_refCount <= 0 && count > 0); + + m_refCount = count; + + if (notifyChange) + emit isDepencyChanged(); +} + QString CompositionNode::name() const { return m_name; diff --git a/src/plugins/effectmakernew/compositionnode.h b/src/plugins/effectmakernew/compositionnode.h index 4736f1d8afa..04f5dd5c02b 100644 --- a/src/plugins/effectmakernew/compositionnode.h +++ b/src/plugins/effectmakernew/compositionnode.h @@ -15,7 +15,8 @@ class CompositionNode : public QObject Q_OBJECT Q_PROPERTY(QString nodeName MEMBER m_name CONSTANT) - Q_PROPERTY(bool nodeEnabled READ isEnabled WRITE setIsEnabled NOTIFY isEnabledChanged) + Q_PROPERTY(bool nodeEnabled READ isEnabled WRITE setIsEnabled NOTIFY isEnabledChanged) + Q_PROPERTY(bool isDependency READ isDependency NOTIFY isDepencyChanged) Q_PROPERTY(QObject *nodeUniformsModel READ uniformsModel NOTIFY uniformsModelChanged) public: @@ -30,6 +31,7 @@ public: QString fragmentCode() const; QString vertexCode() const; QString description() const; + QString id() const; QObject *uniformsModel(); @@ -40,13 +42,20 @@ public: bool isEnabled() const; void setIsEnabled(bool newIsEnabled); + bool isDependency() const; + QString name() const; QList uniforms() const; + int incRefCount(); + int decRefCount(); + void setRefCount(int count); + signals: void uniformsModelChanged(); void isEnabledChanged(); + void isDepencyChanged(); private: void parse(const QString &effectName, const QString &qenPath, const QJsonObject &json); @@ -57,7 +66,9 @@ private: QString m_vertexCode; QString m_description; QStringList m_requiredNodes; + QString m_id; bool m_isEnabled = true; + int m_refCount = 0; QList m_uniforms; diff --git a/src/plugins/effectmakernew/effectmakermodel.cpp b/src/plugins/effectmakernew/effectmakermodel.cpp index 2c9dd051638..fbb439ff17d 100644 --- a/src/plugins/effectmakernew/effectmakermodel.cpp +++ b/src/plugins/effectmakernew/effectmakermodel.cpp @@ -4,6 +4,8 @@ #include "effectmakermodel.h" #include "compositionnode.h" +#include "effectutils.h" +#include "propertyhandler.h" #include "syntaxhighlighterdata.h" #include "uniform.h" @@ -21,6 +23,7 @@ #include #include +#include #include namespace EffectMaker { @@ -57,6 +60,7 @@ QHash EffectMakerModel::roleNames() const roles[NameRole] = "nodeName"; roles[EnabledRole] = "nodeEnabled"; roles[UniformsRole] = "nodeUniformsModel"; + roles[Dependency] = "isDependency"; return roles; } @@ -103,14 +107,45 @@ void EffectMakerModel::setIsEmpty(bool val) void EffectMakerModel::addNode(const QString &nodeQenPath) { - beginInsertRows({}, m_nodes.size(), m_nodes.size()); - auto *node = new CompositionNode("", nodeQenPath); + beginResetModel(); + auto *node = new CompositionNode({}, nodeQenPath); + connect(qobject_cast(node->uniformsModel()), + &EffectMakerUniformsModel::dataChanged, this, [this] { + setHasUnsavedChanges(true); + }); + + const QList requiredNodes = node->requiredNodes(); + if (requiredNodes.size() > 0) { + for (const QString &requiredId : requiredNodes) { + if (auto reqNode = findNodeById(requiredId)) { + reqNode->incRefCount(); + continue; + } + + const QString path = EffectUtils::nodesSourcesPath() + "/common/" + requiredId + ".qen"; + auto requiredNode = new CompositionNode({}, path); + requiredNode->setRefCount(1); + m_nodes.prepend(requiredNode); + } + } m_nodes.append(node); - endInsertRows(); + endResetModel(); setIsEmpty(false); bakeShaders(); + setHasUnsavedChanges(true); + + emit nodesChanged(); +} + +CompositionNode *EffectMakerModel::findNodeById(const QString &id) const +{ + for (CompositionNode *node : std::as_const(m_nodes)) { + if (node->id() == id) + return node; + } + return {}; } void EffectMakerModel::moveNode(int fromIdx, int toIdx) @@ -123,21 +158,67 @@ void EffectMakerModel::moveNode(int fromIdx, int toIdx) m_nodes.move(fromIdx, toIdx); endMoveRows(); + setHasUnsavedChanges(true); bakeShaders(); } void EffectMakerModel::removeNode(int idx) { - beginRemoveRows({}, idx, idx); - CompositionNode *node = m_nodes.at(idx); - m_nodes.removeAt(idx); + beginResetModel(); + CompositionNode *node = m_nodes.takeAt(idx); + + const QStringList reqNodes = node->requiredNodes(); + for (const QString &reqId : reqNodes) { + CompositionNode *depNode = findNodeById(reqId); + if (depNode && depNode->decRefCount() <= 0) { + m_nodes.removeOne(depNode); + delete depNode; + } + } + delete node; - endRemoveRows(); + endResetModel(); if (m_nodes.isEmpty()) setIsEmpty(true); else bakeShaders(); + + setHasUnsavedChanges(true); + emit nodesChanged(); +} + +void EffectMakerModel::clear() +{ + beginResetModel(); + qDeleteAll(m_nodes); + m_nodes.clear(); + endResetModel(); + setHasUnsavedChanges(!m_currentComposition.isEmpty()); + setCurrentComposition(""); + + setIsEmpty(true); + emit nodesChanged(); +} + +void EffectMakerModel::assignToSelected() +{ + const QString effectsAssetsDir = QmlDesigner::ModelNodeOperations::getEffectsDefaultDirectory(); + const QString path = effectsAssetsDir + QDir::separator() + m_currentComposition + ".qep"; + emit assignToSelectedTriggered(path); +} + +QString EffectMakerModel::getUniqueEffectName() const +{ + const QString effectsDir = QmlDesigner::ModelNodeOperations::getEffectsDefaultDirectory(); + const QString path = effectsDir + QDir::separator() + "Effect%1.qep"; + + int num = 0; + + while (QFile::exists(path.arg(++num, 2, 10, QChar('0')))) + ; // empty body + + return QString("Effect%1").arg(num, 2, 10, QChar('0')); } QString EffectMakerModel::fragmentShader() const @@ -171,25 +252,7 @@ const QString &EffectMakerModel::qmlComponentString() const return m_qmlComponentString; } -void EffectMakerModel::clear() -{ - if (m_nodes.isEmpty()) - return; - - beginRemoveRows({}, 0, m_nodes.count()); - - for (CompositionNode *node : std::as_const(m_nodes)) - delete node; - - m_nodes.clear(); - - endRemoveRows(); - - setIsEmpty(true); - bakeShaders(); -} - -const QList EffectMakerModel::allUniforms() +const QList EffectMakerModel::allUniforms() const { QList uniforms = {}; for (const auto &node : std::as_const(m_nodes)) @@ -416,6 +479,8 @@ QJsonObject nodeToJson(const CompositionNode &node) nodeObject.insert("description", node.description()); nodeObject.insert("enabled", node.isEnabled()); nodeObject.insert("version", 1); + nodeObject.insert("id", node.id()); + // Add properties QJsonArray propertiesArray; const QList uniforms = node.uniforms(); @@ -523,14 +588,24 @@ QString EffectMakerModel::getQmlEffectString() s += '\n'; } - //TODO: Blue stuff goes here + if (m_shaderFeatures.enabled(ShaderFeatures::BlurSources)) { + s += " BlurHelper {\n"; + s += " id: blurHelper\n"; + s += " anchors.fill: parent\n"; + int blurMax = 32; + if (g_propertyData.contains("BLUR_HELPER_MAX_LEVEL")) + blurMax = g_propertyData["BLUR_HELPER_MAX_LEVEL"].toInt(); + s += QString(" property int blurMax: %1\n").arg(blurMax); + s += " property real blurMultiplier: rootItem.blurMultiplier\n"; + s += " }\n"; + } s += getQmlComponentString(true); s += "}\n"; return s; } -void EffectMakerModel::exportComposition(const QString &name) +void EffectMakerModel::saveComposition(const QString &name) { const QString effectsAssetsDir = QmlDesigner::ModelNodeOperations::getEffectsDefaultDirectory(); const QString path = effectsAssetsDir + QDir::separator() + name + ".qep"; @@ -561,12 +636,19 @@ void EffectMakerModel::exportComposition(const QString &name) saveFile.write(jsonDoc.toJson()); saveFile.close(); + setCurrentComposition(name); + setHasUnsavedChanges(false); + + saveResources(name); } void EffectMakerModel::openComposition(const QString &path) { clear(); + const QString effectName = QFileInfo(path).baseName(); + setCurrentComposition(effectName); + QFile compFile(path); if (!compFile.open(QIODevice::ReadOnly)) { QString error = QString("Couldn't open composition file: '%1'").arg(path); @@ -576,6 +658,10 @@ void EffectMakerModel::openComposition(const QString &path) } QByteArray data = compFile.readAll(); + + if (data.isEmpty()) + return; + QJsonParseError parseError; QJsonDocument jsonDoc(QJsonDocument::fromJson(data, &parseError)); if (parseError.error != QJsonParseError::NoError) { @@ -605,28 +691,40 @@ void EffectMakerModel::openComposition(const QString &path) return; } - // Get effects dir - const QString effectName = QFileInfo(path).baseName(); - const Utils::FilePath effectsResDir = QmlDesigner::ModelNodeOperations::getEffectsImportDirectory(); - const QString effectsResPath = effectsResDir.pathAppended(effectName).toString(); - if (json.contains("nodes") && json["nodes"].isArray()) { + beginResetModel(); + QHash refCounts; const QJsonArray nodesArray = json["nodes"].toArray(); + for (const auto &nodeElement : nodesArray) { - beginInsertRows({}, m_nodes.size(), m_nodes.size()); - auto *node = new CompositionNode(effectName, "", nodeElement.toObject()); + auto *node = new CompositionNode(effectName, {}, nodeElement.toObject()); + connect(qobject_cast(node->uniformsModel()), + &EffectMakerUniformsModel::dataChanged, this, [this] { + setHasUnsavedChanges(true); + }); m_nodes.append(node); - endInsertRows(); + const QStringList reqIds = node->requiredNodes(); + for (const QString &reqId : reqIds) + ++refCounts[reqId]; } + for (auto it = refCounts.cbegin(), end = refCounts.cend(); it != end; ++it) { + CompositionNode *depNode = findNodeById(it.key()); + if (depNode) + depNode->setRefCount(it.value()); + } + + endResetModel(); + setIsEmpty(m_nodes.isEmpty()); bakeShaders(); } - setCurrentComposition(effectName); + setHasUnsavedChanges(false); + emit nodesChanged(); } -void EffectMakerModel::exportResources(const QString &name) +void EffectMakerModel::saveResources(const QString &name) { // Make sure that uniforms are up-to-date updateCustomUniforms(); @@ -682,7 +780,7 @@ void EffectMakerModel::exportResources(const QString &name) if (line.startsWith("vertexShader")) { QString vsLine = " vertexShader: '" + vsFilename + "'"; qmlStringList[i] = vsLine; - } else if (line.startsWith("fragmentShader")) { + } else if (line.startsWith("fragmentShader")) { QString fsLine = " fragmentShader: '" + fsFilename + "'"; qmlStringList[i] = fsLine; } @@ -692,7 +790,7 @@ void EffectMakerModel::exportResources(const QString &name) QString qmlFilePath = effectsResPath + qmlFilename; writeToFile(qmlString.toUtf8(), qmlFilePath, FileType::Text); - // Export shaders and images + // Save shaders and images QStringList sources = {m_vertexShaderFilename, m_fragmentShaderFilename}; QStringList dests = {vsFilename, fsFilename}; @@ -702,11 +800,28 @@ void EffectMakerModel::exportResources(const QString &name) QString imagePath = uniform->value().toString(); QFileInfo fi(imagePath); QString imageFilename = fi.fileName(); - sources.append(imagePath.remove(0, 7)); // Removes "file://" + if (imagePath.startsWith("file:")) { + QUrl url(imagePath); + imagePath = url.toLocalFile(); + } + sources.append(imagePath); dests.append(imageFilename); } } + if (m_shaderFeatures.enabled(ShaderFeatures::BlurSources)) { + QString blurHelperFilename("BlurHelper.qml"); + QString blurFsFilename("bluritems.frag.qsb"); + QString blurVsFilename("bluritems.vert.qsb"); + QString blurHelperPath(EffectUtils::nodesSourcesPath() + "/common/"); + sources.append(blurHelperPath + blurHelperFilename); + sources.append(blurHelperPath + blurFsFilename); + sources.append(blurHelperPath + blurVsFilename); + dests.append(blurHelperFilename); + dests.append(blurFsFilename); + dests.append(blurVsFilename); + } + for (int i = 0; i < sources.count(); ++i) { Utils::FilePath source = Utils::FilePath::fromString(sources[i]); Utils::FilePath target = Utils::FilePath::fromString(effectsResPath + dests[i]); @@ -716,6 +831,8 @@ void EffectMakerModel::exportResources(const QString &name) if (!source.copyFile(target)) qWarning() << __FUNCTION__ << " Failed to copy file: " << source; } + + emit resourcesSaved(QString("Effects.%1.%1").arg(name).toUtf8(), effectPath); } void EffectMakerModel::resetEffectError(int type) @@ -737,16 +854,18 @@ QString EffectMakerModel::valueAsString(const Uniform &uniform) return QString::number(uniform.value().toDouble()); } else if (uniform.type() == Uniform::Type::Vec2) { QVector2D v2 = uniform.value().value(); - return QString("Qt.point(%1, %2)").arg(v2.x(), v2.y()); + return QString("Qt.point(%1, %2)").arg(v2.x()).arg(v2.y()); } else if (uniform.type() == Uniform::Type::Vec3) { QVector3D v3 = uniform.value().value(); - return QString("Qt.vector3d(%1, %2, %3)").arg(v3.x(), v3.y(), v3.z()); + return QString("Qt.vector3d(%1, %2, %3)").arg(v3.x()).arg(v3.y()).arg(v3.z()); } else if (uniform.type() == Uniform::Type::Vec4) { QVector4D v4 = uniform.value().value(); - return QString("Qt.vector4d(%1, %2, %3, %4)").arg(v4.x(), v4.y(), v4.z(), v4.w()); + return QString("Qt.vector4d(%1, %2, %3, %4)").arg(v4.x()).arg(v4.y()).arg(v4.z()).arg(v4.w()); } else if (uniform.type() == Uniform::Type::Sampler) { return getImageElementName(uniform); - } else if (uniform.type() == Uniform::Type::Define || uniform.type() == Uniform::Type::Color) { + } else if (uniform.type() == Uniform::Type::Color) { + return QString("\"%1\"").arg(uniform.value().toString()); + } else if (uniform.type() == Uniform::Type::Define) { return uniform.value().toString(); } else { qWarning() << QString("Unhandled const variable type: %1").arg(int(uniform.type())).toLatin1(); @@ -1085,18 +1204,25 @@ QString EffectMakerModel::generateFragmentShader(bool includeUniforms) return s; } -void EffectMakerModel::handleQsbProcessExit(Utils::Process *qsbProcess, const QString &shader) +void EffectMakerModel::handleQsbProcessExit(Utils::Process *qsbProcess, const QString &shader, bool preview) { --m_remainingQsbTargets; const QString errStr = qsbProcess->errorString(); const QByteArray errStd = qsbProcess->readAllRawStandardError(); - if (!errStr.isEmpty()) - qWarning() << QString("Failed to generate QSB file for: %1 %2").arg(shader, errStr); + QString previewStr; + if (preview) + previewStr = QStringLiteral("preview"); - if (!errStd.isEmpty()) - qWarning() << QString("Failed to generate QSB file for: %1 %2") - .arg(shader, QString::fromUtf8(errStd)); + if (!errStr.isEmpty()) { + qWarning() << QString("Failed to generate %3 QSB file for: %1 %2") + .arg(shader, errStr, previewStr); + } + + if (!errStd.isEmpty()) { + qWarning() << QString("Failed to generate %3 QSB file for: %1 %2") + .arg(shader, QString::fromUtf8(errStd), previewStr); + } if (m_remainingQsbTargets <= 0) { Q_EMIT shadersBaked(); @@ -1182,21 +1308,30 @@ void EffectMakerModel::createFiles() QFile(m_vertexShaderFilename).remove(); if (QFileInfo(m_fragmentShaderFilename).exists()) QFile(m_fragmentShaderFilename).remove(); + if (QFileInfo(m_vertexShaderPreviewFilename).exists()) + QFile(m_vertexShaderPreviewFilename).remove(); + if (QFileInfo(m_fragmentShaderPreviewFilename).exists()) + QFile(m_fragmentShaderPreviewFilename).remove(); auto vertexShaderFile = QTemporaryFile(QDir::tempPath() + "/dsem_XXXXXX.vert.qsb"); auto fragmentShaderFile = QTemporaryFile(QDir::tempPath() + "/dsem_XXXXXX.frag.qsb"); + auto vertexShaderPreviewFile = QTemporaryFile(QDir::tempPath() + "/dsem_prev_XXXXXX.vert.qsb"); + auto fragmentShaderPreviewFile = QTemporaryFile(QDir::tempPath() + "/dsem_prev_XXXXXX.frag.qsb"); m_vertexSourceFile.setFileTemplate(QDir::tempPath() + "/dsem_XXXXXX.vert"); m_fragmentSourceFile.setFileTemplate(QDir::tempPath() + "/dsem_XXXXXX.frag"); if (!m_vertexSourceFile.open() || !m_fragmentSourceFile.open() - || !vertexShaderFile.open() || !fragmentShaderFile.open()) { + || !vertexShaderFile.open() || !fragmentShaderFile.open() + || !vertexShaderPreviewFile.open() || !fragmentShaderPreviewFile.open()) { qWarning() << "Unable to open temporary files"; } else { m_vertexSourceFilename = m_vertexSourceFile.fileName(); m_fragmentSourceFilename = m_fragmentSourceFile.fileName(); m_vertexShaderFilename = vertexShaderFile.fileName(); m_fragmentShaderFilename = fragmentShaderFile.fileName(); + m_vertexShaderPreviewFilename = vertexShaderPreviewFile.fileName(); + m_fragmentShaderPreviewFilename = fragmentShaderPreviewFile.fileName(); } } @@ -1243,27 +1378,43 @@ void EffectMakerModel::bakeShaders() Utils::FilePath qsbPath = qtVer->binPath().pathAppended("qsb").withExecutableSuffix(); if (!qsbPath.exists()) { - qWarning() << failMessage << "QSB tool not found"; + qWarning() << failMessage << "QSB tool for target kit not found"; + return; + } + + Utils::FilePath binPath = Utils::FilePath::fromString( + QLibraryInfo::path(QLibraryInfo::BinariesPath)); + Utils::FilePath qsbPrevPath = binPath.pathAppended("qsb").withExecutableSuffix(); + if (!qsbPrevPath.exists()) { + qWarning() << failMessage << "QSB tool for preview shaders not found"; return; } m_remainingQsbTargets = 2; // We only have 2 shaders const QStringList srcPaths = {m_vertexSourceFilename, m_fragmentSourceFilename}; const QStringList outPaths = {m_vertexShaderFilename, m_fragmentShaderFilename}; - for (int i = 0; i < 2; ++i) { - const auto workDir = Utils::FilePath::fromString(outPaths[i]); - // TODO: Optional legacy glsl support like standalone effect maker needs to add "100es,120" - QStringList args = {"-s", "--glsl", "300es,140,330,410", "--hlsl", "50", "--msl", "12"}; - args << "-o" << outPaths[i] << srcPaths[i]; + const QStringList outPrevPaths = {m_vertexShaderPreviewFilename, m_fragmentShaderPreviewFilename}; + + auto runQsb = [this, srcPaths](const Utils::FilePath &qsbPath, const QStringList &outPaths, bool preview) { + for (int i = 0; i < 2; ++i) { + const auto workDir = Utils::FilePath::fromString(outPaths[i]); + // TODO: Optional legacy glsl support like standalone effect maker needs to add "100es,120" + QStringList args = {"-s", "--glsl", "300es,140,330,410", "--hlsl", "50", "--msl", "12"}; + args << "-o" << outPaths[i] << srcPaths[i]; + + auto qsbProcess = new Utils::Process(this); + connect(qsbProcess, &Utils::Process::done, this, [=] { + handleQsbProcessExit(qsbProcess, srcPaths[i], preview); + }); + qsbProcess->setWorkingDirectory(workDir.absolutePath()); + qsbProcess->setCommand({qsbPath, args}); + qsbProcess->start(); + } + }; + + runQsb(qsbPath, outPaths, false); + runQsb(qsbPrevPath, outPrevPaths, true); - auto qsbProcess = new Utils::Process(this); - connect(qsbProcess, &Utils::Process::done, this, [=] { - handleQsbProcessExit(qsbProcess, srcPaths[i]); - }); - qsbProcess->setWorkingDirectory(workDir.absolutePath()); - qsbProcess->setCommand({qsbPath, args}); - qsbProcess->start(); - } } bool EffectMakerModel::shadersUpToDate() const @@ -1375,8 +1526,10 @@ QString EffectMakerModel::getQmlComponentString(bool localFiles) s += '\n' + customImagesString; s += '\n'; - s += l2 + "vertexShader: 'file:///" + m_vertexShaderFilename + "'\n"; - s += l2 + "fragmentShader: 'file:///" + m_fragmentShaderFilename + "'\n"; + const QString vertFile = localFiles ? m_vertexShaderFilename : m_vertexShaderPreviewFilename; + const QString fragFile = localFiles ? m_fragmentShaderFilename : m_fragmentShaderPreviewFilename; + s += l2 + "vertexShader: 'file:///" + vertFile + "'\n"; + s += l2 + "fragmentShader: 'file:///" + fragFile + "'\n"; s += l2 + "anchors.fill: parent\n"; if (m_shaderFeatures.enabled(ShaderFeatures::GridMesh)) { QString gridSize = QString("%1, %2").arg(m_shaderFeatures.gridMeshWidth()) @@ -1398,10 +1551,41 @@ void EffectMakerModel::setCurrentComposition(const QString &newCurrentCompositio { if (m_currentComposition == newCurrentComposition) return; + m_currentComposition = newCurrentComposition; emit currentCompositionChanged(); } +bool EffectMakerModel::hasUnsavedChanges() const +{ + return m_hasUnsavedChanges; +} + +void EffectMakerModel::setHasUnsavedChanges(bool val) +{ + if (m_hasUnsavedChanges == val) + return; + + m_hasUnsavedChanges = val; + emit hasUnsavedChangesChanged(); +} + +QStringList EffectMakerModel::uniformNames() const +{ + QStringList usedList; + const QList uniforms = allUniforms(); + for (const auto uniform : uniforms) + usedList.append(uniform->name()); + return usedList; +} + +bool EffectMakerModel::isDependencyNode(int index) const +{ + if (m_nodes.size() > index) + return m_nodes[index]->isDependency(); + return false; +} + void EffectMakerModel::updateQmlComponent() { // Clear possible QML runtime errors diff --git a/src/plugins/effectmakernew/effectmakermodel.h b/src/plugins/effectmakernew/effectmakermodel.h index 96eb0e19b46..a25a4e40913 100644 --- a/src/plugins/effectmakernew/effectmakermodel.h +++ b/src/plugins/effectmakernew/effectmakermodel.h @@ -7,10 +7,10 @@ #include +#include #include #include #include -#include #include namespace ProjectExplorer { @@ -44,6 +44,7 @@ class EffectMakerModel : public QAbstractListModel Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged) + Q_PROPERTY(bool hasUnsavedChanges MEMBER m_hasUnsavedChanges WRITE setHasUnsavedChanges NOTIFY hasUnsavedChangesChanged) Q_PROPERTY(bool shadersUpToDate READ shadersUpToDate WRITE setShadersUpToDate NOTIFY shadersUpToDateChanged) Q_PROPERTY(QString qmlComponentString READ qmlComponentString) Q_PROPERTY(QString currentComposition READ currentComposition WRITE setCurrentComposition NOTIFY currentCompositionChanged) @@ -61,8 +62,13 @@ public: void addNode(const QString &nodeQenPath); + CompositionNode *findNodeById(const QString &id) const; + Q_INVOKABLE void moveNode(int fromIdx, int toIdx); Q_INVOKABLE void removeNode(int idx); + Q_INVOKABLE void clear(); + Q_INVOKABLE void assignToSelected(); + Q_INVOKABLE QString getUniqueEffectName() const; bool shadersUpToDate() const; void setShadersUpToDate(bool newShadersUpToDate); @@ -75,35 +81,43 @@ public: const QString &qmlComponentString() const; - void clear(); - Q_INVOKABLE void updateQmlComponent(); Q_INVOKABLE void resetEffectError(int type); Q_INVOKABLE void setEffectError(const QString &errorMessage, int type = -1, int lineNumber = -1); - Q_INVOKABLE void exportComposition(const QString &name); - Q_INVOKABLE void exportResources(const QString &name); + Q_INVOKABLE void saveComposition(const QString &name); void openComposition(const QString &path); QString currentComposition() const; void setCurrentComposition(const QString &newCurrentComposition); + bool hasUnsavedChanges() const; + void setHasUnsavedChanges(bool val); + + QStringList uniformNames() const; + + Q_INVOKABLE bool isDependencyNode(int index) const; + signals: void isEmptyChanged(); void selectedIndexChanged(int idx); void effectErrorChanged(); void shadersUpToDateChanged(); void shadersBaked(); - void currentCompositionChanged(); + void nodesChanged(); + void resourcesSaved(const QByteArray &type, const Utils::FilePath &path); + void hasUnsavedChangesChanged(); + void assignToSelectedTriggered(const QString &effectPath); private: enum Roles { NameRole = Qt::UserRole + 1, EnabledRole, - UniformsRole + UniformsRole, + Dependency }; enum ErrorTypes { @@ -117,7 +131,7 @@ private: bool isValidIndex(int idx) const; - const QList allUniforms(); + const QList allUniforms() const; const QString getBufUniform(); const QString getVSUniforms(); @@ -142,13 +156,14 @@ private: QString getCustomShaderVaryings(bool outState); QString generateVertexShader(bool includeUniforms = true); QString generateFragmentShader(bool includeUniforms = true); - void handleQsbProcessExit(Utils::Process *qsbProcess, const QString &shader); + void handleQsbProcessExit(Utils::Process *qsbProcess, const QString &shader, bool preview); QString stripFileFromURL(const QString &urlString) const; QString getQmlEffectString(); void updateCustomUniforms(); void createFiles(); void bakeShaders(); + void saveResources(const QString &name); QString mipmapPropertyName(const QString &name) const; QString getQmlImagesString(bool localFiles); @@ -158,6 +173,7 @@ private: int m_selectedIndex = -1; bool m_isEmpty = true; + bool m_hasUnsavedChanges = false; // True when shaders haven't changed since last baking bool m_shadersUpToDate = true; int m_remainingQsbTargets = 0; @@ -175,6 +191,8 @@ private: QString m_vertexSourceFilename; QString m_fragmentShaderFilename; QString m_vertexShaderFilename; + QString m_fragmentShaderPreviewFilename; + QString m_vertexShaderPreviewFilename; // Used in exported QML, at root of the file QString m_exportedRootPropertiesString; // Used in exported QML, at ShaderEffect component of the file diff --git a/src/plugins/effectmakernew/effectmakernodesmodel.cpp b/src/plugins/effectmakernew/effectmakernodesmodel.cpp index dc028aef8e8..7f35e935ba9 100644 --- a/src/plugins/effectmakernew/effectmakernodesmodel.cpp +++ b/src/plugins/effectmakernew/effectmakernodesmodel.cpp @@ -2,7 +2,9 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "effectmakernodesmodel.h" +#include "effectutils.h" +#include #include #include @@ -38,44 +40,21 @@ QVariant EffectMakerNodesModel::data(const QModelIndex &index, int role) const return m_categories.at(index.row())->property(roleNames().value(role)); } -void EffectMakerNodesModel::findNodesPath() -{ - if (m_nodesPath.exists() || m_probeNodesDir) - return; - - QDir nodesDir; - - if (!qEnvironmentVariable("EFFECT_MAKER_NODES_PATH").isEmpty()) - nodesDir.setPath(qEnvironmentVariable("EFFECT_MAKER_NODES_PATH")); - else if (Utils::HostOsInfo::isMacHost()) - nodesDir.setPath(QCoreApplication::applicationDirPath() + "/../Resources/effect_maker_nodes"); - - // search for nodesDir from exec dir and up - if (nodesDir.dirName() == ".") { - m_probeNodesDir = true; // probe only once - nodesDir.setPath(QCoreApplication::applicationDirPath()); - while (!nodesDir.cd("effect_maker_nodes") && nodesDir.cdUp()) - ; // do nothing - - if (nodesDir.dirName() != "effect_maker_nodes") // bundlePathDir not found - return; - } - - m_nodesPath = Utils::FilePath::fromString(nodesDir.path()); -} - void EffectMakerNodesModel::loadModel() { - findNodesPath(); + if (m_modelLoaded) + return; - if (!m_nodesPath.exists()) { + auto nodesPath = Utils::FilePath::fromString(EffectUtils::nodesSourcesPath()); + + if (!nodesPath.exists()) { qWarning() << __FUNCTION__ << "Effects not found."; return; } m_categories = {}; - QDirIterator itCategories(m_nodesPath.toString(), QDir::Dirs | QDir::NoDotAndDotDot); + QDirIterator itCategories(nodesPath.toString(), QDir::Dirs | QDir::NoDotAndDotDot); while (itCategories.hasNext()) { itCategories.next(); @@ -85,7 +64,7 @@ void EffectMakerNodesModel::loadModel() QString catName = itCategories.fileName(); QList effects = {}; - Utils::FilePath categoryPath = m_nodesPath.resolvePath(itCategories.fileName()); + Utils::FilePath categoryPath = nodesPath.resolvePath(itCategories.fileName()); QDirIterator itEffects(categoryPath.toString(), {"*.qen"}, QDir::Files); while (itEffects.hasNext()) { itEffects.next(); @@ -102,6 +81,8 @@ void EffectMakerNodesModel::loadModel() return a->name() < b->name(); }); + m_modelLoaded = true; + resetModel(); } @@ -111,5 +92,20 @@ void EffectMakerNodesModel::resetModel() endResetModel(); } -} // namespace EffectMaker +void EffectMakerNodesModel::updateCanBeAdded(const QStringList &uniforms) +{ + for (const EffectNodesCategory *cat : std::as_const(m_categories)) { + const QList nodes = cat->nodes(); + for (EffectNode *node : nodes) { + bool match = false; + for (const QString &uniform : uniforms) { + match = node->hasUniform(uniform); + if (match) + break; + } + node->setCanBeAdded(!match); + } + } +} +} // namespace EffectMaker diff --git a/src/plugins/effectmakernew/effectmakernodesmodel.h b/src/plugins/effectmakernew/effectmakernodesmodel.h index 28a4e8484f0..7320ca729fc 100644 --- a/src/plugins/effectmakernew/effectmakernodesmodel.h +++ b/src/plugins/effectmakernew/effectmakernodesmodel.h @@ -5,8 +5,6 @@ #include "effectnodescategory.h" -#include - #include namespace EffectMaker { @@ -32,12 +30,14 @@ public: QList categories() const { return m_categories; } + void updateCanBeAdded(const QStringList &uniforms); + private: - void findNodesPath(); + QString nodesSourcesPath() const; QList m_categories; - Utils::FilePath m_nodesPath; bool m_probeNodesDir = false; + bool m_modelLoaded = false; }; } // namespace EffectMaker diff --git a/src/plugins/effectmakernew/effectmakerplugin.cpp b/src/plugins/effectmakernew/effectmakerplugin.cpp index 7c8cc08ad2f..5d36035e2f6 100644 --- a/src/plugins/effectmakernew/effectmakerplugin.cpp +++ b/src/plugins/effectmakernew/effectmakerplugin.cpp @@ -3,6 +3,7 @@ #include "effectmakerview.h" +#include #include #include @@ -10,6 +11,14 @@ namespace EffectMaker { +static bool enableEffectMaker() +{ + Utils::QtcSettings *settings = Core::ICore::settings(); + const Utils::Key enableModelManagerKey = "QML/Designer/UseExperimentalFeatures44"; + + return settings->value(enableModelManagerKey, false).toBool(); +} + class EffectMakerPlugin final : public ExtensionSystem::IPlugin { Q_OBJECT @@ -17,11 +26,13 @@ class EffectMakerPlugin final : public ExtensionSystem::IPlugin bool delayedInitialize() final { - auto designerPlugin = QmlDesigner::QmlDesignerPlugin::instance(); - auto &viewManager = designerPlugin->viewManager(); - viewManager.registerView(std::make_unique( - QmlDesigner::QmlDesignerPlugin::externalDependenciesForPluginInitializationOnly())); + if (enableEffectMaker()) { + auto *designerPlugin = QmlDesigner::QmlDesignerPlugin::instance(); + auto &viewManager = designerPlugin->viewManager(); + viewManager.registerView(std::make_unique( + QmlDesigner::QmlDesignerPlugin::externalDependenciesForPluginInitializationOnly())); + } return true; } }; diff --git a/src/plugins/effectmakernew/effectmakeruniformsmodel.cpp b/src/plugins/effectmakernew/effectmakeruniformsmodel.cpp index cfbbf3f5778..c4f9796f835 100644 --- a/src/plugins/effectmakernew/effectmakeruniformsmodel.cpp +++ b/src/plugins/effectmakernew/effectmakeruniformsmodel.cpp @@ -26,6 +26,7 @@ QHash EffectMakerUniformsModel::roleNames() const roles[MinValueRole] = "uniformMinValue"; roles[MaxValueRole] = "uniformMaxValue"; roles[TypeRole] = "uniformType"; + roles[UseCustomValueRole] = "uniformUseCustomValue"; return roles; } diff --git a/src/plugins/effectmakernew/effectmakeruniformsmodel.h b/src/plugins/effectmakernew/effectmakeruniformsmodel.h index 9b9651a8720..8b83a63dfe3 100644 --- a/src/plugins/effectmakernew/effectmakeruniformsmodel.h +++ b/src/plugins/effectmakernew/effectmakeruniformsmodel.h @@ -37,6 +37,7 @@ private: MaxValueRole, MinValueRole, TypeRole, + UseCustomValueRole }; QList m_uniforms; diff --git a/src/plugins/effectmakernew/effectmakerview.cpp b/src/plugins/effectmakernew/effectmakerview.cpp index 637c12f6d5a..42624826943 100644 --- a/src/plugins/effectmakernew/effectmakerview.cpp +++ b/src/plugins/effectmakernew/effectmakerview.cpp @@ -7,16 +7,11 @@ #include "effectmakernodesmodel.h" #include "effectmakerwidget.h" -#include "nodeinstanceview.h" #include "qmldesignerconstants.h" -#include +#include -#include -#include -#include -#include -#include +#include namespace EffectMaker { @@ -51,6 +46,15 @@ QmlDesigner::WidgetInfo EffectMakerView::widgetInfo() if (m_widget.isNull()) { m_widget = new EffectMakerWidget{this}; + connect(m_widget->effectMakerModel(), &EffectMakerModel::assignToSelectedTriggered, this, + [&] (const QString &effectPath) { + executeInTransaction("EffectMakerView::widgetInfo", [&] { + const QList selectedNodes = selectedModelNodes(); + for (const QmlDesigner::ModelNode &node : selectedNodes) + QmlDesigner::ModelNodeOperations::handleItemLibraryEffectDrop(effectPath, node); + }); + }); + auto context = new EffectMakerContext(m_widget.data()); Core::ICore::addContextObject(context); } @@ -66,7 +70,7 @@ void EffectMakerView::customNotification([[maybe_unused]] const AbstractView *vi { if (identifier == "open_effectmaker_composition" && data.count() > 0) { const QString compositionPath = data[0].toString(); - m_widget->effectMakerModel()->openComposition(compositionPath); + m_widget->openComposition(compositionPath); } } @@ -85,4 +89,3 @@ void EffectMakerView::modelAboutToBeDetached(QmlDesigner::Model *model) } } // namespace EffectMaker - diff --git a/src/plugins/effectmakernew/effectmakerview.h b/src/plugins/effectmakernew/effectmakerview.h index 2bed1cfc103..297e5acc776 100644 --- a/src/plugins/effectmakernew/effectmakerview.h +++ b/src/plugins/effectmakernew/effectmakerview.h @@ -43,4 +43,3 @@ private: }; } // namespace EffectMaker - diff --git a/src/plugins/effectmakernew/effectmakerwidget.cpp b/src/plugins/effectmakernew/effectmakerwidget.cpp index 738c0a00168..5151c0568ca 100644 --- a/src/plugins/effectmakernew/effectmakerwidget.cpp +++ b/src/plugins/effectmakernew/effectmakerwidget.cpp @@ -7,26 +7,33 @@ #include "effectmakermodel.h" #include "effectmakernodesmodel.h" #include "effectmakerview.h" +#include "effectutils.h" #include "propertyhandler.h" //#include "qmldesigner/designercore/imagecache/midsizeimagecacheprovider.h" #include "qmldesignerconstants.h" #include "qmldesignerplugin.h" -#include "qqmlcontext.h" #include "theme.h" #include +#include #include #include #include +#include + #include +#include #include #include #include +#include #include +#include +#include namespace EffectMaker { @@ -55,6 +62,7 @@ EffectMakerWidget::EffectMakerWidget(EffectMakerView *view) m_quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); QmlDesigner::Theme::setupTheme(m_quickWidget->engine()); m_quickWidget->engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); + m_quickWidget->engine()->addImportPath(EffectUtils::nodesSourcesPath() + "/common"); m_quickWidget->setClearColor(QmlDesigner::Theme::getColor( QmlDesigner::Theme::Color::QmlDesigner_BackgroundColorDarkAlternate)); @@ -70,12 +78,38 @@ EffectMakerWidget::EffectMakerWidget(EffectMakerView *view) m_quickWidget->rootContext()->setContextProperty("g_propertyData", &g_propertyData); + QString blurPath = "file:" + EffectUtils::nodesSourcesPath() + "/common/"; + g_propertyData.insert(QString("blur_vs_path"), QString(blurPath + "bluritems.vert.qsb")); + g_propertyData.insert(QString("blur_fs_path"), QString(blurPath + "bluritems.frag.qsb")); + auto map = m_quickWidget->registerPropertyMap("EffectMakerBackend"); map->setProperties({{"effectMakerNodesModel", QVariant::fromValue(m_effectMakerNodesModel.data())}, {"effectMakerModel", QVariant::fromValue(m_effectMakerModel.data())}, {"rootView", QVariant::fromValue(this)}}); QmlDesigner::QmlDesignerPlugin::trackWidgetFocusTime( this, QmlDesigner::Constants::EVENT_NEWEFFECTMAKER_TIME); + + connect(m_effectMakerModel.data(), &EffectMakerModel::nodesChanged, this, [this]() { + m_effectMakerNodesModel->updateCanBeAdded(m_effectMakerModel->uniformNames()); + }); + + connect(m_effectMakerModel.data(), &EffectMakerModel::resourcesSaved, + this, [this](const QmlDesigner::TypeName &type, const Utils::FilePath &path) { + if (!m_importScan.timer) { + m_importScan.timer = new QTimer(this); + connect(m_importScan.timer, &QTimer::timeout, + this, &EffectMakerWidget::handleImportScanTimer); + } + + if (m_importScan.timer->isActive() && !m_importScan.future.isFinished()) + m_importScan.future.cancel(); + + m_importScan.counter = 0; + m_importScan.type = type; + m_importScan.path = path; + + m_importScan.timer->start(100); + }); } @@ -119,6 +153,20 @@ void EffectMakerWidget::focusSection(int section) Q_UNUSED(section) } +QRect EffectMakerWidget::screenRect() const +{ + if (m_quickWidget && m_quickWidget->screen()) + return m_quickWidget->screen()->availableGeometry(); + return {}; +} + +QPoint EffectMakerWidget::globalPos(const QPoint &point) const +{ + if (m_quickWidget) + return m_quickWidget->mapToGlobal(point); + return point; +} + QSize EffectMakerWidget::sizeHint() const { return {420, 420}; @@ -152,6 +200,21 @@ void EffectMakerWidget::initView() reloadQmlSource(); } +void EffectMakerWidget::openComposition(const QString &path) +{ + m_compositionPath = path; + + if (effectMakerModel()->hasUnsavedChanges()) + QMetaObject::invokeMethod(quickWidget()->rootObject(), "promptToSaveBeforeOpen"); + else + doOpenComposition(); +} + +void EffectMakerWidget::doOpenComposition() +{ + effectMakerModel()->openComposition(m_compositionPath); +} + void EffectMakerWidget::reloadQmlSource() { const QString effectMakerQmlPath = qmlSourcesPath() + "/EffectMaker.qml"; @@ -159,5 +222,65 @@ void EffectMakerWidget::reloadQmlSource() m_quickWidget->setSource(QUrl::fromLocalFile(effectMakerQmlPath)); } +void EffectMakerWidget::handleImportScanTimer() +{ + ++m_importScan.counter; + + if (m_importScan.counter == 1) { + // Rescan the effect import to update code model + auto modelManager = QmlJS::ModelManagerInterface::instance(); + if (modelManager) { + QmlJS::PathsAndLanguages pathToScan; + pathToScan.maybeInsert(m_importScan.path); + m_importScan.future = ::Utils::asyncRun(&QmlJS::ModelManagerInterface::importScan, + modelManager->workingCopy(), + pathToScan, modelManager, true, true, true); + } + } else if (m_importScan.counter < 100) { + // We have to wait a while to ensure qmljs detects new files and updates its + // internal model. Then we force amend on rewriter to trigger qmljs snapshot update. + if (m_importScan.future.isCanceled() || m_importScan.future.isFinished()) + m_importScan.counter = 100; // skip the timeout step + } else if (m_importScan.counter == 100) { + // Scanning is taking too long, abort + m_importScan.future.cancel(); + m_importScan.timer->stop(); + m_importScan.counter = 0; + } else if (m_importScan.counter == 101) { + if (m_effectMakerView->model() && m_effectMakerView->model()->rewriterView()) { + QmlDesigner::QmlDesignerPlugin::instance()->documentManager().resetPossibleImports(); + m_effectMakerView->model()->rewriterView()->forceAmend(); + } + } else if (m_importScan.counter == 102) { + if (m_effectMakerView->model()) { + // If type is in use, we have to reset puppet to update 2D view + if (!m_effectMakerView->allModelNodesOfType( + m_effectMakerView->model()->metaInfo(m_importScan.type)).isEmpty()) { + m_effectMakerView->resetPuppet(); + } + } + } else if (m_importScan.counter >= 103) { + // Refresh property view by resetting selection if any selected node is of updated type + if (m_effectMakerView->model() && m_effectMakerView->hasSelectedModelNodes()) { + const auto nodes = m_effectMakerView->selectedModelNodes(); + QmlDesigner::MetaInfoType metaType + = m_effectMakerView->model()->metaInfo(m_importScan.type).type(); + bool match = false; + for (const QmlDesigner::ModelNode &node : nodes) { + if (node.metaInfo().type() == metaType) { + match = true; + break; + } + } + if (match) { + m_effectMakerView->clearSelectedModelNodes(); + m_effectMakerView->setSelectedModelNodes(nodes); + } + } + m_importScan.timer->stop(); + m_importScan.counter = 0; + } +} + } // namespace EffectMaker diff --git a/src/plugins/effectmakernew/effectmakerwidget.h b/src/plugins/effectmakernew/effectmakerwidget.h index 24efac7b586..35f43f99b3e 100644 --- a/src/plugins/effectmakernew/effectmakerwidget.h +++ b/src/plugins/effectmakernew/effectmakerwidget.h @@ -9,9 +9,14 @@ #include #include +#include class StudioQuickWidget; +QT_BEGIN_NAMESPACE +class QTimer; +QT_END_NAMESPACE + namespace EffectMaker { class EffectMakerView; @@ -34,6 +39,7 @@ public: void delayedUpdateModel(); void updateModel(); void initView(); + void openComposition(const QString &path); StudioQuickWidget *quickWidget() const; QPointer effectMakerModel() const; @@ -41,6 +47,9 @@ public: Q_INVOKABLE void addEffectNode(const QString &nodeQenPath); Q_INVOKABLE void focusSection(int section); + Q_INVOKABLE void doOpenComposition(); + Q_INVOKABLE QRect screenRect() const; + Q_INVOKABLE QPoint globalPos(const QPoint &point) const; QSize sizeHint() const override; @@ -49,6 +58,7 @@ protected: private: void reloadQmlSource(); + void handleImportScanTimer(); QPointer m_effectMakerModel; QPointer m_effectMakerNodesModel; @@ -56,6 +66,17 @@ private: QPointer m_quickWidget; QmlDesigner::QmlModelNodeProxy m_backendModelNode; QmlDesigner::QmlAnchorBindingProxy m_backendAnchorBinding; + + struct ImportScanData { + QFuture future; + int counter = 0; + QTimer *timer = nullptr; + QmlDesigner::TypeName type; + Utils::FilePath path; + }; + + ImportScanData m_importScan; + QString m_compositionPath; }; } // namespace EffectMaker diff --git a/src/plugins/effectmakernew/effectnode.cpp b/src/plugins/effectmakernew/effectnode.cpp index 292c04d13e5..fc9d8ae7bfd 100644 --- a/src/plugins/effectmakernew/effectnode.cpp +++ b/src/plugins/effectmakernew/effectnode.cpp @@ -2,6 +2,8 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "effectnode.h" +#include "compositionnode.h" +#include "uniform.h" #include #include @@ -22,6 +24,12 @@ EffectNode::EffectNode(const QString &qenPath) iconPath = QStringLiteral("%1/%2").arg(parentDir.path(), "placeholder.svg"); } m_iconPath = QUrl::fromLocalFile(iconPath); + + CompositionNode node({}, qenPath); + const QList uniforms = node.uniforms(); + + for (const Uniform *uniform : uniforms) + m_uniformNames.insert(uniform->name()); } QString EffectNode::name() const @@ -39,5 +47,18 @@ QString EffectNode::qenPath() const return m_qenPath; } +void EffectNode::setCanBeAdded(bool enabled) +{ + if (enabled != m_canBeAdded) { + m_canBeAdded = enabled; + emit canBeAddedChanged(); + } +} + +bool EffectNode::hasUniform(const QString &name) +{ + return m_uniformNames.contains(name); +} + } // namespace EffectMaker diff --git a/src/plugins/effectmakernew/effectnode.h b/src/plugins/effectmakernew/effectnode.h index 5c457e2a6de..864904e088c 100644 --- a/src/plugins/effectmakernew/effectnode.h +++ b/src/plugins/effectmakernew/effectnode.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include namespace EffectMaker { @@ -16,6 +17,7 @@ class EffectNode : public QObject Q_PROPERTY(QString nodeDescription MEMBER m_description CONSTANT) Q_PROPERTY(QUrl nodeIcon MEMBER m_iconPath CONSTANT) Q_PROPERTY(QString nodeQenPath MEMBER m_qenPath CONSTANT) + Q_PROPERTY(bool canBeAdded MEMBER m_canBeAdded NOTIFY canBeAddedChanged) public: EffectNode(const QString &qenPath); @@ -24,11 +26,20 @@ public: QString description() const; QString qenPath() const; + void setCanBeAdded(bool enabled); + + bool hasUniform(const QString &name); + +signals: + void canBeAddedChanged(); + private: QString m_name; QString m_description; QString m_qenPath; QUrl m_iconPath; + bool m_canBeAdded = true; + QSet m_uniformNames; }; } // namespace EffectMaker diff --git a/src/plugins/effectmakernew/effectutils.cpp b/src/plugins/effectmakernew/effectutils.cpp index 8e2bb625431..a0159c520da 100644 --- a/src/plugins/effectmakernew/effectutils.cpp +++ b/src/plugins/effectmakernew/effectutils.cpp @@ -3,6 +3,8 @@ #include "effectutils.h" +#include + #include namespace EffectMaker { @@ -20,5 +22,14 @@ QString EffectUtils::codeFromJsonArray(const QJsonArray &codeArray) return codeString; } +QString EffectUtils::nodesSourcesPath() +{ +#ifdef SHARE_QML_PATH + if (Utils::qtcEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) + return QLatin1String(SHARE_QML_PATH) + "/effectMakerNodes"; +#endif + return Core::ICore::resourcePath("qmldesigner/effectMakerNodes").toString(); +} + } // namespace EffectMaker diff --git a/src/plugins/effectmakernew/effectutils.h b/src/plugins/effectmakernew/effectutils.h index e3de9312dce..eede7952c5c 100644 --- a/src/plugins/effectmakernew/effectutils.h +++ b/src/plugins/effectmakernew/effectutils.h @@ -15,6 +15,8 @@ public: EffectUtils() = delete; static QString codeFromJsonArray(const QJsonArray &codeArray); + + static QString nodesSourcesPath(); }; } // namespace EffectMaker diff --git a/src/plugins/effectmakernew/uniform.h b/src/plugins/effectmakernew/uniform.h index f5731af00a8..7216c6d9d6b 100644 --- a/src/plugins/effectmakernew/uniform.h +++ b/src/plugins/effectmakernew/uniform.h @@ -25,6 +25,8 @@ class Uniform : public QObject Q_PROPERTY(QVariant uniformBackendValue READ backendValue NOTIFY uniformBackendValueChanged) Q_PROPERTY(QVariant uniformMinValue MEMBER m_minValue CONSTANT) Q_PROPERTY(QVariant uniformMaxValue MEMBER m_maxValue CONSTANT) + Q_PROPERTY(QVariant uniformDefaultValue MEMBER m_defaultValue CONSTANT) + Q_PROPERTY(QVariant uniformUseCustomValue MEMBER m_useCustomValue CONSTANT) public: enum class Type diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 8f2b2d22187..e167a83db35 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -839,10 +839,12 @@ extend_qtc_plugin(QmlDesigner collectiondetailssortfiltermodel.cpp collectiondetailssortfiltermodel.h collectioneditorconstants.h collectioneditorutils.cpp collectioneditorutils.h + collectionimporttools.cpp collectionimporttools.h collectionlistmodel.cpp collectionlistmodel.h collectionsourcemodel.cpp collectionsourcemodel.h collectionview.cpp collectionview.h collectionwidget.cpp collectionwidget.h + datastoremodelnode.cpp datastoremodelnode.h ) extend_qtc_plugin(QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp index a141c696978..5c6ebf07c5d 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp @@ -13,19 +13,12 @@ #include "qmldesignerplugin.h" #include "theme.h" -#include -#include - #include #include #include #include -#include -#include -#include - #include #include #include @@ -192,12 +185,12 @@ QString AssetsLibraryWidget::getUniqueEffectPath(const QString &parentFolder, co return path; } -bool AssetsLibraryWidget::createNewEffect(const QString &effectPath, bool openEffectMaker) +bool AssetsLibraryWidget::createNewEffect(const QString &effectPath, bool openInEffectMaker) { bool created = QFile(effectPath).open(QIODevice::WriteOnly); - if (created && openEffectMaker) { - ModelNodeOperations::openEffectMaker(effectPath); + if (created && openInEffectMaker) { + openEffectMaker(effectPath); emit directoryCreated(QFileInfo(effectPath).absolutePath()); } @@ -367,22 +360,9 @@ QSet AssetsLibraryWidget::supportedAssetSuffixes(bool complex) return suffixes; } -bool isEffectMakerActivated() -{ - const QVector specs = ExtensionSystem::PluginManager::plugins(); - return std::find_if(specs.begin(), specs.end(), - [](ExtensionSystem::PluginSpec *spec) { - return spec->name() == "EffectMakerNew" && spec->isEffectivelyEnabled(); - }) - != specs.end(); -} - void AssetsLibraryWidget::openEffectMaker(const QString &filePath) { - if (isEffectMakerActivated()) - m_assetsView->emitCustomNotification("open_effectmaker_composition", {}, {filePath}); - else - ModelNodeOperations::openEffectMaker(filePath); + ModelNodeOperations::openEffectMaker(filePath); } QString AssetsLibraryWidget::qmlSourcesPath() diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h index 33ad100c0b1..4b3976ebae2 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h @@ -92,7 +92,7 @@ public: Q_INVOKABLE void updateContextMenuActionsEnableState(); Q_INVOKABLE QString getUniqueEffectPath(const QString &parentFolder, const QString &effectName); - Q_INVOKABLE bool createNewEffect(const QString &effectPath, bool openEffectMaker = true); + Q_INVOKABLE bool createNewEffect(const QString &effectPath, bool openInEffectMaker = true); Q_INVOKABLE bool canCreateEffects() const; diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.cpp index cd8296ca2de..98eef0daea2 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -21,6 +22,10 @@ struct CollectionProperty DataType type; }; +const QMap DataTypeWarning::dataTypeWarnings = { + {DataTypeWarning::CellDataTypeMismatch, "Cell and column data types do not match."} +}; + class CollectionDetails::Private { using SourceFormat = CollectionEditor::SourceFormat; @@ -258,7 +263,7 @@ bool CollectionDetails::setPropertyName(int column, const QString &value) return true; } -bool CollectionDetails::forcePropertyType(int column, DataType type, bool force) +bool CollectionDetails::setPropertyType(int column, DataType type) { if (!isValid() || !d->isValidColumnId(column)) return false; @@ -270,13 +275,11 @@ bool CollectionDetails::forcePropertyType(int column, DataType type, bool force) property.type = type; - if (force) { - for (QJsonObject &element : d->elements) { - if (element.contains(property.name)) { - QJsonValue value = element.value(property.name); - element.insert(property.name, valueToVariant(value, type).toJsonValue()); - changed = true; - } + for (QJsonObject &element : d->elements) { + if (element.contains(property.name)) { + QJsonValue value = element.value(property.name); + element.insert(property.name, valueToVariant(value, type).toJsonValue()); + changed = true; } } @@ -346,6 +349,13 @@ CollectionDetails::DataType CollectionDetails::typeAt(int row, int column) const return {}; } +DataTypeWarning::Warning CollectionDetails::cellWarningCheck(int row, int column) const +{ + if (typeAt(column) != typeAt(row, column) && !d->elements.at(row).isEmpty()) + return DataTypeWarning::Warning::CellDataTypeMismatch; + return DataTypeWarning::Warning::None; +} + bool CollectionDetails::containsPropertyName(const QString &propertyName) { if (!isValid()) @@ -395,6 +405,9 @@ void CollectionDetails::registerDeclarativeType() typedef CollectionDetails::DataType DataType; qRegisterMetaType("DataType"); qmlRegisterUncreatableType("CollectionDetails", 1, 0, "DataType", "Enum type"); + + qRegisterMetaType("Warning"); + qmlRegisterUncreatableType("CollectionDetails", 1, 0, "Warning", "Enum type"); } CollectionDetails &CollectionDetails::operator=(const CollectionDetails &other) @@ -438,16 +451,19 @@ void CollectionDetails::resetPropertyTypes() resetPropertyType(property); } -QJsonArray CollectionDetails::getJsonCollection() const +QString CollectionDetails::getCollectionAsJsonString() const { QJsonArray collectionArray; + for (const QJsonObject &element : std::as_const(d->elements)) collectionArray.push_back(element); - return collectionArray; + QString collectionString = QString::fromUtf8(QJsonDocument(collectionArray).toJson()); + + return collectionString; } -QString CollectionDetails::getCsvCollection() const +QString CollectionDetails::getCollectionAsCsvString() const { QString content; if (d->properties.count() <= 0) diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.h b/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.h index 33e5552884f..c35068ce6f0 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.h @@ -35,6 +35,26 @@ struct CollectionReference struct CollectionProperty; +struct DataTypeWarning { + Q_GADGET + +public: + enum Warning { None, CellDataTypeMismatch }; + Q_ENUM(Warning) + + Warning warning = None; + DataTypeWarning(Warning warning) + : warning(warning) + {} + + static QString getDataTypeWarningString(Warning warning) { + return dataTypeWarnings.value(warning); + } + +private: + static const QMap dataTypeWarnings; +}; + class CollectionDetails { Q_GADGET @@ -63,7 +83,7 @@ public: bool setPropertyValue(int row, int column, const QVariant &value); bool setPropertyName(int column, const QString &value); - bool forcePropertyType(int column, DataType type, bool force = false); + bool setPropertyType(int column, DataType type); CollectionReference reference() const; CollectionEditor::SourceFormat sourceFormat() const; @@ -71,6 +91,7 @@ public: QString propertyAt(int column) const; DataType typeAt(int column) const; DataType typeAt(int row, int column) const; + DataTypeWarning::Warning cellWarningCheck(int row, int column) const; bool containsPropertyName(const QString &propertyName); bool isValid() const; @@ -82,8 +103,8 @@ public: bool markSaved(); void swap(CollectionDetails &other); - QJsonArray getJsonCollection() const; - QString getCsvCollection() const; + QString getCollectionAsJsonString() const; + QString getCollectionAsCsvString() const; static void registerDeclarativeType(); diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.cpp index 2374726f92c..533c2d72b6c 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.cpp @@ -4,12 +4,13 @@ #include "collectiondetailsmodel.h" #include "collectioneditorconstants.h" +#include "collectioneditorutils.h" #include "modelnode.h" -#include "variantproperty.h" #include #include +#include #include #include #include @@ -101,6 +102,7 @@ QHash CollectionDetailsModel::roleNames() const roles.insert(SelectedRole, "itemSelected"); roles.insert(DataTypeRole, "dataType"); roles.insert(ColumnDataTypeRole, "columnType"); + roles.insert(DataTypeWarningRole, "dataTypeWarning"); } return roles; } @@ -132,6 +134,9 @@ QVariant CollectionDetailsModel::data(const QModelIndex &index, int role) const if (role == Qt::EditRole) return m_currentCollection.data(index.row(), index.column()); + if (role == DataTypeWarningRole ) + return QVariant::fromValue(m_currentCollection.cellWarningCheck(index.row(), index.column())); + return m_currentCollection.data(index.row(), index.column()).toString(); } @@ -141,11 +146,19 @@ bool CollectionDetailsModel::setData(const QModelIndex &index, const QVariant &v return {}; if (role == Qt::EditRole) { + DataTypeWarning::Warning prevWarning = m_currentCollection.cellWarningCheck(index.row(), index.column()); bool changed = m_currentCollection.setPropertyValue(index.row(), index.column(), value); + if (changed) { - emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole}); - return true; + QList roles = {Qt::DisplayRole, Qt::EditRole}; + + if (prevWarning != m_currentCollection.cellWarningCheck(index.row(), index.column())) + roles << DataTypeWarningRole; + + emit dataChanged(index, index, roles); } + + return true; } return false; @@ -222,15 +235,10 @@ Qt::ItemFlags CollectionDetailsModel::flags(const QModelIndex &index) const QVariant CollectionDetailsModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal) { - if (role == DataTypeRole) { + if (role == DataTypeRole) return CollectionDataTypeHelper::typeToString(m_currentCollection.typeAt(section)); - } else if (role == Qt::DisplayRole) { - return QString("%1 <%2>").arg(m_currentCollection.propertyAt(section), - CollectionDataTypeHelper::typeToString( - m_currentCollection.typeAt(section))); - } else { + else return m_currentCollection.propertyAt(section); - } } if (orientation == Qt::Vertical) @@ -323,20 +331,15 @@ bool CollectionDetailsModel::renameColumn(int section, const QString &newValue) return setHeaderData(section, Qt::Horizontal, newValue); } -bool CollectionDetailsModel::setPropertyType(int column, const QString &newValue, bool force) +bool CollectionDetailsModel::setPropertyType(int column, const QString &newValue) { - bool changed = m_currentCollection.forcePropertyType(column, - CollectionDataTypeHelper::typeFromString( - newValue), - force); + bool changed = m_currentCollection.setPropertyType(column, + CollectionDataTypeHelper::typeFromString( + newValue)); if (changed) { emit headerDataChanged(Qt::Horizontal, column, column); - - if (force) { - emit dataChanged(index(0, column), - index(rowCount() - 1, column), - {Qt::DisplayRole, DataTypeRole}); - } + emit dataChanged(index(0, column), index(rowCount() - 1, column), + {Qt::DisplayRole, DataTypeRole, DataTypeWarningRole}); } return changed; @@ -384,7 +387,7 @@ QStringList CollectionDetailsModel::typesList() void CollectionDetailsModel::loadCollection(const ModelNode &sourceNode, const QString &collection) { - QString fileName = sourceNode.variantProperty(CollectionEditor::SOURCEFILE_PROPERTY).value().toString(); + QString fileName = CollectionEditor::getSourceCollectionPath(sourceNode); CollectionReference newReference{sourceNode, collection}; bool alreadyOpen = m_openedCollections.contains(newReference); @@ -406,20 +409,14 @@ void CollectionDetailsModel::loadCollection(const ModelNode &sourceNode, const Q } } -bool CollectionDetailsModel::exportCollection(const QString &path, const QString &collectionName, const QString &exportType) +bool CollectionDetailsModel::saveCurrentCollection() { - QUrl url(path); - QString fileAddress = url.isLocalFile() ? url.toLocalFile() : path; + return saveCollection({}, &m_currentCollection); +} - if (exportType == "JSON") { - QJsonArray content = m_currentCollection.getJsonCollection(); - return saveCollectionAsJson(fileAddress, content, collectionName); - } else if (exportType == "CSV") { - QString content = m_currentCollection.getCsvCollection(); - return saveCollectionAsCsv(fileAddress, content); - } - - return false; +bool CollectionDetailsModel::exportCollection(const QString &filePath) +{ + return saveCollection(filePath, &m_currentCollection); } void CollectionDetailsModel::updateEmpty() @@ -596,36 +593,40 @@ void CollectionDetailsModel::setCollectionName(const QString &newCollectionName) } } -bool CollectionDetailsModel::saveCollectionAsJson(const QString &path, const QJsonArray &content, const QString &collectionName) +bool CollectionDetailsModel::saveCollection(const QString &filePath, CollectionDetails *collection) { - QFile sourceFile(path); - QJsonDocument document; + bool saved = false; - if (sourceFile.exists() && sourceFile.open(QFile::ReadWrite)) { - QJsonParseError jpe; - document = QJsonDocument::fromJson(sourceFile.readAll(), &jpe); + auto saveSingleCollection = [&](CollectionDetails &singleCollection) { - if (jpe.error == QJsonParseError::NoError) { - QJsonObject collectionMap = document.object(); - collectionMap[collectionName] = content; - document.setObject(collectionMap); + const ModelNode node = singleCollection.reference().node; + QString path = CollectionEditor::getSourceCollectionPath(node); + QString saveFormat = CollectionEditor::getSourceCollectionType(node); + + if (!filePath.isEmpty()) { + QUrl url(filePath); + path = url.isLocalFile() ? QFileInfo(url.toLocalFile()).absoluteFilePath() : url.toString(); + saveFormat = url.isLocalFile() ? QFileInfo(url.toLocalFile()).suffix().toLower() : saveFormat; } - sourceFile.resize(0); + saved = saveCollectionFromString(path, (saveFormat == "json") ? singleCollection.getCollectionAsJsonString() : + (saveFormat == "csv") ? singleCollection.getCollectionAsCsvString() : QString()); - } else if (sourceFile.open(QFile::WriteOnly)) { - QJsonObject collection; - collection[collectionName] = content; - document.setObject(collection); + if (saved && filePath.isEmpty()) + singleCollection.markSaved(); + }; + + if (!collection) { + for (CollectionDetails &openedCollection : m_openedCollections) + saveSingleCollection(openedCollection); + } else { + saveSingleCollection(*collection); } - if (sourceFile.write(document.toJson())) - return true; - - return false; + return saved; } -bool CollectionDetailsModel::saveCollectionAsCsv(const QString &path, const QString &content) +bool CollectionDetailsModel::saveCollectionFromString(const QString &path, const QString &content) { QFile file(path); @@ -635,4 +636,10 @@ bool CollectionDetailsModel::saveCollectionAsCsv(const QString &path, const QStr return false; } +QString CollectionDetailsModel::warningToString(DataTypeWarning::Warning warning) const +{ + return DataTypeWarning::getDataTypeWarningString(warning); +} + + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.h b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.h index 38c59d8fe92..bf87d3838f1 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.h @@ -22,8 +22,7 @@ class CollectionDetailsModel : public QAbstractTableModel Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) public: - enum DataRoles { SelectedRole = Qt::UserRole + 1, DataTypeRole, ColumnDataTypeRole }; - + enum DataRoles { SelectedRole = Qt::UserRole + 1, DataTypeRole, ColumnDataTypeRole, DataTypeWarningRole }; explicit CollectionDetailsModel(QObject *parent = nullptr); QHash roleNames() const override; @@ -55,15 +54,16 @@ public: Q_INVOKABLE bool addColumn(int column, const QString &name, const QString &propertyType = {}); Q_INVOKABLE bool selectColumn(int section); Q_INVOKABLE bool renameColumn(int section, const QString &newValue); - Q_INVOKABLE bool setPropertyType(int column, const QString &newValue, bool force = false); + Q_INVOKABLE bool setPropertyType(int column, const QString &newValue); Q_INVOKABLE bool selectRow(int row); Q_INVOKABLE void deselectAll(); - + Q_INVOKABLE QString warningToString(DataTypeWarning::Warning warning) const; static Q_INVOKABLE QStringList typesList(); void loadCollection(const ModelNode &sourceNode, const QString &collection); - Q_INVOKABLE bool exportCollection(const QString &path, const QString &collectionName, const QString &exportType); + Q_INVOKABLE bool saveCurrentCollection(); + Q_INVOKABLE bool exportCollection(const QString &filePath); signals: void collectionNameChanged(const QString &collectionName); @@ -81,8 +81,8 @@ private: void setCollectionName(const QString &newCollectionName); void loadJsonCollection(const QString &source, const QString &collection); void loadCsvCollection(const QString &source, const QString &collectionName); - bool saveCollectionAsJson(const QString &path, const QJsonArray &content, const QString &collectionName); - bool saveCollectionAsCsv(const QString &path, const QString &content); + bool saveCollection(const QString &filePath = {}, CollectionDetails *collection = nullptr); + bool saveCollectionFromString(const QString &path, const QString &content); QVariant variantFromString(const QString &value); QHash m_openedCollections; diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorconstants.h b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorconstants.h index 11ceb034fa5..e914891de34 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorconstants.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorconstants.h @@ -7,10 +7,14 @@ namespace QmlDesigner::CollectionEditor { enum class SourceFormat { Unknown, Json, Csv }; -inline constexpr char SOURCEFILE_PROPERTY[] = "source"; +inline constexpr char SOURCEFILE_PROPERTY[] = "source"; +inline constexpr char ALLMODELS_PROPERTY[] = "allModels"; +inline constexpr char JSONCHILDMODELNAME_PROPERTY[] = "modelName"; -inline constexpr char COLLECTIONMODEL_IMPORT[] = "QtQuick.Studio.Utils"; -inline constexpr char JSONCOLLECTIONMODEL_TYPENAME[] = "QtQuick.Studio.Utils.JsonListModel"; -inline constexpr char CSVCOLLECTIONMODEL_TYPENAME[] = "QtQuick.Studio.Utils.CsvTableModel"; +inline constexpr char COLLECTIONMODEL_IMPORT[] = "QtQuick.Studio.Utils"; +inline constexpr char JSONCOLLECTIONMODEL_TYPENAME[] = "QtQuick.Studio.Utils.JsonListModel"; +inline constexpr char CSVCOLLECTIONMODEL_TYPENAME[] = "QtQuick.Studio.Utils.CsvTableModel"; +inline constexpr char JSONCOLLECTIONCHILDMODEL_TYPENAME[] = "QtQuick.Studio.Utils.ChildListModel"; +inline constexpr char JSONBACKEND_TYPENAME[] = "JsonData"; } // namespace QmlDesigner::CollectionEditor diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.cpp index 1e47f6460f6..4eeca52964d 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.cpp @@ -7,12 +7,20 @@ #include "bindingproperty.h" #include "nodemetainfo.h" #include "propertymetainfo.h" +#include "variantproperty.h" #include +#include #include +#include +#include +#include + #include +#include +#include #include namespace { @@ -59,6 +67,43 @@ struct LessThanVisitor } }; +Utils::FilePath findFile(const Utils::FilePath &path, const QString &fileName) +{ + QDirIterator it(path.toString(), QDirIterator::Subdirectories); + + while (it.hasNext()) { + QFileInfo file(it.next()); + if (file.isDir()) + continue; + + if (file.fileName() == fileName) + return Utils::FilePath::fromFileInfo(file); + } + return {}; +} + +Utils::FilePath dataStoreDir() +{ + using Utils::FilePath; + ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectManager::startupProject(); + + if (!currentProject) + return {}; + + return currentProject->projectDirectory().pathAppended("/imports/" + + currentProject->displayName()); +} + +inline Utils::FilePath collectionPath(const QString &filePath) +{ + return dataStoreDir().pathAppended("/" + filePath); +} + +inline Utils::FilePath qmlDirFilePath() +{ + return collectionPath("qmldir"); +} + } // namespace namespace QmlDesigner::CollectionEditor { @@ -68,6 +113,18 @@ bool variantIslessThan(const QVariant &a, const QVariant &b, CollectionDetails:: return std::visit(LessThanVisitor{}, valueToVariant(a, type), valueToVariant(b, type)); } +SourceFormat getSourceCollectionFormat(const ModelNode &node) +{ + using namespace QmlDesigner; + if (node.type() == CollectionEditor::JSONCOLLECTIONMODEL_TYPENAME) + return CollectionEditor::SourceFormat::Json; + + if (node.type() == CollectionEditor::CSVCOLLECTIONMODEL_TYPENAME) + return CollectionEditor::SourceFormat::Csv; + + return CollectionEditor::SourceFormat::Unknown; +} + QString getSourceCollectionType(const ModelNode &node) { using namespace QmlDesigner; @@ -80,23 +137,43 @@ QString getSourceCollectionType(const ModelNode &node) return {}; } -void assignCollectionSourceToNode(AbstractView *view, - const ModelNode &modelNode, - const ModelNode &collectionSourceNode) +void assignCollectionToNode(AbstractView *view, + const ModelNode &modelNode, + const ModelNode &collectionSourceNode, + const QString &collectionName) { QTC_ASSERT(modelNode.isValid() && collectionSourceNode.isValid(), return); - if (collectionSourceNode.id().isEmpty() || !canAcceptCollectionAsModel(modelNode)) + QString sourceId = isDataStoreNode(collectionSourceNode) ? "DataStore" + : collectionSourceNode.id(); + + if (sourceId.isEmpty() || !canAcceptCollectionAsModel(modelNode)) + return; + + VariantProperty sourceProperty = collectionSourceNode.variantProperty(collectionName.toLatin1()); + if (!sourceProperty.exists()) return; BindingProperty modelProperty = modelNode.bindingProperty("model"); - view->executeInTransaction("CollectionEditor::assignCollectionSourceToNode", - [&modelProperty, &collectionSourceNode]() { - modelProperty.setExpression(collectionSourceNode.id()); + QString identifier = QString("%1.%2").arg(sourceId, QString::fromLatin1(sourceProperty.name())); + + view->executeInTransaction("CollectionEditor::assignCollectionToNode", + [&modelProperty, &identifier]() { + modelProperty.setExpression(identifier); }); } +Utils::FilePath dataStoreJsonFilePath() +{ + return collectionPath("models.json"); +} + +Utils::FilePath dataStoreQmlFilePath() +{ + return collectionPath("DataStore.qml"); +} + bool canAcceptCollectionAsModel(const ModelNode &node) { const NodeMetaInfo nodeMetaInfo = node.metaInfo(); @@ -111,4 +188,125 @@ bool canAcceptCollectionAsModel(const ModelNode &node) && modelProperty.propertyType().isVariant(); } +QString getSourceCollectionPath(const ModelNode &dataStoreNode) +{ + using Utils::FilePath; + if (!dataStoreNode.isValid()) + return {}; + + const FilePath expectedFile = dataStoreJsonFilePath(); + + if (expectedFile.exists()) + return expectedFile.toFSPathString(); + + return {}; +} + +bool isDataStoreNode(const ModelNode &dataStoreNode) +{ + using Utils::FilePath; + + if (!dataStoreNode.isValid()) + return false; + + const FilePath expectedFile = dataStoreQmlFilePath(); + + if (!expectedFile.exists()) + return false; + + FilePath modelPath = FilePath::fromUserInput(dataStoreNode.model()->fileUrl().toLocalFile()); + + return modelPath.isSameFile(expectedFile); +} + +QJsonArray defaultCollectionArray() +{ + QJsonObject initialObject; + QJsonArray initialCollection; + + initialObject.insert("Column1", ""); + initialCollection.append(initialObject); + return initialCollection; +} + +bool ensureDataStoreExists(bool &justCreated) +{ + using Utils::FilePath; + using Utils::FileReader; + using Utils::FileSaver; + + FilePath qmlDestinationPath = dataStoreQmlFilePath(); + justCreated = false; + + auto extractDependency = [&justCreated](const FilePath &filePath) -> bool { + if (filePath.exists()) + return true; + + const QString templateFileName = filePath.fileName() + u".tpl"; + const FilePath templatePath = findFile(Core::ICore::resourcePath(), templateFileName); + if (!templatePath.exists()) { + qWarning() << Q_FUNC_INFO << __LINE__ << templateFileName << "does not exist"; + return false; + } + + templatePath.copyFile(filePath); + if (filePath.exists()) { + justCreated = true; + return true; + } + + qWarning() << Q_FUNC_INFO << __LINE__ << "Cannot copy" << templateFileName << "to" << filePath; + return false; + }; + + if (!extractDependency(dataStoreJsonFilePath())) + return false; + + if (!extractDependency(collectionPath("data.json"))) + return false; + + if (!extractDependency(collectionPath("JsonData.qml"))) + return false; + + if (!qmlDestinationPath.exists()) { + if (qmlDestinationPath.ensureExistingFile()) { + justCreated = true; + } else { + qWarning() << Q_FUNC_INFO << __LINE__ << "Can't create DataStore Qml File"; + return false; + } + } + + FilePath qmlDirPath = qmlDirFilePath(); + qmlDirPath.ensureExistingFile(); + + FileReader qmlDirReader; + if (!qmlDirReader.fetch(qmlDirPath)) { + qWarning() << Q_FUNC_INFO << __LINE__ << "Can't read the content of the qmldir"; + return false; + } + + QByteArray qmlDirContent = qmlDirReader.data(); + const QList qmlDirLines = qmlDirContent.split('\n'); + for (const QByteArray &line : qmlDirLines) { + if (line.startsWith("singleton DataStore ")) + return true; + } + + if (!qmlDirContent.isEmpty() && qmlDirContent.back() != '\n') + qmlDirContent.append("\n"); + qmlDirContent.append("singleton DataStore 1.0 DataStore.qml\n"); + + FileSaver qmlDirSaver(qmlDirPath); + qmlDirSaver.write(qmlDirContent); + + if (qmlDirSaver.finalize()) { + justCreated = true; + return true; + } + + qWarning() << Q_FUNC_INFO << __LINE__ << "Can't write to the qmldir file"; + return false; +} + } // namespace QmlDesigner::CollectionEditor diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.h b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.h index 464e9e49689..835960f671e 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.h @@ -4,17 +4,41 @@ #pragma once #include "collectiondetails.h" +#include "collectioneditorconstants.h" + +QT_BEGIN_NAMESPACE +class QJsonArray; +QT_END_NAMESPACE + +namespace Utils { +class FilePath; +} namespace QmlDesigner::CollectionEditor { bool variantIslessThan(const QVariant &a, const QVariant &b, CollectionDetails::DataType type); +SourceFormat getSourceCollectionFormat(const QmlDesigner::ModelNode &node); + QString getSourceCollectionType(const QmlDesigner::ModelNode &node); -void assignCollectionSourceToNode(AbstractView *view, - const ModelNode &modelNode, - const ModelNode &collectionSourceNode = {}); +QString getSourceCollectionPath(const QmlDesigner::ModelNode &dataStoreNode); + +void assignCollectionToNode(AbstractView *view, + const ModelNode &modelNode, + const ModelNode &collectionSourceNode, + const QString &collectionName); + +Utils::FilePath dataStoreJsonFilePath(); + +Utils::FilePath dataStoreQmlFilePath(); + +bool isDataStoreNode(const ModelNode &dataStoreNode); + +bool ensureDataStoreExists(bool &justCreated); bool canAcceptCollectionAsModel(const ModelNode &node); +QJsonArray defaultCollectionArray(); + } // namespace QmlDesigner::CollectionEditor diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionimporttools.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionimporttools.cpp new file mode 100644 index 00000000000..183730873d6 --- /dev/null +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionimporttools.cpp @@ -0,0 +1,113 @@ +// 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 "collectionimporttools.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace QmlDesigner::CollectionEditor::ImportTools { + +QJsonArray loadAsSingleJsonCollection(const QUrl &url) +{ + QFile file(url.isLocalFile() ? url.toLocalFile() : url.toString()); + QJsonArray collection; + QByteArray jsonData; + if (file.open(QFile::ReadOnly)) + jsonData = file.readAll(); + + file.close(); + if (jsonData.isEmpty()) + return {}; + + QJsonParseError parseError; + QJsonDocument document = QJsonDocument::fromJson(jsonData, &parseError); + if (parseError.error != QJsonParseError::NoError) + return {}; + + auto refineJsonArray = [](const QJsonArray &array) -> QJsonArray { + QJsonArray resultArray; + for (const QJsonValue &collectionData : array) { + if (collectionData.isObject()) { + QJsonObject rowObject = collectionData.toObject(); + const QStringList rowKeys = rowObject.keys(); + for (const QString &key : rowKeys) { + QJsonValue cellValue = rowObject.value(key); + if (cellValue.isArray()) + rowObject.remove(key); + } + resultArray.push_back(rowObject); + } + } + return resultArray; + }; + + if (document.isArray()) { + collection = refineJsonArray(document.array()); + } else if (document.isObject()) { + QJsonObject documentObject = document.object(); + const QStringList mainKeys = documentObject.keys(); + + bool arrayFound = false; + for (const QString &key : mainKeys) { + const QJsonValue &value = documentObject.value(key); + if (value.isArray()) { + arrayFound = true; + collection = refineJsonArray(value.toArray()); + break; + } + } + + if (!arrayFound) { + QJsonObject singleObject; + for (const QString &key : mainKeys) { + const QJsonValue value = documentObject.value(key); + + if (!value.isObject()) + singleObject.insert(key, value); + } + collection.push_back(singleObject); + } + } + return collection; +} + +QJsonArray loadAsCsvCollection(const QUrl &url) +{ + QFile sourceFile(url.isLocalFile() ? url.toLocalFile() : url.toString()); + QStringList headers; + QJsonArray elements; + + if (sourceFile.open(QFile::ReadOnly)) { + QTextStream stream(&sourceFile); + + if (!stream.atEnd()) + headers = stream.readLine().split(','); + + for (QString &header : headers) + header = header.trimmed(); + + if (!headers.isEmpty()) { + while (!stream.atEnd()) { + const QStringList recordDataList = stream.readLine().split(','); + int column = -1; + QJsonObject recordData; + for (const QString &cellData : recordDataList) { + if (++column == headers.size()) + break; + recordData.insert(headers.at(column), cellData); + } + elements.append(recordData); + } + } + } + + return elements; +} + +} // namespace QmlDesigner::CollectionEditor::ImportTools diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionimporttools.h b/src/plugins/qmldesigner/components/collectioneditor/collectionimporttools.h new file mode 100644 index 00000000000..6ee4f590f76 --- /dev/null +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionimporttools.h @@ -0,0 +1,18 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +QT_BEGIN_NAMESPACE +class QJsonArray; +class QUrl; +QT_END_NAMESPACE + +namespace QmlDesigner::CollectionEditor::ImportTools { + +QJsonArray loadAsSingleJsonCollection(const QUrl &url); +QJsonArray loadAsCsvCollection(const QUrl &url); + +} // namespace QmlDesigner::CollectionEditor::ImportTools diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.cpp index bc0e1dc6662..d910569f0d2 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.cpp @@ -121,7 +121,7 @@ ModelNode CollectionListModel::sourceNode() const QString CollectionListModel::sourceAddress() const { - return m_sourceNode.variantProperty(CollectionEditor::SOURCEFILE_PROPERTY).value().toString(); + return CollectionEditor::getSourceCollectionPath(m_sourceNode); } bool CollectionListModel::contains(const QString &collectionName) const diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.cpp index 74cf8d41b43..4d7773ade33 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.cpp @@ -26,7 +26,7 @@ QSharedPointer loadCollection( QSharedPointer initialCollection = {}) { using namespace QmlDesigner::CollectionEditor; - QString sourceFileAddress = sourceNode.variantProperty(SOURCEFILE_PROPERTY).value().toString(); + QString sourceFileAddress = getSourceCollectionPath(sourceNode); QSharedPointer collectionsList; auto setupCollectionList = [&sourceNode, &initialCollection, &collectionsList]() { @@ -83,8 +83,8 @@ QVariant CollectionSourceModel::data(const QModelIndex &index, int role) const const ModelNode *collectionSource = &m_collectionSources.at(index.row()); switch (role) { - case NameRole: - return collectionSource->variantProperty("objectName").value(); + case NameRole: // Not used, to be removed + return collectionSource->variantProperty("objectName").value().toString(); case NodeRole: return QVariant::fromValue(*collectionSource); case CollectionTypeRole: @@ -268,6 +268,7 @@ bool CollectionSourceModel::collectionExists(const ModelNode &node, const QStrin bool CollectionSourceModel::addCollectionToSource(const ModelNode &node, const QString &collectionName, + const QJsonArray &newCollectionData, QString *errorString) { auto returnError = [errorString](const QString &msg) -> bool { @@ -284,11 +285,9 @@ bool CollectionSourceModel::addCollectionToSource(const ModelNode &node, return returnError(tr("Node should be a JSON model.")); if (collectionExists(node, collectionName)) - return returnError(tr("Model does not exist.")); + return returnError(tr("A model with the identical name already exists.")); - QString sourceFileAddress = node.variantProperty(CollectionEditor::SOURCEFILE_PROPERTY) - .value() - .toString(); + QString sourceFileAddress = CollectionEditor::getSourceCollectionPath(node); QFileInfo sourceFileInfo(sourceFileAddress); if (!sourceFileInfo.isFile()) @@ -307,7 +306,7 @@ bool CollectionSourceModel::addCollectionToSource(const ModelNode &node, if (document.isObject()) { QJsonObject sourceObject = document.object(); - sourceObject.insert(collectionName, QJsonArray{}); + sourceObject.insert(collectionName, newCollectionData); document.setObject(sourceObject); if (!jsonFile.resize(0)) return returnError(tr("Can't clean \"%1\".").arg(sourceFileInfo.absoluteFilePath())); @@ -395,11 +394,6 @@ void CollectionSourceModel::updateNodeSource(const ModelNode &node) updateCollectionList(index); } -QString CollectionSourceModel::selectedSourceAddress() const -{ - return index(m_selectedIndex).data(SourceRole).toString(); -} - void CollectionSourceModel::onSelectedCollectionChanged(int collectionIndex) { CollectionListModel *collectionList = qobject_cast(sender()); @@ -442,9 +436,7 @@ void CollectionSourceModel::onCollectionNameChanged(const QString &oldName, cons return; } - QString sourceFileAddress = node.variantProperty(CollectionEditor::SOURCEFILE_PROPERTY) - .value() - .toString(); + QString sourceFileAddress = CollectionEditor::getSourceCollectionPath(node); QFileInfo sourceFileInfo(sourceFileAddress); if (!sourceFileInfo.isFile()) { @@ -533,9 +525,7 @@ void CollectionSourceModel::onCollectionsRemoved(const QStringList &removedColle return; } - QString sourceFileAddress = node.variantProperty(CollectionEditor::SOURCEFILE_PROPERTY) - .value() - .toString(); + QString sourceFileAddress = CollectionEditor::getSourceCollectionPath(node); QFileInfo sourceFileInfo(sourceFileAddress); if (!sourceFileInfo.isFile()) { @@ -644,7 +634,8 @@ void CollectionSourceModel::updateCollectionList(QModelIndex index) QSharedPointer newList = loadCollection(sourceNode, currentList); if (currentList != newList) { m_collectionList.replace(index.row(), newList); - emit this->dataChanged(index, index, {CollectionsRole}); + emit dataChanged(index, index, {CollectionsRole}); + emit collectionNamesChanged(sourceNode, newList->stringList()); } } @@ -667,6 +658,9 @@ void CollectionSourceModel::registerCollection(const QSharedPointersourceNode()) + emit collectionNamesChanged(collection->sourceNode(), collection->stringList()); } QModelIndex CollectionSourceModel::indexOfNode(const ModelNode &node) const diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.h b/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.h index 39c82ec5b31..36226138c30 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.h @@ -54,6 +54,7 @@ public: bool collectionExists(const ModelNode &node, const QString &collectionName) const; bool addCollectionToSource(const ModelNode &node, const QString &collectionName, + const QJsonArray &newCollectionData, QString *errorString = nullptr); ModelNode sourceNodeAt(int idx); @@ -67,11 +68,10 @@ public: void updateNodeName(const ModelNode &node); void updateNodeSource(const ModelNode &node); - Q_INVOKABLE QString selectedSourceAddress() const; - signals: void selectedIndexChanged(int idx); void collectionSelected(const ModelNode &sourceNode, const QString &collectionName); + void collectionNamesChanged(const ModelNode &sourceNode, QStringList collections); void isEmptyChanged(bool); void warning(const QString &title, const QString &body); diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp index e3c8d26519c..9140f02ec53 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp @@ -8,12 +8,17 @@ #include "collectioneditorutils.h" #include "collectionsourcemodel.h" #include "collectionwidget.h" +#include "datastoremodelnode.h" #include "designmodecontext.h" #include "nodeabstractproperty.h" #include "nodemetainfo.h" #include "qmldesignerplugin.h" #include "variantproperty.h" +#include +#include +#include + #include #include #include @@ -23,6 +28,7 @@ #include namespace { + inline bool isStudioCollectionModel(const QmlDesigner::ModelNode &node) { using namespace QmlDesigner::CollectionEditor; @@ -36,7 +42,15 @@ namespace QmlDesigner { CollectionView::CollectionView(ExternalDependenciesInterface &externalDependencies) : AbstractView(externalDependencies) -{} + , m_dataStore(std::make_unique()) + +{ + connect(ProjectExplorer::ProjectManager::instance(), + &ProjectExplorer::ProjectManager::startupProjectChanged, + this, + &CollectionView::resetDataStoreNode); + resetDataStoreNode(); +} bool CollectionView::hasWidget() const { @@ -47,6 +61,7 @@ QmlDesigner::WidgetInfo CollectionView::widgetInfo() { if (m_widget.isNull()) { m_widget = new CollectionWidget(this); + m_widget->setMinimumSize(m_widget->minimumSizeHint()); auto collectionEditorContext = new Internal::CollectionEditorContext(m_widget.data()); Core::ICore::addContextObject(collectionEditorContext); @@ -58,6 +73,19 @@ QmlDesigner::WidgetInfo CollectionView::widgetInfo() [this](const ModelNode &sourceNode, const QString &collection) { m_widget->collectionDetailsModel()->loadCollection(sourceNode, collection); }); + + connect(sourceModel, &CollectionSourceModel::isEmptyChanged, this, [this](bool isEmpty) { + if (isEmpty) + m_widget->collectionDetailsModel()->loadCollection({}, {}); + }); + + connect(sourceModel, + &CollectionSourceModel::collectionNamesChanged, + this, + [this](const ModelNode &sourceNode, const QStringList &collectionNames) { + if (sourceNode == m_dataStore->modelNode()) + m_dataStore->setCollectionNames(collectionNames); + }); } return createWidgetInfo(m_widget.data(), @@ -71,7 +99,7 @@ QmlDesigner::WidgetInfo CollectionView::widgetInfo() void CollectionView::modelAttached(Model *model) { AbstractView::modelAttached(model); - refreshModel(); + resetDataStoreNode(); } void CollectionView::nodeReparented(const ModelNode &node, @@ -147,7 +175,15 @@ void CollectionView::addResource(const QUrl &url, const QString &name, const QSt { executeInTransaction(Q_FUNC_INFO, [this, &url, &name, &type]() { ensureStudioModelImport(); - QString sourceAddress = url.isLocalFile() ? url.toLocalFile() : url.toString(); + QString sourceAddress; + if (url.isLocalFile()) { + Utils::FilePath fp = QmlDesignerPlugin::instance()->currentDesignDocument()->fileName().parentDir(); + sourceAddress = Utils::FilePath::calcRelativePath(url.toLocalFile(), + fp.absoluteFilePath().toString()); + } else { + sourceAddress = url.toString(); + } + const NodeMetaInfo resourceMetaInfo = type.compare("json", Qt::CaseInsensitive) == 0 ? jsonCollectionMetaInfo() : csvCollectionMetaInfo(); @@ -170,16 +206,28 @@ void CollectionView::registerDeclarativeType() CollectionJsonSourceFilterModel::registerDeclarativeType(); } +void CollectionView::resetDataStoreNode() +{ + m_dataStore->reloadModel(); + refreshModel(); +} + +ModelNode CollectionView::dataStoreNode() const +{ + return m_dataStore->modelNode(); +} + void CollectionView::refreshModel() { if (!model()) return; // Load Model Groups - const ModelNodes collectionSourceNodes = rootModelNode().subModelNodesOfType( - jsonCollectionMetaInfo()) - + rootModelNode().subModelNodesOfType( - csvCollectionMetaInfo()); + ModelNodes collectionSourceNodes; + + if (ModelNode dataStore = m_dataStore->modelNode()) + collectionSourceNodes << dataStore; + m_widget->sourceModel()->setSources(collectionSourceNodes); } diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionview.h b/src/plugins/qmldesigner/components/collectioneditor/collectionview.h index 994105f9745..dd946776ed1 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionview.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionview.h @@ -4,13 +4,13 @@ #pragma once #include "abstractview.h" +#include "datastoremodelnode.h" #include "modelnode.h" -#include - namespace QmlDesigner { class CollectionWidget; +class DataStoreModelNode; class CollectionView : public AbstractView { @@ -45,6 +45,9 @@ public: static void registerDeclarativeType(); + void resetDataStoreNode(); + ModelNode dataStoreNode() const; + private: void refreshModel(); NodeMetaInfo jsonCollectionMetaInfo() const; @@ -52,5 +55,6 @@ private: void ensureStudioModelImport(); QPointer m_widget; + std::unique_ptr m_dataStore; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp index 8f6168dfb2d..30ae4418ed6 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp @@ -6,6 +6,7 @@ #include "collectiondetailsmodel.h" #include "collectiondetailssortfiltermodel.h" #include "collectioneditorutils.h" +#include "collectionimporttools.h" #include "collectionsourcemodel.h" #include "collectionview.h" #include "qmldesignerconstants.h" @@ -39,21 +40,6 @@ QString collectionViewResourcesPath() return Core::ICore::resourcePath("qmldesigner/collectionEditorQmlSource").toString(); } -QString urlToLocalPath(const QUrl &url) -{ - QString localPath; - - if (url.isLocalFile()) - localPath = url.toLocalFile(); - - if (url.scheme() == QLatin1String("qrc")) { - const QString &path = url.path(); - localPath = QStringLiteral(":") + path; - } - - return localPath; -} - QString getPreferredCollectionName(const QUrl &url, const QString &collectionName) { if (collectionName.isEmpty()) { @@ -162,32 +148,27 @@ QSize CollectionWidget::minimumSizeHint() const return {300, 400}; } -bool CollectionWidget::loadJsonFile(const QString &jsonFileAddress, const QString &collectionName) +bool CollectionWidget::loadJsonFile(const QUrl &url, const QString &collectionName) { - if (!isJsonFile(jsonFileAddress)) + if (!isJsonFile(url)) return false; - m_view->addResource(jsonFileAddress, - getPreferredCollectionName(jsonFileAddress, collectionName), - "json"); + m_view->addResource(url, getPreferredCollectionName(url, collectionName), "json"); return true; } -bool CollectionWidget::loadCsvFile(const QString &csvFileAddress, const QString &collectionName) +bool CollectionWidget::loadCsvFile(const QUrl &url, const QString &collectionName) { - m_view->addResource(csvFileAddress, - getPreferredCollectionName(csvFileAddress, collectionName), - "csv"); + m_view->addResource(url, getPreferredCollectionName(url, collectionName), "csv"); return true; } -bool CollectionWidget::isJsonFile(const QString &jsonFileAddress) const +bool CollectionWidget::isJsonFile(const QUrl &url) const { - QUrl jsonUrl(jsonFileAddress); - QString fileAddress = jsonUrl.isLocalFile() ? jsonUrl.toLocalFile() : jsonUrl.toString(); - QFile file(fileAddress); + QString filePath = url.isLocalFile() ? url.toLocalFile() : url.toString(); + QFile file(filePath); if (!file.exists() || !file.open(QFile::ReadOnly)) return false; @@ -200,33 +181,42 @@ bool CollectionWidget::isJsonFile(const QString &jsonFileAddress) const return true; } -bool CollectionWidget::isCsvFile(const QString &csvFilePath) const +bool CollectionWidget::isCsvFile(const QUrl &url) const { - QUrl csvUrl(csvFilePath); - QString filePath = csvUrl.isLocalFile() ? csvUrl.toLocalFile() : csvUrl.toString(); + QString filePath = url.isLocalFile() ? url.toLocalFile() : url.toString(); QFile file(filePath); return file.exists() && file.fileName().endsWith(".csv"); } +bool CollectionWidget::isValidUrlToImport(const QUrl &url) const +{ + using Utils::FilePath; + FilePath fileInfo = FilePath::fromUserInput(url.isLocalFile() ? url.toLocalFile() + : url.toString()); + if (fileInfo.suffix() == "json") + return isJsonFile(url); + + if (fileInfo.suffix() == "csv") + return isCsvFile(url); + + return false; +} + bool CollectionWidget::addCollection(const QString &collectionName, const QString &collectionType, - const QString &sourceAddress, + const QUrl &sourceUrl, const QVariant &sourceNode) { const ModelNode node = sourceNode.value(); bool isNewCollection = !node.isValid(); if (isNewCollection) { - QString sourcePath = ::urlToLocalPath(sourceAddress); + QString sourcePath = sourceUrl.isLocalFile() ? sourceUrl.toLocalFile() : sourceUrl.toString(); + if (collectionType == "json") { QJsonObject jsonObject; - QJsonObject initialObject; - QJsonArray initialCollection; - - initialObject.insert("Column1", ""); - initialCollection.append(initialObject); - jsonObject.insert(collectionName, initialCollection); + jsonObject.insert(collectionName, CollectionEditor::defaultCollectionArray()); QFile sourceFile(sourcePath); if (!sourceFile.open(QFile::WriteOnly)) { @@ -268,7 +258,10 @@ bool CollectionWidget::addCollection(const QString &collectionName, } } else if (collectionType == "json") { QString errorMsg; - bool added = m_sourceModel->addCollectionToSource(node, collectionName, &errorMsg); + bool added = m_sourceModel->addCollectionToSource(node, + collectionName, + CollectionEditor::defaultCollectionArray(), + &errorMsg); if (!added) warn(tr("Can not add a model to the JSON file"), errorMsg); return added; @@ -277,20 +270,97 @@ bool CollectionWidget::addCollection(const QString &collectionName, return false; } -void CollectionWidget::assignSourceNodeToSelectedItem(const QVariant &sourceNode) +bool CollectionWidget::importToJson(const QVariant &sourceNode, + const QString &collectionName, + const QUrl &url) { - ModelNode sourceModel = sourceNode.value(); + using CollectionEditor::SourceFormat; + using Utils::FilePath; + const ModelNode node = sourceNode.value(); + const SourceFormat nodeFormat = CollectionEditor::getSourceCollectionFormat(node); + QTC_ASSERT(node.isValid() && nodeFormat == SourceFormat::Json, return false); + + FilePath fileInfo = FilePath::fromUserInput(url.isLocalFile() ? url.toLocalFile() + : url.toString()); + bool added = false; + QString errorMsg; + QJsonArray loadedCollection; + + if (fileInfo.suffix() == "json") + loadedCollection = CollectionEditor::ImportTools::loadAsSingleJsonCollection(url); + else if (fileInfo.suffix() == "csv") + loadedCollection = CollectionEditor::ImportTools::loadAsCsvCollection(url); + + if (!loadedCollection.isEmpty()) { + const QString newCollectionName = generateUniqueCollectionName(node, collectionName); + added = m_sourceModel->addCollectionToSource(node, newCollectionName, loadedCollection, &errorMsg); + } else { + errorMsg = tr("The imported model is empty or is not supported."); + } + + if (!added) + warn(tr("Can not add a model to the JSON file"), errorMsg); + return added; +} + +bool CollectionWidget::importCollectionToDataStore(const QString &collectionName, const QUrl &url) +{ + using Utils::FilePath; + const ModelNode node = dataStoreNode(); + if (node.isValid()) + return importToJson(QVariant::fromValue(node), collectionName, url); + + warn(tr("Can not import to the main model"), tr("The data store is not available.")); + return false; +} + +bool CollectionWidget::addCollectionToDataStore(const QString &collectionName) +{ + ensureDataStoreExists(); + const ModelNode node = dataStoreNode(); + if (!node.isValid()) { + warn(tr("Can not import to the main model"), tr("The default model node is not available.")); + return false; + } + + QString errorMsg; + bool added = m_sourceModel->addCollectionToSource(node, + generateUniqueCollectionName(node, + collectionName), + CollectionEditor::defaultCollectionArray(), + &errorMsg); + if (!added) + warn(tr("Failed to add a model to the default model group"), errorMsg); + + return added; +} + +void CollectionWidget::assignCollectionToSelectedNode(const QString collectionName) +{ + ModelNode dsNode = dataStoreNode(); ModelNode targetNode = m_view->singleSelectedModelNode(); - QTC_ASSERT(sourceModel.isValid() && targetNode.isValid(), return); + QTC_ASSERT(dsNode.isValid() && targetNode.isValid(), return); - if (sourceModel.id().isEmpty()) { - warn(tr("Assigning the model group"), - tr("The model group must have a valid id to be assigned.")); + if (dsNode.id().isEmpty()) { + warn(tr("Assigning the model"), tr("The model must have a valid id to be assigned.")); return; } - CollectionEditor::assignCollectionSourceToNode(m_view, targetNode, sourceModel); + CollectionEditor::assignCollectionToNode(m_view, targetNode, dsNode, collectionName); +} + +void CollectionWidget::ensureDataStoreExists() +{ + bool filesJustCreated = false; + bool filesExist = CollectionEditor::ensureDataStoreExists(filesJustCreated); + if (filesExist && filesJustCreated) + m_view->resetDataStoreNode(); +} + +ModelNode CollectionWidget::dataStoreNode() const +{ + return m_view->dataStoreNode(); } void CollectionWidget::warn(const QString &title, const QString &body) @@ -310,4 +380,20 @@ void CollectionWidget::setTargetNodeSelected(bool selected) emit targetNodeSelectedChanged(m_targetNodeSelected); } +QString CollectionWidget::generateUniqueCollectionName(const ModelNode &node, const QString &name) +{ + if (!m_sourceModel->collectionExists(node, name)) + return name; + + static QRegularExpression reg("^(?[\\w\\d\\.\\_\\-]+)\\_(?\\d+)$"); + QRegularExpressionMatch match = reg.match(name); + if (match.hasMatch()) { + int nextNumber = match.captured("number").toInt() + 1; + return generateUniqueCollectionName( + node, QString("%1_%2").arg(match.captured("mainName")).arg(nextNumber)); + } else { + return generateUniqueCollectionName(node, QString("%1_1").arg(name)); + } +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h index 5bf728660cb..2be98df1901 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h @@ -34,16 +34,29 @@ public: virtual QSize minimumSizeHint() const; - Q_INVOKABLE bool loadJsonFile(const QString &jsonFileAddress, const QString &collectionName = {}); - Q_INVOKABLE bool loadCsvFile(const QString &csvFileAddress, const QString &collectionName = {}); - Q_INVOKABLE bool isJsonFile(const QString &jsonFileAddress) const; - Q_INVOKABLE bool isCsvFile(const QString &csvFileAddress) const; + Q_INVOKABLE bool loadJsonFile(const QUrl &url, const QString &collectionName = {}); + Q_INVOKABLE bool loadCsvFile(const QUrl &url, const QString &collectionName = {}); + Q_INVOKABLE bool isJsonFile(const QUrl &url) const; + Q_INVOKABLE bool isCsvFile(const QUrl &url) const; + Q_INVOKABLE bool isValidUrlToImport(const QUrl &url) const; Q_INVOKABLE bool addCollection(const QString &collectionName, const QString &collectionType, - const QString &sourceAddress, + const QUrl &sourceUrl, const QVariant &sourceNode); - Q_INVOKABLE void assignSourceNodeToSelectedItem(const QVariant &sourceNode); + Q_INVOKABLE bool importToJson(const QVariant &sourceNode, + const QString &collectionName, + const QUrl &url); + + Q_INVOKABLE bool importCollectionToDataStore(const QString &collectionName, const QUrl &url); + + Q_INVOKABLE bool addCollectionToDataStore(const QString &collectionName); + + Q_INVOKABLE void assignCollectionToSelectedNode(const QString collectionName); + + Q_INVOKABLE void ensureDataStoreExists(); + + Q_INVOKABLE ModelNode dataStoreNode() const; void warn(const QString &title, const QString &body); void setTargetNodeSelected(bool selected); @@ -52,6 +65,8 @@ signals: void targetNodeSelectedChanged(bool); private: + QString generateUniqueCollectionName(const ModelNode &node, const QString &name); + QPointer m_view; QPointer m_sourceModel; QPointer m_collectionDetailsModel; diff --git a/src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.cpp b/src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.cpp new file mode 100644 index 00000000000..446d7ef08fc --- /dev/null +++ b/src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.cpp @@ -0,0 +1,198 @@ +// 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 "datastoremodelnode.h" + +#include "collectioneditorconstants.h" +#include "collectioneditorutils.h" +#include "model/qmltextgenerator.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +namespace { + +QmlDesigner::PropertyNameList createNameList(const QmlDesigner::ModelNode &node) +{ + using QmlDesigner::AbstractProperty; + using QmlDesigner::PropertyName; + using QmlDesigner::PropertyNameList; + static PropertyNameList defaultsNodeProps = {"id", + QmlDesigner::CollectionEditor::SOURCEFILE_PROPERTY, + QmlDesigner::CollectionEditor::JSONCHILDMODELNAME_PROPERTY, + "backend"}; + PropertyNameList dynamicPropertyNames = Utils::transform( + node.dynamicProperties(), + [](const AbstractProperty &property) -> PropertyName { return property.name(); }); + + Utils::sort(dynamicPropertyNames); + + return defaultsNodeProps + dynamicPropertyNames; +} + +} // namespace + +namespace QmlDesigner { + +DataStoreModelNode::DataStoreModelNode() +{ + reloadModel(); +} + +void DataStoreModelNode::reloadModel() +{ + using Utils::FilePath; + if (!ProjectExplorer::ProjectManager::startupProject()) { + reset(); + return; + } + bool forceUpdate = false; + + const FilePath dataStoreQmlPath = CollectionEditor::dataStoreQmlFilePath(); + const FilePath dataStoreJsonPath = CollectionEditor::dataStoreJsonFilePath(); + QUrl dataStoreQmlUrl = dataStoreQmlPath.toUrl(); + + if (dataStoreQmlPath.exists() && dataStoreJsonPath.exists()) { + if (!m_model.get() || m_model->fileUrl() != dataStoreQmlUrl) { + m_model = Model::create(CollectionEditor::JSONCOLLECTIONMODEL_TYPENAME, 1, 1); + forceUpdate = true; + Import import = Import::createLibraryImport(CollectionEditor::COLLECTIONMODEL_IMPORT); + try { + if (!m_model->hasImport(import, true, true)) + m_model->changeImports({import}, {}); + } catch (const Exception &) { + QTC_ASSERT(false, return); + } + } + } else { + reset(); + } + + QTC_ASSERT(m_model.get(), return); + m_model->setFileUrl(dataStoreQmlUrl); + + m_dataRelativePath = dataStoreJsonPath.relativePathFrom(dataStoreQmlPath).toFSPathString(); + + if (forceUpdate) { + updateDataStoreProperties(); + updateSingletonFile(); + } +} + +QStringList DataStoreModelNode::collectionNames() const +{ + return m_collectionNames; +} + +Model *DataStoreModelNode::model() const +{ + return m_model.get(); +} + +ModelNode DataStoreModelNode::modelNode() const +{ + QTC_ASSERT(m_model.get(), return {}); + return m_model->rootModelNode(); +} + +QString DataStoreModelNode::getModelQmlText() +{ + ModelNode node = modelNode(); + QTC_ASSERT(node, return {}); + + Internal::QmlTextGenerator textGen(createNameList(node), + QmlJSTools::QmlJSToolsSettings::globalCodeStyle()->tabSettings()); + + QString genText = textGen(node); + return genText; +} + +void DataStoreModelNode::reset() +{ + if (m_model) + m_model.reset(); + + m_dataRelativePath.clear(); + setCollectionNames({}); +} + +void DataStoreModelNode::updateDataStoreProperties() +{ + QTC_ASSERT(model(), return); + + ModelNode rootNode = modelNode(); + QTC_ASSERT(rootNode.isValid(), return); + + static TypeName childNodeTypename = "ChildListModel"; + + const QList formerPropertyNames = rootNode.dynamicProperties(); + for (const AbstractProperty &property : formerPropertyNames) + rootNode.removeProperty(property.name()); + + rootNode.setIdWithoutRefactoring("models"); + + for (const QString &collectionName : std::as_const(m_collectionNames)) { + PropertyName newName = collectionName.toLatin1(); + + ModelNode collectionNode = model()->createModelNode(childNodeTypename); + + VariantProperty modelNameProperty = collectionNode.variantProperty( + CollectionEditor::JSONCHILDMODELNAME_PROPERTY); + modelNameProperty.setValue(newName); + + NodeProperty nodeProp = rootNode.nodeProperty(newName); + nodeProp.setDynamicTypeNameAndsetModelNode(childNodeTypename, collectionNode); + } + + // Backend Property + ModelNode backendNode = model()->createModelNode(CollectionEditor::JSONBACKEND_TYPENAME); + NodeProperty backendProperty = rootNode.nodeProperty("backend"); + backendProperty.setDynamicTypeNameAndsetModelNode(CollectionEditor::JSONBACKEND_TYPENAME, + backendNode); + // Source Property + VariantProperty sourceProp = rootNode.variantProperty(CollectionEditor::SOURCEFILE_PROPERTY); + sourceProp.setValue(m_dataRelativePath); +} + +void DataStoreModelNode::updateSingletonFile() +{ + using Utils::FilePath; + using Utils::FileSaver; + QTC_ASSERT(m_model.get(), return); + + const QString pragmaSingleTone = "pragma Singleton\n"; + QString imports; + + for (const Import &import : m_model->imports()) + imports += QStringLiteral("import %1\n").arg(import.toString(true)); + + QString content = pragmaSingleTone + imports + getModelQmlText(); + QUrl modelUrl = m_model->fileUrl(); + FileSaver file(FilePath::fromUserInput(modelUrl.isLocalFile() ? modelUrl.toLocalFile() + : modelUrl.toString())); + file.write(content.toLatin1()); + file.finalize(); +} + +void DataStoreModelNode::setCollectionNames(const QStringList &newCollectionNames) +{ + if (m_collectionNames != newCollectionNames) { + m_collectionNames = newCollectionNames; + updateDataStoreProperties(); + updateSingletonFile(); + } +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.h b/src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.h new file mode 100644 index 00000000000..e76d7f50e4a --- /dev/null +++ b/src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.h @@ -0,0 +1,37 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +namespace QmlDesigner { + +class Model; + +class DataStoreModelNode +{ +public: + DataStoreModelNode(); + + void reloadModel(); + QStringList collectionNames() const; + + Model *model() const; + ModelNode modelNode() const; + + void setCollectionNames(const QStringList &newCollectionNames); + +private: + QString getModelQmlText(); + + void reset(); + void updateDataStoreProperties(); + void updateSingletonFile(); + + ModelPointer m_model; + QStringList m_collectionNames; + QString m_dataRelativePath; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/changestyleaction.cpp b/src/plugins/qmldesigner/components/componentcore/changestyleaction.cpp index aa386929400..2bf06c1a1a6 100644 --- a/src/plugins/qmldesigner/components/componentcore/changestyleaction.cpp +++ b/src/plugins/qmldesigner/components/componentcore/changestyleaction.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "changestyleaction.h" -#include "designermcumanager.h" #include #include @@ -47,6 +46,17 @@ static QString styleConfigFileName(const QString &qmlFileName) return QString(); } +static bool isQtForMCUs() +{ + if (ProjectExplorer::ProjectManager::startupTarget()) { + const QmlProjectManager::QmlBuildSystem *buildSystem = qobject_cast( + ProjectExplorer::ProjectManager::startupTarget()->buildSystem()); + if (buildSystem) + return buildSystem->qtForMCUs(); + } + return false; +} + ChangeStyleWidgetAction::ChangeStyleWidgetAction(QObject *parent) : QWidgetAction(parent) { items = getAllStyleItems(); @@ -78,7 +88,7 @@ QList ChangeStyleWidgetAction::getAllStyleItems() if (Utils::HostOsInfo::isWindowsHost()) items.append({"Windows", "Windows", {}}); - if (DesignerMcuManager::instance().isMCUProject()) + if (isQtForMCUs()) items.append({"MCUDefaultStyle", "MCUDefaultStyle", {}}); //what if we have a custom style set in .conf? @@ -178,7 +188,7 @@ QWidget *ChangeStyleWidgetAction::createWidget(QWidget *parent) comboBox->setDisabled(true); comboBox->setToolTip(tr(disbledTooltip)); comboBox->setCurrentIndex(0); - } else if (DesignerMcuManager::instance().isMCUProject()) { + } else if (isQtForMCUs()) { comboBox->setDisabled(true); comboBox->setEditText(style); } else { diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp index ffa51710f6f..79ff364c403 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp @@ -43,6 +43,9 @@ #include #include +#include +#include + #include #include @@ -1620,7 +1623,28 @@ void updateImported3DAsset(const SelectionContext &selectionContext) } } +bool isNewEffectMakerActivated() +{ + const QVector specs = ExtensionSystem::PluginManager::plugins(); + return std::find_if(specs.begin(), specs.end(), + [](ExtensionSystem::PluginSpec *spec) { + return spec->name() == "EffectMakerNew" && spec->isEffectivelyEnabled(); + }) + != specs.end(); +} + void openEffectMaker(const QString &filePath) +{ + if (ModelNodeOperations::isNewEffectMakerActivated()) { + QmlDesignerPlugin::instance()->viewManager() + .emitCustomNotification("open_effectmaker_composition", {}, {filePath}); + QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("Effect Maker", true); + } else { + ModelNodeOperations::openOldEffectMaker(filePath); + } +} + +void openOldEffectMaker(const QString &filePath) { const ProjectExplorer::Target *target = ProjectExplorer::ProjectTree::currentTarget(); if (!target) { diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h index 7d7a985283a..dec37f0f9ef 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h @@ -127,14 +127,17 @@ void updateImported3DAsset(const SelectionContext &selectionContext); QMLDESIGNERCOMPONENTS_EXPORT Utils::FilePath getEffectsImportDirectory(); QMLDESIGNERCOMPONENTS_EXPORT QString getEffectsDefaultDirectory(const QString &defaultDir = {}); void openEffectMaker(const QString &filePath); +void openOldEffectMaker(const QString &filePath); QString getEffectIcon(const QString &effectPath); bool useLayerEffect(); bool validateEffect(const QString &effectPath); +bool isNewEffectMakerActivated(); Utils::FilePath getImagesDefaultDirectory(); //Item Library and Assets related drop operations -ModelNode handleItemLibraryEffectDrop(const QString &effectPath, const ModelNode &targetNode); +QMLDESIGNERCOMPONENTS_EXPORT ModelNode handleItemLibraryEffectDrop(const QString &effectPath, + const ModelNode &targetNode); void handleTextureDrop(const QMimeData *mimeData, const ModelNode &targetModelNode); void handleMaterialDrop(const QMimeData *mimeData, const ModelNode &targetNode); ModelNode handleItemLibraryImageDrop(const QString &imagePath, diff --git a/src/plugins/qmldesigner/components/componentcore/theme.h b/src/plugins/qmldesigner/components/componentcore/theme.h index c18f5a9b91d..3ffc41c3c61 100644 --- a/src/plugins/qmldesigner/components/componentcore/theme.h +++ b/src/plugins/qmldesigner/components/componentcore/theme.h @@ -87,6 +87,8 @@ public: centerHorizontal, centerVertical, cleanLogs_medium, + clearList_large, + clearList_medium, closeCross, closeFile_large, closeLink, @@ -288,7 +290,9 @@ public: s_snapping, s_timeline, s_visibility, + saveAs_medium, saveLogs_medium, + save_medium, scale_medium, search, search_small, diff --git a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp index 9ba4bfd26b1..05d6f5fdf0c 100644 --- a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,8 @@ #include #include +#include + #include #include @@ -39,6 +42,14 @@ namespace QmlDesigner { +static bool enableModelEditor() +{ + Utils::QtcSettings *settings = Core::ICore::settings(); + const Utils::Key enableModelManagerKey = "QML/Designer/UseExperimentalFeatures44"; + + return settings->value(enableModelManagerKey, false).toBool(); +} + static Q_LOGGING_CATEGORY(viewBenchmark, "qtc.viewmanager.attach", QtWarningMsg) class ViewManagerData @@ -203,8 +214,10 @@ QList ViewManager::standardViews() const &d->materialBrowserView, &d->textureEditorView, &d->statesEditorView, - &d->designerActionManagerView, - &d->collectionView}; + &d->designerActionManagerView}; + + if (enableModelEditor()) + list.append(&d->collectionView); if (QmlDesignerPlugin::instance() ->settings() @@ -212,12 +225,8 @@ QList ViewManager::standardViews() const .toBool()) list.append(&d->debugView); -#ifdef CHECK_LICENSE - if (checkLicense() == FoundLicense::enterprise) + if (checkEnterpriseLicense()) list.append(&d->contentLibraryView); -#else - list.append(&d->contentLibraryView); -#endif return list; } @@ -386,14 +395,11 @@ QList ViewManager::widgetInfos() const widgetInfoList.append(d->materialBrowserView.widgetInfo()); widgetInfoList.append(d->textureEditorView.widgetInfo()); widgetInfoList.append(d->statesEditorView.widgetInfo()); - widgetInfoList.append(d->collectionView.widgetInfo()); + if (enableModelEditor()) + widgetInfoList.append(d->collectionView.widgetInfo()); -#ifdef CHECK_LICENSE - if (checkLicense() == FoundLicense::enterprise) + if (checkEnterpriseLicense()) widgetInfoList.append(d->contentLibraryView.widgetInfo()); -#else - widgetInfoList.append(d->contentLibraryView.widgetInfo()); -#endif if (d->debugView.hasWidget()) widgetInfoList.append(d->debugView.widgetInfo()); @@ -452,6 +458,12 @@ const AbstractView *ViewManager::view() const return &d->nodeInstanceView; } +void ViewManager::emitCustomNotification(const QString &identifier, const QList &nodeList, + const QList &data) +{ + d->nodeInstanceView.emitCustomNotification(identifier, nodeList, data); +} + QWidgetAction *ViewManager::componentViewAction() const { return d->componentView.action(); diff --git a/src/plugins/qmldesigner/components/componentcore/viewmanager.h b/src/plugins/qmldesigner/components/componentcore/viewmanager.h index 935ff54cfdb..3cabf5109ec 100644 --- a/src/plugins/qmldesigner/components/componentcore/viewmanager.h +++ b/src/plugins/qmldesigner/components/componentcore/viewmanager.h @@ -72,6 +72,8 @@ public: void nextFileIsCalledInternally(); const AbstractView *view() const; + void emitCustomNotification(const QString &identifier, const QList &nodeList, + const QList &data); void exportAsImage(); QImage takeFormEditorScreenshot(); diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp index 0193b5d5ea9..e25f14411e7 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -387,8 +388,7 @@ void ConnectionModel::addConnection(const PropertyName &signalName) || QmlVisualNode(selectedNode).isFlowTransition()) source = selectedNode.validId() + ".trigger()"; - if (!connectionView()->selectedModelNodes().constFirst().id().isEmpty()) - newNode.bindingProperty("target").setExpression(selectedNode.validId()); + newNode.bindingProperty("target").setExpression(selectedNode.validId()); } else { rootModelNode .nodeAbstractProperty(rootModelNode.metaInfo().defaultPropertyName()) @@ -859,6 +859,11 @@ 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(); diff --git a/src/plugins/qmldesigner/components/curveeditor/curveeditorview.cpp b/src/plugins/qmldesigner/components/curveeditor/curveeditorview.cpp index 86ee900aa2c..8b7f3062eb5 100644 --- a/src/plugins/qmldesigner/components/curveeditor/curveeditorview.cpp +++ b/src/plugins/qmldesigner/components/curveeditor/curveeditorview.cpp @@ -307,10 +307,20 @@ void CurveEditorView::commitKeyframes(TreeItem *item) auto replaceKeyframes = [&group, pitem, this] { m_block = true; - for (auto& frame : group.keyframes()) - frame.destroy(); - AnimationCurve curve = pitem->curve(); + + unsigned int i = 0; + const size_t numberOfKeyFrames = curve.keyframes().size(); + for (auto &frame : group.keyframes()) { + if (i < numberOfKeyFrames) { + QPointF pos = curve.keyframes().at(i).position(); + frame.variantProperty("frame").setValue(pos.x()); + } else { + frame.destroy(); + } + i++; + } + if (curve.valueType() == AnimationCurve::ValueType::Bool) { for (const auto& frame : curve.keyframes()) { QPointF pos = frame.position(); diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index b56986e79df..ff5852c6312 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -8,7 +8,6 @@ #include "edit3dcanvas.h" #include "edit3dtoolbarmenu.h" #include "edit3dview.h" -#include "edit3dviewconfig.h" #include "externaldependenciesinterface.h" #include "materialutils.h" #include "metainfo.h" @@ -312,8 +311,8 @@ void Edit3DWidget::createContextMenu() auto addOverrideMenuAction = [&](const QString &label, const QString &toolTip, MaterialOverrideType type) { - QAction *action = overridesSubMenu->addAction( - label, this, &Edit3DWidget::onMatOverrideAction); + QAction *action = overridesSubMenu->addAction(label); + connect(action, &QAction::triggered, this, [this, action] { onMatOverrideAction(action); }); action->setData(int(type)); action->setCheckable(true); action->setToolTip(toolTip); @@ -457,11 +456,8 @@ void Edit3DWidget::updateCreateSubMenu(const QList &entriesL m_createSubMenu->addMenu(catMenu); } - QAction *action = catMenu->addAction( - getEntryIcon(entry), - entry.name(), - this, - &Edit3DWidget::onCreateAction); + QAction *action = catMenu->addAction(getEntryIcon(entry), entry.name()); + connect(action, &QAction::triggered, this, [this, action] { onCreateAction(action); }); action->setData(entry.name()); m_nameToEntry.insert(entry.name(), entry); } @@ -469,10 +465,9 @@ void Edit3DWidget::updateCreateSubMenu(const QList &entriesL } // Action triggered from the "create" sub-menu -void Edit3DWidget::onCreateAction() +void Edit3DWidget::onCreateAction(QAction *action) { - QAction *action = qobject_cast(sender()); - if (!action || !m_view || !m_view->model() || isSceneLocked()) + if (!m_view || !m_view->model() || isSceneLocked()) return; m_view->executeInTransaction(__FUNCTION__, [&] { @@ -499,10 +494,9 @@ void Edit3DWidget::onCreateAction() }); } -void Edit3DWidget::onMatOverrideAction() +void Edit3DWidget::onMatOverrideAction(QAction *action) { - QAction *action = qobject_cast(sender()); - if (!action || !m_view || !m_view->model()) + if (!m_view || !m_view->model()) return; QVariantList list; @@ -522,17 +516,16 @@ void Edit3DWidget::onMatOverrideAction() void Edit3DWidget::onWireframeAction() { - QAction *action = qobject_cast(sender()); - if (!action || !m_view || !m_view->model()) + if (!m_view || !m_view->model()) return; QVariantList list; for (int i = 0; i < m_view->splitToolStates().size(); ++i) { Edit3DView::SplitToolState state = m_view->splitToolStates()[i]; if (i == m_view->activeSplit()) { - state.showWireframe = action->isChecked(); + state.showWireframe = m_wireFrameAction->isChecked(); m_view->setSplitToolState(i, state); - list.append(action->isChecked()); + list.append(m_wireFrameAction->isChecked()); } else { list.append(state.showWireframe); } diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h index 67e23b0b98c..0c9c807473b 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h @@ -53,8 +53,8 @@ public: void updateCreateSubMenu(const QList &entriesList); private slots: - void onCreateAction(); - void onMatOverrideAction(); + void onCreateAction(QAction *action); + void onMatOverrideAction(QAction *action); void onWireframeAction(); void onResetAllOverridesAction(); diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp index 7703f352b0e..b710a8226f2 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp @@ -251,7 +251,11 @@ void ItemLibraryWidget::handleAddImport(int index) imports.append(dependencyImport); } imports.append(import); - model->changeImports(imports, {}); + try { + model->changeImports(imports, {}); + } catch (const Exception &e) { + e.showException(); + } switchToComponentsView(); updateSearch(); diff --git a/src/plugins/qmldesigner/components/navigator/iconcheckboxitemdelegate.cpp b/src/plugins/qmldesigner/components/navigator/iconcheckboxitemdelegate.cpp index c24aa1933bf..5b36bee7f97 100644 --- a/src/plugins/qmldesigner/components/navigator/iconcheckboxitemdelegate.cpp +++ b/src/plugins/qmldesigner/components/navigator/iconcheckboxitemdelegate.cpp @@ -83,12 +83,16 @@ void IconCheckboxItemDelegate::paint(QPainter *painter, QTC_ASSERT(window, return); const QSize iconSize(16, 16); - const QPoint iconPosition(styleOption.rect.left() + (styleOption.rect.width() - iconSize.width()) / 2, - styleOption.rect.top() + 2 + delegateMargin); + QPoint iconPosition(styleOption.rect.left() + (styleOption.rect.width() - iconSize.width()) / 2, + styleOption.rect.top() + 2 + delegateMargin); const QIcon::State state = isChecked(modelIndex) ? QIcon::State::On : QIcon::State::Off; const QPixmap iconPixmap = m_icon.pixmap(window, iconSize, mode, state); + // Shift the lock icon (last column) slightly to the left due to vertical scrollbar width + if (modelIndex.column() == NavigatorTreeModel::ColumnType::Lock) + iconPosition.rx() -= 4; + painter->save(); if (isThisOrAncestorLocked(modelIndex)) diff --git a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp index a4a7ead0426..a0c5ebacd26 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp @@ -128,10 +128,22 @@ void NavigatorView::modelAttached(Model *model) QTreeView *treeView = treeWidget(); - treeView->header()->setSectionResizeMode(NavigatorTreeModel::ColumnType::Name, QHeaderView::Stretch); - treeView->header()->resizeSection(NavigatorTreeModel::ColumnType::Alias, 26); - treeView->header()->resizeSection(NavigatorTreeModel::ColumnType::Visibility, 26); - treeView->header()->resizeSection(NavigatorTreeModel::ColumnType::Lock, 26); + treeView->header()->setSectionResizeMode(NavigatorTreeModel::ColumnType::Name, + QHeaderView::Stretch); + treeView->header()->setSectionResizeMode(NavigatorTreeModel::ColumnType::Alias, + QHeaderView::Fixed); + treeView->header()->setSectionResizeMode(NavigatorTreeModel::ColumnType::Visibility, + QHeaderView::Fixed); + treeView->header()->setSectionResizeMode(NavigatorTreeModel::ColumnType::Lock, QHeaderView::Fixed); + + treeView->header()->setStretchLastSection(false); + treeView->header()->setMinimumSectionSize(24); + treeView->header()->setDefaultSectionSize(24); + + treeView->header()->resizeSection(NavigatorTreeModel::ColumnType::Alias, 24); + treeView->header()->resizeSection(NavigatorTreeModel::ColumnType::Visibility, 24); + // Make last column a bit wider to compensate the shift to the left due to vertical scrollbar + treeView->header()->resizeSection(NavigatorTreeModel::ColumnType::Lock, 32); treeView->setIndentation(20); m_currentModelInterface->setFilter(false); @@ -760,7 +772,7 @@ void NavigatorView::setupWidget() connect(m_widget.data(), &NavigatorWidget::textFilterChanged, this, &NavigatorView::textFilterChanged); const QString fontName = "qtds_propertyIconFont.ttf"; - const QSize size = QSize(28, 28); + const QSize size = QSize(32, 32); const QString visibilityOnUnicode = Theme::getIconUnicode(Theme::Icon::visibilityOn); const QString visibilityOffUnicode = Theme::getIconUnicode(Theme::Icon::visibilityOff); diff --git a/src/plugins/qmldesigner/components/propertyeditor/colorpalettebackend.cpp b/src/plugins/qmldesigner/components/propertyeditor/colorpalettebackend.cpp index 5f15f351110..fc5b9c6ebb3 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/colorpalettebackend.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/colorpalettebackend.cpp @@ -3,13 +3,14 @@ #include "colorpalettebackend.h" -#include -#include -#include +#include #include -#include +#include +#include #include +#include +#include namespace QmlDesigner { @@ -205,17 +206,22 @@ void ColorPaletteBackend::showDialog(QColor color) void ColorPaletteBackend::eyeDropper() { - QWindow *window = QGuiApplication::focusWindow(); - if (!window) + QWidget *widget = Core::ICore::mainWindow(); + if (!widget) return; + m_eyeDropperActive = true; + emit eyeDropperActiveChanged(); + if (!m_colorPickingEventFilter) m_colorPickingEventFilter = new QColorPickingEventFilter(this); - window->installEventFilter(m_colorPickingEventFilter); - window->setMouseGrabEnabled(true); - window->setKeyboardGrabEnabled(true); - + widget->installEventFilter(m_colorPickingEventFilter); +#ifndef QT_NO_CURSOR + widget->grabMouse(/*Qt::CrossCursor*/); +#else + widget->grabMouse(); +#endif #ifdef Q_OS_WIN32 // excludes WinRT // On Windows mouse tracking doesn't work over other processes's windows updateTimer->start(30); @@ -224,6 +230,14 @@ void ColorPaletteBackend::eyeDropper() // and loose focus. dummyTransparentWindow.show(); #endif + widget->grabKeyboard(); + /* With setMouseTracking(true) the desired color can be more precisely picked up, + * and continuously pushing the mouse button is not necessary. + */ + widget->setMouseTracking(true); + + QGuiApplication::setOverrideCursor(QCursor()); + updateEyeDropperPosition(QCursor::pos()); } @@ -245,7 +259,12 @@ QImage ColorPaletteBackend::grabScreenRect(const QPoint &p) if (!screen) screen = QGuiApplication::primaryScreen(); - const QPixmap pixmap = screen->grabWindow(0, p.x(), p.y(), g_screenGrabWidth, g_screenGrabHeight); + const QRect screenRect = screen->geometry(); + const QPixmap pixmap = screen->grabWindow(0, + p.x() - screenRect.x(), + p.y() - screenRect.y(), + g_screenGrabWidth, + g_screenGrabHeight); return pixmap.toImage(); } @@ -273,8 +292,8 @@ void ColorPaletteBackend::updateEyeDropperPosition(const QPoint &globalPos) void ColorPaletteBackend::updateCursor(const QImage &image) { - QWindow *window = QGuiApplication::focusWindow(); - if (!window) + QWidget *widget = Core::ICore::mainWindow(); + if (!widget) return; QPixmap pixmap(QSize(g_cursorWidth, g_cursorHeight)); @@ -307,24 +326,28 @@ void ColorPaletteBackend::updateCursor(const QImage &image) painter.drawRect(centerRect); painter.end(); - QCursor cursor(pixmap); - window->setCursor(cursor); + QGuiApplication::changeOverrideCursor(QCursor(pixmap)); } void ColorPaletteBackend::releaseEyeDropper() { - QWindow *window = QGuiApplication::focusWindow(); - if (!window) + QWidget *widget = Core::ICore::mainWindow(); + if (!widget) return; - window->removeEventFilter(m_colorPickingEventFilter); - window->setMouseGrabEnabled(false); + m_eyeDropperActive = false; + emit eyeDropperActiveChanged(); + + widget->removeEventFilter(m_colorPickingEventFilter); + widget->releaseMouse(); #ifdef Q_OS_WIN32 updateTimer->stop(); dummyTransparentWindow.setVisible(false); #endif - window->setKeyboardGrabEnabled(false); - window->unsetCursor(); + widget->releaseKeyboard(); + widget->setMouseTracking(false); + + QGuiApplication::restoreOverrideCursor(); } bool ColorPaletteBackend::handleEyeDropperMouseMove(QMouseEvent *e) @@ -360,4 +383,9 @@ bool ColorPaletteBackend::handleEyeDropperKeyPress(QKeyEvent *e) return true; } +bool ColorPaletteBackend::eyeDropperActive() const +{ + return m_eyeDropperActive; +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/colorpalettebackend.h b/src/plugins/qmldesigner/components/propertyeditor/colorpalettebackend.h index 152a8a6d1e7..9ab60d7c9e6 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/colorpalettebackend.h +++ b/src/plugins/qmldesigner/components/propertyeditor/colorpalettebackend.h @@ -71,6 +71,7 @@ class ColorPaletteBackend : public QObject Q_PROPERTY(QStringList palettes READ palettes NOTIFY palettesChanged) + Q_PROPERTY(bool eyeDropperActive READ eyeDropperActive NOTIFY eyeDropperActiveChanged) public: ~ColorPaletteBackend(); @@ -82,7 +83,6 @@ public: void removeColor(int id, const QString &palette); Q_INVOKABLE void addRecentColor(const QString &color); - Q_INVOKABLE void addFavoriteColor(const QString &color); Q_INVOKABLE void removeFavoriteColor(int id); @@ -100,7 +100,6 @@ public: Q_INVOKABLE void showDialog(QColor color); - Q_INVOKABLE void eyeDropper(); QColor grabScreenColor(const QPoint &p); @@ -116,6 +115,7 @@ public: bool handleEyeDropperMouseButtonRelease(QMouseEvent *e); bool handleEyeDropperKeyPress(QKeyEvent *e); + bool eyeDropperActive() const; ColorPaletteBackend(const ColorPaletteBackend &) = delete; void operator=(const ColorPaletteBackend &) = delete; @@ -129,6 +129,7 @@ signals: void currentColorChanged(const QColor &color); void eyeDropperRejected(); + void eyeDropperActiveChanged(); private: ColorPaletteBackend(); @@ -140,6 +141,7 @@ private: QHash m_data; QColorPickingEventFilter *m_colorPickingEventFilter; + bool m_eyeDropperActive = false; #ifdef Q_OS_WIN32 QTimer *updateTimer; QWindow dummyTransparentWindow; diff --git a/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp b/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp index aae0867c915..3c419a3e82d 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp @@ -158,6 +158,13 @@ QString FileResourcesModel::resolve(const QString &relative) const if (!QUrl::fromUserInput(relative, m_docPath.path()).isLocalFile()) return relative; + const QUrl relUrl(relative); + if (relUrl.isLocalFile()) { + QString localFile = relUrl.toLocalFile(); + if (QDir::isAbsolutePath(localFile)) + return localFile; + } + return QFileInfo(m_docPath, relative).absoluteFilePath(); } diff --git a/src/plugins/qmldesigner/components/resources/dockwidgets.css b/src/plugins/qmldesigner/components/resources/dockwidgets.css index 2fc05b6014f..88ccadc4873 100644 --- a/src/plugins/qmldesigner/components/resources/dockwidgets.css +++ b/src/plugins/qmldesigner/components/resources/dockwidgets.css @@ -286,7 +286,8 @@ ADS--AutoHideDockContainer ADS--DockAreaWidget[focused="true"] ADS--DockAreaTitl /* AutoHideDockContainer titlebar buttons */ #dockAreaAutoHideButton { - /*qproperty-icon: url(:/ads/images/vs-pin-button.svg);*/ + /*qproperty-icon: url(:/ads/images/vs-pin-button.svg), + url(:/ads/images/vs-pin-button-disabled.svg) disabled;*/ qproperty-iconSize: 16px; } @@ -295,6 +296,11 @@ ADS--AutoHideDockContainer #dockAreaAutoHideButton { qproperty-iconSize: 16px; } +ADS--AutoHideDockContainer #dockAreaMinimizeButton { + /*qproperty-icon: url(:/ads/images/minimize-button-focused.svg);*/ + qproperty-iconSize: 16px; +} + ADS--AutoHideDockContainer #dockAreaCloseButton{ /*qproperty-icon: url(:/ads/images/close-button-focused.svg)*/ } diff --git a/src/plugins/qmldesigner/components/texteditor/texteditorwidget.cpp b/src/plugins/qmldesigner/components/texteditor/texteditorwidget.cpp index 93976f10d9e..97ef6c4b689 100644 --- a/src/plugins/qmldesigner/components/texteditor/texteditorwidget.cpp +++ b/src/plugins/qmldesigner/components/texteditor/texteditorwidget.cpp @@ -202,6 +202,10 @@ void TextEditorWidget::setBlockCursorSelectionSynchronisation(bool b) bool TextEditorWidget::eventFilter(QObject *, QEvent *event) { + //do not call the eventfilter when the m_textEditor is gone + if (!TextEditor::TextEditorWidget::fromEditor(m_textEditor.get())) + return false; + static std::vector overrideKeys = { Qt::Key_Delete, Qt::Key_Backspace, Qt::Key_Insert, Qt::Key_Escape }; diff --git a/src/plugins/qmldesigner/components/toolbar/toolbarbackend.cpp b/src/plugins/qmldesigner/components/toolbar/toolbarbackend.cpp index e680a892bf4..ff0fa2ab9f4 100644 --- a/src/plugins/qmldesigner/components/toolbar/toolbarbackend.cpp +++ b/src/plugins/qmldesigner/components/toolbar/toolbarbackend.cpp @@ -353,8 +353,9 @@ ToolBarBackend::ToolBarBackend(QObject *parent) [this](ProjectExplorer::Project *project) { disconnect(m_kitConnection); emit isQt6Changed(); - emit isMCUsChanged(); emit projectOpenedChanged(); + emit stylesChanged(); + emit isMCUsChanged(); if (project) { m_kitConnection = connect(project, &ProjectExplorer::Project::activeTargetChanged, @@ -503,7 +504,7 @@ void ToolBarBackend::setCurrentStyle(int index) const QList items = ChangeStyleWidgetAction::getAllStyleItems(); QTC_ASSERT(items.size() > index, return); - QTC_ASSERT(index > 0, return ); + QTC_ASSERT(index >= 0, return ); QTC_ASSERT(currentDesignDocument(), return ); @@ -513,7 +514,7 @@ void ToolBarBackend::setCurrentStyle(int index) const QString qmlFile = view->model()->fileUrl().toLocalFile(); - ChangeStyleWidgetAction::changeCurrentStyle(item.styleName, qmlFile); + ChangeStyleWidgetAction::changeCurrentStyle(item.displayName, qmlFile); view->resetPuppet(); } diff --git a/src/plugins/qmldesigner/components/toolbar/toolbarbackend.h b/src/plugins/qmldesigner/components/toolbar/toolbarbackend.h index ce8dd62b524..307704d63a0 100644 --- a/src/plugins/qmldesigner/components/toolbar/toolbarbackend.h +++ b/src/plugins/qmldesigner/components/toolbar/toolbarbackend.h @@ -84,7 +84,7 @@ class ToolBarBackend : public QObject Q_PROPERTY(int documentIndex READ documentIndex NOTIFY documentIndexChanged) Q_PROPERTY(QString currentWorkspace READ currentWorkspace NOTIFY currentWorkspaceChanged) Q_PROPERTY(bool lockWorkspace READ lockWorkspace WRITE setLockWorkspace NOTIFY lockWorkspaceChanged) - Q_PROPERTY(QStringList styles READ styles CONSTANT) + Q_PROPERTY(QStringList styles READ styles NOTIFY stylesChanged) Q_PROPERTY(bool isInDesignMode READ isInDesignMode NOTIFY isInDesignModeChanged) Q_PROPERTY(bool isInEditMode READ isInEditMode NOTIFY isInEditModeChanged) Q_PROPERTY(bool isInSessionMode READ isInSessionMode NOTIFY isInSessionModeChanged) @@ -151,6 +151,7 @@ signals: void documentIndexChanged(); void currentWorkspaceChanged(); void lockWorkspaceChanged(); + void stylesChanged(); void isInDesignModeChanged(); void isInEditModeChanged(); void isInSessionModeChanged(); diff --git a/src/plugins/qmldesigner/designercore/include/import.h b/src/plugins/qmldesigner/designercore/include/import.h index 741c5ae54da..cde683a105d 100644 --- a/src/plugins/qmldesigner/designercore/include/import.h +++ b/src/plugins/qmldesigner/designercore/include/import.h @@ -5,9 +5,10 @@ #include +#include +#include #include #include -#include #include "qmldesignercorelib_global.h" @@ -91,6 +92,12 @@ public: return std::tie(first.m_url, first.m_type) < std::tie(second.m_url, second.m_type); } + friend QDebug operator<<(QDebug debug, const Import &import) + { + debug << import.toString(); + return debug; + } + private: Import(const QString &url, const QString &version, diff --git a/src/plugins/qmldesigner/designmodewidget.cpp b/src/plugins/qmldesigner/designmodewidget.cpp index d88e51e4e54..a8567dec380 100644 --- a/src/plugins/qmldesigner/designmodewidget.cpp +++ b/src/plugins/qmldesigner/designmodewidget.cpp @@ -70,6 +70,27 @@ static void hideToolButtons(QList &buttons) button->hide(); } +static void ensureMinimumSize(QWidget *widget) +{ + if (widget->minimumSize().isEmpty()) + widget->setMinimumSize(widget->minimumSize().expandedTo(QSize(60, 60))); +} + +static ADS::DockWidget *createDockWidget(QWidget *widget, + const QString &uniqueId, + const QString &title, + ADS::DockWidget::eMinimumSizeHintMode minimumSizeHintMode) +{ + ADS::DockWidget *dockWidget = new ADS::DockWidget(uniqueId); + dockWidget->setWidget(widget, ADS::DockWidget::ForceNoScrollArea); + dockWidget->setWindowTitle(title); + dockWidget->setMinimumSizeHintMode(minimumSizeHintMode); + + widget->setObjectName(uniqueId); // Set unique id as object name + + return dockWidget; +} + namespace QmlDesigner { namespace Internal { @@ -308,15 +329,10 @@ void DesignModeWidget::setup() sheet += "QLabel { background-color: creatorTheme.DSsectionHeadBackground; }"; navigationView.widget->setStyleSheet(Theme::replaceCssColors(QString::fromUtf8(sheet))); - // Create DockWidget - ADS::DockWidget *dockWidget = new ADS::DockWidget(uniqueId); - dockWidget->setWidget(navigationView.widget, ADS::DockWidget::ForceNoScrollArea); - dockWidget->setWindowTitle(title); - dockWidget->setMinimumSizeHintMode(m_minimumSizeHintMode); - m_dockManager->addDockWidget(ADS::NoDockWidgetArea, dockWidget); + ensureMinimumSize(navigationView.widget); - // Set unique id as object name - navigationView.widget->setObjectName(uniqueId); + auto dockWidget = createDockWidget(navigationView.widget, uniqueId, title, m_minimumSizeHintMode); + m_dockManager->addDockWidget(ADS::NoDockWidgetArea, dockWidget); // Create menu action auto command = Core::ActionManager::registerAction(dockWidget->toggleViewAction(), @@ -328,19 +344,17 @@ void DesignModeWidget::setup() // Afterwards get all the other widgets for (const WidgetInfo &widgetInfo : viewManager().widgetInfos()) { - // Create DockWidget - ADS::DockWidget *dockWidget = new ADS::DockWidget(widgetInfo.uniqueId); - dockWidget->setWidget(widgetInfo.widget, ADS::DockWidget::ForceNoScrollArea); - dockWidget->setWindowTitle(widgetInfo.tabName); - dockWidget->setMinimumSizeHintMode(m_minimumSizeHintMode); + ensureMinimumSize(widgetInfo.widget); + + auto dockWidget = createDockWidget(widgetInfo.widget, + widgetInfo.uniqueId, + widgetInfo.tabName, + m_minimumSizeHintMode); m_dockManager->addDockWidget(ADS::NoDockWidgetArea, dockWidget); // Add to view widgets m_viewWidgets.append(widgetInfo.widget); - // Set unique id as object name - widgetInfo.widget->setObjectName(widgetInfo.uniqueId); - // Create menu action auto command = Core::ActionManager::registerAction(dockWidget->toggleViewAction(), actionToggle.withSuffix( @@ -354,14 +368,14 @@ void DesignModeWidget::setup() { const QString uniqueId = "OutputPane"; auto outputPanePlaceholder = new Core::OutputPanePlaceHolder(Core::Constants::MODE_DESIGN); - m_outputPaneDockWidget = new ADS::DockWidget(uniqueId); - m_outputPaneDockWidget->setWidget(outputPanePlaceholder, ADS::DockWidget::ForceNoScrollArea); - m_outputPaneDockWidget->setWindowTitle(tr("Output")); - m_outputPaneDockWidget->setMinimumSizeHintMode(m_minimumSizeHintMode); - m_dockManager->addDockWidget(ADS::NoDockWidgetArea, m_outputPaneDockWidget); - // Set unique id as object name - outputPanePlaceholder->setObjectName(uniqueId); + ensureMinimumSize(outputPanePlaceholder); + + m_outputPaneDockWidget = createDockWidget(outputPanePlaceholder, + uniqueId, + tr("Output"), + m_minimumSizeHintMode); + m_dockManager->addDockWidget(ADS::NoDockWidgetArea, m_outputPaneDockWidget); // Create menu action auto command = Core::ActionManager::registerAction(m_outputPaneDockWidget->toggleViewAction(), @@ -449,6 +463,7 @@ void DesignModeWidget::setup() this, [this](Utils::Id mode, Utils::Id previousMode) { if (mode == Core::Constants::MODE_DESIGN) { + m_dockManager->aboutToShow(); m_dockManager->reloadActiveWorkspace(); m_dockManager->setModeChangeState(false); } diff --git a/src/plugins/qmldesigner/settingspage.cpp b/src/plugins/qmldesigner/settingspage.cpp index 19b329ad283..2f3582cdf70 100644 --- a/src/plugins/qmldesigner/settingspage.cpp +++ b/src/plugins/qmldesigner/settingspage.cpp @@ -4,7 +4,6 @@ #include "settingspage.h" #include "designersettings.h" -#include "designmodewidget.h" #include "qmldesignerexternaldependencies.h" #include "qmldesignerplugin.h" @@ -293,13 +292,6 @@ SettingsPageWidget::SettingsPageWidget(ExternalDependencies &externalDependencie m_styleLineEdit->setText(m_controls2StyleComboBox->currentText()); }); - connect(m_featureDockWidgetContentMinSize, &QCheckBox::toggled, this, [=](bool checked) { - if (checked && !m_featureDockWidgetContentMinSize->isChecked()) - m_featureDockWidgetContentMinSize->setChecked(true); - - QmlDesignerPlugin::instance()->mainWidget()->setMinimumSizeHintFromContentMinimumSize(checked); - }); - m_forwardPuppetOutputComboBox->addItems(puppetModes()); m_debugPuppetComboBox->addItems(puppetModes()); @@ -491,7 +483,8 @@ void SettingsPageWidget::apply() DesignerSettingsKey::FORWARD_PUPPET_OUTPUT, DesignerSettingsKey::DEBUG_PUPPET, DesignerSettingsKey::ENABLE_MODEL_EXCEPTION_OUTPUT, - DesignerSettingsKey::ENABLE_TIMELINEVIEW}; + DesignerSettingsKey::ENABLE_TIMELINEVIEW, + DesignerSettingsKey::ENABLE_DOCKWIDGET_CONTENT_MIN_SIZE}; for (const char * const key : restartNecessaryKeys) { if (QmlDesignerPlugin::settings().value(key) != settings.value(key)) { diff --git a/src/plugins/qmldesignerbase/utils/windowmanager.cpp b/src/plugins/qmldesignerbase/utils/windowmanager.cpp index c52d5d469a6..146535ceac1 100644 --- a/src/plugins/qmldesignerbase/utils/windowmanager.cpp +++ b/src/plugins/qmldesignerbase/utils/windowmanager.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include namespace QmlDesignerBase { @@ -43,4 +44,14 @@ QPoint WindowManager::globalCursorPosition() return QCursor::pos(); } +QRect WindowManager::getScreenGeometry(QPoint point) +{ + QScreen *screen = QGuiApplication::screenAt(point); + + if (!screen) + return {}; + + return screen->geometry(); +} + } // namespace QmlDesignerBase diff --git a/src/plugins/qmldesignerbase/utils/windowmanager.h b/src/plugins/qmldesignerbase/utils/windowmanager.h index 3d8e692c1aa..87a0b701463 100644 --- a/src/plugins/qmldesignerbase/utils/windowmanager.h +++ b/src/plugins/qmldesignerbase/utils/windowmanager.h @@ -25,6 +25,7 @@ public: static void registerDeclarativeType(); Q_INVOKABLE QPoint globalCursorPosition(); + Q_INVOKABLE QRect getScreenGeometry(QPoint point); signals: void focusWindowChanged(QWindow *window); diff --git a/src/plugins/studiowelcome/studiowelcomeplugin.cpp b/src/plugins/studiowelcome/studiowelcomeplugin.cpp index aa8bb370b07..5a494f5b4ef 100644 --- a/src/plugins/studiowelcome/studiowelcomeplugin.cpp +++ b/src/plugins/studiowelcome/studiowelcomeplugin.cpp @@ -296,7 +296,8 @@ public: Q_INVOKABLE void showHelp() { - QDesktopServices::openUrl(QUrl("qthelp://org.qt-project.qtdesignstudio/doc/index.html")); + QDesktopServices::openUrl( + QUrl("qthelp://org.qt-project.qtdesignstudio/doc/studio-getting-started.html")); } Q_INVOKABLE void openExample(const QString &examplePath, diff --git a/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp b/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp index 147a7a21592..7ea140d1aaf 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp +++ b/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp @@ -10,13 +10,10 @@ #include #include "servernodeinstance.h" -#include "childrenchangeeventfilter.h" #include "propertyabstractcontainer.h" #include "propertybindingcontainer.h" #include "propertyvaluecontainer.h" #include "instancecontainer.h" -#include "createinstancescommand.h" -#include "changefileurlcommand.h" #include "clearscenecommand.h" #include "reparentinstancescommand.h" #include "update3dviewstatecommand.h" @@ -28,8 +25,7 @@ #include "removepropertiescommand.h" #include "valueschangedcommand.h" #include "informationchangedcommand.h" -#include "pixmapchangedcommand.h" -#include "commondefines.h" +#include "imagecontainer.h" #include "changestatecommand.h" #include "childrenchangedcommand.h" #include "completecomponentcommand.h" @@ -44,7 +40,6 @@ #include "requestmodelnodepreviewimagecommand.h" #include "changeauxiliarycommand.h" -#include "dummycontextobject.h" #include "../editor3d/generalhelper.h" #include "../editor3d/mousearea3d.h" #include "../editor3d/camerageometry.h" @@ -2775,15 +2770,21 @@ void Qt5InformationNodeInstanceServer::handlePickTarget( if (checkNode->property("_pickTarget").isNull()) { if (checkRepeater) { QObject::connect(checkRepeater, &QQuick3DRepeater::objectAdded, - this, &Qt5InformationNodeInstanceServer::handleDynamicAddObject); + this, [this, checkNode] { + handleDynamicAddObject(checkNode); + }); #if defined(QUICK3D_ASSET_UTILS_MODULE) } else if (checkRunLoader) { QObject::connect(checkRunLoader, &QQuick3DRuntimeLoader::statusChanged, - this, &Qt5InformationNodeInstanceServer::handleDynamicAddObject); + this, [this, checkNode] { + handleDynamicAddObject(checkNode); + }); #endif } else { QObject::connect(checkLoader, &QQuick3DLoader::loaded, - this, &Qt5InformationNodeInstanceServer::handleDynamicAddObject); + this, [this, checkNode] { + handleDynamicAddObject(checkNode); + }); } } checkNode->setProperty("_pickTarget", QVariant::fromValue(obj)); @@ -2804,9 +2805,9 @@ bool Qt5InformationNodeInstanceServer::isInformationServer() const // This method should be connected to signals indicating a new object has been constructed outside // normal scene creation. E.g. QQuick3DRepeater::objectAdded. -void Qt5InformationNodeInstanceServer::handleDynamicAddObject() +void Qt5InformationNodeInstanceServer::handleDynamicAddObject(QObject *object) { - m_dynamicObjectConstructors.insert(sender()); + m_dynamicObjectConstructors.insert(object); m_dynamicAddObjectTimer.start(); } diff --git a/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.h b/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.h index b603bb134eb..cb7dd20a96e 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.h +++ b/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.h @@ -8,8 +8,6 @@ #include "valueschangedcommand.h" #include "changeselectioncommand.h" #include "requestmodelnodepreviewimagecommand.h" -#include "propertybindingcontainer.h" -#include "propertyabstractcontainer.h" #include "animationdriver.h" #ifdef QUICK3D_PARTICLES_MODULE @@ -61,7 +59,7 @@ public: void handlePickTarget(const ServerNodeInstance &instance) override; bool isInformationServer() const override; - void handleDynamicAddObject(); + void handleDynamicAddObject(QObject *object); private slots: void handleSelectionChanged(const QVariant &objs); diff --git a/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceserver.cpp b/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceserver.cpp index 18d4536dfff..682ba8f2457 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceserver.cpp +++ b/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceserver.cpp @@ -268,22 +268,15 @@ void Qt5NodeInstanceServer::setPipelineCacheConfig([[maybe_unused]] QQuickWindow #ifdef USE_SHADER_CACHE QtQuick3DEditorHelpers::ShaderCache::setAutomaticDiskCache(false); auto wa = QQuick3DSceneManager::getOrSetWindowAttachment(*w); - connect(wa, &QQuick3DWindowAttachment::renderContextInterfaceChanged, - this, &Qt5NodeInstanceServer::handleRciSet); + connect(wa, &QQuick3DWindowAttachment::renderContextInterfaceChanged, this, [this, wa] { + auto context = wa->rci().get(); + if (context && context->shaderCache()) + context->shaderCache()->persistentShaderBakingCache().load(m_shaderCacheFile); + }); #endif #endif } -void Qt5NodeInstanceServer::handleRciSet() -{ -#ifdef USE_SHADER_CACHE - auto wa = qobject_cast(sender()); - auto context = wa ? wa->rci().get() : nullptr; - if (context && context->shaderCache()) - context->shaderCache()->persistentShaderBakingCache().load(m_shaderCacheFile); -#endif -} - bool Qt5NodeInstanceServer::initRhi([[maybe_unused]] RenderViewData &viewData) { if (!viewData.renderControl) { diff --git a/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceserver.h b/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceserver.h index 9294a064e08..d684046fc5b 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceserver.h +++ b/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceserver.h @@ -77,8 +77,6 @@ protected: virtual QImage grabRenderControl(RenderViewData &viewData); private: - void handleRciSet(); - RenderViewData m_viewData; QByteArray m_pipelineCacheData; QString m_pipelineCacheLocation; diff --git a/src/tools/qml2puppet/qml2puppet/instances/quick3dnodeinstance.cpp b/src/tools/qml2puppet/qml2puppet/instances/quick3dnodeinstance.cpp index 9ab66649cfa..938de00c5bd 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/quick3dnodeinstance.cpp +++ b/src/tools/qml2puppet/qml2puppet/instances/quick3dnodeinstance.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "quick3dnodeinstance.h" -#include "qt5nodeinstanceserver.h" #include "qt5informationnodeinstanceserver.h" #ifdef QUICK3D_MODULE @@ -50,16 +49,16 @@ void Quick3DNodeInstance::initialize( #endif if (auto infoServer = qobject_cast(nodeInstanceServer())) { if (repObj) { - QObject::connect(repObj, &QQuick3DRepeater::objectAdded, - infoServer, &Qt5InformationNodeInstanceServer::handleDynamicAddObject); + QObject::connect(repObj, &QQuick3DRepeater::objectAdded, infoServer, + [infoServer, obj] { infoServer->handleDynamicAddObject(obj); }); #if defined(QUICK3D_ASSET_UTILS_MODULE) } else if (runLoadObj) { - QObject::connect(runLoadObj, &QQuick3DRuntimeLoader::statusChanged, - infoServer, &Qt5InformationNodeInstanceServer::handleDynamicAddObject); + QObject::connect(runLoadObj, &QQuick3DRuntimeLoader::statusChanged, infoServer, + [infoServer, obj] { infoServer->handleDynamicAddObject(obj); }); #endif } else { - QObject::connect(loadObj, &QQuick3DLoader::loaded, - infoServer, &Qt5InformationNodeInstanceServer::handleDynamicAddObject); + QObject::connect(loadObj, &QQuick3DLoader::loaded, infoServer, + [infoServer, obj] { infoServer->handleDynamicAddObject(obj); }); } } }