diff --git a/doc/qtcreator/src/qtquick/qtquick-from-qmlproject-to-pro.qdoc b/doc/qtcreator/src/qtquick/qtquick-from-qmlproject-to-pro.qdoc index 78f5e8398df..1e6956a449a 100644 --- a/doc/qtcreator/src/qtquick/qtquick-from-qmlproject-to-pro.qdoc +++ b/doc/qtcreator/src/qtquick/qtquick-from-qmlproject-to-pro.qdoc @@ -155,56 +155,24 @@ {Qt Code Review}. For example: - \badcode - git clone https://code.qt.io/qt-labs/qtquickdesigner-components.git - \endcode + \list 1 + \li Clone the module repository. + \badcode + git clone https://code.qt.io/qt-labs/qtquickdesigner-components.git + \endcode - Then use qmake from your Qt installation to build the module and to add it - to your Qt. Switch to the directory that contains the sources (usually, - qtquickdesigner-components), make sure you checkout the qmake branch, and enter - the following commands: - - \badcode - \qmake -r - make - make install - \endcode - - On Windows, use the \c nmake and \c {nmake install} commands instead. - - If you prefer CMake instead and you want to benefit from the QML compilation, - then you can checkout the dev branch instead. CMake is only supported since Qt 6.2. - Enter the following commands: - - \badcode - mkdir build - cd build - cmake -GNinja -DCMAKE_INSTALL_PREFIX= - cmake --build . - cmake --install . - \endcode - - \section1 Adding Qt Quick Timeline Module to Qt Installations - - \note You only need to do this if your Qt version is older than 5.14. - - Check out the \l{Qt Quick Timeline} module from - \l{https://codereview.qt-project.org/#/admin/projects/qt/qtquicktimeline} - {Qt Code Review}. - - For example: - \badcode - git clone "https://codereview.qt-project.org/qt/qtquicktimeline" - \endcode - - To use qmake, you need to check out a branch or tag that contains the - qmake configuration files. - - For example: - \badcode - git checkout v5.15.2 - \endcode - - Then build the module and add it to your Qt as described in the previous - section. + \li Install the Qt Quick Designer Components module. + Enter the following commands: + \badcode + mkdir build + cd build + cmake -GNinja -DCMAKE_INSTALL_PREFIX= + cmake --build . + cmake --install . + \endcode + \note Here, \e and \e + needs to be replaced with the real location on your local drive. For example, + \e can be something like \e /Qt/6.3.0/msvc2019_64 + and \e like this \e ../qtquickdesigner-components/ + \endlist */ diff --git a/doc/qtcreator/src/user-interface/creator-open-documents-view.qdoc b/doc/qtcreator/src/user-interface/creator-open-documents-view.qdoc index 5250f79af3c..47b6b3dd00e 100644 --- a/doc/qtcreator/src/user-interface/creator-open-documents-view.qdoc +++ b/doc/qtcreator/src/user-interface/creator-open-documents-view.qdoc @@ -4,7 +4,11 @@ /*! \page creator-open-documents-view.html \previouspage creator-file-system-view.html + \if defined(qtdesignstudio) + \nextpage studio-content-library.html + \else \nextpage creator-output-panes.html + \endif \title Open Documents diff --git a/doc/qtdesignstudio/examples/doc/progressbar.qdoc b/doc/qtdesignstudio/examples/doc/progressbar.qdoc index db027155d9f..6ce9ff1562c 100644 --- a/doc/qtdesignstudio/examples/doc/progressbar.qdoc +++ b/doc/qtdesignstudio/examples/doc/progressbar.qdoc @@ -108,7 +108,7 @@ We will now copy the color animation from the text label to the indicator. First, we right-click the text component in the \uicontrol Timeline view to - open a context menu and select \uicontrol {Copy All Keyframes from Item} to + open a context menu and select \uicontrol {Copy All Keyframes} to copy the keyframe values we specified for the text label. Next, we select the indicator in the \uicontrol Navigator, and then select diff --git a/doc/qtdesignstudio/images/content-library-add-texture.png b/doc/qtdesignstudio/images/content-library-add-texture.png new file mode 100644 index 00000000000..ae820a39c86 Binary files /dev/null and b/doc/qtdesignstudio/images/content-library-add-texture.png differ diff --git a/doc/qtdesignstudio/images/content-library.webp b/doc/qtdesignstudio/images/content-library.webp new file mode 100644 index 00000000000..ac5ca5f6971 Binary files /dev/null and b/doc/qtdesignstudio/images/content-library.webp differ diff --git a/doc/qtdesignstudio/images/icons/area.png b/doc/qtdesignstudio/images/icons/area.png new file mode 100644 index 00000000000..0e0f773b075 Binary files /dev/null and b/doc/qtdesignstudio/images/icons/area.png differ diff --git a/doc/qtdesignstudio/images/icons/directional.png b/doc/qtdesignstudio/images/icons/directional.png new file mode 100644 index 00000000000..6ff7d1bbd34 Binary files /dev/null and b/doc/qtdesignstudio/images/icons/directional.png differ diff --git a/doc/qtdesignstudio/images/icons/line-particles-16px.png b/doc/qtdesignstudio/images/icons/line-particles-16px.png new file mode 100644 index 00000000000..318ecbc3d6d Binary files /dev/null and b/doc/qtdesignstudio/images/icons/line-particles-16px.png differ diff --git a/doc/qtdesignstudio/images/icons/point.png b/doc/qtdesignstudio/images/icons/point.png new file mode 100644 index 00000000000..2b4856ca8a2 Binary files /dev/null and b/doc/qtdesignstudio/images/icons/point.png differ diff --git a/doc/qtdesignstudio/images/icons/repeller-16px.png b/doc/qtdesignstudio/images/icons/repeller-16px.png new file mode 100644 index 00000000000..4516cb1f281 Binary files /dev/null and b/doc/qtdesignstudio/images/icons/repeller-16px.png differ diff --git a/doc/qtdesignstudio/images/icons/scale-affector-16px.png b/doc/qtdesignstudio/images/icons/scale-affector-16px.png new file mode 100644 index 00000000000..4258f563a1b Binary files /dev/null and b/doc/qtdesignstudio/images/icons/scale-affector-16px.png differ diff --git a/doc/qtdesignstudio/images/icons/spot.png b/doc/qtdesignstudio/images/icons/spot.png new file mode 100644 index 00000000000..fa49858b67e Binary files /dev/null and b/doc/qtdesignstudio/images/icons/spot.png differ diff --git a/doc/qtdesignstudio/images/material-editor-browser.webp b/doc/qtdesignstudio/images/material-editor-browser.webp index 8933b9d8e63..160c96e877c 100644 Binary files a/doc/qtdesignstudio/images/material-editor-browser.webp and b/doc/qtdesignstudio/images/material-editor-browser.webp differ diff --git a/doc/qtdesignstudio/images/select-material-property.png b/doc/qtdesignstudio/images/select-material-property.png new file mode 100644 index 00000000000..3202c587473 Binary files /dev/null and b/doc/qtdesignstudio/images/select-material-property.png differ diff --git a/doc/qtdesignstudio/images/share-online.webp b/doc/qtdesignstudio/images/share-online.webp index c91661f18cb..7ba08aa4271 100644 Binary files a/doc/qtdesignstudio/images/share-online.webp and b/doc/qtdesignstudio/images/share-online.webp differ diff --git a/doc/qtdesignstudio/images/studio-3d-particles.png b/doc/qtdesignstudio/images/studio-3d-particles.png index 14711846f32..21715f577c5 100644 Binary files a/doc/qtdesignstudio/images/studio-3d-particles.png and b/doc/qtdesignstudio/images/studio-3d-particles.png differ diff --git a/doc/qtdesignstudio/images/studio-3d-properties-line-particle.png b/doc/qtdesignstudio/images/studio-3d-properties-line-particle.png new file mode 100644 index 00000000000..e22adc41fb0 Binary files /dev/null and b/doc/qtdesignstudio/images/studio-3d-properties-line-particle.png differ diff --git a/doc/qtdesignstudio/images/studio-3d-properties-particle-dynamic-burst.png b/doc/qtdesignstudio/images/studio-3d-properties-particle-dynamic-burst.png new file mode 100644 index 00000000000..40d4915aba3 Binary files /dev/null and b/doc/qtdesignstudio/images/studio-3d-properties-particle-dynamic-burst.png differ diff --git a/doc/qtdesignstudio/images/studio-3d-properties-particle-repeller.png b/doc/qtdesignstudio/images/studio-3d-properties-particle-repeller.png new file mode 100644 index 00000000000..755c76376a5 Binary files /dev/null and b/doc/qtdesignstudio/images/studio-3d-properties-particle-repeller.png differ diff --git a/doc/qtdesignstudio/images/studio-3d-properties-particle-scale-affector.png b/doc/qtdesignstudio/images/studio-3d-properties-particle-scale-affector.png new file mode 100644 index 00000000000..98d5ad7f727 Binary files /dev/null and b/doc/qtdesignstudio/images/studio-3d-properties-particle-scale-affector.png differ diff --git a/doc/qtdesignstudio/images/texture-editor.png b/doc/qtdesignstudio/images/texture-editor.png new file mode 100644 index 00000000000..e8eecbd13b6 Binary files /dev/null and b/doc/qtdesignstudio/images/texture-editor.png differ diff --git a/doc/qtdesignstudio/src/components/qtquick-positioning.qdoc b/doc/qtdesignstudio/src/components/qtquick-positioning.qdoc index 54857d91222..5ea37b040c0 100644 --- a/doc/qtdesignstudio/src/components/qtquick-positioning.qdoc +++ b/doc/qtdesignstudio/src/components/qtquick-positioning.qdoc @@ -44,23 +44,19 @@ Property bindings are created implicitly whenever a property is assigned a JavaScript expression. To set JavaScript expressions as values of properties - in the \l Properties view, select the \inlineimage icons/action-icon.png - (\uicontrol Actions) menu next to a property, and then select - \uicontrol {Set Binding}. + in the \l Properties view: + \list 1 + \li Select the \inlineimage icons/action-icon.png + (\uicontrol Actions) menu next to a property, and then select + \uicontrol {Set Binding}. - \image qmldesigner-set-expression.png "Actions menu" + \image qmldesigner-set-expression.png "Actions menu" - In \uicontrol {Binding Editor}, select a component and a property from - lists of available components and their properties. + \li In \uicontrol {Binding Editor}, select a component and a property from + lists of available components and their properties. - \image qmldesigner-binding-editor.png "Binding Editor" - - Alternatively, start typing a - string and press \key Ctrl+Space to display a list of properties, IDs, and - code snippets. When you enter a period (.) after a property name, a list of - available values is displayed. Press \key Enter to accept the first - suggestion in the list and to complete the code. For more information, see - \l{Completing Code}. + \image qmldesigner-binding-editor.png "Binding Editor" + \endlist When a binding is set, the \uicontrol Actions menu icon changes to \inlineimage icons/action-icon-binding.png diff --git a/doc/qtdesignstudio/src/overviews/qt-design-viewer.qdoc b/doc/qtdesignstudio/src/overviews/qt-design-viewer.qdoc index 9d869a72d0d..069ad71f274 100644 --- a/doc/qtdesignstudio/src/overviews/qt-design-viewer.qdoc +++ b/doc/qtdesignstudio/src/overviews/qt-design-viewer.qdoc @@ -31,11 +31,15 @@ \list 1 \li Open the application in \QDS. \li Select \uicontrol File > \uicontrol {Share Application Online}. - \li In the dialog, select \uicontrol Share. + \li Optionally, select \uicontrol {Protect with password}, and enter a password to prevent + unauthorized viewing of the application. + \note If you share the same application again, you must set the password + again. Otherwise, the application is not password protected any longer. + \li Select \uicontrol Share. \image share-online.webp \endlist - In the dialog, you can now open the application in a web + You can now open the application in a web browser, copy the link to share with others, or manage your shared applications. diff --git a/doc/qtdesignstudio/src/qtdesignstudio-toc.qdoc b/doc/qtdesignstudio/src/qtdesignstudio-toc.qdoc index d6532059598..5271e5507fb 100644 --- a/doc/qtdesignstudio/src/qtdesignstudio-toc.qdoc +++ b/doc/qtdesignstudio/src/qtdesignstudio-toc.qdoc @@ -32,6 +32,8 @@ \li \l{Projects} \li \l{File System} \li \l{Open Documents} + \li \l{Content Library} + \li \l{Texture Editor} \endlist \li \l{Managing Workspaces} \li \l{Managing Sessions} diff --git a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-lights.qdoc b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-lights.qdoc index 4bd44874ab0..f2063e6a05c 100644 --- a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-lights.qdoc +++ b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-lights.qdoc @@ -41,25 +41,25 @@ \li More Information \row - \li \inlineimage directional.png + \li \inlineimage icons/directional.png \li Directional Light \li \li \l{DirectionalLight}{Light Directional} \row - \li \inlineimage point.png + \li \inlineimage icons/point.png \li Point Light \li \li \l{PointLight}{Light Point} \row - \li \inlineimage spot.png + \li \inlineimage icons/spot.png \li Spot Light \li \li \l{SpotLight}{Light Spot} \row - \li \inlineimage area.png + \li \inlineimage icons/area.png \li Area Light \li \inlineimage ok.png \li \l{AreaLight}{Light Area} diff --git a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-particles.qdoc b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-particles.qdoc index 545b091d53c..b9f5463c83d 100644 --- a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-particles.qdoc +++ b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-particles.qdoc @@ -70,10 +70,15 @@ \li \inlineimage icons/attractor-16px.png \li Attractor \li Attracts particles towards a specific point. + \row + \li \inlineimage icons/emit-burst-16px.png + \li Dynamic Burst + \li Emits particles in dynamic bursts. Use dynamic burst for + emitters that are moving. \row \li \inlineimage icons/emit-burst-16px.png \li Emit Burst - \li Generates declarative emitter bursts. + \li Emits particles in bursts. \row \li \inlineimage icons/emitter-16px.png \li Emitter @@ -83,6 +88,10 @@ \li Gravity \li Accelerates particles to a vector of the specified magnitude in the specified direction. + \row + \li \inlineimage icons/line-particles-16px.png + \li Line Particle + \li Creates line-shaped sprite particles. \row \li \inlineimage icons/model-blend-particle-16px.png \li Model Blend Particle @@ -110,6 +119,15 @@ \li \inlineimage icons/point-rotator-16px.png \li Point Rotator \li Rotates particles around a pivot point. + \row + \li \inlineimage icons/repeller-16px.png + \li Repeller + \li Repels particles from its location. + \row + \li \inlineimage icons/scale-affector-16px.png + \li Scale Affector + \li Scales particles based on the particles' lifetime and other + parameters. \row \li \inlineimage icons/sprite-particle-16px.png \li Sprite Particle @@ -399,19 +417,23 @@ visualized, and some logical particles could lead to multiple visual particles being drawn on screen. - Two different logical particle components are supported: - \uicontrol {Sprite Particle} for \l{Textures}{2D texture} particles and - \uicontrol {Model Particle} for \l{3D Models}{3D model} particles. Model - particles use \l{Instanced Rendering}{instanced rendering} to enabled the - rendering of thousands of particles, with full \l{3D Materials}{materials} - and \l{Lights}{lights} support. + \QDS supports the following logical particle components: - The following components are available for adding logical particles and - for modifying their actions and appearance: + \list + \li \uicontrol {Sprite Particle} and \uicontrol{Line Particle} for + \l{Textures}{2D texture} particles. + \li \uicontrol {Model Particle} for \l{3D Models}{3D model} particles. + Model particles use \l{Instanced Rendering}{instanced rendering} to render + thousands of particles, with full \l{3D Materials}{materials} and + \l{Lights}{lights} support. + \endlist + + You can use the following components to add and modify logical particles: \list \li \l{Sprite Particle} \li \l{Sprite Sequence} + \li \l{Line Particle} \li \l{Model Particle} \li \l{Model Blend Particle} \endlist @@ -509,6 +531,48 @@ \uicontrol {Frame index} is rendered. When it is enabled, each particle renders a random frame. + \section1 Line Particle + + Specify properties for line particles in \uicontrol Properties > + \uicontrol {Line Particle}. + + \image studio-3d-properties-line-particle.png + + \uicontrol {Segments} defines the number of segments in each line. + + \uicontrol {Alpha Fade} defines the alpha fade factor of the lines. The + value range is [0, 1]. When the value is greater than 0.0, the line fades + more the further the segment is from the first particle segment. + + \uicontrol {Scale Multiplier} modifies the line size for the line segments. + The value range is [0, 2]. If the value is less than 1.0, + the line gets smaller the further a segment is from the first segment and + if the value is greater than 1.0 the line gets bigger. + + \uicontrol {Texcoord Multipier} defines the texture coordinate multiplier of + the line. This value is factored to the texture coordinate values of the + line. + + \uicontrol {Texcoord Mode} defines the texture coordinate mode of the line. + + \uicontrol {Line Length} defines the length of the line. If the value is + set, the line length is limited to the value. In this case the minimum + delta of the line is the length divided by the segment count. If the value + is not set, the line length varies based on the particle speed, segment + count, and minimum delta. + + \uicontrol {Line Length Variation} defines the length variation of the line. + This parameter is not used if \uicontrol {Line Length} has not been set. + When the length is set, this parameter can be used to vary the length of + each line. + + \uicontrol {Minimum Segment Length} defines he minimum length between + segment points. This parameter is ignored if \uicontrol {Line Length} is set. + + \uicontrol {Eol Fade Out Duration} defines the end-of-life fade-out duration + of the line. If set, each line remains in the place it was when the particle + reached the end of its lifetime, then fades out during this time period. + \section1 Model Particle Specify properties for model particles in \uicontrol Properties > @@ -678,6 +742,7 @@ \li \l Emitter \li \l {Trail Emitter} \li \l {Emit Burst} + \li \l {Dynamic Burst} \li \l {Model Shape} \li \l {Particle Shape} \endlist @@ -775,6 +840,30 @@ instance, set \uicontrol Time to 2000, \uicontrol Amount to 50, and \uicontrol Duration to 200. + \section1 Dynamic Burst + + Specify properties for emit bursts in \uicontrol Properties > + \uicontrol {Dynamic Burst}. + + \image studio-3d-properties-particle-dynamic-burst.png + + \uicontrol {Trigger Mode} defines when the burst is triggered: + + \list + \li Select \uicontrol TriggerTime to emit the burst when the burst time + is due. + \li Select \uicontrol TriggerStart to emit the burst when the followed + particle is emitted. + \note This mode only works with trail emitters. + \note In this mode, \uicontrol Time and \uicontrol Duration don't have an + effect. + \li Select \uicontrol TriggerEnd to emit the burst when the followed + particle life span ends. + \endlist + + \uicontrol {Amount Variation} defines the random variation in the number of + emitted particles. + \section1 Particle Shape The \uicontrol {Particle Shape} component supports shapes, such as cube, @@ -830,6 +919,9 @@ \li \l Gravity accelerates particles to a vector of the specified magnitude in the specified direction. \li \l {Point Rotator} rotates particles around a pivot point. + \li \l Repeller repels particles from its location. + \li \l {Scale Affector} scales particles based on its lifetime and other + parameters. \li \l Wander applies random wave curves to particles. \endlist @@ -909,20 +1001,57 @@ \section1 Point Rotator - Specify settings for \uicontrol {Point Rotator} component instances in - \uicontrol Properties > \uicontrol {Point Rotator}. - - \image studio-3d-properties-particle-point-rotator.png "Particle Point Rotator properties" - The \uicontrol {Point Rotator} component rotates particles around the pivot point specified in \uicontrol {Pivot point} towards the direction specified in \uicontrol Direction. Direction \uicontrol X, \uicontrol Y, and \uicontrol Z values are automatically normalized to a unit vector. + Specify settings for \uicontrol {Point Rotator} component instances in + \uicontrol Properties > \uicontrol {Point Rotator}. + + \image studio-3d-properties-particle-point-rotator.png "Particle Point Rotator properties" + \uicontrol Magnitude defines the magnitude in particle position change in degrees per second. A negative value accelerates in the opposite way from the direction specified in \uicontrol Direction. + \section1 Repeller + + The \uicontrol Repeller component repels particles from its location. + + Specify settings for \uicontrol Repeller component instances in + \uicontrol Properties > \uicontrol {Particle Repeller}. + + \image studio-3d-properties-particle-repeller.png + + \uicontrol {Outer Radius} defines the outer radius of the repeller. The + particle is not affected until it enters this radius and the repel + strength grows smoothly until the particle reaches \uicontrol Radius. + + \uicontrol Radius defines the inner radius of the repeller. Particles + located inside \uicontrol Radius are repelled at full strength. + + \uicontrol Strength defines the strength of the repeller. + + \section1 Scale Affector + + \uicontrol {Scale Affector} scales particles based on their lifetime and + other parameters. + + \image studio-3d-properties-particle-scale-affector.png + + \uicontrol {Minimum Size} defines the minimum size that the affector can + scale particles to. + + \uicontrol {Maximum Size} defines the maximum size that the affector can + scale particles to. + + \uicontrol Duration defines the the duration of the scaling cycle in + milliseconds. + + \uicontrol {Easing Curve} defines the + \l{Editing Easing Curves}{easing curve} for the scaling animation. + \section1 Wander The \uicontrol Wander component applies random wave curves to particles. diff --git a/doc/qtdesignstudio/src/qtquickdesigner-components/qtdesignstudio-logic-helpers.qdoc b/doc/qtdesignstudio/src/qtquickdesigner-components/qtdesignstudio-logic-helpers.qdoc index 906d1a9ba46..e616f82c972 100644 --- a/doc/qtdesignstudio/src/qtquickdesigner-components/qtdesignstudio-logic-helpers.qdoc +++ b/doc/qtdesignstudio/src/qtquickdesigner-components/qtdesignstudio-logic-helpers.qdoc @@ -92,7 +92,7 @@ \image studio-logic-helper-not.png "NOT operator properties" We then select the other check box instance and bind the value of its - \uicontrol Checked field to the value of of \uicontrol Output + \uicontrol Checked field to the value of \uicontrol Output field of the \uicontrol {Not Operator} component. \image studio-logic-helper-not-check-box.png "Check box checked property bound to NOT operator output" diff --git a/doc/qtdesignstudio/src/views/qtquick-designer.qdoc b/doc/qtdesignstudio/src/views/qtquick-designer.qdoc index 4dfe24718b6..d7af982392d 100644 --- a/doc/qtdesignstudio/src/views/qtquick-designer.qdoc +++ b/doc/qtdesignstudio/src/views/qtquick-designer.qdoc @@ -51,6 +51,12 @@ \li Provides an editor for files you created using 3D graphics applications and stored in one of the supported formats. \li \l {3D} + \row + \li \l {Material Editor and Browser} + \li In the \uicontrol {Material Editor} and + \uicontrol {Material Browser} views, you create and manage materials and + textures. + \li \l {Material Editor and Browser} \row \li \l Components \li Contains preset components and your own components, that you can use @@ -121,6 +127,16 @@ \li \l{Open Documents} \li Shows currently open files. \li \l{Open Documents} + \row + \li \l{Content Library} + \li The \uicontrol {Content Library} view contains material, texture, + and environment bundles with assets that you can use in your project. + \li \l{Content Library} + \row + \li \l{Texture Editor} + \li In the \uicontrol {Texture Editor} view, you create and manage + textures. + \li \l{Texture Editor} \endtable \section1 Summary of Main Toolbar Actions diff --git a/doc/qtdesignstudio/src/views/studio-content-library.qdoc b/doc/qtdesignstudio/src/views/studio-content-library.qdoc new file mode 100644 index 00000000000..67d67c91534 --- /dev/null +++ b/doc/qtdesignstudio/src/views/studio-content-library.qdoc @@ -0,0 +1,66 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \page studio-content-library.html + \previouspage creator-open-documents-view.html + \nextpage studio-texture-editor.html + + \title Content Library + + \note The \uicontrol {Content Library} view is included in the + \l{https://www.qt.io/pricing}{Qt Design Studio Enterprise license}. + + The \uicontrol{Content Library} view contains material, texture, and + environment bundles with assets that you can use in your project. When you + have added an asset from \uicontrol {Content Library}, you can use it in + your project. + + \image content-library.webp + + \section1 Adding a Material to Your Project + + You can use materials on 3D models. + + To add a material from the \uicontrol {Content Library} view to your + project, do one of the following: + + \list + \li Select the \inlineimage icons/plus.png + next to the material in the \uicontrol {Content Library} view. + \li Right-click the material in the \uicontrol {Content Library} view and + select \uicontrol{Add an instance to the project}. + \endlist + + You can also add a material to a 3D model straight from the \uicontrol {Content Library} view. + This also adds the material to the project. + + To add a material to a 3D model, do one of the following: + + \list + \li Drag the material from the \uicontrol {Content Library} view to a 3D + model in the \uicontrol 3D or \uicontrol Navigator view. + \li With a 3D model selected, right-click the material in the + \uicontrol {Content Library} view and select \uicontrol{Apply to selected}. + \endlist + + \section1 Adding a Texture or Environment to Your Project + + To add a texture or environment to your project, right-click the image + in the \uicontrol {Content Library} view and select one of the options: + \list + \li \uicontrol {Add image}. This adds the image as an asset to your project. You can + access the image from the \uicontrol Assets view. + \li \uicontrol {Add texture}. This adds the image as a texture to your project. You can + access the texture from the \uicontrol Textures section in the \uicontrol{Material Browser} + view. + \li \uicontrol {Add light probe}. This adds the image as a light probe for your scene, using + the image to illuminate the scene and as a skybox. When you set an image as light probe, the + following properties are set for \uicontrol {Scene Environment}: + \list + \li \uicontrol Background Mode is set to skybox. + \li \uicontrol {Light Probe} > \uicontrol Image is set to the selected texture. + \endlist + \endlist + +*/ diff --git a/doc/qtdesignstudio/src/views/studio-material-editor.qdoc b/doc/qtdesignstudio/src/views/studio-material-editor.qdoc index 4120bb633e4..94209c7bcb9 100644 --- a/doc/qtdesignstudio/src/views/studio-material-editor.qdoc +++ b/doc/qtdesignstudio/src/views/studio-material-editor.qdoc @@ -10,11 +10,13 @@ \title Material Editor and Browser In the \uicontrol {Material Editor} and \uicontrol {Material Browser} views, - you create and manage materials. + you create and manage materials and textures. \image material-editor-browser.webp "Material Editor and Browser" - \section1 Creating a Material + \section1 Working with Materials + + \section2 Creating a Material To create a new material, do one of the following: @@ -25,7 +27,7 @@ . \endlist - \section1 Editing a Material + \section2 Editing a Material To edit a material, select it in \uicontrol{Material Browser} and edit its properties in \uicontrol{Material Editor}. If \uicontrol {Material Editor} @@ -37,7 +39,7 @@ \li In \uicontrol{Material Browser}, double-click a material. \endlist - \section1 Assigning a Material to an Object + \section2 Assigning a Material to an Object To assign a material to a 3D object in your project, drag the material from \uicontrol {Material Browser} to the object in the \uicontrol Navigator or @@ -57,7 +59,7 @@ . This replaces any material already assigned to the object. \endlist - \section1 Removing a Material from an Object + \section2 Removing a Material from an Object To remove an assigned material from an object: \list 1 @@ -68,7 +70,7 @@ \image materials-remove-material.png \endlist - \section1 Copying and Pasting Material Properties + \section2 Copying and Pasting Material Properties You can copy properties from one material to another. You can choose if you want to copy all properties or certain property groups. @@ -88,7 +90,7 @@ \note You can't copy material properties between materials of different material types. - \section1 Using Texture Maps + \section2 Using Texture Maps In \QDS you can add many different texture maps to your material. @@ -100,7 +102,7 @@ the image to \uicontrol{Diffuse Map} in \uicontrol{Material Editor}. \endlist - \section2 Using a Reflection Map for Environmental Mapping + \section3 Using a Reflection Map for Environmental Mapping To use a texture for environmental mapping, you need to set the mapping mode to \e {environment}. @@ -122,7 +124,7 @@ \uicontrol {Environment}. \endlist - \section1 Blending Colors + \section2 Blending Colors To determine how the colors of a model blend with the colors of the models behind it, set the \uicontrol {Blend mode} property. To make opaque objects @@ -144,7 +146,7 @@ For a result with higher contrast, select \uicontrol Overlay, which is a mix of the multiply and screen modes. - \section1 Lighting Materials + \section2 Lighting Materials To set the lighting method for generating a material, use the \uicontrol Lighting property. Select \uicontrol {Fragment lighting} to @@ -164,7 +166,7 @@ the opacity of the material independently of the model as the value of the \uicontrol Opacity property. - \section1 Self-Illuminating Materials + \section2 Self-Illuminating Materials To set the color and amount of self-illumination for a material, use the \uicontrol {Emissive color} and \uicontrol {Emissive factor} properties. In @@ -177,7 +179,7 @@ image does not affect the color of the result, while using a color image produces glowing regions with the color affected by the emissive map. - \section1 Using Highlights and Reflections + \section2 Using Highlights and Reflections You can control the highlights and reflections on a material by setting the properties in the \uicontrol Specular group. You can use the color picker @@ -221,7 +223,7 @@ highlights and blurring reflections. To control the specular roughness of the material using a Texture, set the \uicontrol {Roughness map property}. - \section1 Simulating Geometry Displacement + \section2 Simulating Geometry Displacement Specify the properties in the \uicontrol {Bump/Normal} group to simulate fine geometry displacement across the surface of the material. Set the @@ -240,7 +242,7 @@ of the material. The \uicontrol {Displacement amount} property specifies the offset amount. - \section1 Specifying Material Translucency + \section2 Specifying Material Translucency Set the properties in the \uicontrol Translucency group to control how much light can pass through the material from behind. To use a grayscale texture, @@ -254,7 +256,7 @@ the angle of the normals of the object to the light source, set the \uicontrol {Translucency falloff} property. - \section1 Culling Faces + \section2 Culling Faces Set the \uicontrol {Culling mode} property to determine whether the front and back faces of a model are rendered. Culling modes check whether the @@ -265,4 +267,32 @@ is not rendered. Culling makes rendering objects quicker and more efficient by reducing the number of polygons to draw. + \section1 Working with Textures + + \target creating texture material browser + + \section2 Creating a Texture + + To create a new texture, do one of the following in + \uicontrol {Material Browser}: + \list + \li Select \inlineimage icons/plus.png + in the \uicontrol Textures section. + \li Right-click anywhere in the \uicontrol Textures section and select + \uicontrol {Create new texture}. + \endlist + + \note You can also create textures from the \l {Assets} or + \l {Texture Editor} views. + + \section2 Applying a Texture to a Material + + To apply a texture to a material, select the material in + \uicontrol {Material Browser} and do one of the following: + \list + \li Right-click the texture and select + \uicontrol {Apply to selected material}. + \li Drag the texture to a supported property in the + \uicontrol {Material Editor} + \endlist */ diff --git a/doc/qtdesignstudio/src/views/studio-texture-editor.qdoc b/doc/qtdesignstudio/src/views/studio-texture-editor.qdoc new file mode 100644 index 00000000000..95a99401a9c --- /dev/null +++ b/doc/qtdesignstudio/src/views/studio-texture-editor.qdoc @@ -0,0 +1,47 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \page studio-texture-editor.html + \previouspage studio-content-library.html + \nextpage creator-project-managing-workspaces.html + + \title Texture Editor + + In the \uicontrol {Texture Editor} view, you create and manage textures. + + \image texture-editor.png + + \section1 Creating a Texture + + To create a texture, select \inlineimage icons/plus.png + in the \uicontrol {Texture Editor} view. + + \note You can also create textures from the + \l{creating texture material browser}{Material Browser view}. + + When you create a texture, it is empty. To add an image to the texture, + do one of the following: + + \list + \li In the \uicontrol{Texture Editor} view, set the image in the + \uicontrol Source property. + \li From the \uicontrol Assets view, drag an image to the + \uicontrol Source property in the \uicontrol {Texture Editor} view. + \endlist + + \section1 Applying a Texture to a Material + + To apply a texture to a material, first select the material in the + \uicontrol {Material Browser} view and then: + \list 1 + \li Select \inlineimage icons/apply-material.png + . + \li Select the material and property that you want to add the texture to. + \image select-material-property.png + \endlist + + \note You can also apply textures to materials in the + \l {Material Editor and Browser}{Material Browser view}. + +*/ diff --git a/doc/qtdesignstudio/src/views/studio-workspaces.qdoc b/doc/qtdesignstudio/src/views/studio-workspaces.qdoc index a6a6d97dfb0..fdc359d96d1 100644 --- a/doc/qtdesignstudio/src/views/studio-workspaces.qdoc +++ b/doc/qtdesignstudio/src/views/studio-workspaces.qdoc @@ -3,7 +3,11 @@ /*! \page creator-project-managing-workspaces.html + \if defined(qtdesignstudio) + \previouspage studio-texture-editor.html + \else \previouspage creator-open-documents-view.html + \endif \nextpage creator-project-managing-sessions.html \title Managing Workspaces diff --git a/qtcreator_ide_branding.pri b/qtcreator_ide_branding.pri index 3fa9863d5dd..4be9bfcd99a 100644 --- a/qtcreator_ide_branding.pri +++ b/qtcreator_ide_branding.pri @@ -14,3 +14,4 @@ IDE_DOC_FILES_ONLINE = $$PWD/doc/qtcreator/qtcreator-online.qdocconf \ $$PWD/doc/qtcreatordev/qtcreator-dev-online.qdocconf IDE_DOC_FILES = $$PWD/doc/qtcreator/qtcreator.qdocconf \ $$PWD/doc/qtcreatordev/qtcreator-dev.qdocconf + diff --git a/scripts/deployqtHelper_mac.sh b/scripts/deployqtHelper_mac.sh index cc09b80ad67..4109e6f31db 100755 --- a/scripts/deployqtHelper_mac.sh +++ b/scripts/deployqtHelper_mac.sh @@ -126,11 +126,15 @@ if [ ! -d "$app_path/Contents/Frameworks/QtCore.framework" ]; then if [ -f "$qml2puppetapp" ]; then qml2puppetArgument="-executable=$qml2puppetapp" fi + sdktoolapp="$libexec_path/sdktool" + if [ -f "$sdktoolapp" ]; then + sdktoolArgument="-executable=$sdktoolapp" + fi "$bin_src/macdeployqt" "$app_path" \ "-executable=$app_path/Contents/MacOS/qtdiag" \ "-executable=$libexec_path/qtpromaker" \ - "-executable=$libexec_path/sdktool" \ + "$sdktoolArgument" \ "-executable=$libexec_path/ios/iostool" \ "-executable=$libexec_path/buildoutputparser" \ "-executable=$libexec_path/cpaster" \ diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml index bbe9641419e..19c1a039c16 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml @@ -35,45 +35,32 @@ Item { id: searchBox width: root.width - enabled: !materialsModel.hasMaterialRoot && materialsModel.hasQuick3DImport + enabled: { + if (tabBar.currIndex == 0) { // Materials tab + materialsModel.matBundleExists + && rootView.hasMaterialLibrary + && materialsModel.hasRequiredQuick3DImport + } else { // Textures / Environments tabs + texturesModel.texBundleExists + } + } onSearchChanged: (searchText) => { rootView.handleSearchFilterChanged(searchText) // make sure categories with matches are expanded materialsView.expandVisibleSections() + texturesView.expandVisibleSections() + environmentsView.expandVisibleSections() } } - Text { - // TODO: only disable the materials section, textures should be available - text: { - if (materialsModel.hasMaterialRoot) - qsTr("Content Library is disabled inside a material component.") - else if (!materialsModel.hasQuick3DImport) - qsTr("To use Content Library, first add the QtQuick3D module in the Components view.") - else - "" - } - - textFormat: Text.RichText - color: StudioTheme.Values.themeTextColor - font.pixelSize: StudioTheme.Values.mediumFontSize - topPadding: 30 - horizontalAlignment: Text.AlignHCenter - wrapMode: Text.WordWrap - width: root.width - visible: text !== "" - } - UnimportBundleMaterialDialog { id: confirmUnimportDialog } ContentLibraryTabBar { id: tabBar - - visible: materialsModel.hasQuick3DImport // TODO: update icons tabsModel: [{name: qsTr("Materials"), icon: StudioTheme.Constants.gradient}, {name: qsTr("Textures"), icon: StudioTheme.Constants.materialPreviewEnvironment}, @@ -84,7 +71,6 @@ Item { width: root.width height: root.height - y currentIndex: tabBar.currIndex - visible: materialsModel.hasQuick3DImport ContentLibraryMaterialsView { id: materialsView @@ -94,8 +80,8 @@ Item { searchBox: searchBox onUnimport: (bundleMat) => { - unimportBundleMaterialDialog.targetBundleMaterial = bundleMat - unimportBundleMaterialDialog.open() + confirmUnimportDialog.targetBundleMaterial = bundleMat + confirmUnimportDialog.open() } } diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterial.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterial.qml index 4d6377359ce..23a8c5891e7 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterial.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterial.qml @@ -19,6 +19,7 @@ Item { MouseArea { id: mouseArea + hoverEnabled: true anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton @@ -81,6 +82,7 @@ Item { anchors.right: img.right anchors.bottom: img.bottom enabled: !materialsModel.importerRunning + visible: containsMouse || mouseArea.containsMouse onClicked: { materialsModel.addToProject(modelData) diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialContextMenu.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialContextMenu.qml index a85699e6dc3..4d5d75b6e58 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialContextMenu.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialContextMenu.qml @@ -10,7 +10,13 @@ StudioControls.Menu { id: root property var targetMaterial: null + property bool hasModelSelection: false + property bool importerRunning: false + + readonly property bool targetAvailable: targetMaterial && !importerRunning + signal unimport(var bundleMat); + signal addToProject(var bundleMat) function popupMenu(targetMaterial = null) { @@ -22,31 +28,31 @@ StudioControls.Menu { StudioControls.MenuItem { text: qsTr("Apply to selected (replace)") - enabled: root.targetMaterial && materialsModel.hasModelSelection - onTriggered: materialsModel.applyToSelected(root.targetMaterial, false) + enabled: root.targetAvailable && root.hasModelSelection + onTriggered: root.applyToSelected(root.targetMaterial, false) } StudioControls.MenuItem { text: qsTr("Apply to selected (add)") - enabled: root.targetMaterial && materialsModel.hasModelSelection - onTriggered: materialsModel.applyToSelected(root.targetMaterial, true) + enabled: root.targetAvailable && root.hasModelSelection + onTriggered: root.applyToSelected(root.targetMaterial, true) } StudioControls.MenuSeparator {} StudioControls.MenuItem { - enabled: !materialsModel.importerRunning + enabled: root.targetAvailable text: qsTr("Add an instance to project") onTriggered: { - materialsModel.addToProject(root.targetMaterial) + root.addToProject(root.targetMaterial) } } StudioControls.MenuItem { - enabled: !materialsModel.importerRunning && root.targetMaterial && root.targetMaterial.bundleMaterialImported + enabled: root.targetAvailable && root.targetMaterial.bundleMaterialImported text: qsTr("Remove from project") - onTriggered: root.unimport(root.targetMaterial); + onTriggered: root.unimport(root.targetMaterial) } } diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml index 589e0250b5e..50c0336a483 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml @@ -10,6 +10,7 @@ HelperWidgets.ScrollView { id: root clip: true + interactive: !ctxMenu.opened readonly property int cellWidth: 100 readonly property int cellHeight: 120 @@ -31,7 +32,7 @@ HelperWidgets.ScrollView { for (let i = 0; i < categoryRepeater.count; ++i) { let cat = categoryRepeater.itemAt(i) if (cat.visible && !cat.expanded) - cat.expanded = true + cat.expandSection() } } @@ -39,7 +40,11 @@ HelperWidgets.ScrollView { ContentLibraryMaterialContextMenu { id: ctxMenu + hasModelSelection: materialsModel.hasModelSelection + importerRunning: materialsModel.importerRunning + onUnimport: (bundleMat) => root.unimport(bundleMat) + onAddToProject: (bundleMat) => materialsModel.addToProject(bundleMat) } Repeater { @@ -52,10 +57,16 @@ HelperWidgets.ScrollView { caption: bundleCategoryName addTopPadding: false sectionBackgroundColor: "transparent" - visible: bundleCategoryVisible + visible: bundleCategoryVisible && !materialsModel.isEmpty expanded: bundleCategoryExpanded expandOnClick: false onToggleExpand: bundleCategoryExpanded = !bundleCategoryExpanded + onExpand: bundleCategoryExpanded = true + onCollapse: bundleCategoryExpanded = false + + function expandSection() { + bundleCategoryExpanded = true + } Grid { width: root.width @@ -79,13 +90,26 @@ HelperWidgets.ScrollView { } Text { - id: noMatchText - text: qsTr("No match found."); + id: infoText + text: { + if (!materialsModel.matBundleExists) + qsTr("Content Library materials are not installed.") + else if (!rootView.hasQuick3DImport) + qsTr("To use Content Library, first add the QtQuick3D module in the Components view.") + else if (!materialsModel.hasRequiredQuick3DImport) + qsTr("To use Content Library, version 6.3 or later of the QtQuick3D module is required.") + else if (!rootView.hasMaterialLibrary) + qsTr("Content Library is disabled inside a non-visual component.") + else if (!searchBox.isEmpty()) + qsTr("No match found.") + else + "" + } color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.baseFontSize topPadding: 10 leftPadding: 10 - visible: materialsModel.isEmpty && !searchBox.isEmpty() && !materialsModel.hasMaterialRoot + visible: materialsModel.isEmpty } } } diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTextureContextMenu.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTextureContextMenu.qml index 61bd0ebfd7e..b1617578ce1 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTextureContextMenu.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTextureContextMenu.qml @@ -12,9 +12,12 @@ StudioControls.Menu { property var targetTexture: null property bool hasSceneEnv: false + property bool canUse3D: targetTexture && rootView.hasQuick3DImport && rootView.hasMaterialLibrary + function popupMenu(targetTexture = null) { this.targetTexture = targetTexture + rootView.updateSceneEnvState(); popup() } @@ -28,13 +31,13 @@ StudioControls.Menu { StudioControls.MenuItem { text: qsTr("Add texture") - enabled: root.targetTexture + enabled: canUse3D onTriggered: rootView.addTexture(root.targetTexture) } StudioControls.MenuItem { text: qsTr("Add light probe") - enabled: root.hasSceneEnv && root.targetTexture + enabled: root.hasSceneEnv && canUse3D onTriggered: rootView.addLightProbe(root.targetTexture) } } diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml index 4e2854a48bf..c17dcd3a6fb 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml @@ -10,6 +10,7 @@ HelperWidgets.ScrollView { id: root clip: true + interactive: !ctxMenu.opened readonly property int cellWidth: 100 readonly property int cellHeight: 100 @@ -32,7 +33,7 @@ HelperWidgets.ScrollView { for (let i = 0; i < categoryRepeater.count; ++i) { let cat = categoryRepeater.itemAt(i) if (cat.visible && !cat.expanded) - cat.expanded = true + cat.expandSection() } } Column { @@ -52,10 +53,16 @@ HelperWidgets.ScrollView { caption: bundleCategoryName addTopPadding: false sectionBackgroundColor: "transparent" - visible: bundleCategoryVisible + visible: bundleCategoryVisible && !root.model.isEmpty expanded: bundleCategoryExpanded expandOnClick: false onToggleExpand: bundleCategoryExpanded = !bundleCategoryExpanded + onExpand: bundleCategoryExpanded = true + onCollapse: bundleCategoryExpanded = false + + function expandSection() { + bundleCategoryExpanded = true + } Grid { width: root.width @@ -80,13 +87,20 @@ HelperWidgets.ScrollView { } Text { - id: noMatchText - text: qsTr("No match found."); + id: infoText + text: { + if (!root.model.texBundleExists) + qsTr("Content Library textures are not installed.") + else if (!searchBox.isEmpty()) + qsTr("No match found.") + else + "" + } color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.baseFontSize topPadding: 10 leftPadding: 10 - visible: root.model.isEmpty && !searchBox.isEmpty() && !root.model.hasMaterialRoot + visible: root.model.isEmpty } } } diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetDelegate.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetDelegate.qml new file mode 100644 index 00000000000..a28becff06b --- /dev/null +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetDelegate.qml @@ -0,0 +1,327 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import StudioTheme as StudioTheme + +TreeViewDelegate { + id: root + + required property Item assetsView + required property Item assetsRoot + + property bool hasChildWithDropHover: false + property bool isHoveringDrop: false + readonly property string suffix: model.fileName.substr(-4) + readonly property bool isFont: root.suffix === ".ttf" || root.suffix === ".otf" + readonly property bool isEffect: root.suffix === ".qep" + property bool currFileSelected: false + property int initialDepth: -1 + property bool __isDirectory: assetsModel.isDirectory(model.filePath) + property int __currentRow: model.index + property string __itemPath: model.filePath + + readonly property int __fileItemHeight: thumbnailImage.height + readonly property int __dirItemHeight: 21 + + implicitHeight: root.__isDirectory ? root.__dirItemHeight : root.__fileItemHeight + implicitWidth: root.assetsView.width > 0 ? root.assetsView.width : 10 + + leftMargin: root.__isDirectory ? 0 : thumbnailImage.width + + Component.onCompleted: { + // the depth of the root path will become available before we get to the actual + // items we display, so it's safe to set assetsView.rootPathDepth here. All other + // tree items (below the root) will have the indentation (basically, depth) adjusted. + if (model.filePath === assetsModel.rootPath()) { + root.assetsView.rootPathDepth = root.depth + root.assetsView.rootPathRow = root.__currentRow + } else if (model.filePath.includes(assetsModel.rootPath())) { + root.depth -= root.assetsView.rootPathDepth + root.initialDepth = root.depth + } + } + + // workaround for a bug -- might be fixed by https://codereview.qt-project.org/c/qt/qtdeclarative/+/442721 + onYChanged: { + if (root.__currentRow === root.assetsView.firstRow) { + if (root.y > root.assetsView.contentY) { + let item = root.assetsView.itemAtCell(0, root.assetsView.rootPathRow) + if (!item) + root.assetsView.contentY = root.y + } + } + } + + onImplicitWidthChanged: { + // a small hack, to fix a glitch: when resizing the width of the tree view, + // the widths of the delegate items remain the same as before, unless we re-set + // that width explicitly. + var newWidth = root.implicitWidth - (root.assetsView.verticalScrollBar.scrollBarVisible + ? root.assetsView.verticalScrollBar.width + : 0) + bg.width = newWidth + bg.implicitWidth = newWidth + } + + onDepthChanged: { + if (root.depth > root.initialDepth && root.initialDepth >= 0) + root.depth = root.initialDepth + } + + background: Rectangle { + id: bg + + color: { + if (root.__isDirectory && (root.isHoveringDrop || root.hasChildWithDropHover)) + return StudioTheme.Values.themeInteraction + + if (!root.__isDirectory && root.assetsView.selectedAssets[root.__itemPath]) + return StudioTheme.Values.themeInteraction + + if (mouseArea.containsMouse) + return StudioTheme.Values.themeSectionHeadBackground + + return root.__isDirectory + ? StudioTheme.Values.themeSectionHeadBackground + : "transparent" + } + + // this rectangle exists so as to have some visual indentation for the directories + // We prepend a default pane-colored rectangle so that the nested directory will + // look moved a bit to the right + Rectangle { + anchors.top: bg.top + anchors.bottom: bg.bottom + anchors.left: bg.left + + width: root.indentation * root.depth + implicitWidth: root.indentation * root.depth + color: StudioTheme.Values.themePanelBackground + } + } + + contentItem: Text { + id: assetLabel + text: assetLabel.__computeText() + color: StudioTheme.Values.themeTextColor + font.pixelSize: 14 + anchors.verticalCenter: parent.verticalCenter + verticalAlignment: Qt.AlignVCenter + + function __computeText() + { + return root.__isDirectory + ? (root.hasChildren + ? model.display.toUpperCase() + : model.display.toUpperCase() + qsTr(" (empty)")) + : model.display + } + } + + DropArea { + id: treeDropArea + + enabled: true + anchors.fill: parent + + onEntered: (drag) => { + root.assetsRoot.updateDropExtFiles(drag) + root.isHoveringDrop = drag.accepted && root.assetsRoot.dropSimpleExtFiles.length > 0 + if (root.isHoveringDrop) + root.assetsView.startDropHoverOver(root.__currentRow) + } + + onDropped: (drag) => { + root.isHoveringDrop = false + root.assetsView.endDropHover(root.__currentRow) + + let dirPath = root.__isDirectory + ? model.filePath + : assetsModel.parentDirPath(model.filePath); + + rootView.emitExtFilesDrop(root.assetsRoot.dropSimpleExtFiles, + root.assetsRoot.dropComplexExtFiles, + dirPath) + } + + onExited: { + if (root.isHoveringDrop) { + root.isHoveringDrop = false + root.assetsView.endDropHover(root.__currentRow) + } + } + } + + MouseArea { + id: mouseArea + + property bool allowTooltip: true + + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton + + onExited: tooltipBackend.hideTooltip() + onEntered: mouseArea.allowTooltip = true + + onCanceled: { + tooltipBackend.hideTooltip() + mouseArea.allowTooltip = true + } + + onPositionChanged: tooltipBackend.reposition() + + onPressed: (mouse) => { + forceActiveFocus() + mouseArea.allowTooltip = false + tooltipBackend.hideTooltip() + + if (root.__isDirectory) + return + + var ctrlDown = mouse.modifiers & Qt.ControlModifier + if (mouse.button === Qt.LeftButton) { + if (!root.assetsView.isAssetSelected(root.__itemPath) && !ctrlDown) + root.assetsView.clearSelectedAssets() + root.currFileSelected = ctrlDown ? !root.assetsView.isAssetSelected(root.__itemPath) : true + root.assetsView.setAssetSelected(root.__itemPath, root.currFileSelected) + + if (root.currFileSelected) { + let selectedPaths = root.assetsView.selectedPathsAsList() + rootView.startDragAsset(selectedPaths, mapToGlobal(mouse.x, mouse.y)) + } + } else { + if (!root.assetsView.isAssetSelected(root.__itemPath) && !ctrlDown) + root.assetsView.clearSelectedAssets() + root.currFileSelected = root.assetsView.isAssetSelected(root.__itemPath) || !ctrlDown + root.assetsView.setAssetSelected(root.__itemPath, root.currFileSelected) + } + } + + onReleased: (mouse) => { + mouseArea.allowTooltip = true + + if (mouse.button === Qt.LeftButton) { + if (!(mouse.modifiers & Qt.ControlModifier)) + root.assetsView.selectedAssets = {} + root.assetsView.selectedAssets[root.__itemPath] = root.currFileSelected + root.assetsView.selectedAssetsChanged() + } + } + + onDoubleClicked: (mouse) => { + forceActiveFocus() + allowTooltip = false + tooltipBackend.hideTooltip() + if (mouse.button === Qt.LeftButton && isEffect) + rootView.openEffectMaker(filePath) + } + + ToolTip { + visible: !root.isFont && mouseArea.containsMouse && !root.assetsView.contextMenu.visible + text: model.filePath + delay: 1000 + } + + Timer { + interval: 1000 + running: mouseArea.containsMouse && mouseArea.allowTooltip + onTriggered: { + if (suffix === ".ttf" || suffix === ".otf") { + tooltipBackend.name = model.fileName + tooltipBackend.path = model.filePath + tooltipBackend.showTooltip() + } + } + } // Timer + + onClicked: (mouse) => { + if (mouse.button === Qt.LeftButton) + root.__toggleExpandCurrentRow() + else + root.__openContextMenuForCurrentRow() + + + } + } // MouseArea + + function __openContextMenuForCurrentRow() + { + let modelIndex = assetsModel.indexForPath(model.filePath) + + function onFolderCreated(path) { + root.assetsView.addCreatedFolder(path) + } + + if (root.__isDirectory) { + var row = root.assetsView.rowAtIndex(modelIndex) + var expanded = root.assetsView.isExpanded(row) + + var allExpandedState = root.assetsView.computeAllExpandedState() + + function onFolderRenamed() { + if (expanded) + root.assetsView.rowToExpand = row + } + + root.assetsView.contextMenu.openContextMenuForDir(modelIndex, model.filePath, + model.fileName, allExpandedState, onFolderCreated, onFolderRenamed) + } else { + let parentDirIndex = assetsModel.parentDirIndex(model.filePath) + let selectedPaths = root.assetsView.selectedPathsAsList() + root.assetsView.contextMenu.openContextMenuForFile(modelIndex, parentDirIndex, + selectedPaths, onFolderCreated) + } + } + + function __toggleExpandCurrentRow() + { + if (!root.__isDirectory) + return + + let index = root.assetsView.__modelIndex(root.__currentRow) + // if the user manually clicked on a directory, then this is definitely not a + // an automatic request to expand all. + root.assetsView.requestedExpandAll = false + + if (root.assetsView.isExpanded(root.__currentRow)) { + root.assetsView.requestedExpandAll = false + root.assetsView.collapse(root.__currentRow) + } else { + root.assetsView.expand(root.__currentRow) + } + } + + function reloadImage() + { + if (root.__isDirectory) + return + + thumbnailImage.source = "" + thumbnailImage.source = thumbnailImage.__computeSource() + } + + Image { + id: thumbnailImage + visible: !root.__isDirectory + x: root.depth * root.indentation + width: 48 + height: 48 + cache: false + sourceSize.width: 48 + sourceSize.height: 48 + asynchronous: true + fillMode: Image.PreserveAspectFit + source: thumbnailImage.__computeSource() + + function __computeSource() + { + return root.__isDirectory + ? "" + : "image://qmldesigner_assets/" + model.filePath + } + + } // Image +} // TreeViewDelegate diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml index f8c06057346..211cb07d9e2 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml @@ -1,31 +1,26 @@ -// Copyright (C) 2021 The Qt Company Ltd. +// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 -import QtQuick 2.15 -import QtQuick.Controls 2.15 -import QtQuick.Layouts 1.15 -import QtQuickDesignerTheme 1.0 -import HelperWidgets 2.0 -import StudioControls 1.0 as StudioControls -import StudioTheme 1.0 as StudioTheme +import QtQuick +import HelperWidgets as HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme Item { id: root - property var selectedAssets: ({}) - property int allExpandedState: 0 - property string contextFilePath: "" - property var contextDir: undefined - property bool isDirContextMenu: false - // Array of supported externally dropped files that are imported as-is property var dropSimpleExtFiles: [] // Array of supported externally dropped files that trigger custom import process property var dropComplexExtFiles: [] + readonly property int qtVersionAtLeast6_4: rootView.qtVersionIsAtLeast6_4() + property bool __searchBoxEmpty: true + AssetsContextMenu { id: contextMenu + assetsView: assetsView } function clearSearchFilter() @@ -63,7 +58,7 @@ Item { onDropped: { rootView.handleExtFilesDrop(root.dropSimpleExtFiles, root.dropComplexExtFiles, - assetsModel.rootDir().dirPath) + assetsModel.rootPath()) } Canvas { // marker for the drop area @@ -90,11 +85,15 @@ Item { anchors.fill: parent acceptedButtons: Qt.RightButton onClicked: { - if (!assetsModel.isEmpty) { - root.contextFilePath = "" - root.contextDir = assetsModel.rootDir() - root.isDirContextMenu = false - contextMenu.popup() + if (assetsModel.haveFiles) { + function onFolderCreated(path) { + assetsView.addCreatedFolder(path) + } + + var rootIndex = assetsModel.rootIndex() + var dirPath = assetsModel.filePath(rootIndex) + var dirName = assetsModel.fileName(rootIndex) + contextMenu.openContextMenuForRoot(rootIndex, dirPath, dirName, onFolderCreated) } } } @@ -103,13 +102,8 @@ Item { function handleViewFocusOut() { contextMenu.close() - root.selectedAssets = {} - root.selectedAssetsChanged() - } - - RegExpValidator { - id: folderNameValidator - regExp: /^(\w[^*/> rootView.handleSearchFilterChanged(searchText) + onSearchChanged: (searchText) => { + updateSearchFilterTimer.restart() + } } - IconButton { + Timer { + id: updateSearchFilterTimer + interval: 200 + repeat: false + + onTriggered: { + assetsView.resetVerticalScrollPosition() + rootView.handleSearchFilterChanged(searchBox.text) + assetsView.expandAll() + + if (root.__searchBoxEmpty && searchBox.text) + root.__searchBoxEmpty = false + else if (!root.__searchBoxEmpty && !searchBox.text) + root.__searchBoxEmpty = true + } + } + + HelperWidgets.IconButton { id: addAssetButton anchors.verticalCenter: parent.verticalCenter tooltip: qsTr("Add a new asset to the project.") @@ -146,14 +159,13 @@ Item { leftPadding: 10 color: StudioTheme.Values.themeTextColor font.pixelSize: 12 - visible: assetsModel.isEmpty && !searchBox.isEmpty() + visible: !assetsModel.haveFiles && !root.__searchBoxEmpty } - Item { // placeholder when the assets library is empty width: parent.width height: parent.height - searchRow.height - visible: assetsModel.isEmpty && searchBox.isEmpty() + visible: !assetsModel.haveFiles && root.__searchBoxEmpty clip: true DropArea { // handles external drop (goes into default folder based on suffix) @@ -164,7 +176,7 @@ Item { } onDropped: { - rootView.handleExtFilesDrop(root.dropSimpleExtFiles, root.dropComplexExtFiles) + rootView.emitExtFilesDrop(root.dropSimpleExtFiles, root.dropComplexExtFiles) } Column { @@ -217,8 +229,11 @@ Item { AssetsView { id: assetsView + assetsRoot: root + contextMenu: contextMenu + width: parent.width height: parent.height - y } - } + } // Column } diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml index 5caa139651f..6dedaf79012 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml @@ -1,90 +1,148 @@ -/**************************************************************************** -** -** Copyright (C) 2022 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -****************************************************************************/ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 import QtQuick import QtQuick.Controls -import QtQuick.Layouts -import QtQuickDesignerTheme -import HelperWidgets as HelperWidgets import StudioControls as StudioControls import StudioTheme as StudioTheme StudioControls.Menu { - id: contextMenu + id: root + + required property Item assetsView + + property bool __isDirectory: false + property var __fileIndex: null + property string __dirPath: "" + property string __dirName: "" + property var __onFolderCreated: null + property var __onFolderRenamed: null + property var __dirIndex: null + property string __allExpandedState: "" + property var __selectedAssetPathsList: null closePolicy: Popup.CloseOnPressOutside | Popup.CloseOnEscape - onOpened: { - var numSelected = Object.values(root.selectedAssets).filter(p => p).length - deleteFileItem.text = numSelected > 1 ? qsTr("Delete Files") : qsTr("Delete File") + function openContextMenuForRoot(rootModelIndex, dirPath, dirName, onFolderCreated) + { + root.__onFolderCreated = onFolderCreated + root.__fileIndex = "" + root.__dirPath = dirPath + root.__dirName = dirName + root.__dirIndex = rootModelIndex + root.__isDirectory = false + root.popup() + } + + function openContextMenuForDir(dirModelIndex, dirPath, dirName, allExpandedState, + onFolderCreated, onFolderRenamed) + { + root.__onFolderCreated = onFolderCreated + root.__onFolderRenamed = onFolderRenamed + root.__dirPath = dirPath + root.__dirName = dirName + root.__fileIndex = "" + root.__dirIndex = dirModelIndex + root.__isDirectory = true + root.__allExpandedState = allExpandedState + root.popup() + } + + function openContextMenuForFile(fileIndex, dirModelIndex, selectedAssetPathsList, onFolderCreated) + { + if (selectedAssetPathsList.length > 1) { + deleteFileItem.text = qsTr("Delete Files") + addTexturesItem.text = qsTr("Add Textures") + } else { + deleteFileItem.text = qsTr("Delete File") + addTexturesItem.text = qsTr("Add Texture") + } + + root.__onFolderCreated = onFolderCreated + root.__selectedAssetPathsList = selectedAssetPathsList + root.__fileIndex = fileIndex + root.__dirIndex = dirModelIndex + root.__dirPath = assetsModel.filePath(dirModelIndex) + root.__isDirectory = false + root.popup() } StudioControls.MenuItem { text: qsTr("Expand All") - enabled: root.allExpandedState !== 1 - visible: root.isDirContextMenu + enabled: root.__allExpandedState !== "all_expanded" + visible: root.__isDirectory height: visible ? implicitHeight : 0 - onTriggered: assetsModel.toggleExpandAll(true) + onTriggered: root.assetsView.expandAll() } StudioControls.MenuItem { text: qsTr("Collapse All") - enabled: root.allExpandedState !== 2 - visible: root.isDirContextMenu + enabled: root.__allExpandedState !== "all_collapsed" + visible: root.__isDirectory height: visible ? implicitHeight : 0 - onTriggered: assetsModel.toggleExpandAll(false) + onTriggered: root.assetsView.collapseAll() } StudioControls.MenuSeparator { - visible: root.isDirContextMenu + visible: root.__isDirectory height: visible ? StudioTheme.Values.border : 0 } + StudioControls.MenuItem { + id: addTexturesItem + text: qsTr("Add Texture") + visible: root.__fileIndex && assetsModel.allFilePathsAreImages(root.__selectedAssetPathsList) + height: addTexturesItem.visible ? addTexturesItem.implicitHeight : 0 + onTriggered: rootView.addTextures(root.__selectedAssetPathsList) + } + + StudioControls.MenuItem { + id: addLightProbes + text: qsTr("Add Light Probe") + visible: root.__fileIndex && root.__selectedAssetPathsList.length === 1 + && assetsModel.allFilePathsAreImages(root.__selectedAssetPathsList) + height: addLightProbes.visible ? addLightProbes.implicitHeight : 0 + onTriggered: rootView.addLightProbe(root.__selectedAssetPathsList[0]) + } + StudioControls.MenuItem { id: deleteFileItem text: qsTr("Delete File") - visible: root.contextFilePath + visible: root.__fileIndex height: deleteFileItem.visible ? deleteFileItem.implicitHeight : 0 onTriggered: { - assetsModel.deleteFiles(Object.keys(root.selectedAssets).filter(p => root.selectedAssets[p])) + let deleted = assetsModel.requestDeleteFiles(root.__selectedAssetPathsList) + if (!deleted) + confirmDeleteFiles.open() + } + + ConfirmDeleteFilesDialog { + id: confirmDeleteFiles + parent: root.assetsView + files: root.__selectedAssetPathsList + + onAccepted: root.assetsView.selectedAssets = {} } } StudioControls.MenuSeparator { - visible: root.contextFilePath + visible: root.__fileIndex height: visible ? StudioTheme.Values.border : 0 } StudioControls.MenuItem { text: qsTr("Rename Folder") - visible: root.isDirContextMenu + visible: root.__isDirectory height: visible ? implicitHeight : 0 onTriggered: renameFolderDialog.open() RenameFolderDialog { id: renameFolderDialog + parent: root.assetsView + dirPath: root.__dirPath + dirName: root.__dirName + + onAccepted: root.__onFolderRenamed() } } @@ -93,6 +151,10 @@ StudioControls.Menu { NewFolderDialog { id: newFolderDialog + parent: root.assetsView + dirPath: root.__dirPath + + onAccepted: root.__onFolderCreated(newFolderDialog.createdDirPath) } onTriggered: newFolderDialog.open() @@ -100,21 +162,25 @@ StudioControls.Menu { StudioControls.MenuItem { text: qsTr("Delete Folder") - visible: root.isDirContextMenu + visible: root.__isDirectory height: visible ? implicitHeight : 0 ConfirmDeleteFolderDialog { id: confirmDeleteFolderDialog + parent: root.assetsView + dirName: root.__dirName + dirIndex: root.__dirIndex } onTriggered: { - var dirEmpty = !(root.contextDir.dirsModel && root.contextDir.dirsModel.rowCount() > 0) - && !(root.contextDir.filesModel && root.contextDir.filesModel.rowCount() > 0); - - if (dirEmpty) - assetsModel.deleteFolder(root.contextDir.dirPath) - else + if (!assetsModel.hasChildren(root.__dirIndex)) { + // NOTE: the folder may still not be empty -- it doesn't have files visible to the + // user, but that doesn't mean that there are no other files (e.g. files of unknown + // types) on disk in this directory. + assetsModel.deleteFolderRecursively(root.__dirIndex) + } else { confirmDeleteFolderDialog.open() + } } } } diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsView.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsView.qml index 77a784d92d5..782cca5ebc6 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsView.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsView.qml @@ -1,255 +1,311 @@ -/**************************************************************************** -** -** Copyright (C) 2022 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -****************************************************************************/ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 import QtQuick import QtQuick.Controls -import QtQuick.Layouts -import QtQuickDesignerTheme -import HelperWidgets +import HelperWidgets as HelperWidgets import StudioControls as StudioControls -import StudioTheme as StudioTheme -ScrollView { // TODO: experiment using ListView instead of ScrollView + Column - id: assetsView +TreeView { + id: root clip: true - interactive: assetsView.verticalScrollBarVisible && !contextMenu.opened + interactive: verticalScrollBar.visible && !root.contextMenu.opened + reuseItems: false + boundsBehavior: Flickable.StopAtBounds + rowSpacing: 5 - Column { - Repeater { - model: assetsModel // context property - delegate: dirSection + required property Item assetsRoot + required property StudioControls.Menu contextMenu + property alias verticalScrollBar: verticalScrollBar + + property var selectedAssets: ({}) + + // used to see if the op requested is to expand or to collapse. + property int lastRowCount: -1 + // we need this to know if we need to expand further, while we're in onRowsChanged() + property bool requestedExpandAll: true + // used to compute the visual depth of the items we show to the user. + property int rootPathDepth: 0 + property int rootPathRow: 0 + // i.e. first child of the root path + readonly property int firstRow: root.rootPathRow + 1 + property int rowToExpand: -1 + property var __createdDirectories: [] + + rowHeightProvider: (row) => { + if (row <= root.rootPathRow) + return 0 + + return -1 + } + + ScrollBar.vertical: HelperWidgets.VerticalScrollBar { + id: verticalScrollBar + scrollBarVisible: root.contentHeight > root.height + } + + model: assetsModel + + onRowsChanged: { + if (root.rows > root.rootPathRow + 1 && !assetsModel.haveFiles || + root.rows <= root.rootPathRow + 1 && assetsModel.haveFiles) { + assetsModel.syncHaveFiles() } - Component { - id: dirSection + updateRows() + } - Section { - id: section + Timer { + id: updateRowsTimer + interval: 200 + repeat: false - width: assetsView.width - - (assetsView.verticalScrollBarVisible ? assetsView.verticalThickness : 0) - 5 - caption: dirName - sectionHeight: 30 - sectionFontSize: 15 - leftPadding: 0 - topPadding: dirDepth > 0 ? 5 : 0 - bottomPadding: 0 - hideHeader: dirDepth === 0 - showLeftBorder: dirDepth > 0 - expanded: dirExpanded - visible: dirVisible - expandOnClick: false - useDefaulContextMenu: false - dropEnabled: true - - onToggleExpand: { - dirExpanded = !dirExpanded - } - - onDropEnter: (drag)=> { - root.updateDropExtFiles(drag) - section.highlight = drag.accepted && root.dropSimpleExtFiles.length > 0 - } - - onDropExit: { - section.highlight = false - } - - onDrop: { - section.highlight = false - rootView.handleExtFilesDrop(root.dropSimpleExtFiles, - root.dropComplexExtFiles, - dirPath) - } - - onShowContextMenu: { - root.contextFilePath = "" - root.contextDir = model - root.isDirContextMenu = true - root.allExpandedState = assetsModel.getAllExpandedState() - contextMenu.popup() - } - - Column { - spacing: 5 - leftPadding: 5 - - Repeater { - model: dirsModel - delegate: dirSection - } - - Repeater { - model: filesModel - delegate: fileSection - } - - Text { - text: qsTr("Empty folder") - color: StudioTheme.Values.themeTextColorDisabled - font.pixelSize: 12 - visible: !(dirsModel && dirsModel.rowCount() > 0) - && !(filesModel && filesModel.rowCount() > 0) - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.RightButton - onClicked: { - root.contextFilePath = "" - root.contextDir = model - root.isDirContextMenu = true - contextMenu.popup() - } - } - } - } - } - } - - Component { - id: fileSection - - Rectangle { - width: assetsView.width - - (assetsView.verticalScrollBarVisible ? assetsView.verticalThickness : 0) - height: img.height - color: root.selectedAssets[filePath] - ? StudioTheme.Values.themeInteraction - : (mouseArea.containsMouse ? StudioTheme.Values.themeSectionHeadBackground - : "transparent") - - Row { - spacing: 5 - - Image { - id: img - asynchronous: true - fillMode: Image.PreserveAspectFit - width: 48 - height: 48 - source: "image://qmldesigner_assets/" + filePath - } - - Text { - text: fileName - color: StudioTheme.Values.themeTextColor - font.pixelSize: 14 - anchors.verticalCenter: parent.verticalCenter - } - } - - readonly property string suffix: fileName.substr(-4) - readonly property bool isFont: suffix === ".ttf" || suffix === ".otf" - readonly property bool isEffect: suffix === ".qep" - property bool currFileSelected: false - - MouseArea { - id: mouseArea - - property bool allowTooltip: true - - anchors.fill: parent - hoverEnabled: true - acceptedButtons: Qt.LeftButton | Qt.RightButton - - onExited: tooltipBackend.hideTooltip() - onEntered: allowTooltip = true - onCanceled: { - tooltipBackend.hideTooltip() - allowTooltip = true - } - onPositionChanged: tooltipBackend.reposition() - onPressed: (mouse) => { - forceActiveFocus() - allowTooltip = false - tooltipBackend.hideTooltip() - var ctrlDown = mouse.modifiers & Qt.ControlModifier - if (mouse.button === Qt.LeftButton) { - if (!root.selectedAssets[filePath] && !ctrlDown) - root.selectedAssets = {} - currFileSelected = ctrlDown ? !root.selectedAssets[filePath] : true - root.selectedAssets[filePath] = currFileSelected - root.selectedAssetsChanged() - - if (currFileSelected) { - rootView.startDragAsset( - Object.keys(root.selectedAssets).filter(p => root.selectedAssets[p]), - mapToGlobal(mouse.x, mouse.y)) - } - } else { - if (!root.selectedAssets[filePath] && !ctrlDown) - root.selectedAssets = {} - currFileSelected = root.selectedAssets[filePath] || !ctrlDown - root.selectedAssets[filePath] = currFileSelected - root.selectedAssetsChanged() - - root.contextFilePath = filePath - root.contextDir = model.fileDir - root.isDirContextMenu = false - - contextMenu.popup() - } - } - - onReleased: (mouse) => { - allowTooltip = true - if (mouse.button === Qt.LeftButton) { - if (!(mouse.modifiers & Qt.ControlModifier)) - root.selectedAssets = {} - root.selectedAssets[filePath] = currFileSelected - root.selectedAssetsChanged() - } - } - - onDoubleClicked: (mouse) => { - forceActiveFocus() - allowTooltip = false - tooltipBackend.hideTooltip() - if (mouse.button === Qt.LeftButton && isEffect) - rootView.openEffectMaker(filePath) - } - - ToolTip { - visible: !isFont && mouseArea.containsMouse && !contextMenu.visible - text: filePath - delay: 1000 - } - - Timer { - interval: 1000 - running: mouseArea.containsMouse && mouseArea.allowTooltip - onTriggered: { - if (suffix === ".ttf" || suffix === ".otf") { - tooltipBackend.name = fileName - tooltipBackend.path = filePath - tooltipBackend.showTooltip() - } - } - } - } - } + onTriggered: { + root.updateRows() } } -} + + Connections { + target: rootView + + function onDirectoryCreated(path) + { + root.__createdDirectories.push(path) + + updateRowsTimer.restart() + } + } + + Connections { + target: assetsModel + function onDirectoryLoaded(path) + { + // updating rows for safety: the rows might have been created before the + // directory (esp. the root path) has been loaded, so we must make sure all rows are + // expanded -- otherwise, the tree may not become visible. + + updateRowsTimer.restart() + + let idx = assetsModel.indexForPath(path) + let row = root.rowAtIndex(idx) + let column = root.columnAtIndex(idx) + + if (row >= root.rootPathRow && !root.isExpanded(row)) + root.expand(row) + } + + function onRootPathChanged() + { + // when we switch from one project to another, we need to reset the state of the + // view: make sure we will do an "expand all" (otherwise, the whole tree might + // be collapsed, and with our visible root not being the actual root of the tree, + // the entire tree would be invisible) + root.lastRowCount = -1 + root.requestedExpandAll = true + } + + function onFileChanged(filePath) + { + rootView.invalidateThumbnail(filePath) + + let index = assetsModel.indexForPath(filePath) + let cell = root.cellAtIndex(index) + let fileItem = root.itemAtCell(cell) + + if (fileItem) + fileItem.reloadImage() + } + + } // Connections + + function addCreatedFolder(path) + { + root.__createdDirectories.push(path) + } + + function selectedPathsAsList() + { + return Object.keys(root.selectedAssets) + .filter(itemPath => root.selectedAssets[itemPath]) + } + + // workaround for a bug -- might be fixed by https://codereview.qt-project.org/c/qt/qtdeclarative/+/442721 + function resetVerticalScrollPosition() + { + root.contentY = 0 + } + + function updateRows() + { + if (root.rows <= 0) + return + + while (root.__createdDirectories.length > 0) { + let dirPath = root.__createdDirectories.pop() + let index = assetsModel.indexForPath(dirPath) + let row = root.rowAtIndex(index) + + if (row > 0) + root.expand(row) + else if (row === -1 && assetsModel.indexIsValid(index)) { + // It is possible that this directory, dirPath, was created inside of a parent + // directory that was not yet expanded in the TreeView. This can happen with the + // bridge plugin. In such a situation, we don't have a "row" for it yet, so we have + // to expand its parents, from root to our `index` + let parents = assetsModel.parentIndices(index); + parents.reverse().forEach(idx => { + let row = root.rowAtIndex(idx) + if (row > 0) + root.expand(row) + }) + } + } + + // we have no way to know beyond doubt here if updateRows() was called due + // to a request to expand or to collapse rows - but it should be safe to + // assume that, if we have more rows now than the last time, then it's an expand + var expanding = (root.rows >= root.lastRowCount) + + if (expanding) { + if (root.requestedExpandAll) + root.__doExpandAll() + } else { + if (root.rowToExpand > 0) { + root.expand(root.rowToExpand) + root.rowToExpand = -1 + } + + // on collapsing, set expandAll flag to false. + root.requestedExpandAll = false; + } + + root.lastRowCount = root.rows + } + + function __doExpandAll() + { + let expandedAny = false + for (let nRow = 0; nRow < root.rows; ++nRow) { + let index = root.__modelIndex(nRow) + if (assetsModel.isDirectory(index) && !root.isExpanded(nRow)) { + root.expand(nRow); + expandedAny = true + } + } + + if (!expandedAny) + Qt.callLater(root.forceLayout) + } + + function expandAll() + { + // In order for __doExpandAll() to be called repeatedly (every time a new node is + // loaded, and then, expanded), we need to set requestedExpandAll to true. + root.requestedExpandAll = true + root.__doExpandAll() + } + + function collapseAll() + { + root.resetVerticalScrollPosition() + + // collapse all, except for the root path - from the last item (leaves) up to the root + for (let nRow = root.rows - 1; nRow >= 0; --nRow) { + let index = root.__modelIndex(nRow) + // we don't want to collapse the root path, because doing so will hide the contents + // of the tree. + if (assetsModel.filePath(index) === assetsModel.rootPath()) + break + + root.collapse(nRow) + } + } + + // workaround for a bug -- might be fixed by https://codereview.qt-project.org/c/qt/qtdeclarative/+/442721 + onContentHeightChanged: { + if (root.contentHeight <= root.height) { + let first = root.itemAtCell(0, root.firstRow) + if (!first) + root.contentY = 0 + } + } + + function computeAllExpandedState() + { + var dirsWithChildren = [...Array(root.rows).keys()].filter(row => { + let index = root.__modelIndex(row) + return assetsModel.isDirectory(index) && assetsModel.hasChildren(index) + }) + + var countExpanded = dirsWithChildren.filter(row => root.isExpanded(row)).length + + if (countExpanded === dirsWithChildren.length) + return "all_expanded" + + if (countExpanded === 0) + return "all_collapsed" + return "" + } + + function startDropHoverOver(row) + { + let index = root.__modelIndex(row) + if (assetsModel.isDirectory(index)) + return + + let parentItem = root.__getDelegateParentForIndex(index) + if (parentItem) + parentItem.hasChildWithDropHover = true + } + + function endDropHover(row) + { + let index = root.__modelIndex(row) + if (assetsModel.isDirectory(index)) + return + + let parentItem = root.__getDelegateParentForIndex(index) + if (parentItem) + parentItem.hasChildWithDropHover = false + } + + function isAssetSelected(itemPath) + { + return root.selectedAssets[itemPath] ? true : false + } + + function clearSelectedAssets() + { + root.selectedAssets = {} + } + + function setAssetSelected(itemPath, selected) + { + root.selectedAssets[itemPath] = selected + root.selectedAssetsChanged() + } + + function __getDelegateParentForIndex(index) + { + let parentIndex = assetsModel.parentDirIndex(index) + let parentCell = root.cellAtIndex(parentIndex) + return root.itemAtCell(parentCell) + } + + function __modelIndex(row) + { + // The modelIndex() function exists since 6.3. In Qt 6.3, this modelIndex() function was a + // member of the TreeView, while in Qt6.4 it was moved to TableView. In Qt6.4, the order of + // the arguments was changed. + if (assetsRoot.qtVersionAtLeast6_4) + return root.modelIndex(0, row) + else + return root.modelIndex(row, 0) + } + + delegate: AssetDelegate { + assetsView: root + assetsRoot: root.assetsRoot + indentation: 5 + } +} // TreeView diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/ConfirmDeleteFilesDialog.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ConfirmDeleteFilesDialog.qml new file mode 100644 index 00000000000..57253864eba --- /dev/null +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ConfirmDeleteFilesDialog.qml @@ -0,0 +1,107 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import HelperWidgets as HelperWidgets +import StudioTheme as StudioTheme +import StudioControls as StudioControls + +Dialog { + id: root + title: qsTr("Confirm Delete Files") + anchors.centerIn: parent + closePolicy: Popup.CloseOnEscape + implicitWidth: 350 + modal: true + + required property var files + + contentItem: Column { + spacing: 20 + width: parent.width + + Text { + id: confirmDeleteText + + text: root.files && root.files.length + ? qsTr("Some files might be in use. Delete anyway?") + : "" + color: StudioTheme.Values.themeTextColor + wrapMode: Text.WordWrap + width: root.width + leftPadding: 10 + rightPadding: 10 + + Keys.onEnterPressed: btnDelete.onClicked() + Keys.onReturnPressed: btnDelete.onClicked() + } + + Loader { + id: fileListLoader + sourceComponent: null + width: parent.width - 20 + x: 10 + height: 50 + } + + StudioControls.CheckBox { + id: dontAskAgain + text: qsTr("Do not ask this again") + actionIndicatorVisible: false + width: parent.width + x: 10 + } + + Row { + anchors.right: parent.right + + HelperWidgets.Button { + id: btnDelete + + text: qsTr("Delete") + onClicked: { + assetsModel.deleteFiles(root.files, dontAskAgain.checked) + root.accept() + } + } + + HelperWidgets.Button { + text: qsTr("Cancel") + onClicked: root.reject() + } + } + } + + onOpened: { + fileListLoader.sourceComponent = null + fileListLoader.sourceComponent = fileListComponent + + confirmDeleteText.forceActiveFocus() + } + + onClosed: fileListLoader.sourceComponent = null + + Component { + id: fileListComponent + + ListView { + id: filesListView + model: root.files + boundsBehavior: Flickable.StopAtBounds + clip: true + + ScrollBar.vertical: HelperWidgets.VerticalScrollBar { + id: verticalScrollBar + scrollBarVisible: filesListView.contentHeight > filesListView.height + } + + delegate: Text { + elide: Text.ElideLeft + text: model.modelData.replace(assetsModel.currentProjectDirPath(), "") + color: StudioTheme.Values.themeTextColor + width: parent.width - (verticalScrollBar.scrollBarVisible ? verticalScrollBar.width : 0) + } + } // ListView + } // Component +} diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/ConfirmDeleteFolderDialog.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ConfirmDeleteFolderDialog.qml index a4fd300975e..c623af862f5 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/ConfirmDeleteFolderDialog.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ConfirmDeleteFolderDialog.qml @@ -1,38 +1,13 @@ -/**************************************************************************** -** -** Copyright (C) 2022 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -****************************************************************************/ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 import QtQuick import QtQuick.Controls -import QtQuick.Layouts -import QtQuickDesignerTheme -import HelperWidgets -import StudioControls as StudioControls +import HelperWidgets as HelperWidgets import StudioTheme as StudioTheme Dialog { - id: confirmDeleteFolderDialog + id: root title: qsTr("Folder Not Empty") anchors.centerIn: parent @@ -40,6 +15,9 @@ Dialog { implicitWidth: 300 modal: true + required property string dirName + required property var dirIndex + contentItem: Column { spacing: 20 width: parent.width @@ -47,11 +25,10 @@ Dialog { Text { id: folderNotEmpty - text: qsTr("Folder \"%1\" is not empty. Delete it anyway?") - .arg(root.contextDir ? root.contextDir.dirName : "") + text: qsTr("Folder \"%1\" is not empty. Delete it anyway?").arg(root.dirName) color: StudioTheme.Values.themeTextColor wrapMode: Text.WordWrap - width: confirmDeleteFolderDialog.width + width: root.width leftPadding: 10 rightPadding: 10 @@ -63,27 +40,27 @@ Dialog { text: qsTr("If the folder has assets in use, deleting it might cause the project to not work correctly.") color: StudioTheme.Values.themeTextColor wrapMode: Text.WordWrap - width: confirmDeleteFolderDialog.width + width: root.width leftPadding: 10 rightPadding: 10 } Row { anchors.right: parent.right - Button { + HelperWidgets.Button { id: btnDelete text: qsTr("Delete") onClicked: { - assetsModel.deleteFolder(root.contextDir.dirPath) - confirmDeleteFolderDialog.accept() + assetsModel.deleteFolderRecursively(root.dirIndex) + root.accept() } } - Button { + HelperWidgets.Button { text: qsTr("Cancel") - onClicked: confirmDeleteFolderDialog.reject() + onClicked: root.reject() } } } diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/ErrorDialog.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ErrorDialog.qml new file mode 100644 index 00000000000..ce5989e2e6c --- /dev/null +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ErrorDialog.qml @@ -0,0 +1,40 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import HelperWidgets as HelperWidgets +import StudioTheme as StudioTheme + +Dialog { + id: root + + required property string message + + anchors.centerIn: parent + closePolicy: Popup.CloseOnEscape + implicitWidth: 300 + modal: true + + contentItem: Column { + spacing: 20 + width: parent.width + + Text { + text: root.message + color: StudioTheme.Values.themeTextColor + wrapMode: Text.WordWrap + width: root.width + leftPadding: 10 + rightPadding: 10 + } + + HelperWidgets.Button { + text: qsTr("Close") + anchors.right: parent.right + onClicked: root.reject() + } + } + + onOpened: root.forceActiveFocus() +} diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemDelegate.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemDelegate.qml index b454ca7d846..72e7b8ba39d 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemDelegate.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemDelegate.qml @@ -68,6 +68,8 @@ Item { id: mouseRegion anchors.fill: parent + tooltip: toolTip + onShowContextMenu: delegateRoot.showContextMenu() onPressed: (mouse)=> { allowTooltip = false diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/NewFolderDialog.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/NewFolderDialog.qml index 130026ddce1..7b38c922156 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/NewFolderDialog.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/NewFolderDialog.qml @@ -1,44 +1,35 @@ -/**************************************************************************** -** -** Copyright (C) 2022 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -****************************************************************************/ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 import QtQuick import QtQuick.Controls -import QtQuick.Layouts -import QtQuickDesignerTheme -import HelperWidgets +import HelperWidgets as HelperWidgets import StudioControls as StudioControls import StudioTheme as StudioTheme Dialog { - id: newFolderDialog + id: root title: qsTr("Create New Folder") anchors.centerIn: parent closePolicy: Popup.CloseOnEscape modal: true + required property string dirPath + property string createdDirPath: "" + readonly property int __maxPath: 260 + + HelperWidgets.RegExpValidator { + id: folderNameValidator + regExp: /^(\w[^*/> root.__maxPath + } + Item { // spacer width: 1 height: 20 @@ -76,20 +78,23 @@ Dialog { Row { anchors.right: parent.right - Button { + HelperWidgets.Button { id: btnCreate text: qsTr("Create") - enabled: folderName.text !== "" + enabled: folderName.text !== "" && root.createdDirPath.length <= root.__maxPath onClicked: { - assetsModel.addNewFolder(root.contextDir.dirPath + '/' + folderName.text) - newFolderDialog.accept() + root.createdDirPath = root.dirPath + '/' + folderName.text + if (assetsModel.addNewFolder(root.createdDirPath)) + root.accept() + else + creationFailedDialog.open() } } - Button { + HelperWidgets.Button { text: qsTr("Cancel") - onClicked: newFolderDialog.reject() + onClicked: root.reject() } } } @@ -99,4 +104,8 @@ Dialog { folderName.selectAll() folderName.forceActiveFocus() } + + onRejected: { + root.createdDirPath = "" + } } diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/RenameFolderDialog.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/RenameFolderDialog.qml index 351c0a35fc8..e94ba47f732 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/RenameFolderDialog.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/RenameFolderDialog.qml @@ -1,38 +1,14 @@ -/**************************************************************************** -** -** Copyright (C) 2022 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -****************************************************************************/ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 import QtQuick import QtQuick.Controls -import QtQuick.Layouts -import QtQuickDesignerTheme -import HelperWidgets +import HelperWidgets as HelperWidgets import StudioControls as StudioControls import StudioTheme as StudioTheme Dialog { - id: renameFolderDialog + id: root title: qsTr("Rename Folder") anchors.centerIn: parent @@ -41,6 +17,13 @@ Dialog { modal: true property bool renameError: false + required property string dirPath + required property string dirName + + HelperWidgets.RegExpValidator { + id: folderNameValidator + regExp: /^(\w[^*/> { - if (materialBrowserModel.hasMaterialRoot || !materialBrowserModel.hasQuick3DImport) + if (!root.enableUiElements) return; var matsSecBottom = mapFromItem(materialsSection, 0, materialsSection.y).y + materialsSection.height; - if (!materialBrowserModel.hasMaterialRoot && materialBrowserModel.hasQuick3DImport - && mouse.y < matsSecBottom) { + if (mouse.y < matsSecBottom) ctxMenu.popupMenu() - } + else + ctxMenuTextures.popupMenu() } } @@ -83,6 +86,10 @@ Item { id: ctxMenu } + TextureBrowserContextMenu { + id: ctxMenuTextures + } + Column { id: col y: 5 @@ -90,37 +97,25 @@ Item { Row { width: root.width - enabled: !materialBrowserModel.hasMaterialRoot && materialBrowserModel.hasQuick3DImport + enabled: root.enableUiElements StudioControls.SearchBox { id: searchBox - width: root.width - addMaterialButton.width + width: root.width onSearchChanged: (searchText) => { rootView.handleSearchFilterChanged(searchText) } } - - IconButton { - id: addMaterialButton - - tooltip: qsTr("Add a material.") - - icon: StudioTheme.Constants.plus - anchors.verticalCenter: parent.verticalCenter - buttonSize: searchBox.height - onClicked: materialBrowserModel.addNewMaterial() - enabled: materialBrowserModel.hasQuick3DImport - } } Text { text: { - if (materialBrowserModel.hasMaterialRoot) - qsTr("Material Browser is disabled inside a material component.") - else if (!materialBrowserModel.hasQuick3DImport) + if (!materialBrowserModel.hasQuick3DImport) qsTr("To use Material Browser, first add the QtQuick3D module in the Components view.") + else if (!materialBrowserModel.hasMaterialLibrary) + qsTr("Material Browser is disabled inside a non-visual component.") else "" } @@ -141,122 +136,191 @@ Item { width: root.width height: root.height - searchBox.height clip: true - visible: materialBrowserModel.hasQuick3DImport && !materialBrowserModel.hasMaterialRoot - interactive: !ctxMenu.opened + visible: root.enableUiElements + interactive: !ctxMenu.opened && !ctxMenuTextures.opened Column { - Section { - id: materialsSection - + Item { width: root.width - caption: qsTr("Materials") - dropEnabled: true + height: materialsSection.height - onDropEnter: (drag) => { - drag.accepted = drag.formats[0] === "application/vnd.qtdesignstudio.bundlematerial" - materialsSection.highlight = drag.accepted - } + Section { + id: materialsSection - onDropExit: { - materialsSection.highlight = false - } + width: root.width + caption: qsTr("Materials") + dropEnabled: true - onDrop: { - materialsSection.highlight = false - rootView.acceptBundleMaterialDrop() - } + onDropEnter: (drag) => { + drag.accepted = drag.formats[0] === "application/vnd.qtdesignstudio.bundlematerial" + materialsSection.highlight = drag.accepted + } - Grid { - id: grid + onDropExit: { + materialsSection.highlight = false + } - width: scrollView.width - leftPadding: 5 - rightPadding: 5 - bottomPadding: 5 - columns: root.width / root.cellWidth + onDrop: { + materialsSection.highlight = false + rootView.acceptBundleMaterialDrop() + } - Repeater { - id: materialRepeater + Grid { + id: grid - model: materialBrowserModel - delegate: MaterialItem { - width: root.cellWidth - height: root.cellHeight + width: scrollView.width + leftPadding: 5 + rightPadding: 5 + bottomPadding: 5 + columns: root.width / root.cellWidth - onShowContextMenu: { - ctxMenu.popupMenu(this, model) + Repeater { + id: materialRepeater + + model: materialBrowserModel + delegate: MaterialItem { + width: root.cellWidth + height: root.cellHeight + + onShowContextMenu: { + ctxMenu.popupMenu(this, model) + } } } } + + Text { + text: qsTr("No match found."); + color: StudioTheme.Values.themeTextColor + font.pixelSize: StudioTheme.Values.baseFontSize + leftPadding: 10 + visible: materialBrowserModel.isEmpty && !searchBox.isEmpty() + } + + Text { + text:qsTr("There are no materials in this project.
Select '+' to create one.") + visible: materialBrowserModel.isEmpty && searchBox.isEmpty() + textFormat: Text.RichText + color: StudioTheme.Values.themeTextColor + font.pixelSize: StudioTheme.Values.mediumFontSize + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + width: root.width + } } - Text { - text: qsTr("No match found."); - color: StudioTheme.Values.themeTextColor - font.pixelSize: StudioTheme.Values.baseFontSize - leftPadding: 10 - visible: materialBrowserModel.isEmpty && !searchBox.isEmpty() && !materialBrowserModel.hasMaterialRoot - } + IconButton { + id: addMaterialButton - Text { - text:qsTr("There are no materials in this project.
Select '+' to create one.") - visible: materialBrowserModel.isEmpty && searchBox.isEmpty() - textFormat: Text.RichText - color: StudioTheme.Values.themeTextColor - font.pixelSize: StudioTheme.Values.mediumFontSize - horizontalAlignment: Text.AlignHCenter - wrapMode: Text.WordWrap - width: root.width + tooltip: qsTr("Add a material.") + + anchors.right: parent.right + anchors.rightMargin: scrollView.verticalScrollBarVisible ? 10 : 0 + icon: StudioTheme.Constants.plus + normalColor: "transparent" + buttonSize: StudioTheme.Values.sectionHeadHeight + onClicked: materialBrowserModel.addNewMaterial() + enabled: root.enableUiElements } } - Section { - id: texturesSection - + Item { width: root.width - caption: qsTr("Textures") + height: texturesSection.height - Grid { - width: scrollView.width - leftPadding: 5 - rightPadding: 5 - bottomPadding: 5 - columns: root.width / root.cellWidth + Section { + id: texturesSection - Repeater { - id: texturesRepeater + width: root.width + caption: qsTr("Textures") - model: materialBrowserTexturesModel - delegate: TextureItem { - width: root.cellWidth - height: root.cellWidth + dropEnabled: true - onShowContextMenu: { -// ctxMenuTexture.popupMenu(this, model) // TODO: implement textures context menu + onDropEnter: (drag) => { + drag.accepted = drag.formats[0] === "application/vnd.qtdesignstudio.bundletexture" + highlight = drag.accepted + } + + onDropExit: { + highlight = false + } + + onDrop: { + highlight = false + rootView.acceptBundleTextureDrop() + } + + Grid { + width: scrollView.width + leftPadding: 5 + rightPadding: 5 + bottomPadding: 5 + columns: root.width / root.cellWidth + + Repeater { + id: texturesRepeater + + model: materialBrowserTexturesModel + delegate: TextureItem { + width: root.cellWidth + height: root.cellWidth + + onShowContextMenu: { + ctxMenuTextures.popupMenu(model) + } } } } + + Text { + text: qsTr("No match found."); + color: StudioTheme.Values.themeTextColor + font.pixelSize: StudioTheme.Values.baseFontSize + leftPadding: 10 + visible: materialBrowserTexturesModel.isEmpty && !searchBox.isEmpty() + } + + Text { + text:qsTr("There are no textures in this project.") + visible: materialBrowserTexturesModel.isEmpty && searchBox.isEmpty() + textFormat: Text.RichText + color: StudioTheme.Values.themeTextColor + font.pixelSize: StudioTheme.Values.mediumFontSize + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + width: root.width + } } - Text { - text: qsTr("No match found."); - color: StudioTheme.Values.themeTextColor - font.pixelSize: StudioTheme.Values.baseFontSize - leftPadding: 10 - visible: materialBrowserModel.isEmpty && !searchBox.isEmpty() && !materialBrowserModel.hasMaterialRoot - } + IconButton { + id: addTextureButton - Text { - text:qsTr("There are no texture in this project.") - visible: materialBrowserTexturesModel.isEmpty && searchBox.isEmpty() - textFormat: Text.RichText - color: StudioTheme.Values.themeTextColor - font.pixelSize: StudioTheme.Values.mediumFontSize - horizontalAlignment: Text.AlignHCenter - wrapMode: Text.WordWrap - width: root.width + tooltip: qsTr("Add a texture.") + + anchors.right: parent.right + anchors.rightMargin: scrollView.verticalScrollBarVisible ? 10 : 0 + icon: StudioTheme.Constants.plus + normalColor: "transparent" + buttonSize: StudioTheme.Values.sectionHeadHeight + onClicked: materialBrowserTexturesModel.addNewTexture() + enabled: root.enableUiElements } } + + DropArea { + id: masterDropArea + + property int emptyHeight: scrollView.height - materialsSection.height - texturesSection.height + + width: root.width + height: emptyHeight > 0 ? emptyHeight : 0 + + enabled: true + + onEntered: (drag) => texturesSection.dropEnter(drag) + onDropped: (drag) => texturesSection.drop(drag) + onExited: texturesSection.dropExit() + } } } } diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml index 6eaf0351d2f..ec36e61d854 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml @@ -36,13 +36,25 @@ Rectangle { mouseArea.forceActiveFocus() } - border.width: materialBrowserModel.selectedIndex === index ? 1 : 0 + border.width: materialBrowserModel.selectedIndex === index ? rootView.materialSectionFocused ? 3 : 1 : 0 border.color: materialBrowserModel.selectedIndex === index ? StudioTheme.Values.themeControlOutlineInteraction : "transparent" color: "transparent" visible: materialVisible + DropArea { + anchors.fill: parent + + onEntered: (drag) => { + drag.accepted = drag.formats[0] === "application/vnd.qtdesignstudio.texture" + } + + onDropped: (drag) => { + rootView.acceptTextureDropOnMaterial(index, drag.getDataAsString(drag.keys[0])) + } + } + MouseArea { id: mouseArea @@ -50,6 +62,7 @@ Rectangle { acceptedButtons: Qt.LeftButton | Qt.RightButton onPressed: (mouse) => { + rootView.focusMaterialSection(true) materialBrowserModel.selectMaterial(index) if (mouse.button === Qt.LeftButton) @@ -116,7 +129,10 @@ Rectangle { anchors.fill: parent - onClicked: materialBrowserModel.selectMaterial(index) + onClicked: { + rootView.focusMaterialSection(true) + materialBrowserModel.selectMaterial(index) + } onDoubleClicked: root.startRename() } } diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureBrowserContextMenu.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureBrowserContextMenu.qml new file mode 100644 index 00000000000..4323998ee0f --- /dev/null +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureBrowserContextMenu.qml @@ -0,0 +1,63 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +import QtQuick +import HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme + +StudioControls.Menu { + id: root + + property var targetTexture: null + property int copiedTextureInternalId: -1 + + function popupMenu(targetTexture = null) + { + this.targetTexture = targetTexture + materialBrowserTexturesModel.updateSceneEnvState() + materialBrowserTexturesModel.updateModelSelectionState() + popup() + } + + closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside + + StudioControls.MenuItem { + text: qsTr("Apply to selected model") + enabled: root.targetTexture && materialBrowserTexturesModel.hasSingleModelSelection + onTriggered: materialBrowserTexturesModel.applyToSelectedModel(root.targetTexture.textureInternalId) + } + + StudioControls.MenuItem { + text: qsTr("Apply to selected material") + enabled: root.targetTexture && materialBrowserModel.selectedIndex >= 0 + onTriggered: materialBrowserTexturesModel.applyToSelectedMaterial(root.targetTexture.textureInternalId) + } + + StudioControls.MenuItem { + text: qsTr("Apply as light probe") + enabled: root.targetTexture && materialBrowserTexturesModel.hasSceneEnv + onTriggered: materialBrowserTexturesModel.applyAsLightProbe(root.targetTexture.textureInternalId) + } + + StudioControls.MenuSeparator {} + + StudioControls.MenuItem { + text: qsTr("Duplicate") + enabled: root.targetTexture + onTriggered: materialBrowserTexturesModel.duplicateTexture(materialBrowserTexturesModel.selectedIndex) + } + + StudioControls.MenuItem { + text: qsTr("Delete") + enabled: root.targetTexture + onTriggered: materialBrowserTexturesModel.deleteTexture(materialBrowserTexturesModel.selectedIndex) + } + + StudioControls.MenuSeparator {} + + StudioControls.MenuItem { + text: qsTr("Create New Texture") + onTriggered: materialBrowserTexturesModel.addNewTexture() + } +} diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml index b5ef129a6c3..a223668dc9b 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 import QtQuick +import QtQuick.Controls import QtQuick.Layouts import QtQuickDesignerTheme import HelperWidgets @@ -13,7 +14,8 @@ Rectangle { visible: textureVisible color: "transparent" - border.width: materialBrowserTexturesModel.selectedIndex === index ? 1 : 0 + border.width: materialBrowserTexturesModel.selectedIndex === index + ? !rootView.materialSectionFocused ? 3 : 1 : 0 border.color: materialBrowserTexturesModel.selectedIndex === index ? StudioTheme.Values.themeControlOutlineInteraction : "transparent" @@ -25,8 +27,10 @@ Rectangle { anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton + hoverEnabled: true onPressed: (mouse) => { + rootView.focusMaterialSection(false) materialBrowserTexturesModel.selectTexture(index) if (mouse.button === Qt.LeftButton) @@ -34,13 +38,32 @@ Rectangle { else if (mouse.button === Qt.RightButton) root.showContextMenu() } + + onDoubleClicked: materialBrowserTexturesModel.openTextureEditor(); + } + + ToolTip { + visible: mouseArea.containsMouse + // contentWidth is not calculated correctly by the toolTip (resulting in a wider tooltip than + // needed). Using a helper Text to calculate the correct width + contentWidth: helperText.width + bottomInset: -2 + text: textureToolTip + delay: 1000 + + Text { + id: helperText + text: textureToolTip + visible: false + } } Image { - source: textureSource + source: "image://materialBrowserTex/" + textureSource + asynchronous: true sourceSize.width: root.width - 10 sourceSize.height: root.height - 10 anchors.centerIn: parent - cache: false + smooth: true } } diff --git a/share/qtcreator/qmldesigner/materialEditorQmlSources/EmptyMaterialEditorPane.qml b/share/qtcreator/qmldesigner/materialEditorQmlSources/EmptyMaterialEditorPane.qml index 5b9044ac4c2..304279b63af 100644 --- a/share/qtcreator/qmldesigner/materialEditorQmlSources/EmptyMaterialEditorPane.qml +++ b/share/qtcreator/qmldesigner/materialEditorQmlSources/EmptyMaterialEditorPane.qml @@ -32,8 +32,14 @@ PropertyEditorPane { height: 150 Text { - text: hasQuick3DImport ? qsTr("There are no materials in this project.
Select '+' to create one.") - : qsTr("To use Material Editor, first add the QtQuick3D module in the Components view.") + text: { + if (!hasQuick3DImport) + qsTr("To use Material Editor, first add the QtQuick3D module in the Components view.") + else if (!hasMaterialLibrary) + qsTr("Material Editor is disabled inside a non-visual component.") + else + qsTr("There are no materials in this project.
Select '+' to create one.") + } textFormat: Text.RichText color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.mediumFontSize diff --git a/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorToolBar.qml b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorToolBar.qml index 065a1a6ba4d..25edbf2d083 100644 --- a/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorToolBar.qml +++ b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorToolBar.qml @@ -5,7 +5,7 @@ import QtQuick 2.15 import QtQuickDesignerTheme 1.0 import HelperWidgets 2.0 import StudioTheme 1.0 as StudioTheme -import ToolBarAction 1.0 +import MaterialToolBarAction 1.0 Rectangle { id: root @@ -28,7 +28,7 @@ Rectangle { normalColor: StudioTheme.Values.themeSectionHeadBackground iconSize: StudioTheme.Values.bigIconFontSize buttonSize: root.height - enabled: hasMaterial && hasModelSelection && hasQuick3DImport && !hasMaterialRoot + enabled: hasMaterial && hasModelSelection && hasQuick3DImport && hasMaterialLibrary onClicked: root.toolBarAction(ToolBarAction.ApplyToSelected) tooltip: qsTr("Apply material to selected model.") } @@ -39,7 +39,7 @@ Rectangle { normalColor: StudioTheme.Values.themeSectionHeadBackground iconSize: StudioTheme.Values.bigIconFontSize buttonSize: root.height - enabled: hasQuick3DImport && !hasMaterialRoot + enabled: hasQuick3DImport && hasMaterialLibrary onClicked: root.toolBarAction(ToolBarAction.AddNewMaterial) tooltip: qsTr("Create new material.") } @@ -50,7 +50,7 @@ Rectangle { normalColor: StudioTheme.Values.themeSectionHeadBackground iconSize: StudioTheme.Values.bigIconFontSize buttonSize: root.height - enabled: hasMaterial && hasQuick3DImport && !hasMaterialRoot + enabled: hasMaterial && hasQuick3DImport && hasMaterialLibrary onClicked: root.toolBarAction(ToolBarAction.DeleteCurrentMaterial) tooltip: qsTr("Delete current material.") } @@ -61,7 +61,7 @@ Rectangle { normalColor: StudioTheme.Values.themeSectionHeadBackground iconSize: StudioTheme.Values.bigIconFontSize buttonSize: root.height - enabled: hasMaterial && hasQuick3DImport && !hasMaterialRoot + enabled: hasMaterial && hasQuick3DImport && hasMaterialLibrary onClicked: root.toolBarAction(ToolBarAction.OpenMaterialBrowser) tooltip: qsTr("Open material browser.") } diff --git a/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorTopSection.qml b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorTopSection.qml index 73e9865c194..b98f7293f3b 100644 --- a/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorTopSection.qml +++ b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorTopSection.qml @@ -140,6 +140,7 @@ Column { anchors.centerIn: parent source: "image://materialEditor/preview" cache: false + smooth: true } } diff --git a/share/qtcreator/qmldesigner/newstateseditor/Main.qml b/share/qtcreator/qmldesigner/newstateseditor/Main.qml index 21385de257e..0176cc79f60 100644 --- a/share/qtcreator/qmldesigner/newstateseditor/Main.qml +++ b/share/qtcreator/qmldesigner/newstateseditor/Main.qml @@ -45,6 +45,10 @@ Rectangle { onWidthChanged: root.responsiveResize(root.width, root.height) onHeightChanged: root.responsiveResize(root.width, root.height) + function showEvent() { + addCanvas.requestPaint() + } + Component.onCompleted: root.responsiveResize(root.width, root.height) function numFit(overall, size, space) { @@ -258,6 +262,14 @@ Rectangle { // the close of the old popup. Using an int keeps track of number of opened popups. property int menuOpen: 0 + Connections { + target: statesEditorModel + function onModelReset() { + root.menuOpen = 0 + editDialog.close() + } + } + // This timer is used to delay the current state animation as it didn't work due to the // repeaters item not being positioned in time resulting in 0 x and y position if the grids // row and column were not changed during the layout algorithm . @@ -303,15 +315,8 @@ Rectangle { standardButtons: Dialog.Apply | Dialog.Cancel x: editButton.x - Math.max(0, editButton.x + editDialog.width - root.width) y: toolBar.height - closePolicy: Popup.NoAutoClose - width: Math.min(300, root.width) - - function apply() { - let renamed = statesEditorModel.renameActiveStateGroup(editTextField.text) - if (renamed) - editDialog.close() - } + closePolicy: Popup.NoAutoClose onApplied: editDialog.accept() @@ -541,7 +546,7 @@ Rectangle { baseState: true defaultChecked: !statesEditorModel.baseState.modelHasDefaultState // TODO Make this one a model property isChecked: root.currentStateInternalId === 0 - thumbnailImageSource: statesEditorModel.baseState.stateImageSource // TODO Get rid of the QVariantMap + thumbnailImageSource: statesEditorModel.baseState.stateImageSource ?? "" // TODO Get rid of the QVariantMap isTiny: root.tinyMode onFocusSignal: root.currentStateInternalId = 0 @@ -854,28 +859,29 @@ Rectangle { Item { id: addWrapper + visible: canAddNewStates Canvas { id: addCanvas width: root.thumbWidth height: root.thumbHeight + property int plusExtend: 20 + property int halfWidth: addCanvas.width / 2 + property int halfHeight: addCanvas.height / 2 + onPaint: { var ctx = getContext("2d") ctx.strokeStyle = StudioTheme.Values.themeStateHighlight ctx.lineWidth = 6 - var plusExtend = 20 - var halfWidth = addCanvas.width / 2 - var halfHeight = addCanvas.height / 2 - ctx.beginPath() - ctx.moveTo(halfWidth, halfHeight - plusExtend) - ctx.lineTo(halfWidth, halfHeight + plusExtend) + ctx.moveTo(addCanvas.halfWidth, addCanvas.halfHeight - addCanvas.plusExtend) + ctx.lineTo(addCanvas.halfWidth, addCanvas.halfHeight + addCanvas.plusExtend) - ctx.moveTo(halfWidth - plusExtend, halfHeight) - ctx.lineTo(halfWidth + plusExtend, halfHeight) + ctx.moveTo(addCanvas.halfWidth - addCanvas.plusExtend, addCanvas.halfHeight) + ctx.lineTo(addCanvas.halfWidth + addCanvas.plusExtend, addCanvas.halfHeight) ctx.stroke() ctx.save() diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml index 3ae08a1249a..fe29c2ef322 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml @@ -15,6 +15,10 @@ PropertyEditorPane { showState: true } + InsightSection { + visible: insightEnabled + } + DynamicPropertiesSection { propertiesModel: SelectionDynamicPropertiesModel {} visible: !hasMultiSelection diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ComboBox.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ComboBox.qml index de53533b352..61f41d8b9c3 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ComboBox.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ComboBox.qml @@ -59,10 +59,10 @@ StudioControls.ComboBox { anchors.fill: parent - property string assetPath: "" + property string dropData: "" onEntered: (drag) => { - dropArea.assetPath = drag.getDataAsString(drag.keys[0]).split(",")[0] + dropArea.dropData = drag.getDataAsString(drag.keys[0]).split(",")[0] drag.accepted = comboBox.backendValue !== undefined && comboBox.backendValue.hasActiveDrag comboBox.hasActiveHoverDrag = drag.accepted } @@ -70,7 +70,7 @@ StudioControls.ComboBox { onExited: comboBox.hasActiveHoverDrag = false onDropped: { - comboBox.backendValue.commitDrop(dropArea.assetPath) + comboBox.backendValue.commitDrop(dropArea.dropData) comboBox.hasActiveHoverDrag = false } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/IconButton.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/IconButton.qml index 98437edb578..e7e8134c44c 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/IconButton.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/IconButton.qml @@ -14,6 +14,7 @@ Rectangle { property alias icon: icon.text property alias tooltip: toolTip.text property alias iconSize: icon.font.pixelSize + property alias containsMouse: mouseArea.containsMouse property bool enabled: true property int buttonSize: StudioTheme.Values.height diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ImagePreviewTooltipArea.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ImagePreviewTooltipArea.qml index e390906b9f6..ae46115889b 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ImagePreviewTooltipArea.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ImagePreviewTooltipArea.qml @@ -5,7 +5,7 @@ import QtQuick 2.15 import QtQuick.Layouts 1.15 import HelperWidgets 2.0 -MouseArea { +ToolTipArea { id: mouseArea property bool allowTooltip: true diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/InsightSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/InsightSection.qml new file mode 100644 index 00000000000..988e5ebbf9e --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/InsightSection.qml @@ -0,0 +1,88 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +import QtQuick +import HelperWidgets 2.0 +import StudioControls 1.0 as StudioControls +import StudioTheme 1.0 as StudioTheme + +Section { + id: root + caption: qsTr("Analytics") + + anchors.left: parent.left + anchors.right: parent.right + + property string defaultItem: qsTr("[None]") + + function addDefaultItem(arr) + { + var copy = arr.slice() + copy.unshift(root.defaultItem) + return copy + } + + SectionLayout { + PropertyLabel { text: qsTr("Category") } + + SecondColumnLayout { + Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth } + + StudioControls.ComboBox { + id: comboBox + property var backendValue: backendValues.InsightCategory_category + property var valueFromBackend: comboBox.backendValue === undefined ? 0 : comboBox.backendValue.value + + onValueFromBackendChanged: comboBox.invalidate() + onModelChanged: comboBox.invalidate() + + actionIndicatorVisible: false + implicitWidth: StudioTheme.Values.singleControlColumnWidth + width: implicitWidth + model: root.addDefaultItem(insightCategories) + editable: false + + onCompressedActivated: function(index, reason) { + if (comboBox.backendValue === undefined) + return + + verifyInsightImport() + + if (index === 0) + comboBox.backendValue.resetValue() + else + comboBox.backendValue.value = comboBox.currentText + } + + Connections { + target: modelNodeBackend + function onSelectionToBeChanged() { + comboBox.popup.close() + } + } + + function invalidate() { + var index = comboBox.find(comboBox.valueFromBackend) + if (index < 0) { + if (comboBox.valueFromBackend === "") { + comboBox.currentIndex = 0 + comboBox.labelColor = StudioTheme.Values.themeTextColor + } else { + comboBox.currentIndex = index + comboBox.editText = comboBox.valueFromBackend + comboBox.labelColor = StudioTheme.Values.themeError + } + } else { + if (index !== comboBox.currentIndex) + comboBox.currentIndex = index + + comboBox.labelColor = StudioTheme.Values.themeTextColor + } + } + Component.onCompleted: comboBox.invalidate() + } + + ExpandingSpacer {} + } + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SpinBox.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SpinBox.qml index ba059e9e4f1..151356148a0 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SpinBox.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SpinBox.qml @@ -13,6 +13,7 @@ Item { property alias minimumValue: spinBox.realFrom property alias maximumValue: spinBox.realTo + property alias value: spinBox.realValue property alias stepSize: spinBox.realStepSize property alias backendValue: spinBox.backendValue diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/TextExtrasSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/TextExtrasSection.qml index 67598bb7605..fe4d7658150 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/TextExtrasSection.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/TextExtrasSection.qml @@ -119,9 +119,9 @@ Section { text: qsTr("Render type quality") tooltip: qsTr("Overrides the default rendering type quality for this component.") blockedByTemplate: !root.isBackendValueAvailable("renderTypeQuality") - enabled: backendValues.renderType !== undefined - ? backendValues.renderType.enumeration === "QtRendering" - : false + enabled: root.isBackendValueAvailable("renderTypeQuality") + && (backendValues.renderType.value === "QtRendering" + || backendValues.renderType.enumeration === "QtRendering") } SecondColumnLayout { @@ -134,7 +134,8 @@ Section { "HighRenderTypeQuality", "VeryHighRenderTypeQuality"] backendValue: backendValues.renderTypeQuality enabled: root.isBackendValueAvailable("renderTypeQuality") - && backendValues.renderType.enumeration === "QtRendering" + && (backendValues.renderType.value === "QtRendering" + || backendValues.renderType.enumeration === "QtRendering") } ExpandingSpacer {} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml index b87005fe4bb..d6373f790ab 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml @@ -102,8 +102,8 @@ Row { Item { visible: thumbnail.status === Image.Ready - Layout.preferredWidth: 100 - Layout.preferredHeight: 100 + Layout.preferredWidth: 96 + Layout.preferredHeight: 96 Image { id: checker @@ -116,7 +116,10 @@ Row { Image { id: thumbnail asynchronous: true - anchors.fill: parent + sourceSize.height: 96 + sourceSize.width: 96 + height: 96 + width: 96 fillMode: Image.PreserveAspectFit source: { if (root.isBuiltInPrimitive(root.absoluteFilePath)) @@ -231,8 +234,8 @@ Row { Item { visible: delegateThumbnail.status === Image.Ready - Layout.preferredWidth: 100 - Layout.preferredHeight: 100 + Layout.preferredWidth: 96 + Layout.preferredHeight: 96 Image { id: delegateChecker @@ -245,7 +248,10 @@ Row { Image { id: delegateThumbnail asynchronous: true - anchors.fill: parent + sourceSize.height: 96 + sourceSize.width: 96 + height: 96 + width: 96 fillMode: Image.PreserveAspectFit source: { if (root.isBuiltInPrimitive(delegateRoot.name)) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir index f145b098776..ca1411a4d6e 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir @@ -42,6 +42,7 @@ IconButton 2.0 IconButton.qml IconLabel 2.0 IconLabel.qml ImagePreviewTooltipArea 2.0 ImagePreviewTooltipArea.qml ImageSection 2.0 ImageSection.qml +InsightSection 2.0 InsightSection.qml ItemFilterComboBox 2.0 ItemFilterComboBox.qml Label 2.0 Label.qml LineEdit 2.0 LineEdit.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ComboBoxInput.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ComboBoxInput.qml index 6ac2df56f2c..f29fc0311aa 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ComboBoxInput.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ComboBoxInput.qml @@ -57,7 +57,7 @@ TextInput { myControl.focus = false } else { myControl.popup.open() - myControl.forceActiveFocus() + //myControl.forceActiveFocus() } } else { textInput.forceActiveFocus() diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/contentmodule.main.qml.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/contentmodule.main.qml.tpl index 0aa4cf67d7f..96f5dbaa36c 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/contentmodule.main.qml.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/contentmodule.main.qml.tpl @@ -1,4 +1,7 @@ import QtQuick %{QtQuickVersion} +@if !%{IsQt6Project} +import QtQuick.Window %{QtQuickVersion} +@endif import %{ApplicationImport} @if %{UseVirtualKeyboard} import QtQuick.VirtualKeyboard %{QtQuickVersion} 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 6c6949442c6..84690258ea3 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/wizard.json @@ -299,6 +299,10 @@ "source": "../common/qmlmodules.tpl", "target": "%{ProjectDirectory}/qmlmodules" }, + { + "source": "../common/qmlcomponents.tpl", + "target": "%{ProjectDirectory}/qmlcomponents" + }, { "source": "../common/main.qml", "target": "%{ProjectDirectory}/main.qml" @@ -319,6 +323,11 @@ "source": "../common/import_qml_plugins.h.tpl", "target": "%{ProjectDirectory}/src/import_qml_plugins.h" }, + { + "source": "../common/import_qml_components_plugins.h.tpl", + "target": "%{ProjectDirectory}/src/import_qml_components_plugins.h" + }, + { "source": "../common/CMakeLists.content.txt.tpl", "target": "%{ProjectDirectory}/content/CMakeLists.txt" diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/application/wizard.json b/share/qtcreator/qmldesigner/studio_templates/projects/application/wizard.json index 235116a1f4c..85f248f957b 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/application/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/application/wizard.json @@ -295,6 +295,10 @@ "source": "../common/qmlmodules.tpl", "target": "%{ProjectDirectory}/qmlmodules" }, + { + "source": "../common/qmlcomponents.tpl", + "target": "%{ProjectDirectory}/qmlcomponents" + }, { "source": "../common/main.qml", "target": "%{ProjectDirectory}/main.qml" @@ -315,6 +319,10 @@ "source": "../common/import_qml_plugins.h.tpl", "target": "%{ProjectDirectory}/src/import_qml_plugins.h" }, + { + "source": "../common/import_qml_components_plugins.h.tpl", + "target": "%{ProjectDirectory}/src/import_qml_components_plugins.h" + }, { "source": "../common/CMakeLists.content.txt.tpl", "target": "%{ProjectDirectory}/content/CMakeLists.txt" diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/common/App.qml.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/common/App.qml.tpl index 21794f23e9f..d093ee8586e 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/common/App.qml.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/common/App.qml.tpl @@ -2,7 +2,9 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 import QtQuick %{QtQuickVersion} +@if !%{IsQt6Project} import QtQuick.Window %{QtQuickVersion} +@endif import %{ImportModuleName} %{ImportModuleVersion} @if %{UseVirtualKeyboard} import QtQuick.VirtualKeyboard %{QtQuickVersion} diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/common/CMakeLists.main.txt.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/common/CMakeLists.main.txt.tpl index f105c83c553..6878a1023c3 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/common/CMakeLists.main.txt.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/common/CMakeLists.main.txt.tpl @@ -1,24 +1,37 @@ -cmake_minimum_required(VERSION 3.18) +cmake_minimum_required(VERSION 3.21.1) + +set(BUILD_QDS_COMPONENTS ON CACHE BOOL "Build design studio components") project(%{ProjectName}App LANGUAGES CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) -find_package(Qt6 COMPONENTS Gui Qml Quick) -qt_add_executable(%{ProjectExecutableName} src/main.cpp) +find_package(QT NAMES Qt6 COMPONENTS Gui Qml Quick) +find_package(Qt6 REQUIRED COMPONENTS Core Qml Quick) -qt_add_resources(%{ProjectExecutableName} "configuration" +qt_add_executable(${CMAKE_PROJECT_NAME} src/main.cpp) + +# qt_standard_project_setup() requires Qt 6.3 or higher. See https://doc.qt.io/qt-6/qt-standard-project-setup.html for details. +if (${QT_VERSION_MINOR} GREATER_EQUAL 3) +qt6_standard_project_setup() +endif() + +qt_add_resources(${CMAKE_PROJECT_NAME} "configuration" PREFIX "/" FILES qtquickcontrols2.conf ) -target_link_libraries(%{ProjectExecutableName} PRIVATE +target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Quick Qt${QT_VERSION_MAJOR}::Qml ) +if (${BUILD_QDS_COMPONENTS}) + include(${CMAKE_CURRENT_SOURCE_DIR}/qmlcomponents) +endif () + include(${CMAKE_CURRENT_SOURCE_DIR}/qmlmodules) 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 2e933c76136..722b0d72ee6 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/common/app.qmlproject.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/common/app.qmlproject.tpl @@ -100,7 +100,7 @@ Project { /* Required for deployment */ targetDirectory: "/opt/%{ProjectName}" - qdsVersion: "3.8" + qdsVersion: "3.9" quickVersion: "%{QtQuickVersion}" diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/common/contentmodule.main.qml.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/common/contentmodule.main.qml.tpl index 55c40e624f1..ac0b0b28c02 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/common/contentmodule.main.qml.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/common/contentmodule.main.qml.tpl @@ -1,4 +1,7 @@ import QtQuick %{QtQuickVersion} +@if !%{IsQt6Project} +import QtQuick.Window %{QtQuickVersion} +@endif import %{ApplicationImport} Window { diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/common/import_qml_components_plugins.h.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/common/import_qml_components_plugins.h.tpl new file mode 100644 index 00000000000..25d0a98384e --- /dev/null +++ b/share/qtcreator/qmldesigner/studio_templates/projects/common/import_qml_components_plugins.h.tpl @@ -0,0 +1,19 @@ +/* + * This file is automatically generated by Qt Design Studio. + * Do not change. +*/ + +#include "qqmlextensionplugin.h" + +#ifdef BULD_QDS_COMPONENTS + +Q_IMPORT_QML_PLUGIN(QtQuick_Studio_ComponentsPlugin) +Q_IMPORT_QML_PLUGIN(QtQuick_Studio_EffectsPlugin) +Q_IMPORT_QML_PLUGIN(QtQuick_Studio_ApplicationPlugin) +Q_IMPORT_QML_PLUGIN(FlowViewPlugin) +Q_IMPORT_QML_PLUGIN(QtQuick_Studio_LogicHelperPlugin) +Q_IMPORT_QML_PLUGIN(QtQuick_Studio_MultiTextPlugin) +Q_IMPORT_QML_PLUGIN(QtQuick_Studio_EventSimulatorPlugin) +Q_IMPORT_QML_PLUGIN(QtQuick_Studio_EventSystemPlugin) + +#endif diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/common/main.cpp.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/common/main.cpp.tpl index f10adc57d72..cd28d11f3e5 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/common/main.cpp.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/common/main.cpp.tpl @@ -5,6 +5,7 @@ #include #include "app_environment.h" +#include "import_qml_components_plugins.h" #include "import_qml_plugins.h" int main(int argc, char *argv[]) diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/common/qmlcomponents.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/common/qmlcomponents.tpl new file mode 100644 index 00000000000..3619df33d33 --- /dev/null +++ b/share/qtcreator/qmldesigner/studio_templates/projects/common/qmlcomponents.tpl @@ -0,0 +1,33 @@ +### This file is automatically generated by Qt Design Studio. +### Do not change + +message("Building designer components.") + +set(QT_QML_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/qml") + +include(FetchContent) +FetchContent_Declare( + ds + GIT_TAG qds-3.9 + GIT_REPOSITORY https://code.qt.io/qt-labs/qtquickdesigner-components.git +) + +FetchContent_GetProperties(ds) +FetchContent_Populate(ds) + +target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE + QuickStudioComponentsplugin + QuickStudioEffectsplugin + QuickStudioApplicationplugin + FlowViewplugin + QuickStudioLogicHelperplugin + QuickStudioMultiTextplugin + QuickStudioEventSimulatorplugin + QuickStudioEventSystemplugin +) + +add_subdirectory(${ds_SOURCE_DIR} ${ds_BINARY_DIR}) + +target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE + BULD_QDS_COMPONENTS=true +) diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/common/qmlmodules.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/common/qmlmodules.tpl index 1a3303600d4..fa3069a770a 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/common/qmlmodules.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/common/qmlmodules.tpl @@ -1,7 +1,7 @@ ### This file is automatically generated by Qt Design Studio. ### Do not change -qt6_add_qml_module(%{ProjectExecutableName} +qt6_add_qml_module(${CMAKE_PROJECT_NAME} URI "Main" VERSION 1.0 NO_PLUGIN @@ -11,7 +11,7 @@ qt6_add_qml_module(%{ProjectExecutableName} add_subdirectory(content) add_subdirectory(imports) -target_link_libraries(%{ProjectExecutableName} PRIVATE +target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE contentplugin %{ProjectPluginName} ) 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 851e3780899..92cbd476cdf 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/desktop-launcher/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/desktop-launcher/wizard.json @@ -293,6 +293,10 @@ "source": "../common/qmlmodules.tpl", "target": "%{ProjectDirectory}/qmlmodules" }, + { + "source": "../common/qmlcomponents.tpl", + "target": "%{ProjectDirectory}/qmlcomponents" + }, { "source": "../common/main.qml", "target": "%{ProjectDirectory}/main.qml" @@ -313,6 +317,10 @@ "source": "../common/import_qml_plugins.h.tpl", "target": "%{ProjectDirectory}/src/import_qml_plugins.h" }, + { + "source": "../common/import_qml_components_plugins.h.tpl", + "target": "%{ProjectDirectory}/src/import_qml_components_plugins.h" + }, { "source": "../common/CMakeLists.content.txt.tpl", "target": "%{ProjectDirectory}/content/CMakeLists.txt" 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 37e40ff8baf..9b3fe1be77a 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-scroll/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-scroll/wizard.json @@ -252,6 +252,10 @@ "source": "../common/qmlmodules.tpl", "target": "%{ProjectDirectory}/qmlmodules" }, + { + "source": "../common/qmlcomponents.tpl", + "target": "%{ProjectDirectory}/qmlcomponents" + }, { "source": "../common/main.qml", "target": "%{ProjectDirectory}/main.qml" @@ -272,6 +276,10 @@ "source": "../common/import_qml_plugins.h.tpl", "target": "%{ProjectDirectory}/src/import_qml_plugins.h" }, + { + "source": "../common/import_qml_components_plugins.h.tpl", + "target": "%{ProjectDirectory}/src/import_qml_components_plugins.h" + }, { "source": "../common/CMakeLists.content.txt.tpl", "target": "%{ProjectDirectory}/content/CMakeLists.txt" diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-stack/App.qml.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-stack/App.qml.tpl index 7097157027d..abddf3d0c11 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-stack/App.qml.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-stack/App.qml.tpl @@ -1,5 +1,8 @@ import QtQuick %{QtQuickVersion} import QtQuick.Controls %{QtQuickVersion} +@if !%{IsQt6Project} +import QtQuick.Window %{QtQuickVersion} +@endif import %{ImportModuleName} %{ImportModuleVersion} Window { 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 642cd7a8e46..a10fe57da6c 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-stack/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-stack/wizard.json @@ -249,6 +249,10 @@ "source": "../common/qmlmodules.tpl", "target": "%{ProjectDirectory}/qmlmodules" }, + { + "source": "../common/qmlcomponents.tpl", + "target": "%{ProjectDirectory}/qmlcomponents" + }, { "source": "../common/main.qml", "target": "%{ProjectDirectory}/main.qml" @@ -269,6 +273,10 @@ "source": "../common/import_qml_plugins.h.tpl", "target": "%{ProjectDirectory}/src/import_qml_plugins.h" }, + { + "source": "../common/import_qml_components_plugins.h.tpl", + "target": "%{ProjectDirectory}/src/import_qml_components_plugins.h" + }, { "source": "CMakeLists.content.txt.tpl", "target": "%{ProjectDirectory}/content/CMakeLists.txt" diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-swipe/App.qml.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-swipe/App.qml.tpl index 8201708f72b..0937e1288eb 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-swipe/App.qml.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-swipe/App.qml.tpl @@ -1,5 +1,8 @@ import QtQuick %{QtQuickVersion} import QtQuick.Controls %{QtQuickVersion} +@if !%{IsQt6Project} +import QtQuick.Window %{QtQuickVersion} +@endif import %{ImportModuleName} %{ImportModuleVersion} Window { 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 a2ece501ad2..77c31a7d3bf 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-swipe/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-swipe/wizard.json @@ -249,6 +249,10 @@ "source": "../common/qmlmodules.tpl", "target": "%{ProjectDirectory}/qmlmodules" }, + { + "source": "../common/qmlcomponents.tpl", + "target": "%{ProjectDirectory}/qmlcomponents" + }, { "source": "../common/main.qml", "target": "%{ProjectDirectory}/main.qml" @@ -269,6 +273,10 @@ "source": "../common/import_qml_plugins.h.tpl", "target": "%{ProjectDirectory}/src/import_qml_plugins.h" }, + { + "source": "../common/import_qml_components_plugins.h.tpl", + "target": "%{ProjectDirectory}/src/import_qml_components_plugins.h" + }, { "source": "CMakeLists.content.txt.tpl", "target": "%{ProjectDirectory}/content/CMakeLists.txt" diff --git a/share/qtcreator/qmldesigner/textureEditorQmlSource/EmptyTextureEditorPane.qml b/share/qtcreator/qmldesigner/textureEditorQmlSource/EmptyTextureEditorPane.qml new file mode 100644 index 00000000000..b6527e082d7 --- /dev/null +++ b/share/qtcreator/qmldesigner/textureEditorQmlSource/EmptyTextureEditorPane.qml @@ -0,0 +1,50 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuickDesignerTheme 1.0 +import HelperWidgets 2.0 +import StudioTheme 1.0 as StudioTheme + +PropertyEditorPane { + id: root + + signal toolBarAction(int action) + + // Called from C++, dummy method to avoid warnings + function closeContextMenu() {} + + Column { + id: col + + TextureEditorToolBar { + width: root.width + + onToolBarAction: (action) => root.toolBarAction(action) + } + + Item { + width: root.width - 2 * col.padding + height: 150 + + Text { + text: { + if (!hasQuick3DImport) + qsTr("To use Texture Editor, first add the QtQuick3D module in the Components view.") + else if (!hasMaterialLibrary) + qsTr("Texture Editor is disabled inside a non-visual component.") + else + qsTr("There are no textures in this project.
Select '+' to create one.") + } + textFormat: Text.RichText + color: StudioTheme.Values.themeTextColor + font.pixelSize: StudioTheme.Values.mediumFontSize + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + width: root.width + anchors.centerIn: parent + } + } + } +} diff --git a/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorPane.qml b/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorPane.qml new file mode 100644 index 00000000000..30e21478226 --- /dev/null +++ b/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorPane.qml @@ -0,0 +1,65 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +import QtQuick 2.15 +import QtQuickDesignerTheme 1.0 +import HelperWidgets 2.0 + +PropertyEditorPane { + id: itemPane + + signal toolBarAction(int action) + + // invoked from C++ to refresh material preview image + function refreshPreview() + { + topSection.refreshPreview() + } + + // Called also from C++ to close context menu on focus out + function closeContextMenu() + { + // Nothing + } + + TextureEditorTopSection { + id: topSection + + onToolBarAction: (action) => itemPane.toolBarAction(action) + } + + Item { width: 1; height: 10 } + + DynamicPropertiesSection { + propertiesModel: TextureEditorDynamicPropertiesModel {} + } + + Loader { + id: specificsTwo + + property string theSource: specificQmlData + + anchors.left: parent.left + anchors.right: parent.right + visible: theSource !== "" + sourceComponent: specificQmlComponent + + onTheSourceChanged: { + active = false + active = true + } + } + + Item { + width: 1 + height: 10 + visible: specificsTwo.visible + } + + Loader { + id: specificsOne + anchors.left: parent.left + anchors.right: parent.right + source: specificsUrl + } +} diff --git a/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorToolBar.qml b/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorToolBar.qml new file mode 100644 index 00000000000..e0f0e1324d3 --- /dev/null +++ b/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorToolBar.qml @@ -0,0 +1,69 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +import QtQuick 2.15 +import QtQuickDesignerTheme 1.0 +import HelperWidgets 2.0 +import StudioTheme 1.0 as StudioTheme +import TextureToolBarAction 1.0 + +Rectangle { + id: root + + color: StudioTheme.Values.themeSectionHeadBackground + width: row.width + height: 40 + + signal toolBarAction(int action) + + Row { + id: row + + anchors.verticalCenter: parent.verticalCenter + leftPadding: 6 + + IconButton { + icon: StudioTheme.Constants.applyMaterialToSelected + + normalColor: StudioTheme.Values.themeSectionHeadBackground + iconSize: StudioTheme.Values.bigIconFontSize + buttonSize: root.height + enabled: hasTexture && hasSingleModelSelection && hasQuick3DImport && hasMaterialLibrary + onClicked: root.toolBarAction(ToolBarAction.ApplyToSelected) + tooltip: qsTr("Apply texture to selected model's material.") + } + + IconButton { + icon: StudioTheme.Constants.newMaterial + + normalColor: StudioTheme.Values.themeSectionHeadBackground + iconSize: StudioTheme.Values.bigIconFontSize + buttonSize: root.height + enabled: hasQuick3DImport && hasMaterialLibrary + onClicked: root.toolBarAction(ToolBarAction.AddNewTexture) + tooltip: qsTr("Create new texture.") + } + + IconButton { + icon: StudioTheme.Constants.deleteMaterial + + normalColor: StudioTheme.Values.themeSectionHeadBackground + iconSize: StudioTheme.Values.bigIconFontSize + buttonSize: root.height + enabled: hasTexture && hasQuick3DImport && hasMaterialLibrary + onClicked: root.toolBarAction(ToolBarAction.DeleteCurrentTexture) + tooltip: qsTr("Delete current texture.") + } + + IconButton { + icon: StudioTheme.Constants.openMaterialBrowser + + normalColor: StudioTheme.Values.themeSectionHeadBackground + iconSize: StudioTheme.Values.bigIconFontSize + buttonSize: root.height + enabled: hasQuick3DImport && hasMaterialLibrary + onClicked: root.toolBarAction(ToolBarAction.OpenMaterialBrowser) + tooltip: qsTr("Open material browser.") + } + } +} diff --git a/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorTopSection.qml b/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorTopSection.qml new file mode 100644 index 00000000000..016049df102 --- /dev/null +++ b/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorTopSection.qml @@ -0,0 +1,44 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +import QtQuick + +Column { + id: root + + signal toolBarAction(int action) + + function refreshPreview() + { + texturePreview.source = "" + texturePreview.source = "image://qmldesigner_thumbnails/" + resolveResourcePath(backendValues.source.valueToString) + } + + anchors.left: parent.left + anchors.right: parent.right + + TextureEditorToolBar { + width: root.width + + onToolBarAction: (action) => root.toolBarAction(action) + } + + Item { width: 1; height: 10 } // spacer + + Rectangle { + id: previewRect + anchors.horizontalCenter: parent.horizontalCenter + width: 152 + height: 152 + color: "#000000" + + Image { + id: texturePreview + asynchronous: true + sourceSize.width: 150 + sourceSize.height: 150 + anchors.centerIn: parent + source: "image://qmldesigner_thumbnails/" + resolveResourcePath(backendValues.source.valueToString) + } + } +} diff --git a/src/libs/qmljs/qmljsimportdependencies.cpp b/src/libs/qmljs/qmljsimportdependencies.cpp index db8e7905440..df87a89cd8e 100644 --- a/src/libs/qmljs/qmljsimportdependencies.cpp +++ b/src/libs/qmljs/qmljsimportdependencies.cpp @@ -525,7 +525,7 @@ Export::Export(ImportKey exportName, bool Export::visibleInVContext(const ViewerContext &vContext) const { - return pathRequired.isEmpty() || vContext.paths.contains(pathRequired); + return pathRequired.isEmpty() || vContext.paths.count(pathRequired); } CoreImport::CoreImport() : language(Dialect::Qml) { } diff --git a/src/libs/qmljs/qmljslink.cpp b/src/libs/qmljs/qmljslink.cpp index 6c04966cedd..2ce98efad3e 100644 --- a/src/libs/qmljs/qmljslink.cpp +++ b/src/libs/qmljs/qmljslink.cpp @@ -123,7 +123,8 @@ Link::Link(const Snapshot &snapshot, const ViewerContext &vContext, const Librar { d->m_valueOwner = new ValueOwner; d->m_snapshot = snapshot; - d->m_importPaths = vContext.paths; + const QList list(vContext.paths.begin(), vContext.paths.end()); + d->m_importPaths = list; d->m_applicationDirectories = vContext.applicationDirectories; d->m_builtins = builtins; d->m_vContext = vContext; diff --git a/src/libs/qmljs/qmljsmodelmanagerinterface.cpp b/src/libs/qmljs/qmljsmodelmanagerinterface.cpp index 9ba1d43fcf7..23e74f819bd 100644 --- a/src/libs/qmljs/qmljsmodelmanagerinterface.cpp +++ b/src/libs/qmljs/qmljsmodelmanagerinterface.cpp @@ -63,8 +63,8 @@ static const char *qtQuickUISuffix = "ui.qml"; static void maybeAddPath(ViewerContext &context, const Utils::FilePath &path) { - if (!path.isEmpty() && !context.paths.contains(path)) - context.paths.append(path); + if (!path.isEmpty() && !(context.paths.count(path) > 0)) + context.paths.insert(path); } static QList environmentImportPaths() diff --git a/src/libs/qmljs/qmljsmodelmanagerinterface.h b/src/libs/qmljs/qmljsmodelmanagerinterface.h index 52af12d6c0e..9ea231923f1 100644 --- a/src/libs/qmljs/qmljsmodelmanagerinterface.h +++ b/src/libs/qmljs/qmljsmodelmanagerinterface.h @@ -188,6 +188,9 @@ public: void removeProjectInfo(ProjectExplorer::Project *project); void maybeQueueCppQmlTypeUpdate(const CPlusPlus::Document::Ptr &doc); + QFuture refreshSourceFiles(const QList &sourceFiles, + bool emitDocumentOnDiskChanged); + signals: void documentUpdated(QmlJS::Document::Ptr doc); void documentChangedOnDisk(QmlJS::Document::Ptr doc); @@ -207,9 +210,6 @@ protected: virtual void addTaskInternal(const QFuture &result, const QString &msg, const char *taskId) const; - QFuture refreshSourceFiles(const QList &sourceFiles, - bool emitDocumentOnDiskChanged); - static void parseLoop(QSet &scannedPaths, QSet &newLibraries, const WorkingCopy &workingCopyInternal, diff --git a/src/libs/qmljs/qmljsviewercontext.h b/src/libs/qmljs/qmljsviewercontext.h index 7d22136844e..043f56b108f 100644 --- a/src/libs/qmljs/qmljsviewercontext.h +++ b/src/libs/qmljs/qmljsviewercontext.h @@ -8,6 +8,8 @@ #include +#include + namespace QmlJS { struct QMLJS_EXPORT ViewerContext @@ -21,7 +23,7 @@ struct QMLJS_EXPORT ViewerContext }; QStringList selectors; - QList paths; + std::set paths; QList applicationDirectories; Dialect language = Dialect::Qml; Flags flags = AddAllPaths; diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 35fe948584b..8e524d8c5bc 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -421,6 +421,7 @@ extend_qtc_plugin(QmlDesigner ${CMAKE_CURRENT_LIST_DIR}/components/itemlibrary ${CMAKE_CURRENT_LIST_DIR}/components/materialbrowser ${CMAKE_CURRENT_LIST_DIR}/components/materialeditor + ${CMAKE_CURRENT_LIST_DIR}/components/textureeditor ${CMAKE_CURRENT_LIST_DIR}/components/navigator ${CMAKE_CURRENT_LIST_DIR}/components/propertyeditor ${CMAKE_CURRENT_LIST_DIR}/components/stateseditor @@ -594,6 +595,7 @@ extend_qtc_plugin(QmlDesigner PUBLIC_INCLUDES components DEFINES QMLDESIGNERCOMPONENTS_LIBRARY SOURCES + createtexture.cpp createtexture.h qmldesignercomponents_global.h ) @@ -623,6 +625,7 @@ extend_qtc_plugin(QmlDesigner selectioncontext.cpp selectioncontext.h theme.cpp theme.h zoomaction.cpp zoomaction.h + anchoraction.cpp anchoraction.h svgpasteaction.cpp svgpasteaction.h viewmanager.cpp viewmanager.h ) @@ -746,9 +749,6 @@ extend_qtc_plugin(QmlDesigner assetslibrarywidget.cpp assetslibrarywidget.h assetslibrarymodel.cpp assetslibrarymodel.h assetslibraryiconprovider.cpp assetslibraryiconprovider.h - assetslibrarydir.cpp assetslibrarydir.h - assetslibrarydirsmodel.cpp assetslibrarydirsmodel.h - assetslibraryfilesmodel.cpp assetslibraryfilesmodel.h ) extend_qtc_plugin(QmlDesigner @@ -821,6 +821,17 @@ extend_qtc_plugin(QmlDesigner materialeditor.qrc ) +extend_qtc_plugin(QmlDesigner + SOURCES_PREFIX components/textureeditor + SOURCES + textureeditorcontextobject.cpp textureeditorcontextobject.h + textureeditordynamicpropertiesproxymodel.cpp textureeditordynamicpropertiesproxymodel.h + textureeditorqmlbackend.cpp textureeditorqmlbackend.h + textureeditortransaction.cpp textureeditortransaction.h + textureeditorview.cpp textureeditorview.h + textureeditor.qrc +) + extend_qtc_plugin(QmlDesigner SOURCES_PREFIX components/materialbrowser SOURCES diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrary.qrc b/src/plugins/qmldesigner/components/assetslibrary/assetslibrary.qrc index f8cea612985..26b4250d0ab 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrary.qrc +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrary.qrc @@ -1,7 +1,5 @@ - images/asset_default.png - images/asset_default@2x.png images/asset_shader.png images/asset_shader@2x.png images/asset_shader_128.png @@ -10,7 +8,16 @@ images/asset_sound_128.png images/asset_video.png images/asset_video@2x.png + images/asset_effectClass.png + images/asset_effectClass@2x.png + images/asset_effectExported.png + images/asset_effectExported@2x.png images/browse.png images/browse@2x.png + images/assets_default.png + images/assets_default@2x.png + images/assets_default_128.png + images/asset_effectClass_128.png + images/asset_effectExported_128.png diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarydir.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarydir.cpp deleted file mode 100644 index a41ce9ce015..00000000000 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarydir.cpp +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 - -#include "assetslibrarydir.h" -#include "assetslibrarydirsmodel.h" -#include "assetslibraryfilesmodel.h" - -namespace QmlDesigner { - -AssetsLibraryDir::AssetsLibraryDir(const QString &path, int depth, bool expanded, QObject *parent) - : QObject(parent) - , m_dirPath(path) - , m_dirDepth(depth) - , m_dirExpanded(expanded) -{ - -} - -QString AssetsLibraryDir::dirName() const { return m_dirPath.split('/').last(); } -QString AssetsLibraryDir::dirPath() const { return m_dirPath; } -int AssetsLibraryDir::dirDepth() const { return m_dirDepth; } -bool AssetsLibraryDir::dirExpanded() const { return m_dirExpanded; } -bool AssetsLibraryDir::dirVisible() const { return m_dirVisible; } - -void AssetsLibraryDir::setDirExpanded(bool expand) -{ - if (m_dirExpanded != expand) { - m_dirExpanded = expand; - emit dirExpandedChanged(); - } -} - -void AssetsLibraryDir::setDirVisible(bool visible) -{ - if (m_dirVisible != visible) { - m_dirVisible = visible; - emit dirVisibleChanged(); - } -} - -QObject *AssetsLibraryDir::filesModel() const -{ - return m_filesModel; -} - -QObject *AssetsLibraryDir::dirsModel() const -{ - return m_dirsModel; -} - -QList AssetsLibraryDir::childAssetsDirs() const -{ - if (m_dirsModel) - return m_dirsModel->assetsDirs(); - - return {}; -} - -void AssetsLibraryDir::addDir(AssetsLibraryDir *assetsDir) -{ - if (!m_dirsModel) - m_dirsModel = new AssetsLibraryDirsModel(this); - - m_dirsModel->addDir(assetsDir); -} - -void AssetsLibraryDir::addFile(const QString &filePath) -{ - if (!m_filesModel) - m_filesModel = new AssetsLibraryFilesModel(this); - - m_filesModel->addFile(filePath); -} - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarydir.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarydir.h deleted file mode 100644 index eb98c700234..00000000000 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarydir.h +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 - -#pragma once - -#include - -namespace QmlDesigner { - -class AssetsLibraryDirsModel; -class AssetsLibraryFilesModel; - -class AssetsLibraryDir : public QObject -{ - Q_OBJECT - - Q_PROPERTY(QString dirName READ dirName NOTIFY dirNameChanged) - Q_PROPERTY(QString dirPath READ dirPath NOTIFY dirPathChanged) - Q_PROPERTY(bool dirExpanded READ dirExpanded WRITE setDirExpanded NOTIFY dirExpandedChanged) - Q_PROPERTY(bool dirVisible READ dirVisible WRITE setDirVisible NOTIFY dirVisibleChanged) - Q_PROPERTY(int dirDepth READ dirDepth NOTIFY dirDepthChanged) - Q_PROPERTY(QObject *filesModel READ filesModel NOTIFY filesModelChanged) - Q_PROPERTY(QObject *dirsModel READ dirsModel NOTIFY dirsModelChanged) - -public: - AssetsLibraryDir(const QString &path, int depth, bool expanded = true, QObject *parent = nullptr); - - QString dirName() const; - QString dirPath() const; - int dirDepth() const; - - bool dirExpanded() const; - bool dirVisible() const; - void setDirExpanded(bool expand); - void setDirVisible(bool visible); - - QObject *filesModel() const; - QObject *dirsModel() const; - - QList childAssetsDirs() const; - - void addDir(AssetsLibraryDir *assetsDir); - void addFile(const QString &filePath); - -signals: - void dirNameChanged(); - void dirPathChanged(); - void dirDepthChanged(); - void dirExpandedChanged(); - void dirVisibleChanged(); - void filesModelChanged(); - void dirsModelChanged(); - -private: - QString m_dirPath; - int m_dirDepth = 0; - bool m_dirExpanded = true; - bool m_dirVisible = true; - AssetsLibraryDirsModel *m_dirsModel = nullptr; - AssetsLibraryFilesModel *m_filesModel = nullptr; -}; - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarydirsmodel.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarydirsmodel.cpp deleted file mode 100644 index 499380accb0..00000000000 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarydirsmodel.cpp +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 - -#include "assetslibrarydirsmodel.h" -#include "assetslibrarymodel.h" - -#include -#include - -namespace QmlDesigner { - -AssetsLibraryDirsModel::AssetsLibraryDirsModel(QObject *parent) - : QAbstractListModel(parent) -{ - // add roles - const QMetaObject meta = AssetsLibraryDir::staticMetaObject; - for (int i = meta.propertyOffset(); i < meta.propertyCount(); ++i) - m_roleNames.insert(i, meta.property(i).name()); -} - -QVariant AssetsLibraryDirsModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) { - qWarning() << Q_FUNC_INFO << "Invalid index requested: " << QString::number(index.row()); - return {}; - } - - if (m_roleNames.contains(role)) - return m_dirs[index.row()]->property(m_roleNames[role]); - - qWarning() << Q_FUNC_INFO << "Invalid role requested: " << QString::number(role); - return {}; -} - -bool AssetsLibraryDirsModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - // currently only dirExpanded property is updatable - if (index.isValid() && m_roleNames.contains(role)) { - QVariant currValue = m_dirs.at(index.row())->property(m_roleNames.value(role)); - if (currValue != value) { - m_dirs.at(index.row())->setProperty(m_roleNames.value(role), value); - if (m_roleNames.value(role) == "dirExpanded") - AssetsLibraryModel::saveExpandedState(value.toBool(), m_dirs.at(index.row())->dirPath()); - emit dataChanged(index, index, {role}); - return true; - } - } - return false; -} - -int AssetsLibraryDirsModel::rowCount([[maybe_unused]] const QModelIndex &parent) const -{ - return m_dirs.size(); -} - -QHash AssetsLibraryDirsModel::roleNames() const -{ - return m_roleNames; -} - -void AssetsLibraryDirsModel::addDir(AssetsLibraryDir *assetsDir) -{ - m_dirs.append(assetsDir); -} - -const QList AssetsLibraryDirsModel::assetsDirs() const -{ - return m_dirs; -} - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarydirsmodel.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarydirsmodel.h deleted file mode 100644 index 7939d1ea5b9..00000000000 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarydirsmodel.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 - -#pragma once - -#include -#include "assetslibrarydir.h" - -namespace QmlDesigner { - -class AssetsLibraryDirsModel : public QAbstractListModel -{ - Q_OBJECT - -public: - AssetsLibraryDirsModel(QObject *parent = nullptr); - - QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; - bool setData(const QModelIndex &index, const QVariant &value, int role) override; - int rowCount(const QModelIndex & parent = QModelIndex()) const override; - QHash roleNames() const override; - - void addDir(AssetsLibraryDir *assetsDir); - - const QList assetsDirs() const; - -private: - QList m_dirs; - QHash m_roleNames; -}; - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryfilesmodel.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryfilesmodel.cpp deleted file mode 100644 index bf8824ae36e..00000000000 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryfilesmodel.cpp +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 -#include "assetslibraryfilesmodel.h" - -#include - -namespace QmlDesigner { - -AssetsLibraryFilesModel::AssetsLibraryFilesModel(QObject *parent) - : QAbstractListModel(parent) -{ - // add roles - m_roleNames.insert(FileNameRole, "fileName"); - m_roleNames.insert(FilePathRole, "filePath"); - m_roleNames.insert(FileDirRole, "fileDir"); -} - -QVariant AssetsLibraryFilesModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) { - qWarning() << Q_FUNC_INFO << "Invalid index requested: " << QString::number(index.row()); - return {}; - } - - if (role == FileNameRole) - return m_files[index.row()].split('/').last(); - - if (role == FilePathRole) - return m_files[index.row()]; - - if (role == FileDirRole) - return QVariant::fromValue(parent()); - - qWarning() << Q_FUNC_INFO << "Invalid role requested: " << QString::number(role); - return {}; -} - -int AssetsLibraryFilesModel::rowCount([[maybe_unused]] const QModelIndex &parent) const -{ - return m_files.size(); -} - -QHash AssetsLibraryFilesModel::roleNames() const -{ - return m_roleNames; -} - -void AssetsLibraryFilesModel::addFile(const QString &filePath) -{ - m_files.append(filePath); -} - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryfilesmodel.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryfilesmodel.h deleted file mode 100644 index 103e57ecd2f..00000000000 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryfilesmodel.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 - -#pragma once - -#include - -namespace QmlDesigner { - -class AssetsLibraryFilesModel : public QAbstractListModel -{ - Q_OBJECT - -public: - AssetsLibraryFilesModel(QObject *parent = nullptr); - - QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; - int rowCount(const QModelIndex & parent = QModelIndex()) const override; - QHash roleNames() const override; - - void addFile(const QString &filePath); - -private: - enum Roles {FileNameRole = Qt::UserRole + 1, - FilePathRole, - FileDirRole}; - - QStringList m_files; - QHash m_roleNames; -}; - -} // QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp index 7d878185378..bc56e9b7f3f 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp @@ -3,6 +3,7 @@ #include "assetslibraryiconprovider.h" #include "assetslibrarymodel.h" +#include "modelnodeoperations.h" #include #include @@ -19,31 +20,18 @@ AssetsLibraryIconProvider::AssetsLibraryIconProvider(SynchronousImageCache &font QPixmap AssetsLibraryIconProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) { QPixmap pixmap; - const QString suffix = "*." + id.split('.').last().toLower(); - if (id == "browse") { - pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/AssetsLibrary/images/browse.png"); - } else if (AssetsLibraryModel::supportedFontSuffixes().contains(suffix)) { - pixmap = generateFontIcons(id, requestedSize); - } else if (AssetsLibraryModel::supportedImageSuffixes().contains(suffix)) { - pixmap = Utils::StyleHelper::dpiSpecificImageFile(id); - } else if (AssetsLibraryModel::supportedTexture3DSuffixes().contains(suffix)) { - pixmap = HdrImage{id}.toPixmap(); + + if (m_thumbnails.contains(id)) { + pixmap = m_thumbnails[id]; } else { - QString type; - if (AssetsLibraryModel::supportedShaderSuffixes().contains(suffix)) - type = "shader"; - else if (AssetsLibraryModel::supportedAudioSuffixes().contains(suffix)) - type = "sound"; - else if (AssetsLibraryModel::supportedVideoSuffixes().contains(suffix)) - type = "video"; - else if (AssetsLibraryModel::supportedEffectMakerSuffixes().contains(suffix)) - type = "default"; + pixmap = fetchPixmap(id, requestedSize); + if (pixmap.isNull()) + pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/AssetsLibrary/images/assets_default.png"); - QString pathTemplate = QString(":/AssetsLibrary/images/asset_%1%2.png").arg(type); - QString path = pathTemplate.arg('_' + QString::number(requestedSize.width())); + if (requestedSize.isValid()) + pixmap = pixmap.scaled(requestedSize, Qt::KeepAspectRatio); - pixmap = Utils::StyleHelper::dpiSpecificImageFile(QFileInfo::exists(path) ? path - : pathTemplate.arg("")); + m_thumbnails[id] = pixmap; } if (size) { @@ -51,12 +39,6 @@ QPixmap AssetsLibraryIconProvider::requestPixmap(const QString &id, QSize *size, size->setHeight(pixmap.height()); } - if (pixmap.isNull()) - pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/AssetsLibrary/images/assets_default.png"); - - if (requestedSize.isValid()) - return pixmap.scaled(requestedSize, Qt::KeepAspectRatio); - return pixmap; } @@ -69,5 +51,46 @@ QPixmap AssetsLibraryIconProvider::generateFontIcons(const QString &filePath, co "Abc"}).pixmap(reqSize); } +QPixmap AssetsLibraryIconProvider::fetchPixmap(const QString &id, const QSize &requestedSize) const +{ + const QString suffix = "*." + id.split('.').last().toLower(); + if (id == "browse") { + return Utils::StyleHelper::dpiSpecificImageFile(":/AssetsLibrary/images/browse.png"); + } else if (AssetsLibraryModel::supportedFontSuffixes().contains(suffix)) { + return generateFontIcons(id, requestedSize); + } else if (AssetsLibraryModel::supportedImageSuffixes().contains(suffix)) { + return Utils::StyleHelper::dpiSpecificImageFile(id); + } else if (AssetsLibraryModel::supportedTexture3DSuffixes().contains(suffix)) { + return HdrImage{id}.toPixmap(); + } else { + QString type; + if (AssetsLibraryModel::supportedShaderSuffixes().contains(suffix)) + type = "shader"; + else if (AssetsLibraryModel::supportedAudioSuffixes().contains(suffix)) + type = "sound"; + else if (AssetsLibraryModel::supportedVideoSuffixes().contains(suffix)) + type = "video"; + else if (AssetsLibraryModel::supportedEffectMakerSuffixes().contains(suffix)) + type = QmlDesigner::ModelNodeOperations::getEffectIcon(id); + + QString pathTemplate = QString(":/AssetsLibrary/images/asset_%1%2.png").arg(type); + QString path = pathTemplate.arg('_' + QString::number(requestedSize.width())); + + return Utils::StyleHelper::dpiSpecificImageFile(QFileInfo::exists(path) + ? path + : pathTemplate.arg("")); + } +} + +void AssetsLibraryIconProvider::clearCache() +{ + m_thumbnails.clear(); +} + +void AssetsLibraryIconProvider::invalidateThumbnail(const QString &id) +{ + m_thumbnails.remove(id); +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.h index cf473c61e32..b18d8e70111 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.h @@ -15,9 +15,12 @@ public: AssetsLibraryIconProvider(SynchronousImageCache &fontImageCache); QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override; + void clearCache(); + void invalidateThumbnail(const QString &id); private: QPixmap generateFontIcons(const QString &filePath, const QSize &requestedSize) const; + QPixmap fetchPixmap(const QString &id, const QSize &requestedSize) const; SynchronousImageCache &m_fontImageCache; @@ -26,6 +29,7 @@ private: std::vector iconSizes = {{128, 128}, // Drag {96, 96}, // list @2x {48, 48}}; // list + QHash m_thumbnails; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp index 737c2d311cd..6587c89cf32 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp @@ -1,154 +1,119 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 +#include +#include +#include +#include +#include +#include + #include "assetslibrarymodel.h" -#include "assetslibrarydir.h" -#include "modelnodeoperations.h" - -#include -#include -#include -#include -#include -#include #include +#include #include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static Q_LOGGING_CATEGORY(assetsLibraryBenchmark, "qtc.assetsLibrary.setRoot", QtWarningMsg) +#include +#include namespace QmlDesigner { -AssetsLibraryModel::AssetsLibraryModel(Utils::FileSystemWatcher *fileSystemWatcher, QObject *parent) - : QAbstractListModel(parent) - , m_fileSystemWatcher(fileSystemWatcher) +AssetsLibraryModel::AssetsLibraryModel(QObject *parent) + : QSortFilterProxyModel{parent} { - // add role names - int role = 0; - const QMetaObject meta = AssetsLibraryDir::staticMetaObject; - for (int i = meta.propertyOffset(); i < meta.propertyCount(); ++i) - m_roleNames.insert(role++, meta.property(i).name()); + createBackendModel(); + + setRecursiveFilteringEnabled(true); +} + +void AssetsLibraryModel::createBackendModel() +{ + m_sourceFsModel = new QFileSystemModel(parent()); + + m_sourceFsModel->setReadOnly(false); + + setSourceModel(m_sourceFsModel); + QObject::connect(m_sourceFsModel, &QFileSystemModel::directoryLoaded, this, &AssetsLibraryModel::directoryLoaded); + + QObject::connect(m_sourceFsModel, &QFileSystemModel::directoryLoaded, this, + [this]([[maybe_unused]] const QString &dir) { + syncHaveFiles(); + }); + + m_fileWatcher = new Utils::FileSystemWatcher(parent()); + QObject::connect(m_fileWatcher, &Utils::FileSystemWatcher::fileChanged, this, + [this] (const QString &path) { + emit fileChanged(path); + }); +} + +void AssetsLibraryModel::destroyBackendModel() +{ + setSourceModel(nullptr); + m_sourceFsModel->disconnect(this); + m_sourceFsModel->deleteLater(); + m_sourceFsModel = nullptr; + + m_fileWatcher->disconnect(this); + m_fileWatcher->deleteLater(); + m_fileWatcher = nullptr; } void AssetsLibraryModel::setSearchText(const QString &searchText) { - if (m_searchText != searchText) { - m_searchText = searchText; - refresh(); - } + m_searchText = searchText; + resetModel(); } -void AssetsLibraryModel::saveExpandedState(bool expanded, const QString &assetPath) +bool AssetsLibraryModel::indexIsValid(const QModelIndex &index) const { - m_expandedStateHash.insert(assetPath, expanded); + static QModelIndex invalidIndex; + return index != invalidIndex; } -bool AssetsLibraryModel::loadExpandedState(const QString &assetPath) +QList AssetsLibraryModel::parentIndices(const QModelIndex &index) const { - return m_expandedStateHash.value(assetPath, true); -} + QModelIndex idx = index; + QModelIndex rootIdx = rootIndex(); + QList result; -bool AssetsLibraryModel::isEffectQmlExist(const QString &effectName) -{ - Utils::FilePath effectsResDir = ModelNodeOperations::getEffectsDirectory(); - Utils::FilePath qmlPath = effectsResDir.resolvePath(effectName + "/" + effectName + ".qml"); - return qmlPath.exists(); -} - -AssetsLibraryModel::DirExpandState AssetsLibraryModel::getAllExpandedState() const -{ - const auto keys = m_expandedStateHash.keys(); - bool allExpanded = true; - bool allCollapsed = true; - for (const QString &assetPath : keys) { - bool expanded = m_expandedStateHash.value(assetPath); - - if (expanded) - allCollapsed = false; - if (!expanded) - allExpanded = false; - - if (!allCollapsed && !allExpanded) - break; + while (idx.isValid() && idx != rootIdx) { + result += idx; + idx = idx.parent(); } - return allExpanded ? DirExpandState::AllExpanded : allCollapsed ? DirExpandState::AllCollapsed - : DirExpandState::SomeExpanded; + return result; } -void AssetsLibraryModel::toggleExpandAll(bool expand) +QString AssetsLibraryModel::currentProjectDirPath() const { - std::function expandDirRecursive; - expandDirRecursive = [&](AssetsLibraryDir *currAssetsDir) { - if (currAssetsDir->dirDepth() > 0) { - currAssetsDir->setDirExpanded(expand); - saveExpandedState(expand, currAssetsDir->dirPath()); - } - - const QList childDirs = currAssetsDir->childAssetsDirs(); - for (const auto childDir : childDirs) - expandDirRecursive(childDir); - }; - - beginResetModel(); - expandDirRecursive(m_assetsDir); - endResetModel(); + return DocumentManager::currentProjectDirPath().toString().append('/'); } -void AssetsLibraryModel::deleteFiles(const QStringList &filePaths) +bool AssetsLibraryModel::requestDeleteFiles(const QStringList &filePaths) { - bool askBeforeDelete = QmlDesignerPlugin::settings().value( - DesignerSettingsKey::ASK_BEFORE_DELETING_ASSET).toBool(); - bool assetDelete = true; + bool askBeforeDelete = QmlDesignerPlugin::settings() + .value(DesignerSettingsKey::ASK_BEFORE_DELETING_ASSET) + .toBool(); - if (askBeforeDelete) { - QMessageBox msg(QMessageBox::Question, tr("Confirm Delete File"), - tr("File%1 might be in use. Delete anyway?\n\n%2") - .arg(filePaths.size() > 1 ? QChar('s') : QChar()) - .arg(filePaths.join('\n').remove(DocumentManager::currentProjectDirPath() - .toString().append('/'))), - QMessageBox::No | QMessageBox::Yes); - QCheckBox cb; - cb.setText(tr("Do not ask this again")); - msg.setCheckBox(&cb); - int ret = msg.exec(); + if (askBeforeDelete) + return false; - if (ret == QMessageBox::No) - assetDelete = false; + deleteFiles(filePaths, false); + return true; +} - if (cb.isChecked()) - QmlDesignerPlugin::settings().insert(DesignerSettingsKey::ASK_BEFORE_DELETING_ASSET, false); - } +void AssetsLibraryModel::deleteFiles(const QStringList &filePaths, bool dontAskAgain) +{ + if (dontAskAgain) + QmlDesignerPlugin::settings().insert(DesignerSettingsKey::ASK_BEFORE_DELETING_ASSET, false); - if (assetDelete) { - for (const QString &filePath : filePaths) { - if (!QFile::exists(filePath)) { - QMessageBox::warning(Core::ICore::dialogParent(), - tr("Failed to Locate File"), - tr("Could not find \"%1\".").arg(filePath)); - } else if (!QFile::remove(filePath)) { - QMessageBox::warning(Core::ICore::dialogParent(), - tr("Failed to Delete File"), - tr("Could not delete \"%1\".").arg(filePath)); - } + for (const QString &filePath : filePaths) { + if (QFile::exists(filePath) && !QFile::remove(filePath)) { + QMessageBox::warning(Core::ICore::dialogParent(), + tr("Failed to Delete File"), + tr("Could not delete \"%1\".").arg(filePath)); } } } @@ -163,15 +128,13 @@ bool AssetsLibraryModel::renameFolder(const QString &folderPath, const QString & dir.cdUp(); - saveExpandedState(loadExpandedState(folderPath), dir.absoluteFilePath(newName)); - return dir.rename(oldName, newName); } -void AssetsLibraryModel::addNewFolder(const QString &folderPath) +bool AssetsLibraryModel::addNewFolder(const QString &folderPath) { QString iterPath = folderPath; - QRegularExpression rgx("\\d+$"); // matches a number at the end of a string + static QRegularExpression rgx("\\d+$"); // matches a number at the end of a string QDir dir{folderPath}; while (dir.exists()) { @@ -192,8 +155,8 @@ void AssetsLibraryModel::addNewFolder(const QString &folderPath) --nPaddingZeros; iterPath = folderPath.mid(0, match.capturedStart()) - + QString('0').repeated(nPaddingZeros) - + QString::number(num); + + QString('0').repeated(nPaddingZeros) + + QString::number(num); } else { iterPath = folderPath + '1'; } @@ -201,136 +164,167 @@ void AssetsLibraryModel::addNewFolder(const QString &folderPath) dir.setPath(iterPath); } - dir.mkpath(iterPath); + return dir.mkpath(iterPath); } -void AssetsLibraryModel::deleteFolder(const QString &folderPath) +bool AssetsLibraryModel::deleteFolderRecursively(const QModelIndex &folderIndex) { - QDir{folderPath}.removeRecursively(); + auto idx = mapToSource(folderIndex); + bool ok = m_sourceFsModel->remove(idx); + if (!ok) + qWarning() << __FUNCTION__ << " could not remove folder recursively: " << m_sourceFsModel->filePath(idx); + + return ok; } -QObject *AssetsLibraryModel::rootDir() const +bool AssetsLibraryModel::allFilePathsAreImages(const QStringList &filePaths) const { - return m_assetsDir; + return Utils::allOf(filePaths, [](const QString &path) { + const QString suffix = "*." + path.split('.').last().toLower(); + + return AssetsLibraryModel::supportedImageSuffixes().contains(suffix); + }); } -bool AssetsLibraryModel::isEmpty() const +bool AssetsLibraryModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { - return m_isEmpty; -} + QString path = m_sourceFsModel->filePath(sourceParent); -void AssetsLibraryModel::setIsEmpty(bool empty) -{ - if (m_isEmpty != empty) { - m_isEmpty = empty; - emit isEmptyChanged(); + QModelIndex sourceIdx = m_sourceFsModel->index(sourceRow, 0, sourceParent); + QString sourcePath = m_sourceFsModel->filePath(sourceIdx); + + if (QFileInfo(sourcePath).isFile() && !m_fileWatcher->watchesFile(sourcePath)) + m_fileWatcher->addFile(sourcePath, Utils::FileSystemWatcher::WatchModifiedDate); + + if (!m_searchText.isEmpty() && path.startsWith(m_rootPath) && QFileInfo{path}.isDir()) { + QString sourceName = m_sourceFsModel->fileName(sourceIdx); + + return QFileInfo{sourcePath}.isFile() && sourceName.contains(m_searchText, Qt::CaseInsensitive); + } else { + return sourcePath.startsWith(m_rootPath) || m_rootPath.startsWith(sourcePath); } } -QVariant AssetsLibraryModel::data(const QModelIndex &index, int role) const +bool AssetsLibraryModel::checkHaveFiles(const QModelIndex &parentIdx) const { - if (!index.isValid()) { - qWarning() << Q_FUNC_INFO << "Invalid index requested: " << QString::number(index.row()); - return {}; + if (!parentIdx.isValid()) + return false; + + const int rowCount = this->rowCount(parentIdx); + for (int i = 0; i < rowCount; ++i) { + auto newIdx = this->index(i, 0, parentIdx); + if (!isDirectory(newIdx)) + return true; + + if (checkHaveFiles(newIdx)) + return true; } - if (m_roleNames.contains(role)) - return m_assetsDir ? m_assetsDir->property(m_roleNames.value(role)) : QVariant(""); - - qWarning() << Q_FUNC_INFO << "Invalid role requested: " << QString::number(role); - return {}; + return false; } -int AssetsLibraryModel::rowCount([[maybe_unused]] const QModelIndex &parent) const +void AssetsLibraryModel::setHaveFiles(bool value) { - return 1; + if (m_haveFiles != value) { + m_haveFiles = value; + emit haveFilesChanged(); + } } -QHash AssetsLibraryModel::roleNames() const +bool AssetsLibraryModel::checkHaveFiles() const { - return m_roleNames; + auto rootIdx = indexForPath(m_rootPath); + return checkHaveFiles(rootIdx); } -// called when a directory is changed to refresh the model for this directory -void AssetsLibraryModel::refresh() +void AssetsLibraryModel::syncHaveFiles() { - setRootPath(m_assetsDir->dirPath()); + setHaveFiles(checkHaveFiles()); } -void AssetsLibraryModel::setRootPath(const QString &path) +void AssetsLibraryModel::setRootPath(const QString &newPath) { - QElapsedTimer time; - if (assetsLibraryBenchmark().isInfoEnabled()) - time.start(); - - qCInfo(assetsLibraryBenchmark) << "start:" << time.elapsed(); - - static const QStringList ignoredTopLevelDirs {"imports", "asset_imports"}; - - m_fileSystemWatcher->clear(); - - std::function parseDir; - parseDir = [this, &parseDir](AssetsLibraryDir *currAssetsDir, int currDepth, bool recursive) { - m_fileSystemWatcher->addDirectory(currAssetsDir->dirPath(), Utils::FileSystemWatcher::WatchAllChanges); - - QDir dir(currAssetsDir->dirPath()); - dir.setNameFilters(supportedSuffixes().values()); - dir.setFilter(QDir::Files); - QDirIterator itFiles(dir); - bool isEmpty = true; - while (itFiles.hasNext()) { - QString filePath = itFiles.next(); - QString fileName = filePath.split('/').last(); - if (m_searchText.isEmpty() || fileName.contains(m_searchText, Qt::CaseInsensitive)) { - currAssetsDir->addFile(filePath); - m_fileSystemWatcher->addFile(filePath, Utils::FileSystemWatcher::WatchAllChanges); - isEmpty = false; - } - } - - if (recursive) { - dir.setNameFilters({}); - dir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); - QDirIterator itDirs(dir); - - while (itDirs.hasNext()) { - QDir subDir = itDirs.next(); - if (currDepth == 1 && ignoredTopLevelDirs.contains(subDir.dirName())) - continue; - - auto assetsDir = new AssetsLibraryDir(subDir.path(), currDepth, - loadExpandedState(subDir.path()), currAssetsDir); - currAssetsDir->addDir(assetsDir); - saveExpandedState(loadExpandedState(assetsDir->dirPath()), assetsDir->dirPath()); - isEmpty &= parseDir(assetsDir, currDepth + 1, true); - } - } - - if (!m_searchText.isEmpty() && isEmpty) - currAssetsDir->setDirVisible(false); - - return isEmpty; - }; - - qCInfo(assetsLibraryBenchmark) << "directories parsed:" << time.elapsed(); - - if (m_assetsDir) - delete m_assetsDir; - beginResetModel(); - m_assetsDir = new AssetsLibraryDir(path, 0, true, this); - bool hasProject = !QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath().isEmpty(); - bool isEmpty = parseDir(m_assetsDir, 1, hasProject); - setIsEmpty(isEmpty); - bool noAssets = m_searchText.isEmpty() && isEmpty; - // noAssets: the model has no asset files (project has no assets added) - // isEmpty: the model has no asset files (assets could exist but are filtered out) + destroyBackendModel(); + createBackendModel(); + + m_rootPath = newPath; + m_sourceFsModel->setRootPath(newPath); + + m_sourceFsModel->setNameFilters(supportedSuffixes().values()); + m_sourceFsModel->setNameFilterDisables(false); - m_assetsDir->setDirVisible(!noAssets); // if there are no assets, hide all empty asset folders endResetModel(); - qCInfo(assetsLibraryBenchmark) << "model reset:" << time.elapsed(); + emit rootPathChanged(); +} + +QString AssetsLibraryModel::rootPath() const +{ + return m_rootPath; +} + +QString AssetsLibraryModel::filePath(const QModelIndex &index) const +{ + QModelIndex fsIdx = mapToSource(index); + return m_sourceFsModel->filePath(fsIdx); +} + +QString AssetsLibraryModel::fileName(const QModelIndex &index) const +{ + QModelIndex fsIdx = mapToSource(index); + return m_sourceFsModel->fileName(fsIdx); +} + +QModelIndex AssetsLibraryModel::indexForPath(const QString &path) const +{ + QModelIndex idx = m_sourceFsModel->index(path, 0); + return mapFromSource(idx); +} + +void AssetsLibraryModel::resetModel() +{ + beginResetModel(); + endResetModel(); +} + +QModelIndex AssetsLibraryModel::rootIndex() const +{ + return indexForPath(m_rootPath); +} + +bool AssetsLibraryModel::isDirectory(const QString &path) const +{ + QFileInfo fi{path}; + return fi.isDir(); +} + +bool AssetsLibraryModel::isDirectory(const QModelIndex &index) const +{ + QString path = filePath(index); + return isDirectory(path); +} + +QModelIndex AssetsLibraryModel::parentDirIndex(const QString &path) const +{ + QModelIndex idx = indexForPath(path); + QModelIndex parentIdx = idx.parent(); + + return parentIdx; +} + +QModelIndex AssetsLibraryModel::parentDirIndex(const QModelIndex &index) const +{ + QModelIndex parentIdx = index.parent(); + return parentIdx; +} + +QString AssetsLibraryModel::parentDirPath(const QString &path) const +{ + QModelIndex idx = indexForPath(path); + QModelIndex parentIdx = idx.parent(); + return filePath(parentIdx); } const QStringList &AssetsLibraryModel::supportedImageSuffixes() @@ -409,17 +403,4 @@ const QSet &AssetsLibraryModel::supportedSuffixes() return allSuffixes; } -const QSet &AssetsLibraryModel::previewableSuffixes() const -{ - static QSet previewableSuffixes; - if (previewableSuffixes.isEmpty()) { - auto insertSuffixes = [](const QStringList &suffixes) { - for (const auto &suffix : suffixes) - previewableSuffixes.insert(suffix); - }; - insertSuffixes(supportedFontSuffixes()); - } - return previewableSuffixes; -} - } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h index c7d3ee493b4..99c96017084 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h @@ -3,39 +3,57 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include -namespace Utils { class FileSystemWatcher; } +#include +#include namespace QmlDesigner { -class SynchronousImageCache; -class AssetsLibraryDir; - -class AssetsLibraryModel : public QAbstractListModel +class AssetsLibraryModel : public QSortFilterProxyModel { Q_OBJECT - Q_PROPERTY(bool isEmpty READ isEmpty WRITE setIsEmpty NOTIFY isEmptyChanged) - public: - AssetsLibraryModel(Utils::FileSystemWatcher *fileSystemWatcher, QObject *parent = nullptr); + AssetsLibraryModel(QObject *parent = nullptr); - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QHash roleNames() const override; - - void refresh(); - void setRootPath(const QString &path); + void setRootPath(const QString &newPath); void setSearchText(const QString &searchText); - bool isEmpty() const; + Q_PROPERTY(bool haveFiles READ haveFiles NOTIFY haveFilesChanged); + + Q_INVOKABLE QString rootPath() const; + Q_INVOKABLE QString filePath(const QModelIndex &index) const; + Q_INVOKABLE QString fileName(const QModelIndex &index) const; + + Q_INVOKABLE QModelIndex indexForPath(const QString &path) const; + Q_INVOKABLE QModelIndex rootIndex() const; + Q_INVOKABLE bool isDirectory(const QString &path) const; + Q_INVOKABLE bool isDirectory(const QModelIndex &index) const; + Q_INVOKABLE QModelIndex parentDirIndex(const QString &path) const; + Q_INVOKABLE QModelIndex parentDirIndex(const QModelIndex &index) const; + Q_INVOKABLE QString parentDirPath(const QString &path) const; + Q_INVOKABLE void syncHaveFiles(); + + Q_INVOKABLE QList parentIndices(const QModelIndex &index) const; + Q_INVOKABLE bool indexIsValid(const QModelIndex &index) const; + Q_INVOKABLE QString currentProjectDirPath() const; + Q_INVOKABLE bool requestDeleteFiles(const QStringList &filePaths); + Q_INVOKABLE void deleteFiles(const QStringList &filePaths, bool dontAskAgain); + Q_INVOKABLE bool renameFolder(const QString &folderPath, const QString &newName); + Q_INVOKABLE bool addNewFolder(const QString &folderPath); + Q_INVOKABLE bool deleteFolderRecursively(const QModelIndex &folderIndex); + Q_INVOKABLE bool allFilePathsAreImages(const QStringList &filePaths) const; + + int columnCount(const QModelIndex &parent = QModelIndex()) const override + { + int result = QSortFilterProxyModel::columnCount(parent); + return std::min(result, 1); + } + + bool haveFiles() const { return m_haveFiles; } static const QStringList &supportedImageSuffixes(); static const QStringList &supportedFragmentShaderSuffixes(); @@ -47,44 +65,26 @@ public: static const QStringList &supportedEffectMakerSuffixes(); static const QSet &supportedSuffixes(); - const QSet &previewableSuffixes() const; - - static void saveExpandedState(bool expanded, const QString &assetPath); - static bool loadExpandedState(const QString &assetPath); - - static bool isEffectQmlExist(const QString &effectName); - - enum class DirExpandState { - SomeExpanded, - AllExpanded, - AllCollapsed - }; - Q_ENUM(DirExpandState) - - Q_INVOKABLE void toggleExpandAll(bool expand); - Q_INVOKABLE DirExpandState getAllExpandedState() const; - Q_INVOKABLE void deleteFiles(const QStringList &filePaths); - Q_INVOKABLE bool renameFolder(const QString &folderPath, const QString &newName); - Q_INVOKABLE void addNewFolder(const QString &folderPath); - Q_INVOKABLE void deleteFolder(const QString &folderPath); - Q_INVOKABLE QObject *rootDir() const; - signals: - void isEmptyChanged(); + void directoryLoaded(const QString &path); + void rootPathChanged(); + void haveFilesChanged(); + void fileChanged(const QString &path); private: - - void setIsEmpty(bool empty); - - QHash> m_iconCache; + void setHaveFiles(bool value); + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + void resetModel(); + void createBackendModel(); + void destroyBackendModel(); + bool checkHaveFiles(const QModelIndex &parentIdx) const; + bool checkHaveFiles() const; QString m_searchText; - Utils::FileSystemWatcher *m_fileSystemWatcher = nullptr; - AssetsLibraryDir *m_assetsDir = nullptr; - bool m_isEmpty = true; - - QHash m_roleNames; - inline static QHash m_expandedStateHash; // + QString m_rootPath; + QFileSystemModel *m_sourceFsModel = nullptr; + bool m_haveFiles = false; + Utils::FileSystemWatcher *m_fileWatcher = nullptr; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp index 56a536c82e8..c43bd485334 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp @@ -2,8 +2,11 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 #include "assetslibraryview.h" + #include "assetslibrarywidget.h" -#include "metainfo.h" +#include "createtexture.h" +#include "qmldesignerplugin.h" + #include #include #include @@ -22,9 +25,7 @@ #include #include #include -#include #include -#include namespace QmlDesigner { @@ -45,6 +46,7 @@ public: AssetsLibraryView::AssetsLibraryView(ExternalDependenciesInterface &externalDependencies) : AbstractView{externalDependencies} + , m_createTextures{this, false} {} AssetsLibraryView::~AssetsLibraryView() @@ -60,6 +62,13 @@ WidgetInfo AssetsLibraryView::widgetInfo() if (m_widget.isNull()) { m_widget = new AssetsLibraryWidget{imageCacheData()->asynchronousFontImageCache, imageCacheData()->synchronousFontImageCache}; + + connect(m_widget, &AssetsLibraryWidget::addTexturesRequested, this, + [&] (const QStringList &filePaths, AddTextureMode mode) { + executeInTransaction("AssetsLibraryView::widgetInfo", [&]() { + m_createTextures.execute(filePaths, mode, m_sceneId); + }); + }); } return createWidgetInfo(m_widget.data(), "Assets", WidgetInfo::LeftPane, 0, tr("Assets")); @@ -73,6 +82,8 @@ void AssetsLibraryView::modelAttached(Model *model) m_widget->setModel(model); setResourcePath(DocumentManager::currentResourcePath().toFileInfo().absoluteFilePath()); + + m_sceneId = model->active3DSceneId(); } void AssetsLibraryView::modelAboutToBeDetached(Model *model) @@ -105,4 +116,9 @@ AssetsLibraryView::ImageCacheData *AssetsLibraryView::imageCacheData() return m_imageCacheData.get(); } +void AssetsLibraryView::active3DSceneChanged(qint32 sceneId) +{ + m_sceneId = sceneId; +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.h index 4194195ec56..95a4c868d52 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.h @@ -3,7 +3,8 @@ #pragma once -#include +#include "abstractview.h" +#include "createtexture.h" #include @@ -30,6 +31,7 @@ public: void modelAboutToBeDetached(Model *model) override; void setResourcePath(const QString &resourcePath); + void active3DSceneChanged(qint32 sceneId) override; private: class ImageCacheData; @@ -39,6 +41,8 @@ private: std::unique_ptr m_imageCacheData; QPointer m_widget; QString m_lastResourcePath; + CreateTextures m_createTextures; + qint32 m_sceneId = -1; }; } diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp index a335227e47d..05ebb729869 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp @@ -17,7 +17,6 @@ #include #include -#include #include #include #include @@ -85,16 +84,12 @@ bool AssetsLibraryWidget::eventFilter(QObject *obj, QEvent *event) AssetsLibraryWidget::AssetsLibraryWidget(AsynchronousImageCache &asynchronousFontImageCache, SynchronousImageCache &synchronousFontImageCache) - : m_itemIconSize(24, 24) - , m_fontImageCache(synchronousFontImageCache) - , m_assetsIconProvider(new AssetsLibraryIconProvider(synchronousFontImageCache)) - , m_fileSystemWatcher(new Utils::FileSystemWatcher(this)) - , m_assetsModel(new AssetsLibraryModel(m_fileSystemWatcher, this)) - , m_assetsWidget(new QQuickWidget(this)) + : m_itemIconSize{24, 24} + , m_fontImageCache{synchronousFontImageCache} + , m_assetsIconProvider{new AssetsLibraryIconProvider(synchronousFontImageCache)} + , m_assetsModel{new AssetsLibraryModel(this)} + , m_assetsWidget{new QQuickWidget(this)} { - m_assetCompressionTimer.setInterval(200); - m_assetCompressionTimer.setSingleShot(true); - setWindowTitle(tr("Assets Library", "Title of assets library widget")); setMinimumWidth(250); @@ -119,21 +114,12 @@ AssetsLibraryWidget::AssetsLibraryWidget(AsynchronousImageCache &asynchronousFon m_assetsWidget->setClearColor(Theme::getColor(Theme::Color::QmlDesigner_BackgroundColorDarkAlternate)); m_assetsWidget->engine()->addImageProvider("qmldesigner_assets", m_assetsIconProvider); m_assetsWidget->rootContext()->setContextProperties(QVector{ - {{"assetsModel"}, QVariant::fromValue(m_assetsModel.data())}, + {{"assetsModel"}, QVariant::fromValue(m_assetsModel)}, {{"rootView"}, QVariant::fromValue(this)}, {{"tooltipBackend"}, QVariant::fromValue(m_fontPreviewTooltipBackend.get())} }); - // If project directory contents change, or one of the asset files is modified, we must - // reconstruct the model to update the icons - connect(m_fileSystemWatcher, - &Utils::FileSystemWatcher::directoryChanged, - [this]([[maybe_unused]] const QString &changedDirPath) { - m_assetCompressionTimer.start(); - }); - - connect(m_fileSystemWatcher, &Utils::FileSystemWatcher::fileChanged, - [](const QString &changeFilePath) { + connect(m_assetsModel, &AssetsLibraryModel::fileChanged, [](const QString &changeFilePath) { QmlDesignerPlugin::instance()->emitAssetChanged(changeFilePath); }); @@ -149,23 +135,7 @@ AssetsLibraryWidget::AssetsLibraryWidget(AsynchronousImageCache &asynchronousFon m_qmlSourceUpdateShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F6), this); connect(m_qmlSourceUpdateShortcut, &QShortcut::activated, this, &AssetsLibraryWidget::reloadQmlSource); - - connect(&m_assetCompressionTimer, &QTimer::timeout, this, [this]() { - // TODO: find a clever way to only refresh the changed directory part of the model - - // Don't bother with asset updates after model has detached, project is probably closing - if (!m_model.isNull()) { - if (QApplication::activeModalWidget()) { - // Retry later, as updating file system watchers can crash when there is an active - // modal widget - m_assetCompressionTimer.start(); - } else { - m_assetsModel->refresh(); - // reload assets qml so that an overridden file's image shows the new image - QTimer::singleShot(100, this, &AssetsLibraryWidget::reloadQmlSource); - } - } - }); + connect(this, &AssetsLibraryWidget::extFilesDrop, this, &AssetsLibraryWidget::handleExtFilesDrop, Qt::QueuedConnection); QmlDesignerPlugin::trackWidgetFocusTime(this, Constants::EVENT_ASSETSLIBRARY_TIME); @@ -173,7 +143,26 @@ AssetsLibraryWidget::AssetsLibraryWidget(AsynchronousImageCache &asynchronousFon reloadQmlSource(); } -AssetsLibraryWidget::~AssetsLibraryWidget() = default; +bool AssetsLibraryWidget::qtVersionIsAtLeast6_4() const +{ + return (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)); +} + + +void AssetsLibraryWidget::addTextures(const QStringList &filePaths) +{ + emit addTexturesRequested(filePaths, AddTextureMode::Texture); +} + +void AssetsLibraryWidget::addLightProbe(const QString &filePath) +{ + emit addTexturesRequested({filePath}, AddTextureMode::LightProbe); +} + +void AssetsLibraryWidget::invalidateThumbnail(const QString &id) +{ + m_assetsIconProvider->invalidateThumbnail(id); +} QList AssetsLibraryWidget::createToolBarWidgets() { @@ -182,8 +171,9 @@ QList AssetsLibraryWidget::createToolBarWidgets() void AssetsLibraryWidget::handleSearchFilterChanged(const QString &filterText) { - if (filterText == m_filterText || (m_assetsModel->isEmpty() && filterText.contains(m_filterText))) - return; + if (filterText == m_filterText || (!m_assetsModel->haveFiles() + && filterText.contains(m_filterText, Qt::CaseInsensitive))) + return; m_filterText = filterText; updateSearch(); @@ -194,6 +184,16 @@ void AssetsLibraryWidget::handleAddAsset() addResources({}); } +void AssetsLibraryWidget::emitExtFilesDrop(const QList &simpleFilePaths, + const QList &complexFilePaths, + const QString &targetDirPath) +{ + // workaround for but QDS-8010: we need to postpone the call to handleExtFilesDrop, otherwise + // the TreeViewDelegate might be recreated (therefore, destroyed) while we're still in a handler + // of a QML DropArea which is a child of the delegate being destroyed - this would cause a crash. + emit extFilesDrop(simpleFilePaths, complexFilePaths, targetDirPath); +} + void AssetsLibraryWidget::handleExtFilesDrop(const QList &simpleFilePaths, const QList &complexFilePaths, const QString &targetDirPath) @@ -210,7 +210,7 @@ void AssetsLibraryWidget::handleExtFilesDrop(const QList &simpleFilePaths, } else { AddFilesResult result = ModelNodeOperations::addFilesToProject(simpleFilePathStrings, targetDirPath); - if (result == AddFilesResult::Failed) { + if (result.status() == AddFilesResult::Failed) { Core::AsynchronousMessageBox::warning(tr("Failed to Add Files"), tr("Could not add %1 to project.") .arg(simpleFilePathStrings.join(' '))); @@ -276,6 +276,7 @@ void AssetsLibraryWidget::updateSearch() void AssetsLibraryWidget::setResourcePath(const QString &resourcePath) { m_assetsModel->setRootPath(resourcePath); + m_assetsIconProvider->clearCache(); updateSearch(); } @@ -408,10 +409,22 @@ void AssetsLibraryWidget::addResources(const QStringList &files) if (operation) { AddFilesResult result = operation(fileNames, document->fileName().parentDir().toString(), true); - if (result == AddFilesResult::Failed) { + if (result.status() == AddFilesResult::Failed) { Core::AsynchronousMessageBox::warning(tr("Failed to Add Files"), tr("Could not add %1 to project.") .arg(fileNames.join(' '))); + } else { + if (!result.directory().isEmpty()) { + emit directoryCreated(result.directory()); + } else if (result.haveDelayedResult()) { + QObject *delayedResult = result.delayedResult(); + QObject::connect(delayedResult, &QObject::destroyed, this, [this, delayedResult]() { + QVariant propValue = delayedResult->property(AddFilesResult::directoryPropName); + QString directory = propValue.toString(); + if (!directory.isEmpty()) + emit directoryCreated(directory); + }); + } } } else { Core::AsynchronousMessageBox::warning(tr("Failed to Add Files"), diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h index 8ac41a44a36..90a7038b5db 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h @@ -4,7 +4,9 @@ #pragma once #include + #include "assetslibrarymodel.h" +#include "createtexture.h" #include #include @@ -21,7 +23,7 @@ class QShortcut; QT_END_NAMESPACE namespace Utils { - class FileSystemWatcher; + class QtcProcess; } namespace QmlDesigner { @@ -42,7 +44,7 @@ class AssetsLibraryWidget : public QFrame public: AssetsLibraryWidget(AsynchronousImageCache &asynchronousFontImageCache, SynchronousImageCache &synchronousFontImageCache); - ~AssetsLibraryWidget(); + ~AssetsLibraryWidget() = default; QList createToolBarWidgets(); @@ -59,14 +61,29 @@ public: Q_INVOKABLE void startDragAsset(const QStringList &assetPaths, const QPointF &mousePos); Q_INVOKABLE void handleAddAsset(); Q_INVOKABLE void handleSearchFilterChanged(const QString &filterText); + Q_INVOKABLE void handleExtFilesDrop(const QList &simpleFilePaths, const QList &complexFilePaths, - const QString &targetDirPath = {}); + const QString &targetDirPath); + + Q_INVOKABLE void emitExtFilesDrop(const QList &simpleFilePaths, + const QList &complexFilePaths, + const QString &targetDirPath = {}); + Q_INVOKABLE QSet supportedAssetSuffixes(bool complex); Q_INVOKABLE void openEffectMaker(const QString &filePath); + Q_INVOKABLE bool qtVersionIsAtLeast6_4() const; + Q_INVOKABLE void invalidateThumbnail(const QString &id); + Q_INVOKABLE void addTextures(const QStringList &filePaths); + Q_INVOKABLE void addLightProbe(const QString &filePaths); signals: void itemActivated(const QString &itemName); + void extFilesDrop(const QList &simpleFilePaths, + const QList &complexFilePaths, + const QString &targetDirPath); + void directoryCreated(const QString &path); + void addTexturesRequested(const QStringList &filePaths, QmlDesigner::AddTextureMode mode); protected: bool eventFilter(QObject *obj, QEvent *event) override; @@ -77,14 +94,12 @@ private: void addResources(const QStringList &files); void updateSearch(); - QTimer m_assetCompressionTimer; QSize m_itemIconSize; SynchronousImageCache &m_fontImageCache; AssetsLibraryIconProvider *m_assetsIconProvider = nullptr; - Utils::FileSystemWatcher *m_fileSystemWatcher = nullptr; - QPointer m_assetsModel; + AssetsLibraryModel *m_assetsModel = nullptr; QScopedPointer m_assetsWidget; std::unique_ptr m_fontPreviewTooltipBackend; diff --git a/src/plugins/qmldesigner/components/assetslibrary/images/asset_default.png b/src/plugins/qmldesigner/components/assetslibrary/images/asset_default.png deleted file mode 100644 index ef59d892791..00000000000 Binary files a/src/plugins/qmldesigner/components/assetslibrary/images/asset_default.png and /dev/null differ diff --git a/src/plugins/qmldesigner/components/assetslibrary/images/asset_default@2x.png b/src/plugins/qmldesigner/components/assetslibrary/images/asset_default@2x.png deleted file mode 100644 index 6540cc859cd..00000000000 Binary files a/src/plugins/qmldesigner/components/assetslibrary/images/asset_default@2x.png and /dev/null differ diff --git a/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectClass.png b/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectClass.png new file mode 100644 index 00000000000..1ce785cbfd2 Binary files /dev/null and b/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectClass.png differ diff --git a/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectClass@2x.png b/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectClass@2x.png new file mode 100644 index 00000000000..a602bdb4ad9 Binary files /dev/null and b/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectClass@2x.png differ diff --git a/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectClass_128.png b/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectClass_128.png new file mode 100644 index 00000000000..55483c263b2 Binary files /dev/null and b/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectClass_128.png differ diff --git a/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectExported.png b/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectExported.png new file mode 100644 index 00000000000..fac52d0f7c5 Binary files /dev/null and b/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectExported.png differ diff --git a/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectExported@2x.png b/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectExported@2x.png new file mode 100644 index 00000000000..ab6a5f29407 Binary files /dev/null and b/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectExported@2x.png differ diff --git a/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectExported_128.png b/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectExported_128.png new file mode 100644 index 00000000000..07960893e87 Binary files /dev/null and b/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectExported_128.png differ diff --git a/src/plugins/qmldesigner/components/assetslibrary/images/assets_default.png b/src/plugins/qmldesigner/components/assetslibrary/images/assets_default.png new file mode 100644 index 00000000000..8e8f94cf5f5 Binary files /dev/null and b/src/plugins/qmldesigner/components/assetslibrary/images/assets_default.png differ diff --git a/src/plugins/qmldesigner/components/assetslibrary/images/assets_default@2x.png b/src/plugins/qmldesigner/components/assetslibrary/images/assets_default@2x.png new file mode 100644 index 00000000000..e54039dcbde Binary files /dev/null and b/src/plugins/qmldesigner/components/assetslibrary/images/assets_default@2x.png differ diff --git a/src/plugins/qmldesigner/components/assetslibrary/images/assets_default_128.png b/src/plugins/qmldesigner/components/assetslibrary/images/assets_default_128.png new file mode 100644 index 00000000000..27997ea2a75 Binary files /dev/null and b/src/plugins/qmldesigner/components/assetslibrary/images/assets_default_128.png differ diff --git a/src/plugins/qmldesigner/components/bindingeditor/actioneditor.cpp b/src/plugins/qmldesigner/components/bindingeditor/actioneditor.cpp index ba2e439fca5..cfb163fd776 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/actioneditor.cpp +++ b/src/plugins/qmldesigner/components/bindingeditor/actioneditor.cpp @@ -132,8 +132,7 @@ bool isLiteral(QmlJS::AST::Node *ast) || QmlJS::AST::cast(ast) || QmlJS::AST::cast(ast)) return true; - else - return false; + return false; } TypeName skipCpp(TypeName typeName) @@ -229,16 +228,18 @@ void ActionEditor::prepareConnections() QmlJS::AST::ExpressionNode *expression = newDoc->expression(); if (expression && !isLiteral(expression)) { - QmlJS::ValueOwner *interp = context->valueOwner(); - const QmlJS::Value *value = interp->convertToObject(scopeChain.evaluate(expression)); + if (QmlJS::ValueOwner *interp = context->valueOwner()) { + if (const QmlJS::Value *value = interp->convertToObject( + scopeChain.evaluate(expression))) { + if (value->asNullValue() && !methodBlackList.contains(slotName)) + connection.methods.append(QString::fromUtf8(slotName)); - if (value->asNullValue() && !methodBlackList.contains(slotName)) - connection.methods.append(QString::fromUtf8(slotName)); - - if (const QmlJS::FunctionValue *f = value->asFunctionValue()) { - // Only add slots with zero arguments - if (f->namedArgumentCount() == 0 && !methodBlackList.contains(slotName)) - connection.methods.append(QString::fromUtf8(slotName)); + if (const QmlJS::FunctionValue *f = value->asFunctionValue()) { + // Only add slots with zero arguments + if (f->namedArgumentCount() == 0 && !methodBlackList.contains(slotName)) + connection.methods.append(QString::fromUtf8(slotName)); + } + } } } } diff --git a/src/plugins/qmldesigner/components/bindingeditor/signallist.cpp b/src/plugins/qmldesigner/components/bindingeditor/signallist.cpp index 298ba7f91ea..b30f0763d35 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/signallist.cpp +++ b/src/plugins/qmldesigner/components/bindingeditor/signallist.cpp @@ -223,7 +223,7 @@ void SignalList::addConnection(const QModelIndex &modelIndex) const ModelNode rootModelNode = view->rootModelNode(); if (rootModelNode.isValid() && rootModelNode.metaInfo().isValid()) { - NodeMetaInfo nodeMetaInfo = view->model()->metaInfo("QtQuick.Connections"); + NodeMetaInfo nodeMetaInfo = view->model()->qtQuickConnectionsMetaInfo(); if (nodeMetaInfo.isValid()) { view->executeInTransaction("ConnectionModel::addConnection", [=, &rootModelNode](){ ModelNode newNode = view->createModelNode("QtQuick.Connections", diff --git a/src/plugins/qmldesigner/components/colortool/colortool.cpp b/src/plugins/qmldesigner/components/colortool/colortool.cpp index d4eaf42d0bc..4efb23a1817 100644 --- a/src/plugins/qmldesigner/components/colortool/colortool.cpp +++ b/src/plugins/qmldesigner/components/colortool/colortool.cpp @@ -27,53 +27,8 @@ namespace QmlDesigner { -class ColorToolAction : public AbstractAction -{ -public: - ColorToolAction() : AbstractAction(QCoreApplication::translate("ColorToolAction","Edit Color")) {} - - QByteArray category() const override - { - return QByteArray(); - } - - QByteArray menuId() const override - { - return "ColorTool"; - } - - int priority() const override - { - return CustomActionsPriority; - } - - Type type() const override - { - return FormEditorAction; - } - -protected: - bool isVisible(const SelectionContext &selectionContext) const override - { - if (selectionContext.singleNodeIsSelected()) - return selectionContext.currentSingleSelectedNode().metaInfo().hasProperty("color"); - - return false; - } - - bool isEnabled(const SelectionContext &selectionContext) const override - { - return isVisible(selectionContext); - } -}; - ColorTool::ColorTool() { - auto colorToolAction = new ColorToolAction; - QmlDesignerPlugin::instance()->designerActionManager().addDesignerAction(colorToolAction); - connect(colorToolAction->action(), &QAction::triggered, [=]() { - view()->changeCurrentToolTo(this); - }); } ColorTool::~ColorTool() = default; diff --git a/src/plugins/qmldesigner/components/componentcore/abstractaction.cpp b/src/plugins/qmldesigner/components/componentcore/abstractaction.cpp index db0cfc591bb..88d5991b129 100644 --- a/src/plugins/qmldesigner/components/componentcore/abstractaction.cpp +++ b/src/plugins/qmldesigner/components/componentcore/abstractaction.cpp @@ -38,9 +38,21 @@ void AbstractAction::updateContext() if (m_selectionContext.isValid()) { m_defaultAction->setEnabled(isEnabled(m_selectionContext)); m_defaultAction->setVisible(isVisible(m_selectionContext)); + if (m_defaultAction->isCheckable()) + m_defaultAction->setChecked(isChecked(m_selectionContext)); } } +bool AbstractAction::isChecked(const SelectionContext &) const +{ + return false; +} + +void AbstractAction::setCheckable(bool checkable) +{ + m_defaultAction->setCheckable(checkable); +} + DefaultAction *AbstractAction::defaultAction() const { return m_defaultAction.data(); diff --git a/src/plugins/qmldesigner/components/componentcore/abstractaction.h b/src/plugins/qmldesigner/components/componentcore/abstractaction.h index 081740b03dc..1d66f947982 100644 --- a/src/plugins/qmldesigner/components/componentcore/abstractaction.h +++ b/src/plugins/qmldesigner/components/componentcore/abstractaction.h @@ -38,8 +38,11 @@ public: protected: virtual void updateContext(); + virtual bool isChecked(const SelectionContext &selectionContext) const; virtual bool isVisible(const SelectionContext &selectionContext) const = 0; virtual bool isEnabled(const SelectionContext &selectionContext) const = 0; + + void setCheckable(bool checkable); SelectionContext selectionContext() const; private: diff --git a/src/plugins/qmldesigner/components/componentcore/actioninterface.h b/src/plugins/qmldesigner/components/componentcore/actioninterface.h index bad16f6d91a..5f487ef4724 100644 --- a/src/plugins/qmldesigner/components/componentcore/actioninterface.h +++ b/src/plugins/qmldesigner/components/componentcore/actioninterface.h @@ -25,10 +25,10 @@ public: }; enum Priorities { - HighestPriority = ComponentCoreConstants::priorityFirst, - CustomActionsPriority = ComponentCoreConstants::priorityCustomActions, - RefactoringActionsPriority = ComponentCoreConstants::priorityRefactoring, - LowestPriority = ComponentCoreConstants::priorityLast + HighestPriority = ComponentCoreConstants::Priorities::Top, + CustomActionsPriority = ComponentCoreConstants::Priorities::CustomActionsSection, + RefactoringActionsPriority = ComponentCoreConstants::Priorities::Refactoring, + LowestPriority = ComponentCoreConstants::Priorities::Last }; enum class TargetView { diff --git a/src/plugins/qmldesigner/components/componentcore/anchoraction.cpp b/src/plugins/qmldesigner/components/componentcore/anchoraction.cpp new file mode 100644 index 00000000000..630a53dc39e --- /dev/null +++ b/src/plugins/qmldesigner/components/componentcore/anchoraction.cpp @@ -0,0 +1,133 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "anchoraction.h" +#include "nodeabstractproperty.h" + +#include + +using namespace QmlDesigner; + +static void setAnchorToTheSameOnTarget(AbstractView *view, + const ModelNode &objectNode, + const AnchorLineType &objectAnchorType, + const ModelNode &targetNode, + double margin = 0) +{ + QmlItemNode node = objectNode; + QmlItemNode dstNode = targetNode; + if (!node.isValid() || !dstNode.isValid()) + return; + + view->executeInTransaction("QmlAnchorAction|setAnchorToTheSameOnTarget", [&] { + int maxFlagBits = 8 * sizeof(AnchorLineType); + for (int i = 0; i < maxFlagBits; ++i) { + AnchorLineType requiredAnchor = AnchorLineType(0x1 << i); + if (requiredAnchor & objectAnchorType) { + node.anchors().setAnchor(requiredAnchor, dstNode, requiredAnchor); + if (qFuzzyIsNull(margin)) + node.anchors().removeMargin(requiredAnchor); + else + node.anchors().setMargin(requiredAnchor, margin); + } + } + }); +} + +static void anchorToParent(const SelectionContext &selectionState, AnchorLineType anchorType) +{ + if (!selectionState.view()) + return; + + ModelNode modelNode = selectionState.currentSingleSelectedNode(); + ModelNode parentModelNode = modelNode.parentProperty().parentModelNode(); + setAnchorToTheSameOnTarget(selectionState.view(), modelNode, anchorType, parentModelNode); +} + +static void removeAnchor(const SelectionContext &selectionState, + const AnchorLineType &objectAnchorType, + double margin = 0) +{ + ModelNode srcModelNode = selectionState.currentSingleSelectedNode(); + QmlItemNode node = srcModelNode; + AbstractView *view = srcModelNode.view(); + if (!node.isValid() || !view) + return; + + view->executeInTransaction("QmlAnchorAction|removeAnchor", [&] { + + int maxFlagBits = 8 * sizeof(AnchorLineType); + for (int i = 0; i < maxFlagBits; ++i) { + AnchorLineType singleAnchor = AnchorLineType(0x1 << i); + if (singleAnchor & objectAnchorType) { + node.anchors().removeAnchor(singleAnchor); + if (qFuzzyIsNull(margin)) + node.anchors().removeMargin(singleAnchor); + else + node.anchors().setMargin(singleAnchor, margin); + } + } + }); +} + +static bool singleSelectionIsAnchoredToParentBy(const SelectionContext &selectionState, + const AnchorLineType &lineType) +{ + QmlItemNode itemNode(selectionState.currentSingleSelectedNode()); + QmlItemNode itemNodeParent(itemNode.modelParentItem()); + if (itemNode.isValid() && itemNodeParent.isValid()) { + bool allSet = false; + int maxFlagBits = 8 * sizeof(AnchorLineType); + for (int i = 0; i < maxFlagBits; ++i) { + AnchorLineType singleAnchor = AnchorLineType(0x1 << i); + if (singleAnchor & lineType) { + if (itemNode.anchors().modelAnchor(singleAnchor).qmlItemNode() == itemNodeParent) + allSet = true; + else + return false; + } + } + return allSet; + } + return false; +} + +static void toggleParentAnchor(const SelectionContext &selectionState, AnchorLineType anchorType) +{ + if (singleSelectionIsAnchoredToParentBy(selectionState, anchorType)) + removeAnchor(selectionState, anchorType); + else + anchorToParent(selectionState, anchorType); +} + +static SelectionContextOperation getSuitableAnchorFunction(AnchorLineType lineType) +{ + return std::bind(toggleParentAnchor, std::placeholders::_1, lineType); +} + +ParentAnchorAction::ParentAnchorAction(const QByteArray &id, + const QString &description, + const QIcon &icon, + const QString &tooltip, + const QByteArray &category, + const QKeySequence &key, + int priority, + AnchorLineType lineType) + : ModelNodeAction(id, + description, + icon, + tooltip, + category, + key, + priority, + getSuitableAnchorFunction(lineType), + &SelectionContextFunctors::singleSelectedItem) + , m_lineType(lineType) +{ + setCheckable(true); +} + +bool ParentAnchorAction::isChecked(const SelectionContext &selectionState) const +{ + return singleSelectionIsAnchoredToParentBy(selectionState, m_lineType); +} diff --git a/src/plugins/qmldesigner/components/componentcore/anchoraction.h b/src/plugins/qmldesigner/components/componentcore/anchoraction.h new file mode 100644 index 00000000000..29a190c0b73 --- /dev/null +++ b/src/plugins/qmldesigner/components/componentcore/anchoraction.h @@ -0,0 +1,27 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include +namespace QmlDesigner { + +class ParentAnchorAction : public ModelNodeAction +{ +public: + ParentAnchorAction(const QByteArray &id, + const QString &description, + const QIcon &icon, + const QString &tooltip, + const QByteArray &category, + const QKeySequence &key, + int priority, + AnchorLineType lineType); + + bool isChecked(const SelectionContext &selectionState) const override; + +private: + const AnchorLineType m_lineType; +}; + +} diff --git a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h index 090d4740fe3..3a53555ccdd 100644 --- a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h +++ b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h @@ -17,8 +17,9 @@ const char arrangeCategory[] = "Arrange"; const char qmlPreviewCategory[] = "QmlPreview"; const char editCategory[] = "Edit"; const char anchorsCategory[] = "Anchors"; -const char positionCategory[] = "Position"; +const char positionerCategory[] = "Position"; const char groupCategory[] = "Group"; +const char snappingCategory[] = "Snapping"; const char layoutCategory[] = "Layout"; const char flowCategory[] = "Flow"; const char flowEffectCategory[] = "FlowEffect"; @@ -40,6 +41,14 @@ const char applyFormatCommandId[] = "ApplyFormat"; const char visiblityCommandId[] = "ToggleVisiblity"; const char anchorsFillCommandId[] = "AnchorsFill"; const char anchorsResetCommandId[] = "AnchorsReset"; + +const char anchorParentTopAndBottomCommandId[] = "AnchorParentTopAndBottom"; +const char anchorParentLeftAndRightCommandId[] = "AnchorParentLeftAndRight"; +const char anchorParentTopCommandId[] = "AnchorParentTop"; +const char anchorParentRightCommandId[] = "AnchorParentRight"; +const char anchorParentBottomCommandId[] = "AnchorParentBottom"; +const char anchorParentLeftCommandId[] = "AnchorParentLeft"; + const char removePositionerCommandId[] = "RemovePositioner"; const char createFlowActionAreaCommandId[] = "CreateFlowActionArea"; const char setFlowStartCommandId[] = "SetFlowStart"; @@ -58,7 +67,7 @@ const char goIntoComponentCommandId[] = "GoIntoComponent"; const char mergeTemplateCommandId[] = "MergeTemplate"; const char goToImplementationCommandId[] = "GoToImplementation"; const char addSignalHandlerCommandId[] = "AddSignalHandler"; -const char moveToComponentCommandId[] = "MoveToComponent"; +const char makeComponentCommandId[] = "MakeComponent"; const char editMaterialCommandId[] = "EditMaterial"; const char addItemToStackedContainerCommandId[] = "AddItemToStackedContainer"; const char addTabBarToStackedContainerCommandId[] = "AddTabBarToStackedContainer"; @@ -70,7 +79,7 @@ const char addToGroupItemCommandId[] = "AddToGroupItem"; const char removeGroupItemCommandId[] = "RemoveToGroupItem"; const char fitRootToScreenCommandId[] = "FitRootToScreen"; const char fitSelectionToScreenCommandId[] = "FitSelectionToScreen"; -const char editAnnotationCommandId[] = "EditAnnotation"; +const char editAnnotationsCommandId[] = "EditAnnotation"; const char addMouseAreaFillCommandId[] = "AddMouseAreaFill"; const char openSignalDialogCommandId[] = "OpenSignalDialog"; @@ -83,8 +92,9 @@ const char selectEffectDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu const char arrangeCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Arrange"); const char editCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit"); const char anchorsCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Anchors"); -const char positionCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Position"); +const char positionerCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Positioner"); const char groupCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Group"); +const char snappingCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Snapping"); const char layoutCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Layout"); const char flowCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Flow"); const char flowEffectCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Flow Effects"); @@ -111,13 +121,13 @@ const char resetPositionDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMen const char copyFormatDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Copy Formatting"); const char applyFormatDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Apply Formatting"); -const char goIntoComponentDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Go into Component"); -const char mergeTemplateDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Merge File With Template"); +const char enterComponentDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Enter Component"); +const char mergeTemplateDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Merge with Template"); const char goToImplementationDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Go to Implementation"); const char addSignalHandlerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Add New Signal Handler"); -const char moveToComponentDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Move Component into Separate File"); +const char makeComponentDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Make Component"); const char editMaterialDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit Material"); -const char editAnnotationDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit Annotation"); +const char editAnnotationsDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit Annotations"); const char addMouseAreaFillDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Add Mouse Area"); const char openSignalDialogDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Open Signal Dialog"); @@ -129,13 +139,20 @@ const char resetZDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Re const char reverseDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Reverse"); -const char anchorsFillDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Fill"); -const char anchorsResetDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Reset"); +const char anchorsFillDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Fill Parent"); +const char anchorsResetDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "No Anchors"); -const char layoutColumnPositionerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Position in Column"); -const char layoutRowPositionerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Position in Row"); -const char layoutGridPositionerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Position in Grid"); -const char layoutFlowPositionerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Position in Flow"); +const char anchorParentTopAndBottomDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Top And Bottom"); +const char anchorParentLeftAndRightDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Left And Right"); +const char anchorParentTopDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Top"); +const char anchorParentRightDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Right"); +const char anchorParentBottomDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Bottom"); +const char anchorParentLeftDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Left"); + +const char layoutColumnPositionerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Column Positioner"); +const char layoutRowPositionerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Row Positioner"); +const char layoutGridPositionerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Grid Positioner"); +const char layoutFlowPositionerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Flow Positioner"); const char removePositionerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Remove Positioner"); const char createFlowActionAreaDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Create Flow Action"); const char setFlowStartDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Set Flow Start"); @@ -150,9 +167,9 @@ const char addTabBarToStackedContainerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesi const char increaseIndexToStackedContainerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Increase Index"); const char decreaseIndexToStackedContainerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Decrease Index"); -const char layoutColumnLayoutDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Layout in Column Layout"); -const char layoutRowLayoutDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Layout in Row Layout"); -const char layoutGridLayoutDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Layout in Grid Layout"); +const char layoutColumnLayoutDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Column Layout"); +const char layoutRowLayoutDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Row Layout"); +const char layoutGridLayoutDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Grid Layout"); const char layoutFillWidthDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Fill Width"); const char layoutFillHeightDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Fill Height"); @@ -182,26 +199,48 @@ const char addFlowActionToolTip[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", const char editListModelDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit List Model..."); - -const int priorityFirst = 280; -const int prioritySelectionCategory = 220; -const int priorityConnectionsCategory = 210; -const int priorityQmlPreviewCategory = 200; -const int priorityStackCategory = 180; -const int priorityEditCategory = 160; -const int priorityAnchorsCategory = 140; -const int priorityFlowCategory = 240; -const int priorityGroupCategory = 140; -const int priorityPositionCategory = 130; -const int priorityLayoutCategory = 120; -const int priorityStackedContainerCategory = priorityLayoutCategory; -const int priorityEventListCategory = 105; -const int priorityTopLevelSeperator = 100; -const int priorityCustomActions = 80; -const int priorityRefactoring = 60; -const int priorityGoIntoComponent = 40; -const int priorityGenericToolBar = 50; -const int priorityLast = 60; +namespace Priorities { +enum PrioritiesEnum : int { + Top = 0, + FlowCategory, + ComponentActions, + /******** Section *****************************/ + ModifySection = 1000, + ConnectionsCategory, + SelectionCategory, + ArrangeCategory, + EditCategory, + /******** Section *****************************/ + PositionSection = 2000, + SnappingCategory, + AnchorsCategory, + LayoutCategory, + PositionCategory, + StackedContainerCategory, + /******** Section *****************************/ + EventSection = 3000, + TimelineCategory, + EventListCategory, + /******** Section *****************************/ + AdditionsSection = 4000, + EditAnnotations, + AddMouseArea, + MergeWithTemplate, + /******** Section *****************************/ + ViewOprionsSection = 5000, + ResetView, + Group, + Visibility, + ShowBoundingRect, + /******** Section *****************************/ + CustomActionsSection = 6000, + QmlPreviewCategory, + SignalsDialog, + Refactoring, + GenericToolBar, + Last +}; +}; const char addImagesDisplayString[] = QT_TRANSLATE_NOOP("QmlDesignerAddResources", "Image Files"); const char addFontsDisplayString[] = QT_TRANSLATE_NOOP("QmlDesignerAddResources", "Font Files"); diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp index bb60e2a1c9d..dd07f433ca5 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -3,6 +3,7 @@ #include "designeractionmanager.h" +#include "anchoraction.h" #include "changestyleaction.h" #include "designeractionmanagerview.h" #include "designermcumanager.h" @@ -40,6 +41,7 @@ #include #include #include +#include #include @@ -72,7 +74,7 @@ DesignerActionToolBar *DesignerActionManager::createToolBar(QWidget *parent) con }); Utils::sort(categories, [](ActionInterface *l, ActionInterface *r) { - return l->priority() > r->priority(); + return l->priority() < r->priority(); }); for (auto *categoryAction : std::as_const(categories)) { @@ -81,7 +83,7 @@ DesignerActionToolBar *DesignerActionManager::createToolBar(QWidget *parent) con }); Utils::sort(actions, [](ActionInterface *l, ActionInterface *r) { - return l->priority() > r->priority(); + return l->priority() < r->priority(); }); bool addSeparator = false; @@ -141,7 +143,7 @@ QGraphicsWidget *DesignerActionManager::createFormEditorToolBar(QGraphicsItem *p }); Utils::sort(actions, [](ActionInterface *l, ActionInterface *r) { - return l->priority() > r->priority(); + return l->priority() < r->priority(); }); QGraphicsWidget *toolbar = new QGraphicsWidget(parent); @@ -277,7 +279,7 @@ QHash DesignerActionManager::handleExternalAssetsDrop(cons AddResourceOperation operation = categoryOperation.value(category); QStringList files = categoryFiles.value(category); AddFilesResult result = operation(files, {}, true); - if (result == AddFilesResult::Succeeded) + if (result.status() == AddFilesResult::Succeeded) addedCategoryFiles.insert(category, files); } @@ -400,8 +402,7 @@ public: if (!ModelNode::isThisOrAncestorLocked(parentNode)) { ActionTemplate *selectionAction = new ActionTemplate("SELECTION", {}, &ModelNodeOperations::select); selectionAction->setParent(menu()); - selectionAction->setText(QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Select parent: %1")).arg( - captionForModelNode(parentNode))); + selectionAction->setText(QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Parent"))); SelectionContext nodeSelectionContext = selectionContext(); nodeSelectionContext.setTargetNode(parentNode); @@ -487,12 +488,27 @@ QStringList getSignalsList(const ModelNode &node) struct SlotEntry { - QString category; QString name; std::function action; }; -QList getSlotsLists(const ModelNode &node) +struct SlotList +{ + QString categoryName; + QList slotEntries; +}; + +QList stateGroups(const ModelNode &node) +{ + if (!node.view()->isAttached()) + return {}; + + const auto groupMetaInfo = node.view()->model()->qtQuickStateGroupMetaInfo(); + + return node.view()->allModelNodesOfType(groupMetaInfo); +} + +QList getSlotsLists(const ModelNode &node) { if (!node.isValid()) return {}; @@ -500,32 +516,53 @@ QList getSlotsLists(const ModelNode &node) if (!node.view()->rootModelNode().isValid()) return {}; - QList resultList; + QList resultList; ModelNode rootNode = node.view()->rootModelNode(); QmlObjectNode rootObjectNode(rootNode); - const QString stateCategory = "Change State"; + const QString changeStateStr = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Change State"); + const QString changeStateGroupStr = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", + "Change State Group"); + const QString defaultStateStr = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Default State"); + auto createStateChangeSlot = [](ModelNode node, + const QString &stateName, + const QString &displayName) { + return SlotEntry( + {displayName, [node, stateName](SignalHandlerProperty signalHandler) mutable { + signalHandler.setSource(QString("%1.state = \"%2\"").arg(node.validId(), stateName)); + }}); + }; - //For now we are using category as part of the state name - //We should change it, once we extend number of categories - const SlotEntry defaultState = {stateCategory, - (stateCategory + " to " + "Default State"), - [rootNode](SignalHandlerProperty signalHandler) { - signalHandler.setSource( - QString("%1.state = \"\"").arg(rootNode.id())); - }}; - resultList.push_back(defaultState); + { + SlotList states = {changeStateStr, {}}; - for (const auto &stateName : rootObjectNode.states().names()) { - SlotEntry entry = {stateCategory, - (stateCategory + " to " + stateName), - [rootNode, stateName](SignalHandlerProperty signalHandler) { - signalHandler.setSource( - QString("%1.state = \"%2\"").arg(rootNode.id(), stateName)); - }}; + const SlotEntry defaultState = createStateChangeSlot(rootNode, "", defaultStateStr); + states.slotEntries.push_back(defaultState); - resultList.push_back(entry); + for (const auto &stateName : rootObjectNode.states().names()) { + const SlotEntry entry = createStateChangeSlot(rootNode, stateName, stateName); + + states.slotEntries.push_back(entry); + } + + resultList.push_back(states); + } + + const QList groups = stateGroups(node); + for (const auto &stateGroup : groups) { + QmlObjectNode stateGroupObjectNode(stateGroup); + SlotList stateGroupCategory = {changeStateGroupStr + " " + stateGroup.displayName(), {}}; + + const SlotEntry defaultGroupState = createStateChangeSlot(stateGroup, "", defaultStateStr); + stateGroupCategory.slotEntries.push_back(defaultGroupState); + + for (const auto &stateName : stateGroupObjectNode.states().names()) { + const SlotEntry entry = createStateChangeSlot(stateGroup, stateName, stateName); + stateGroupCategory.slotEntries.push_back(entry); + } + + resultList.push_back(stateGroupCategory); } return resultList; @@ -534,11 +571,9 @@ QList getSlotsLists(const ModelNode &node) //creates connection without signalHandlerProperty ModelNode createNewConnection(ModelNode targetNode) { - NodeMetaInfo connectionsMetaInfo = targetNode.view()->model()->metaInfo("QtQuick.Connections"); - ModelNode newConnectionNode = targetNode.view() - ->createModelNode("QtQuick.Connections", - connectionsMetaInfo.majorVersion(), - connectionsMetaInfo.minorVersion()); + NodeMetaInfo connectionsMetaInfo = targetNode.view()->model()->qtQuickConnectionsMetaInfo(); + ModelNode newConnectionNode = targetNode.view()->createModelNode( + "QtQuick.Connections", connectionsMetaInfo.majorVersion(), connectionsMetaInfo.minorVersion()); if (QmlItemNode::isValidQmlItemNode(targetNode)) targetNode.nodeAbstractProperty("data").reparentHere(newConnectionNode); @@ -564,9 +599,7 @@ void removeSignal(SignalHandlerProperty signalHandler) class ConnectionsModelNodeActionGroup : public ActionGroup { public: - ConnectionsModelNodeActionGroup(const QString &displayName, - const QByteArray &menuId, - int priority) + ConnectionsModelNodeActionGroup(const QString &displayName, const QByteArray &menuId, int priority) : ActionGroup(displayName, menuId, priority, @@ -578,30 +611,28 @@ public: { menu()->clear(); - menu()->setEnabled(true); - const auto selection = selectionContext(); + + bool showMenu = false; + auto cleanup = qScopeGuard([&]{ menu()->setEnabled(showMenu); }); + if (!selection.isValid()) return; if (!selection.singleNodeIsSelected()) return; - if (!action()->isEnabled()) - return; ModelNode currentNode = selection.currentSingleSelectedNode(); if (!currentNode.isValid()) return; + if (!currentNode.hasId()) + return; + showMenu = true; QmlObjectNode currentObjectNode(currentNode); QStringList signalsList = getSignalsList(currentNode); - QList slotsList = getSlotsLists(currentNode); - - if (!currentNode.hasId()) { - menu()->setEnabled(false); - return; - } + QList slotsLists = getSlotsLists(currentNode); for (const ModelNode &connectionNode : currentObjectNode.getAllConnections()) { for (const AbstractProperty &property : connectionNode.properties()) { @@ -612,7 +643,9 @@ public: QMenu *activeSignalHandlerGroup = new QMenu(propertyName, menu()); - QMenu *editSignalGroup = new QMenu("Change Signal", menu()); + QMenu *editSignalGroup = new QMenu(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", + "Change Signal"), + menu()); for (const auto &signalStr : signalsList) { if (prependSignal(signalStr).toUtf8() == signalHandler.name()) @@ -638,40 +671,60 @@ public: activeSignalHandlerGroup->addMenu(editSignalGroup); - if (!slotsList.isEmpty()) { - QMenu *editSlotGroup = new QMenu("Change Slot", menu()); + if (!slotsLists.isEmpty()) { + QMenu *editSlotGroup = new QMenu(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", + "Change Slot"), + menu()); - for (const auto &slot : slotsList) { - ActionTemplate *newSlotAction = new ActionTemplate( - (slot.name + "Id").toLatin1(), - slot.name, - [slot, signalHandler](const SelectionContext &) { - signalHandler.parentModelNode() - .view() - ->executeInTransaction("ConnectionsModelNodeActionGroup::" - "changeSlot", - [slot, signalHandler]() { - slot.action(signalHandler); - }); - }); - editSlotGroup->addAction(newSlotAction); + if (slotsLists.size() == 1) { + for (const auto &slot : slotsLists.at(0).slotEntries) { + ActionTemplate *newSlotAction = new ActionTemplate( + (slot.name + "Id").toLatin1(), + (slotsLists.at(0).categoryName + + (QT_TRANSLATE_NOOP("QmlDesignerContextMenu", + " to ")) //context: Change State _to_ state1 + + slot.name), + [slot, signalHandler](const SelectionContext &) { + signalHandler.parentModelNode().view()->executeInTransaction( + "ConnectionsModelNodeActionGroup::" + "changeSlot", + [slot, signalHandler]() { slot.action(signalHandler); }); + }); + editSlotGroup->addAction(newSlotAction); + } + } else { + for (const auto &slotCategory : slotsLists) { + QMenu *slotCategoryMenu = new QMenu(slotCategory.categoryName, menu()); + for (const auto &slot : slotCategory.slotEntries) { + ActionTemplate *newSlotAction = new ActionTemplate( + (slot.name + "Id").toLatin1(), + slot.name, + [slot, signalHandler](const SelectionContext &) { + signalHandler.parentModelNode().view()->executeInTransaction( + "ConnectionsModelNodeActionGroup::" + "changeSlot", + [slot, signalHandler]() { + slot.action(signalHandler); + }); + }); + slotCategoryMenu->addAction(newSlotAction); + } + editSlotGroup->addMenu(slotCategoryMenu); + } } activeSignalHandlerGroup->addMenu(editSlotGroup); } ActionTemplate *openEditorAction = new ActionTemplate( (propertyName + "OpenEditorId").toLatin1(), - QString( - QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Open Connections Editor")), + QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Open Connections Editor")), [=](const SelectionContext &) { - signalHandler.parentModelNode() - .view() - ->executeInTransaction("ConnectionsModelNodeActionGroup::" - "openConnectionsEditor", - [signalHandler]() { - ActionEditor::invokeEditor(signalHandler, - removeSignal); - }); + signalHandler.parentModelNode().view()->executeInTransaction( + "ConnectionsModelNodeActionGroup::" + "openConnectionsEditor", + [signalHandler]() { + ActionEditor::invokeEditor(signalHandler, removeSignal); + }); }); activeSignalHandlerGroup->addAction(openEditorAction); @@ -683,9 +736,7 @@ public: signalHandler.parentModelNode().view()->executeInTransaction( "ConnectionsModelNodeActionGroup::" "removeSignalHandler", - [signalHandler]() { - removeSignal(signalHandler); - }); + [signalHandler]() { removeSignal(signalHandler); }); }); activeSignalHandlerGroup->addAction(removeSignalHandlerAction); @@ -703,19 +754,46 @@ public: for (const auto &signalStr : signalsList) { QMenu *newSignal = new QMenu(signalStr, addConnection); - for (const auto &slot : slotsList) { - ActionTemplate *newSlot = new ActionTemplate( - QString(signalStr + slot.name + "Id").toLatin1(), - slot.name, - [=](const SelectionContext &) { - currentNode.view()->executeInTransaction( - "ConnectionsModelNodeActionGroup::addConnection", [=]() { - ModelNode newConnectionNode = createNewConnection(currentNode); - slot.action(newConnectionNode.signalHandlerProperty( - prependSignal(signalStr).toLatin1())); + if (!slotsLists.isEmpty()) { + if (slotsLists.size() == 1) { + for (const auto &slot : slotsLists.at(0).slotEntries) { + ActionTemplate *newSlot = new ActionTemplate( + QString(signalStr + slot.name + "Id").toLatin1(), + (slotsLists.at(0).categoryName + + (QT_TRANSLATE_NOOP("QmlDesignerContextMenu", + " to ")) //context: Change State _to_ state1 + + slot.name), + [=](const SelectionContext &) { + currentNode.view()->executeInTransaction( + "ConnectionsModelNodeActionGroup::addConnection", [=]() { + ModelNode newConnectionNode = createNewConnection(currentNode); + slot.action(newConnectionNode.signalHandlerProperty( + prependSignal(signalStr).toLatin1())); + }); }); - }); - newSignal->addAction(newSlot); + newSignal->addAction(newSlot); + } + } else { + for (const auto &slotCategory : slotsLists) { + QMenu *slotCategoryMenu = new QMenu(slotCategory.categoryName, menu()); + for (const auto &slot : slotCategory.slotEntries) { + ActionTemplate *newSlot = new ActionTemplate( + QString(signalStr + slot.name + "Id").toLatin1(), + slot.name, + [=](const SelectionContext &) { + currentNode.view()->executeInTransaction( + "ConnectionsModelNodeActionGroup::addConnection", [=]() { + ModelNode newConnectionNode = createNewConnection( + currentNode); + slot.action(newConnectionNode.signalHandlerProperty( + prependSignal(signalStr).toLatin1())); + }); + }); + slotCategoryMenu->addAction(newSlot); + } + newSignal->addMenu(slotCategoryMenu); + } + } } ActionTemplate *openEditorAction = new ActionTemplate( @@ -728,9 +806,8 @@ public: [=]() { ModelNode newConnectionNode = createNewConnection(currentNode); - SignalHandlerProperty newHandler - = newConnectionNode.signalHandlerProperty( - prependSignal(signalStr).toLatin1()); + SignalHandlerProperty newHandler = newConnectionNode.signalHandlerProperty( + prependSignal(signalStr).toLatin1()); newHandler.setSource( QString("console.log(\"%1.%2\")").arg(currentNode.id(), signalStr)); @@ -1264,26 +1341,28 @@ void DesignerActionManager::createDefaultDesignerActions() addDesignerAction(new SelectionModelNodeAction( selectionCategoryDisplayName, selectionCategory, - prioritySelectionCategory)); + Priorities::SelectionCategory)); addDesignerAction(new ConnectionsModelNodeActionGroup( connectionsCategoryDisplayName, connectionsCategory, - priorityConnectionsCategory)); + Priorities::ConnectionsCategory)); addDesignerAction(new ActionGroup( arrangeCategoryDisplayName, arrangeCategory, - priorityStackCategory, + Priorities::ArrangeCategory, &selectionNotEmpty)); + addDesignerAction(new SeperatorDesignerAction(arrangeCategory, 10)); + addDesignerAction(new ModelNodeContextMenuAction( toFrontCommandId, toFrontDisplayName, {}, arrangeCategory, QKeySequence(), - 200, + 1, &toFront, &raiseAvailable)); @@ -1293,7 +1372,7 @@ void DesignerActionManager::createDefaultDesignerActions() Utils::Icon({{":/qmldesigner/icon/designeractions/images/raise.png", Utils::Theme::IconsBaseColor}}).icon(), arrangeCategory, QKeySequence(), - 180, + 11, &raise, &raiseAvailable)); @@ -1303,7 +1382,7 @@ void DesignerActionManager::createDefaultDesignerActions() Utils::Icon({{":/qmldesigner/icon/designeractions/images/lower.png", Utils::Theme::IconsBaseColor}}).icon(), arrangeCategory, QKeySequence(), - 160, + 12, &lower, &lowerAvailable)); @@ -1313,23 +1392,25 @@ void DesignerActionManager::createDefaultDesignerActions() {}, arrangeCategory, QKeySequence(), - 140, + 2, &toBack, &lowerAvailable)); + addDesignerAction(new SeperatorDesignerAction(arrangeCategory, 20)); + addDesignerAction(new ModelNodeContextMenuAction( reverseCommandId, reverseDisplayName, {}, arrangeCategory, QKeySequence(), - 100, + 21, &reverse, &multiSelectionAndHasSameParent)); - addDesignerAction(new ActionGroup(editCategoryDisplayName, editCategory, priorityEditCategory, &selectionNotEmpty)); + addDesignerAction(new ActionGroup(editCategoryDisplayName, editCategory, Priorities::EditCategory, &selectionNotEmpty)); - addDesignerAction(new SeperatorDesignerAction(editCategory, 220)); + addDesignerAction(new SeperatorDesignerAction(editCategory, 30)); addDesignerAction( new ModelNodeAction(resetPositionCommandId, @@ -1341,7 +1422,7 @@ void DesignerActionManager::createDefaultDesignerActions() resetPositionTooltip, editCategory, QKeySequence("Ctrl+d"), - 200, + 32, &resetPosition, &selectionNotEmptyAndHasXorYProperty)); @@ -1379,7 +1460,7 @@ void DesignerActionManager::createDefaultDesignerActions() copyFormatTooltip, editCategory, QKeySequence(), - 120, + 41, ©Format, &propertiesCopyable)); @@ -1389,7 +1470,7 @@ void DesignerActionManager::createDefaultDesignerActions() applyFormatTooltip, editCategory, QKeySequence(), - 120, + 42, &applyFormat, &propertiesApplyable)); @@ -1401,24 +1482,24 @@ void DesignerActionManager::createDefaultDesignerActions() resetSizeToolTip, editCategory, QKeySequence("shift+s"), - 180, + 31, &resetSize, &selectionNotEmptyAndHasWidthOrHeightProperty)); - addDesignerAction(new SeperatorDesignerAction(editCategory, 170)); + addDesignerAction(new SeperatorDesignerAction(editCategory, 40)); addDesignerAction(new VisiblityModelNodeAction( visiblityCommandId, visibilityDisplayName, - editCategory, + rootCategory, QKeySequence("Ctrl+g"), - 160, + Priorities::Visibility, &setVisible, &singleSelectedItem)); addDesignerAction(new ActionGroup(anchorsCategoryDisplayName, anchorsCategory, - priorityAnchorsCategory, + Priorities::AnchorsCategory, &anchorsMenuEnabled)); addDesignerAction(new ModelNodeAction( @@ -1428,7 +1509,7 @@ void DesignerActionManager::createDefaultDesignerActions() anchorsFillToolTip, anchorsCategory, QKeySequence(QKeySequence("shift+f")), - 200, + 2, &anchorsFill, &singleSelectionItemIsNotAnchoredAndSingleSelectionNotRoot)); @@ -1440,41 +1521,115 @@ void DesignerActionManager::createDefaultDesignerActions() anchorsResetToolTip, anchorsCategory, QKeySequence(QKeySequence("Ctrl+Shift+r")), - 180, + 1, &anchorsReset, &singleSelectionItemIsAnchored)); - addDesignerAction(new SeperatorDesignerAction(anchorsCategory, 170)); + addDesignerAction(new SeperatorDesignerAction(anchorsCategory, 10)); + + addDesignerAction(new ParentAnchorAction( + anchorParentTopAndBottomCommandId, + anchorParentTopAndBottomDisplayName, + {}, + {}, + anchorsCategory, + QKeySequence(), + 11, + AnchorLineType(AnchorLineTop | AnchorLineBottom))); + + addDesignerAction(new ParentAnchorAction( + anchorParentLeftAndRightCommandId, + anchorParentLeftAndRightDisplayName, + {}, + {}, + anchorsCategory, + QKeySequence(), + 12, + AnchorLineType(AnchorLineLeft | AnchorLineRight))); + + addDesignerAction(new SeperatorDesignerAction(anchorsCategory, 20)); + + addDesignerAction(new ParentAnchorAction( + anchorParentTopCommandId, + anchorParentTopDisplayName, + Utils::Icon({{":/qmldesigner/images/anchor_top.png", Utils::Theme::IconsBaseColor}, + {":/utils/images/iconoverlay_reset.png", Utils::Theme::IconsStopToolBarColor}}).icon(), + {}, + anchorsCategory, + QKeySequence(), + 21, + AnchorLineTop)); + + addDesignerAction(new ParentAnchorAction( + anchorParentBottomCommandId, + anchorParentBottomDisplayName, + Utils::Icon({{":/qmldesigner/images/anchor_bottom.png", Utils::Theme::IconsBaseColor}, + {":/utils/images/iconoverlay_reset.png", Utils::Theme::IconsStopToolBarColor}}).icon(), + {}, + anchorsCategory, + QKeySequence(), + 22, + AnchorLineBottom)); + + addDesignerAction(new ParentAnchorAction( + anchorParentLeftCommandId, + anchorParentLeftDisplayName, + Utils::Icon({{":/qmldesigner/images/anchor_left.png", Utils::Theme::IconsBaseColor}, + {":/utils/images/iconoverlay_reset.png", Utils::Theme::IconsStopToolBarColor}}).icon(), + {}, + anchorsCategory, + QKeySequence(), + 23, + AnchorLineLeft)); + + addDesignerAction(new ParentAnchorAction( + anchorParentRightCommandId, + anchorParentRightDisplayName, + Utils::Icon({{":/qmldesigner/images/anchor_right.png", Utils::Theme::IconsBaseColor}, + {":/utils/images/iconoverlay_reset.png", Utils::Theme::IconsStopToolBarColor}}).icon(), + {}, + anchorsCategory, + QKeySequence(), + 24, + AnchorLineRight)); addDesignerAction(new ActionGroup( - positionCategoryDisplayName, - positionCategory, - priorityPositionCategory, + positionerCategoryDisplayName, + positionerCategory, + Priorities::PositionCategory, &positionOptionVisible)); addDesignerAction(new ActionGroup( layoutCategoryDisplayName, layoutCategory, - priorityLayoutCategory, + Priorities::LayoutCategory, &layoutOptionVisible)); - addDesignerAction(new ActionGroup(groupCategoryDisplayName, - groupCategory, - priorityGroupCategory, - &studioComponentsAvailableAndSelectionCanBeLayouted)); + addDesignerAction(new ActionGroup( + snappingCategoryDisplayName, + snappingCategory, + Priorities::SnappingCategory, + &selectionEnabled, + &selectionEnabled)); addDesignerAction(new ActionGroup( - flowCategoryDisplayName, - flowCategory, - priorityFlowCategory, - &isFlowTargetOrTransition, - &flowOptionVisible)); + groupCategoryDisplayName, + groupCategory, + Priorities::Group, + &studioComponentsAvailableAndSelectionCanBeLayouted)); + + addDesignerAction(new ActionGroup( + flowCategoryDisplayName, + flowCategory, + Priorities::FlowCategory, + &isFlowTargetOrTransition, + &flowOptionVisible)); auto effectMenu = new ActionGroup( flowEffectCategoryDisplayName, flowEffectCategory, - priorityFlowCategory, + Priorities::FlowCategory, &isFlowTransitionItem, &flowOptionVisible); @@ -1488,7 +1643,7 @@ void DesignerActionManager::createDefaultDesignerActions() addFlowActionToolTip, flowCategory, {}, - priorityFirst, + 1, &createFlowActionArea, &isFlowItem, &flowOptionVisible)); @@ -1498,7 +1653,7 @@ void DesignerActionManager::createDefaultDesignerActions() setFlowStartDisplayName, {}, flowCategory, - priorityFirst, + 2, {}, &setFlowStartItem, &isFlowItem, @@ -1507,7 +1662,7 @@ void DesignerActionManager::createDefaultDesignerActions() addDesignerAction(new FlowActionConnectAction( flowConnectionCategoryDisplayName, flowConnectionCategory, - priorityFlowCategory)); + Priorities::FlowCategory)); const QList transitionTypes = {"FlowFadeEffect", @@ -1526,23 +1681,23 @@ void DesignerActionManager::createDefaultDesignerActions() {}, flowCategory, {}, - priorityFlowCategory, + 2, &selectFlowEffect, &isFlowTransitionItemWithEffect)); addDesignerAction(new ActionGroup( stackedContainerCategoryDisplayName, stackedContainerCategory, - priorityStackedContainerCategory, + Priorities::StackedContainerCategory, &isStackedContainer)); addDesignerAction(new ModelNodeContextMenuAction( removePositionerCommandId, removePositionerDisplayName, {}, - positionCategory, + positionerCategory, QKeySequence("Ctrl+Shift+p"), - 210, + 1, &removePositioner, &isPositioner, &isPositioner)); @@ -1551,9 +1706,9 @@ void DesignerActionManager::createDefaultDesignerActions() layoutRowPositionerCommandId, layoutRowPositionerDisplayName, {}, - positionCategory, + positionerCategory, QKeySequence(), - 200, + 2, &layoutRowPositioner, &selectionCanBeLayouted, &selectionCanBeLayouted)); @@ -1562,9 +1717,9 @@ void DesignerActionManager::createDefaultDesignerActions() layoutColumnPositionerCommandId, layoutColumnPositionerDisplayName, {}, - positionCategory, + positionerCategory, QKeySequence(), - 180, + 3, &layoutColumnPositioner, &selectionCanBeLayouted, &selectionCanBeLayouted)); @@ -1573,9 +1728,9 @@ void DesignerActionManager::createDefaultDesignerActions() layoutGridPositionerCommandId, layoutGridPositionerDisplayName, {}, - positionCategory, + positionerCategory, QKeySequence(), - 160, + 4, &layoutGridPositioner, &selectionCanBeLayouted, &selectionCanBeLayouted)); @@ -1584,14 +1739,14 @@ void DesignerActionManager::createDefaultDesignerActions() layoutFlowPositionerCommandId, layoutFlowPositionerDisplayName, {}, - positionCategory, + positionerCategory, QKeySequence("Ctrl+m"), - 140, + 5, &layoutFlowPositioner, &selectionCanBeLayouted, &selectionCanBeLayouted)); - addDesignerAction(new SeperatorDesignerAction(layoutCategory, 120)); + addDesignerAction(new SeperatorDesignerAction(layoutCategory, 0)); addDesignerAction(new ModelNodeContextMenuAction( removeLayoutCommandId, @@ -1599,7 +1754,7 @@ void DesignerActionManager::createDefaultDesignerActions() {}, layoutCategory, QKeySequence(), - 110, + 1, &removeLayout, &isLayout, &isLayout)); @@ -1609,7 +1764,7 @@ void DesignerActionManager::createDefaultDesignerActions() {}, groupCategory, QKeySequence("Ctrl+Shift+g"), - 110, + 1, &addToGroupItem, &selectionCanBeLayouted)); @@ -1618,7 +1773,7 @@ void DesignerActionManager::createDefaultDesignerActions() {}, groupCategory, QKeySequence(), - 110, + 2, &removeGroup, &isGroup)); @@ -1628,7 +1783,7 @@ void DesignerActionManager::createDefaultDesignerActions() addItemToStackedContainerToolTip, stackedContainerCategory, QKeySequence("Ctrl+Shift+a"), - 110, + 1, &addItemToStackedContainer, &isStackedContainer, &isStackedContainer)); @@ -1639,7 +1794,7 @@ void DesignerActionManager::createDefaultDesignerActions() {}, stackedContainerCategory, QKeySequence("Ctrl+Shift+t"), - 100, + 2, &addTabBarToStackedContainer, &isStackedContainerWithoutTabBar, &isStackedContainer)); @@ -1651,7 +1806,7 @@ void DesignerActionManager::createDefaultDesignerActions() decreaseIndexOfStackedContainerToolTip, stackedContainerCategory, QKeySequence("Ctrl+Shift+Left"), - 80, + 3, &decreaseIndexOfStackedContainer, &isStackedContainerAndIndexCanBeDecreased, &isStackedContainer)); @@ -1663,7 +1818,7 @@ void DesignerActionManager::createDefaultDesignerActions() increaseIndexOfStackedContainerToolTip, stackedContainerCategory, QKeySequence("Ctrl+Shift+Right"), - 80, + 4, &increaseIndexOfStackedContainer, &isStackedContainerAndIndexCanBeIncreased, &isStackedContainer)); @@ -1675,7 +1830,7 @@ void DesignerActionManager::createDefaultDesignerActions() layoutRowLayoutToolTip, layoutCategory, QKeySequence("Ctrl+u"), - 100, + 2, &layoutRowLayout, &selectionCanBeLayoutedAndQtQuickLayoutPossible)); @@ -1686,7 +1841,7 @@ void DesignerActionManager::createDefaultDesignerActions() layoutColumnLayoutToolTip, layoutCategory, QKeySequence("Ctrl+l"), - 80, + 3, &layoutColumnLayout, &selectionCanBeLayoutedAndQtQuickLayoutPossible)); @@ -1697,18 +1852,18 @@ void DesignerActionManager::createDefaultDesignerActions() layoutGridLayoutToolTip, layoutCategory, QKeySequence("shift+g"), - 60, + 4, &layoutGridLayout, &selectionCanBeLayoutedAndQtQuickLayoutPossibleAndNotMCU)); - addDesignerAction(new SeperatorDesignerAction(layoutCategory, 50)); + addDesignerAction(new SeperatorDesignerAction(layoutCategory, 10)); addDesignerAction(new FillWidthModelNodeAction( layoutFillWidthCommandId, layoutFillWidthDisplayName, layoutCategory, QKeySequence(), - 40, + 11, &setFillWidth, &singleSelectionAndInQtQuickLayout, &singleSelectionAndInQtQuickLayout)); @@ -1718,30 +1873,35 @@ void DesignerActionManager::createDefaultDesignerActions() layoutFillHeightDisplayName, layoutCategory, QKeySequence(), - 20, + 12, &setFillHeight, &singleSelectionAndInQtQuickLayout, &singleSelectionAndInQtQuickLayout)); - addDesignerAction(new SeperatorDesignerAction(rootCategory, priorityTopLevelSeperator)); + addDesignerAction(new SeperatorDesignerAction(rootCategory, Priorities::ModifySection)); + addDesignerAction(new SeperatorDesignerAction(rootCategory, Priorities::PositionSection)); + addDesignerAction(new SeperatorDesignerAction(rootCategory, Priorities::EventSection)); + addDesignerAction(new SeperatorDesignerAction(rootCategory, Priorities::AdditionsSection)); + addDesignerAction(new SeperatorDesignerAction(rootCategory, Priorities::ViewOprionsSection)); + addDesignerAction(new SeperatorDesignerAction(rootCategory, Priorities::CustomActionsSection)); addDesignerAction(new ModelNodeContextMenuAction( goIntoComponentCommandId, - goIntoComponentDisplayName, + enterComponentDisplayName, {}, rootCategory, QKeySequence(Qt::Key_F2), - priorityGoIntoComponent, + Priorities::ComponentActions + 2, &goIntoComponentOperation, &selectionIsComponent)); addDesignerAction(new ModelNodeContextMenuAction( - editAnnotationCommandId, - editAnnotationDisplayName, + editAnnotationsCommandId, + editAnnotationsDisplayName, {}, rootCategory, QKeySequence(), - (priorityLast+6), + Priorities::EditAnnotations, &editAnnotation, &singleSelection, &singleSelection)); @@ -1752,7 +1912,7 @@ void DesignerActionManager::createDefaultDesignerActions() {}, rootCategory, QKeySequence(), - (priorityLast+7), + Priorities::AddMouseArea, &addMouseAreaFill, &addMouseAreaFillCheck, &singleSelection)); @@ -1781,12 +1941,12 @@ void DesignerActionManager::createDefaultDesignerActions() &singleSelectedAndUiFile)); addDesignerAction(new ModelNodeContextMenuAction( - moveToComponentCommandId, - moveToComponentDisplayName, + makeComponentCommandId, + makeComponentDisplayName, {}, rootCategory, QKeySequence(), - 44, + Priorities::ComponentActions + 1, &moveToComponent, &singleSelection, &singleSelection)); @@ -1807,14 +1967,14 @@ void DesignerActionManager::createDefaultDesignerActions() {}, rootCategory, {}, - 50, + Priorities::MergeWithTemplate, [&] (const SelectionContext& context) { mergeWithTemplate(context, m_externalDependencies); }, &SelectionContextFunctors::always)); addDesignerAction(new ActionGroup( "", genericToolBarCategory, - priorityGenericToolBar)); + Priorities::GenericToolBar)); addDesignerAction(new ChangeStyleAction()); @@ -1826,7 +1986,7 @@ void DesignerActionManager::createDefaultDesignerActions() {}, rootCategory, QKeySequence(), - 66, + Priorities::SignalsDialog, &openSignalDialog, &singleSelectionAndHasSlotTrigger)); @@ -1836,7 +1996,7 @@ void DesignerActionManager::createDefaultDesignerActions() {}, rootCategory, QKeySequence(), - priorityGenericToolBar, + Priorities::GenericToolBar, &updateImported3DAsset, &selectionIsImported3DAsset, &selectionIsImported3DAsset)); @@ -1966,7 +2126,7 @@ void DesignerActionManager::addTransitionEffectAction(const TypeName &typeName) {}, ComponentCoreConstants::flowEffectCategory, {}, - typeName == "None" ? 100 : 140, + typeName == "None" ? 11 : 1, [typeName](const SelectionContext &context) { ModelNodeOperations::addFlowEffect(context, typeName); }, &isFlowTransitionItem)); @@ -1980,7 +2140,7 @@ void DesignerActionManager::addCustomTransitionEffectAction() {}, ComponentCoreConstants::flowEffectCategory, {}, - 80, + 21, &ModelNodeOperations::addCustomFlowEffect, &isFlowTransitionItem)); } diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu.cpp index 1b3fbac3f47..ebe51d8a3a8 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu.cpp @@ -42,7 +42,7 @@ void populateMenu(QSet &actionInterfaces, QList matchingFactoriesList = Utils::toList(matchingFactories); Utils::sort(matchingFactoriesList, [](ActionInterface *l, ActionInterface *r) { - return l->priority() > r->priority(); + return l->priority() < r->priority(); }); for (ActionInterface* actionInterface : std::as_const(matchingFactoriesList)) { diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp index 27a18a09b56..711b84c3798 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp @@ -2,13 +2,13 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 #include "modelnodeoperations.h" +#include "coreplugin/coreplugintr.h" #include "designmodewidget.h" #include "modelnodecontextmenu_helper.h" #include "addimagesdialog.h" #include "layoutingridlayout.h" #include "findimplementation.h" - #include "addsignalhandlerdialog.h" #include @@ -1042,16 +1042,20 @@ AddFilesResult addFilesToProject(const QStringList &fileNames, const QString &de { QString directory = showDialog ? AddImagesDialog::getDirectory(fileNames, defaultDir) : defaultDir; if (directory.isEmpty()) - return AddFilesResult::Cancelled; + return AddFilesResult::cancelled(directory); DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument(); - QTC_ASSERT(document, return AddFilesResult::Failed); + QTC_ASSERT(document, return AddFilesResult::failed(directory)); QList> copyList; QStringList removeList; for (const QString &fileName : fileNames) { const QString targetFile = directory + "/" + QFileInfo(fileName).fileName(); - if (QFileInfo::exists(targetFile)) { + Utils::FilePath srcFilePath = Utils::FilePath::fromString(fileName); + Utils::FilePath targetFilePath = Utils::FilePath::fromString(targetFile); + if (targetFilePath.exists()) { + if (srcFilePath.lastModified() == targetFilePath.lastModified()) + continue; const QString title = QCoreApplication::translate( "ModelNodeOperations", "Overwrite Existing File?"); const QString question = QCoreApplication::translate( @@ -1073,7 +1077,7 @@ AddFilesResult addFilesToProject(const QStringList &fileNames, const QString &de for (const auto &filePair : std::as_const(copyList)) { const bool success = QFile::copy(filePair.first, filePair.second); if (!success) - return AddFilesResult::Failed; + return AddFilesResult::failed(directory); ProjectExplorer::Node *node = ProjectExplorer::ProjectTree::nodeForFile(document->fileName()); if (node) { @@ -1083,7 +1087,7 @@ AddFilesResult addFilesToProject(const QStringList &fileNames, const QString &de } } - return AddFilesResult::Succeeded; + return AddFilesResult::succeeded(directory); } static QString getAssetDefaultDirectory(const QString &assetDir, const QString &defaultDirectory) @@ -1642,7 +1646,7 @@ void openEffectMaker(const QString &filePath) const QtSupport::QtVersion *baseQtVersion = QtSupport::QtKitAspect::qtVersion(target->kit()); if (baseQtVersion) { - auto effectMakerPath = baseQtVersion->binPath().pathAppended("QQEffectMaker").withExecutableSuffix(); + auto effectMakerPath = baseQtVersion->binPath().pathAppended("qqem").withExecutableSuffix(); if (!effectMakerPath.exists()) { qWarning() << __FUNCTION__ << "Cannot find EffectMaker app"; return; @@ -1651,7 +1655,7 @@ void openEffectMaker(const QString &filePath) Utils::FilePath effectPath = Utils::FilePath::fromString(filePath); QStringList arguments; arguments << filePath; - if (effectPath.fileContents()) + if (effectPath.fileContents()->isEmpty()) arguments << "--create"; arguments << "--exportpath" << effectResPath.toString(); @@ -1684,6 +1688,56 @@ Utils::FilePath getEffectsDirectory() return effectsPath; } +QString getEffectIcon(const QString &effectPath) +{ + const ProjectExplorer::Target *target = ProjectExplorer::ProjectTree::currentTarget(); + if (!target) { + qWarning() << __FUNCTION__ << "No project open"; + return QString(); + } + + Utils::FilePath projectPath = target->project()->projectDirectory(); + QString effectName = QFileInfo(effectPath).baseName(); + QString effectResDir = "asset_imports/Effects/" + effectName; + Utils::FilePath effectResPath = projectPath.resolvePath(effectResDir + "/" + effectName + ".qml"); + + return effectResPath.exists() ? QString("effectExported") : QString("effectClass"); +} + +bool useLayerEffect() +{ + QSettings *settings = Core::ICore::settings(); + const QString layerEffectEntry = "QML/Designer/UseLayerEffect"; + + return settings->value(layerEffectEntry, true).toBool(); +} + +bool validateEffect(const QString &effectPath) +{ + const QString effectName = QFileInfo(effectPath).baseName(); + Utils::FilePath effectsResDir = ModelNodeOperations::getEffectsDirectory(); + Utils::FilePath qmlPath = effectsResDir.resolvePath(effectName + "/" + effectName + ".qml"); + if (!qmlPath.exists()) { + QMessageBox msgBox; + msgBox.setText(QObject::tr("Effect %1 not complete").arg(effectName)); + msgBox.setInformativeText(QObject::tr("Do you want to edit %1?").arg(effectName)); + msgBox.setStandardButtons(QMessageBox::No | QMessageBox::Yes); + msgBox.setDefaultButton(QMessageBox::Yes); + msgBox.setIcon(QMessageBox::Question); + if (msgBox.exec() == QMessageBox::Yes) + ModelNodeOperations::openEffectMaker(effectPath); + return false; + } + return true; +} + +Utils::FilePath getImagesDefaultDirectory() +{ + return Utils::FilePath::fromString( + getAssetDefaultDirectory( + "images", QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath().toString())); +} + } // namespace ModelNodeOperations } //QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h index c883edc8f03..2e60b253195 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h @@ -9,7 +9,48 @@ namespace QmlDesigner { -enum class AddFilesResult { Succeeded, Failed, Cancelled }; +class AddFilesResult +{ +public: + enum Status { Succeeded, Failed, Cancelled, Delayed }; + static constexpr char directoryPropName[] = "directory"; + + static AddFilesResult cancelled(const QString &directory = {}) + { + return AddFilesResult{Cancelled, directory}; + } + + static AddFilesResult failed(const QString &directory = {}) + { + return AddFilesResult{Failed, directory}; + } + + static AddFilesResult succeeded(const QString &directory = {}) + { + return AddFilesResult{Succeeded, directory}; + } + + static AddFilesResult delayed(QObject *delayedResult) + { + return AddFilesResult{Delayed, {}, delayedResult}; + } + + Status status() const { return m_status; } + QString directory() const { return m_directory; } + bool haveDelayedResult() const { return m_delayedResult != nullptr; } + QObject *delayedResult() const { return m_delayedResult; } + +private: + AddFilesResult(Status status, const QString &directory, QObject *delayedResult = nullptr) + : m_status{status} + , m_directory{directory} + , m_delayedResult{delayedResult} + {} + + Status m_status; + QString m_directory; + QObject *m_delayedResult = nullptr; +}; namespace ModelNodeOperations { @@ -80,6 +121,11 @@ void updateImported3DAsset(const SelectionContext &selectionContext); QMLDESIGNERCORE_EXPORT Utils::FilePath getEffectsDirectory(); void openEffectMaker(const QString &filePath); +QString getEffectIcon(const QString &effectPath); +bool useLayerEffect(); +bool validateEffect(const QString &effectPath); + +Utils::FilePath getImagesDefaultDirectory(); // ModelNodePreviewImageOperations QVariant previewImageDataForGenericNode(const ModelNode &modelNode); diff --git a/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp b/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp index 6250b6a25fc..487f84229c4 100644 --- a/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp +++ b/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp @@ -1,6 +1,9 @@ // Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 #include "navigation2d.h" +#include +#include +#include #include #include @@ -73,15 +76,24 @@ bool Navigation2dFilter::wheelEvent(QWheelEvent *event) bool zoomChangedConnected = QObject::isSignalConnected(zoomChangedSignal); if (zoomChangedConnected) { + const double globalMouseSpeed = + QmlDesignerPlugin::settings().value(DesignerSettingsKey::EDITOR_ZOOM_FACTOR).toDouble(); + + double speed = globalMouseSpeed/20.; + if (Utils::HostOsInfo::isMacHost()) + speed = 1.0/200.; + if (QPointF delta = event->pixelDelta(); !delta.isNull()) { double dist = std::abs(delta.x()) > std::abs(delta.y()) ? -delta.x() : delta.y(); - emit zoomChanged(dist/200.0, event->position()); + emit zoomChanged(dist * speed, event->position()); event->accept(); return true; } else if (QPointF delta = event->angleDelta(); !delta.isNull()) { + constexpr double degreePerStep = 15.; + constexpr double stepCount = 8.; double dist = std::abs(delta.x()) > std::abs(delta.y()) ? -delta.x() : delta.y(); - dist = dist / (8*15); - emit zoomChanged(dist/200.0, event->position()); + dist = dist / (stepCount*degreePerStep); + emit zoomChanged(dist * speed, event->position()); event->accept(); return true; } diff --git a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp index 5a06b2b1e83..15ea827e6ff 100644 --- a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -68,7 +69,8 @@ public: , navigatorView{externalDependencies} , propertyEditorView(imageCache, externalDependencies) , materialEditorView{externalDependencies} - , materialBrowserView{externalDependencies} + , materialBrowserView{imageCache, externalDependencies} + , textureEditorView{imageCache, externalDependencies} , statesEditorView{externalDependencies} , newStatesEditorView{externalDependencies} {} @@ -90,6 +92,7 @@ public: PropertyEditorView propertyEditorView; MaterialEditorView materialEditorView; MaterialBrowserView materialBrowserView; + TextureEditorView textureEditorView; StatesEditorView statesEditorView; Experimental::StatesEditorView newStatesEditorView; @@ -216,9 +219,9 @@ QList ViewManager::standardViews() const &d->itemLibraryView, &d->navigatorView, &d->propertyEditorView, - &d->contentLibraryView, &d->materialEditorView, &d->materialBrowserView, + &d->textureEditorView, &d->statesEditorView, &d->newStatesEditorView, // TODO &d->designerActionManagerView}; @@ -234,6 +237,13 @@ QList ViewManager::standardViews() const .toBool()) list.append(&d->debugView); +#ifdef CHECK_LICENSE + if (checkLicense() == FoundLicense::enterprise) + list.append(&d->contentLibraryView); +#else + list.append(&d->contentLibraryView); +#endif + return list; } @@ -248,7 +258,7 @@ void ViewManager::registerNanotraceActions() QObject::tr("Start Nanotrace"), ComponentCoreConstants::eventListCategory, QKeySequence(), - 220, + 22, handleShutdownNanotraceAction); QObject::connect(startNanotraceAction->defaultAction(), &QAction::triggered, [&]() { @@ -263,7 +273,7 @@ void ViewManager::registerNanotraceActions() QObject::tr("Shut Down Nanotrace"), ComponentCoreConstants::eventListCategory, QKeySequence(), - 220, + 23, handleShutdownNanotraceAction); QObject::connect(shutDownNanotraceAction->defaultAction(), &QAction::triggered, [&]() { @@ -397,14 +407,21 @@ QList ViewManager::widgetInfos() const widgetInfoList.append(d->itemLibraryView.widgetInfo()); widgetInfoList.append(d->navigatorView.widgetInfo()); widgetInfoList.append(d->propertyEditorView.widgetInfo()); - widgetInfoList.append(d->contentLibraryView.widgetInfo()); widgetInfoList.append(d->materialEditorView.widgetInfo()); widgetInfoList.append(d->materialBrowserView.widgetInfo()); + widgetInfoList.append(d->textureEditorView.widgetInfo()); if (useOldStatesEditor()) widgetInfoList.append(d->statesEditorView.widgetInfo()); else widgetInfoList.append(d->newStatesEditorView.widgetInfo()); +#ifdef CHECK_LICENSE + if (checkLicense() == FoundLicense::enterprise) + widgetInfoList.append(d->contentLibraryView.widgetInfo()); +#else + widgetInfoList.append(d->contentLibraryView.widgetInfo()); +#endif + if (d->debugView.hasWidget()) widgetInfoList.append(d->debugView.widgetInfo()); diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp index 8a4f7bbdb27..5711d696145 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp @@ -297,7 +297,7 @@ void ConnectionModel::addConnection() if (rootModelNode.isValid() && rootModelNode.metaInfo().isValid()) { - NodeMetaInfo nodeMetaInfo = connectionView()->model()->metaInfo("QtQuick.Connections"); + NodeMetaInfo nodeMetaInfo = connectionView()->model()->qtQuickConnectionsMetaInfo(); if (nodeMetaInfo.isValid()) { connectionView()->executeInTransaction("ConnectionModel::addConnection", [=, &rootModelNode](){ diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.cpp index 92be78f016b..2acc4499786 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.cpp @@ -6,7 +6,10 @@ #include "contentlibrarybundleimporter.h" #include "contentlibrarymaterial.h" #include "contentlibrarymaterialscategory.h" +#include "contentlibrarywidget.h" #include "qmldesignerconstants.h" + +#include "utils/algorithm.h" #include "utils/qtcassert.h" #include @@ -16,8 +19,9 @@ namespace QmlDesigner { -ContentLibraryMaterialsModel::ContentLibraryMaterialsModel(QObject *parent) +ContentLibraryMaterialsModel::ContentLibraryMaterialsModel(ContentLibraryWidget *parent) : QAbstractListModel(parent) + , m_widget(parent) { loadMaterialBundle(); } @@ -59,6 +63,22 @@ bool ContentLibraryMaterialsModel::isValidIndex(int idx) const return idx > -1 && idx < rowCount(); } +void ContentLibraryMaterialsModel::updateIsEmpty() +{ + const bool anyCatVisible = Utils::anyOf(m_bundleCategories, + [&](ContentLibraryMaterialsCategory *cat) { + return cat->visible(); + }); + + const bool newEmpty = !anyCatVisible || m_bundleCategories.isEmpty() + || !m_widget->hasMaterialLibrary() || !hasRequiredQuick3DImport(); + + if (newEmpty != m_isEmpty) { + m_isEmpty = newEmpty; + emit isEmptyChanged(); + } +} + QHash ContentLibraryMaterialsModel::roleNames() const { static const QHash roles { @@ -72,7 +92,7 @@ QHash ContentLibraryMaterialsModel::roleNames() const void ContentLibraryMaterialsModel::loadMaterialBundle() { - if (m_matBundleLoaded || m_probeMatBundleDir) + if (m_matBundleExists || m_probeMatBundleDir) return; QDir matBundleDir(qEnvironmentVariable("MATERIAL_BUNDLE_PATH")); @@ -108,7 +128,7 @@ void ContentLibraryMaterialsModel::loadMaterialBundle() } } - m_matBundleLoaded = true; + m_matBundleExists = true; QString bundleId = m_matBundleObj.value("id").toString(); @@ -162,39 +182,18 @@ void ContentLibraryMaterialsModel::loadMaterialBundle() emit importerRunningChanged(); emit bundleMaterialUnimported(metaInfo); }); + + updateIsEmpty(); } -bool ContentLibraryMaterialsModel::hasQuick3DImport() const +bool ContentLibraryMaterialsModel::hasRequiredQuick3DImport() const { - return m_hasQuick3DImport; -} - -void ContentLibraryMaterialsModel::setHasQuick3DImport(bool b) -{ - if (b == m_hasQuick3DImport) - return; - - m_hasQuick3DImport = b; - emit hasQuick3DImportChanged(); -} - -bool ContentLibraryMaterialsModel::hasMaterialRoot() const -{ - return m_hasMaterialRoot; -} - -void ContentLibraryMaterialsModel::setHasMaterialRoot(bool b) -{ - if (m_hasMaterialRoot == b) - return; - - m_hasMaterialRoot = b; - emit hasMaterialRootChanged(); + return m_widget->hasQuick3DImport() && m_quick3dMajorVersion == 6 && m_quick3dMinorVersion >= 3; } bool ContentLibraryMaterialsModel::matBundleExists() const { - return m_matBundleLoaded && m_quick3dMajorVersion == 6 && m_quick3dMinorVersion >= 3; + return m_matBundleExists; } Internal::ContentLibraryBundleImporter *ContentLibraryMaterialsModel::bundleImporter() const @@ -211,18 +210,11 @@ void ContentLibraryMaterialsModel::setSearchText(const QString &searchText) m_searchText = lowerSearchText; - bool anyCatVisible = false; bool catVisibilityChanged = false; - - for (ContentLibraryMaterialsCategory *cat : std::as_const(m_bundleCategories)) { + for (ContentLibraryMaterialsCategory *cat : std::as_const(m_bundleCategories)) catVisibilityChanged |= cat->filter(m_searchText); - anyCatVisible |= cat->visible(); - } - if (anyCatVisible == m_isEmpty) { - m_isEmpty = !anyCatVisible; - emit isEmptyChanged(); - } + updateIsEmpty(); if (catVisibilityChanged) resetModel(); @@ -240,13 +232,19 @@ void ContentLibraryMaterialsModel::updateImportedState(const QStringList &import void ContentLibraryMaterialsModel::setQuick3DImportVersion(int major, int minor) { - bool bundleExisted = matBundleExists(); + bool oldRequiredImport = hasRequiredQuick3DImport(); m_quick3dMajorVersion = major; m_quick3dMinorVersion = minor; - if (bundleExisted != matBundleExists()) - emit matBundleExistsChanged(); + bool newRequiredImport = hasRequiredQuick3DImport(); + + if (oldRequiredImport == newRequiredImport) + return; + + emit hasRequiredQuick3DImportChanged(); + + updateIsEmpty(); } void ContentLibraryMaterialsModel::resetModel() diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.h index 9fe76cd25df..91ec476c51f 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.h @@ -12,6 +12,7 @@ namespace QmlDesigner { class ContentLibraryMaterial; class ContentLibraryMaterialsCategory; +class ContentLibraryWidget; namespace Internal { class ContentLibraryBundleImporter; @@ -23,13 +24,12 @@ class ContentLibraryMaterialsModel : public QAbstractListModel Q_PROPERTY(bool matBundleExists READ matBundleExists NOTIFY matBundleExistsChanged) Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) - Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged) - Q_PROPERTY(bool hasModelSelection READ hasModelSelection WRITE setHasModelSelection NOTIFY hasModelSelectionChanged) - Q_PROPERTY(bool hasMaterialRoot READ hasMaterialRoot WRITE setHasMaterialRoot NOTIFY hasMaterialRootChanged) + Q_PROPERTY(bool hasRequiredQuick3DImport READ hasRequiredQuick3DImport NOTIFY hasRequiredQuick3DImportChanged) + Q_PROPERTY(bool hasModelSelection READ hasModelSelection NOTIFY hasModelSelectionChanged) Q_PROPERTY(bool importerRunning MEMBER m_importerRunning NOTIFY importerRunningChanged) public: - ContentLibraryMaterialsModel(QObject *parent = nullptr); + ContentLibraryMaterialsModel(ContentLibraryWidget *parent = nullptr); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; @@ -41,11 +41,7 @@ public: void setQuick3DImportVersion(int major, int minor); - bool hasQuick3DImport() const; - void setHasQuick3DImport(bool b); - - bool hasMaterialRoot() const; - void setHasMaterialRoot(bool b); + bool hasRequiredQuick3DImport() const; bool matBundleExists() const; @@ -53,6 +49,7 @@ public: void setHasModelSelection(bool b); void resetModel(); + void updateIsEmpty(); Internal::ContentLibraryBundleImporter *bundleImporter() const; @@ -62,9 +59,8 @@ public: signals: void isEmptyChanged(); - void hasQuick3DImportChanged(); + void hasRequiredQuick3DImportChanged(); void hasModelSelectionChanged(); - void hasMaterialRootChanged(); void materialVisibleChanged(); void applyToSelectedTriggered(QmlDesigner::ContentLibraryMaterial *mat, bool add = false); void bundleMaterialImported(const QmlDesigner::NodeMetaInfo &metaInfo); @@ -77,15 +73,14 @@ private: void loadMaterialBundle(); bool isValidIndex(int idx) const; + ContentLibraryWidget *m_widget = nullptr; QString m_searchText; QList m_bundleCategories; QJsonObject m_matBundleObj; Internal::ContentLibraryBundleImporter *m_importer = nullptr; bool m_isEmpty = true; - bool m_hasMaterialRoot = false; - bool m_hasQuick3DImport = false; - bool m_matBundleLoaded = false; + bool m_matBundleExists = false; bool m_hasModelSelection = false; bool m_probeMatBundleDir = false; bool m_importerRunning = false; diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.cpp index caea6dc0934..b12473da616 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.cpp @@ -4,6 +4,8 @@ #include "contentlibrarytexturesmodel.h" #include "contentlibrarytexturescategory.h" + +#include "utils/algorithm.h" #include "utils/qtcassert.h" #include @@ -55,6 +57,21 @@ bool ContentLibraryTexturesModel::isValidIndex(int idx) const return idx > -1 && idx < rowCount(); } +void ContentLibraryTexturesModel::updateIsEmpty() +{ + const bool anyCatVisible = Utils::anyOf(m_bundleCategories, + [&](ContentLibraryTexturesCategory *cat) { + return cat->visible(); + }); + + const bool newEmpty = !anyCatVisible || m_bundleCategories.isEmpty(); + + if (newEmpty != m_isEmpty) { + m_isEmpty = newEmpty; + emit isEmptyChanged(); + } +} + QHash ContentLibraryTexturesModel::roleNames() const { static const QHash roles { @@ -74,7 +91,7 @@ void ContentLibraryTexturesModel::loadTextureBundle(const QString &bundlePath) return; } - if (m_texBundleLoaded) + if (!m_bundleCategories.isEmpty()) return; const QFileInfoList dirs = bundleDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); @@ -85,34 +102,13 @@ void ContentLibraryTexturesModel::loadTextureBundle(const QString &bundlePath) category->addTexture(tex); m_bundleCategories.append(category); } + + updateIsEmpty(); } -bool ContentLibraryTexturesModel::hasQuick3DImport() const +bool ContentLibraryTexturesModel::texBundleExists() const { - return m_hasQuick3DImport; -} - -void ContentLibraryTexturesModel::setHasQuick3DImport(bool b) -{ - if (b == m_hasQuick3DImport) - return; - - m_hasQuick3DImport = b; - emit hasQuick3DImportChanged(); -} - -bool ContentLibraryTexturesModel::hasMaterialRoot() const -{ - return m_hasMaterialRoot; -} - -void ContentLibraryTexturesModel::setHasMaterialRoot(bool b) -{ - if (m_hasMaterialRoot == b) - return; - - m_hasMaterialRoot = b; - emit hasMaterialRootChanged(); + return !m_bundleCategories.isEmpty(); } bool ContentLibraryTexturesModel::hasSceneEnv() const @@ -129,11 +125,6 @@ void ContentLibraryTexturesModel::setHasSceneEnv(bool b) emit hasSceneEnvChanged(); } -bool ContentLibraryTexturesModel::matBundleExists() const -{ - return m_texBundleLoaded && m_quick3dMajorVersion == 6 && m_quick3dMinorVersion >= 3; -} - void ContentLibraryTexturesModel::setSearchText(const QString &searchText) { QString lowerSearchText = searchText.toLower(); @@ -143,58 +134,21 @@ void ContentLibraryTexturesModel::setSearchText(const QString &searchText) m_searchText = lowerSearchText; - bool anyCatVisible = false; bool catVisibilityChanged = false; - for (ContentLibraryTexturesCategory *cat : std::as_const(m_bundleCategories)) { + for (ContentLibraryTexturesCategory *cat : std::as_const(m_bundleCategories)) catVisibilityChanged |= cat->filter(m_searchText); - anyCatVisible |= cat->visible(); - } - if (anyCatVisible == m_isEmpty) { - m_isEmpty = !anyCatVisible; - emit isEmptyChanged(); - } + updateIsEmpty(); if (catVisibilityChanged) resetModel(); } -void ContentLibraryTexturesModel::setQuick3DImportVersion(int major, int minor) -{ - bool bundleExisted = matBundleExists(); - - m_quick3dMajorVersion = major; - m_quick3dMinorVersion = minor; - - if (bundleExisted != matBundleExists()) - emit matBundleExistsChanged(); -} - void ContentLibraryTexturesModel::resetModel() { beginResetModel(); endResetModel(); } -void ContentLibraryTexturesModel::addToProject(const QString &mat) -{ - Q_UNUSED(mat) - // TODO: import asset -} - -bool ContentLibraryTexturesModel::hasModelSelection() const -{ - return m_hasModelSelection; -} - -void ContentLibraryTexturesModel::setHasModelSelection(bool b) -{ - if (b == m_hasModelSelection) - return; - - m_hasModelSelection = b; - emit hasModelSelectionChanged(); -} - } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.h index ca5e7a6963b..cbbd2a364c3 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.h @@ -13,12 +13,9 @@ class ContentLibraryTexturesModel : public QAbstractListModel { Q_OBJECT - Q_PROPERTY(bool matBundleExists READ matBundleExists NOTIFY matBundleExistsChanged) + Q_PROPERTY(bool texBundleExists READ texBundleExists CONSTANT) Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) - Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged) - Q_PROPERTY(bool hasModelSelection READ hasModelSelection WRITE setHasModelSelection NOTIFY hasModelSelectionChanged) - Q_PROPERTY(bool hasMaterialRoot READ hasMaterialRoot WRITE setHasMaterialRoot NOTIFY hasMaterialRootChanged) - Q_PROPERTY(bool hasSceneEnv READ hasSceneEnv WRITE setHasSceneEnv NOTIFY hasSceneEnvChanged) + Q_PROPERTY(bool hasSceneEnv READ hasSceneEnv NOTIFY hasSceneEnvChanged) public: ContentLibraryTexturesModel(QObject *parent = nullptr); @@ -30,18 +27,7 @@ public: void setSearchText(const QString &searchText); - void setQuick3DImportVersion(int major, int minor); - - bool hasQuick3DImport() const; - void setHasQuick3DImport(bool b); - - bool hasMaterialRoot() const; - void setHasMaterialRoot(bool b); - - bool matBundleExists() const; - - bool hasModelSelection() const; - void setHasModelSelection(bool b); + bool texBundleExists() const; bool hasSceneEnv() const; void setHasSceneEnv(bool b); @@ -49,32 +35,21 @@ public: void resetModel(); void loadTextureBundle(const QString &bundlePath); - Q_INVOKABLE void addToProject(const QString &mat); - signals: void isEmptyChanged(); - void hasQuick3DImportChanged(); - void hasModelSelectionChanged(); - void hasMaterialRootChanged(); void materialVisibleChanged(); - void matBundleExistsChanged(); void hasSceneEnvChanged(); private: bool isValidIndex(int idx) const; + void updateIsEmpty(); QString m_searchText; QList m_bundleCategories; bool m_isEmpty = true; - bool m_hasMaterialRoot = false; - bool m_hasQuick3DImport = false; - bool m_texBundleLoaded = false; - bool m_hasModelSelection = false; bool m_hasSceneEnv = false; - - int m_quick3dMajorVersion = -1; - int m_quick3dMinorVersion = -1; + bool m_hasModelSelection = false; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp index 28aa071a8ec..764bb7f155a 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp @@ -3,14 +3,14 @@ #include "contentlibraryview.h" -#include "bindingproperty.h" #include "contentlibrarybundleimporter.h" -#include "contentlibrarywidget.h" #include "contentlibrarymaterial.h" #include "contentlibrarymaterialsmodel.h" +#include "contentlibrarytexture.h" #include "contentlibrarytexturesmodel.h" -#include "modelnodeoperations.h" +#include "contentlibrarywidget.h" #include "nodelistproperty.h" +#include "qmldesignerconstants.h" #include "qmlobjectnode.h" #include "variantproperty.h" @@ -30,6 +30,7 @@ namespace QmlDesigner { ContentLibraryView::ContentLibraryView(ExternalDependenciesInterface &externalDependencies) : AbstractView(externalDependencies) + , m_createTexture(this, true) {} ContentLibraryView::~ContentLibraryView() @@ -49,44 +50,25 @@ WidgetInfo ContentLibraryView::widgetInfo() [&] (QmlDesigner::ContentLibraryMaterial *mat) { m_draggedBundleMaterial = mat; }); + connect(m_widget, &ContentLibraryWidget::bundleTextureDragStarted, this, + [&] (QmlDesigner::ContentLibraryTexture *tex) { + m_draggedBundleTexture = tex; + }); + connect(m_widget, &ContentLibraryWidget::addTextureRequested, this, - [&] (const QString texPath, ContentLibraryWidget::AddTextureMode mode) { - executeInTransaction("ContentLibraryView::widgetInfo", [&] { - // copy image to project - AddFilesResult result = ModelNodeOperations::addImageToProject({texPath}, "images", false); - - if (result == AddFilesResult::Failed) { - Core::AsynchronousMessageBox::warning(tr("Failed to Add Texture"), - tr("Could not add %1 to project.").arg(texPath)); - return; - } - - if (mode == ContentLibraryWidget::AddTextureMode::Image) - return; - - // create a texture from the image - ModelNode matLib = materialLibraryNode(); - if (!matLib.isValid()) - return; - - NodeMetaInfo metaInfo = model()->metaInfo("QtQuick3D.Texture"); - ModelNode newTexNode = createModelNode("QtQuick3D.Texture", metaInfo.majorVersion(), - metaInfo.minorVersion()); - newTexNode.validId(); - VariantProperty sourceProp = newTexNode.variantProperty("source"); - sourceProp.setValue(QLatin1String("images/%1").arg(texPath.split('/').last())); - matLib.defaultNodeListProperty().reparentHere(newTexNode); - - // assign the texture as scene environment's light probe - if (mode == ContentLibraryWidget::AddTextureMode::LightProbe && m_activeSceneEnv.isValid()) { - BindingProperty lightProbeProp = m_activeSceneEnv.bindingProperty("lightProbe"); - lightProbeProp.setExpression(newTexNode.id()); - VariantProperty bgModeProp = m_activeSceneEnv.variantProperty("backgroundMode"); - bgModeProp.setValue(QVariant::fromValue(Enumeration("SceneEnvironment", "SkyBox"))); - } + [&] (const QString &texPath, AddTextureMode mode) { + executeInTransaction("ContentLibraryView::widgetInfo", [&]() { + m_createTexture.execute(texPath, mode, m_sceneId); }); }); + connect(m_widget, &ContentLibraryWidget::updateSceneEnvStateRequested, this, [&]() { + ModelNode activeSceneEnv = m_createTexture.resolveSceneEnv(m_sceneId); + const bool sceneEnvExists = activeSceneEnv.isValid(); + m_widget->texturesModel()->setHasSceneEnv(sceneEnvExists); + m_widget->environmentsModel()->setHasSceneEnv(sceneEnvExists); + }); + ContentLibraryMaterialsModel *materialsModel = m_widget->materialsModel().data(); connect(materialsModel, &ContentLibraryMaterialsModel::applyToSelectedTriggered, this, @@ -142,15 +124,20 @@ void ContentLibraryView::modelAttached(Model *model) m_hasQuick3DImport = model->hasImport("QtQuick3D"); - m_widget->materialsModel()->setHasMaterialRoot(rootModelNode().metaInfo().isQtQuick3DMaterial()); - m_widget->materialsModel()->setHasQuick3DImport(m_hasQuick3DImport); - updateBundleMaterialsQuick3DVersion(); updateBundleMaterialsImportedState(); + + const bool hasLibrary = materialLibraryNode().isValid(); + m_widget->setHasMaterialLibrary(hasLibrary); + m_widget->setHasQuick3DImport(m_hasQuick3DImport); + + m_sceneId = model->active3DSceneId(); } void ContentLibraryView::modelAboutToBeDetached(Model *model) { + m_widget->setHasMaterialLibrary(false); + m_widget->setHasQuick3DImport(false); AbstractView::modelAboutToBeDetached(model); } @@ -168,35 +155,12 @@ void ContentLibraryView::importsChanged(const QList &addedImports, const return; m_hasQuick3DImport = hasQuick3DImport; - m_widget->materialsModel()->setHasQuick3DImport(m_hasQuick3DImport); + m_widget->setHasQuick3DImport(m_hasQuick3DImport); } void ContentLibraryView::active3DSceneChanged(qint32 sceneId) { - m_activeSceneEnv = {}; - bool sceneEnvExists = false; - if (sceneId != -1) { - ModelNode activeScene = active3DSceneNode(); - if (activeScene.isValid()) { - ModelNode view3D; - if (activeScene.metaInfo().isQtQuick3DView3D()) { - view3D = activeScene; - } else { - ModelNode sceneParent = activeScene.parentProperty().parentModelNode(); - if (sceneParent.metaInfo().isQtQuick3DView3D()) - view3D = sceneParent; - } - - if (view3D.isValid()) { - m_activeSceneEnv = modelNodeForId(view3D.bindingProperty("environment").expression()); - if (m_activeSceneEnv.isValid()) - sceneEnvExists = true; - } - } - } - - m_widget->texturesModel()->setHasSceneEnv(sceneEnvExists); - m_widget->environmentsModel()->setHasSceneEnv(sceneEnvExists); + m_sceneId = sceneId; } void ContentLibraryView::selectedNodesChanged(const QList &selectedNodeList, @@ -237,9 +201,32 @@ void ContentLibraryView::customNotification(const AbstractView *view, const QStr } m_draggedBundleMaterial = nullptr; + } else if (identifier == "drop_bundle_texture") { + ModelNode matLib = materialLibraryNode(); + if (!matLib.isValid()) + return; + + m_widget->addTexture(m_draggedBundleTexture); + + m_draggedBundleTexture = nullptr; } } +void ContentLibraryView::nodeReparented(const ModelNode &node, + [[maybe_unused]] const NodeAbstractProperty &newPropertyParent, + [[maybe_unused]] const NodeAbstractProperty &oldPropertyParent, + [[maybe_unused]] PropertyChangeFlags propertyChange) +{ + if (node.id() == Constants::MATERIAL_LIB_ID) + m_widget->setHasMaterialLibrary(true); +} + +void ContentLibraryView::nodeAboutToBeRemoved(const ModelNode &removedNode) +{ + if (removedNode.id() == Constants::MATERIAL_LIB_ID) + m_widget->setHasMaterialLibrary(false); +} + void ContentLibraryView::applyBundleMaterialToDropTarget(const ModelNode &bundleMat, const NodeMetaInfo &metaInfo) { diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h index a55d7d772fa..46055f49a9d 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h @@ -3,6 +3,7 @@ #pragma once +#include "createtexture.h" #include "abstractview.h" #include "nodemetainfo.h" @@ -12,6 +13,7 @@ namespace QmlDesigner { class ContentLibraryMaterial; +class ContentLibraryTexture; class ContentLibraryWidget; class Model; @@ -35,6 +37,10 @@ public: const QList &lastSelectedNodeList) override; void customNotification(const AbstractView *view, const QString &identifier, const QList &nodeList, const QList &data) override; + void nodeReparented(const ModelNode &node, const NodeAbstractProperty &newPropertyParent, + const NodeAbstractProperty &oldPropertyParent, + AbstractView::PropertyChangeFlags propertyChange) override; + void nodeAboutToBeRemoved(const ModelNode &removedNode) override; private: void updateBundleMaterialsImportedState(); @@ -47,9 +53,11 @@ private: QList m_bundleMaterialTargets; QList m_selectedModels; // selected 3D model nodes ContentLibraryMaterial *m_draggedBundleMaterial = nullptr; - ModelNode m_activeSceneEnv; + ContentLibraryTexture *m_draggedBundleTexture = nullptr; bool m_bundleMaterialAddToSelected = false; bool m_hasQuick3DImport = false; + qint32 m_sceneId = -1; + CreateTexture m_createTexture; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp index 99a24e70b40..4f6c268b786 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp @@ -58,11 +58,6 @@ bool ContentLibraryWidget::eventFilter(QObject *obj, QEvent *event) mimeData->setData(Constants::MIME_TYPE_BUNDLE_MATERIAL, data); mimeData->removeFormat("text/plain"); - if (!m_draggedMaterial) { - m_draggedMaterial = m_materialToDrag; - emit draggedMaterialChanged(); - } - emit bundleMaterialDragStarted(m_materialToDrag); model->startDrag(mimeData, m_materialToDrag->icon().toLocalFile()); m_materialToDrag = nullptr; @@ -77,6 +72,7 @@ bool ContentLibraryWidget::eventFilter(QObject *obj, QEvent *event) mimeData->setData(Constants::MIME_TYPE_BUNDLE_TEXTURE, data); mimeData->removeFormat("text/plain"); + emit bundleTextureDragStarted(m_textureToDrag); model->startDrag(mimeData, m_textureToDrag->icon().toLocalFile()); m_textureToDrag = nullptr; } @@ -84,11 +80,6 @@ bool ContentLibraryWidget::eventFilter(QObject *obj, QEvent *event) } else if (event->type() == QMouseEvent::MouseButtonRelease) { m_materialToDrag = nullptr; m_textureToDrag = nullptr; - - if (m_draggedMaterial) { - m_draggedMaterial = nullptr; - emit draggedMaterialChanged(); - } } return QObject::eventFilter(obj, event); @@ -166,6 +157,44 @@ void ContentLibraryWidget::clearSearchFilter() QMetaObject::invokeMethod(m_quickWidget->rootObject(), "clearSearchFilter"); } +bool ContentLibraryWidget::hasQuick3DImport() const +{ + return m_hasQuick3DImport; +} + +void ContentLibraryWidget::setHasQuick3DImport(bool b) +{ + if (b == m_hasQuick3DImport) + return; + + const bool oldRequired = m_materialsModel->hasRequiredQuick3DImport(); + m_hasQuick3DImport = b; + const bool newRequired = m_materialsModel->hasRequiredQuick3DImport(); + + if (oldRequired != newRequired) + emit m_materialsModel->hasRequiredQuick3DImportChanged(); + + emit hasQuick3DImportChanged(); + + m_materialsModel->updateIsEmpty(); +} + +bool ContentLibraryWidget::hasMaterialLibrary() const +{ + return m_hasMaterialLibrary; +} + +void ContentLibraryWidget::setHasMaterialLibrary(bool b) +{ + if (m_hasMaterialLibrary == b) + return; + + m_hasMaterialLibrary = b; + emit hasMaterialLibraryChanged(); + + m_materialsModel->updateIsEmpty(); +} + void ContentLibraryWidget::reloadQmlSource() { const QString materialBrowserQmlPath = qmlSourcesPath() + "/ContentLibrary.qml"; @@ -230,6 +259,11 @@ void ContentLibraryWidget::addLightProbe(ContentLibraryTexture *tex) emit addTextureRequested(tex->path(), AddTextureMode::LightProbe); } +void ContentLibraryWidget::updateSceneEnvState() +{ + emit updateSceneEnvStateRequested(); +} + QPointer ContentLibraryWidget::materialsModel() const { return m_materialsModel; diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h index 1c14794c6c2..ea41027a67c 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h @@ -3,6 +3,8 @@ #pragma once +#include "createtexture.h" + #include #include @@ -23,6 +25,9 @@ class ContentLibraryWidget : public QFrame { Q_OBJECT + Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport NOTIFY hasQuick3DImportChanged) + Q_PROPERTY(bool hasMaterialLibrary READ hasMaterialLibrary NOTIFY hasMaterialLibraryChanged) + public: ContentLibraryWidget(); @@ -31,6 +36,12 @@ public: static QString qmlSourcesPath(); void clearSearchFilter(); + bool hasQuick3DImport() const; + void setHasQuick3DImport(bool b); + + bool hasMaterialLibrary() const; + void setHasMaterialLibrary(bool b); + Q_INVOKABLE void handleSearchFilterChanged(const QString &filterText); void setMaterialsModel(QPointer newMaterialsModel); @@ -44,13 +55,15 @@ public: Q_INVOKABLE void addImage(QmlDesigner::ContentLibraryTexture *tex); Q_INVOKABLE void addTexture(QmlDesigner::ContentLibraryTexture *tex); Q_INVOKABLE void addLightProbe(QmlDesigner::ContentLibraryTexture *tex); - - enum class AddTextureMode { Image, Texture, LightProbe }; + Q_INVOKABLE void updateSceneEnvState(); signals: void bundleMaterialDragStarted(QmlDesigner::ContentLibraryMaterial *bundleMat); - void draggedMaterialChanged(); - void addTextureRequested(const QString texPath, QmlDesigner::ContentLibraryWidget::AddTextureMode mode); + void bundleTextureDragStarted(QmlDesigner::ContentLibraryTexture *bundleTex); + void addTextureRequested(const QString texPath, QmlDesigner::AddTextureMode mode); + void updateSceneEnvStateRequested(); + void hasQuick3DImportChanged(); + void hasMaterialLibraryChanged(); protected: bool eventFilter(QObject *obj, QEvent *event) override; @@ -70,9 +83,11 @@ private: QString m_filterText; ContentLibraryMaterial *m_materialToDrag = nullptr; - ContentLibraryMaterial *m_draggedMaterial = nullptr; ContentLibraryTexture *m_textureToDrag = nullptr; QPoint m_dragStartPoint; + + bool m_hasMaterialLibrary = false; + bool m_hasQuick3DImport = false; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/createtexture.cpp b/src/plugins/qmldesigner/components/createtexture.cpp new file mode 100644 index 00000000000..91e40959806 --- /dev/null +++ b/src/plugins/qmldesigner/components/createtexture.cpp @@ -0,0 +1,125 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "createtexture.h" + +#include "abstractview.h" +#include "documentmanager.h" +#include "modelnodeoperations.h" +#include "nodelistproperty.h" +#include "nodemetainfo.h" +#include "qmlobjectnode.h" +#include "variantproperty.h" + +#include + +#include + +namespace QmlDesigner { + +CreateTexture::CreateTexture(AbstractView *view, bool importFile) + : m_view{view} + , m_importFile{importFile} +{} + +void CreateTexture::execute(const QString &filePath, AddTextureMode mode, int sceneId) +{ + if (m_importFile && !addFileToProject(filePath)) + return; + + ModelNode texture = createTextureFromImage(filePath, mode); + if (!texture.isValid()) + return; + + if (mode == AddTextureMode::LightProbe && sceneId != -1) + assignTextureAsLightProbe(texture, sceneId); + + QTimer::singleShot(0, m_view, [this, texture]() { + if (m_view->model()) + m_view->emitCustomNotification("selected_texture_changed", {texture}); + }); +} + +bool CreateTexture::addFileToProject(const QString &filePath) +{ + AddFilesResult result = ModelNodeOperations::addImageToProject( + {filePath}, ModelNodeOperations::getImagesDefaultDirectory().toString(), false); + + if (result.status() == AddFilesResult::Failed) { + Core::AsynchronousMessageBox::warning(QObject::tr("Failed to Add Texture"), + QObject::tr("Could not add %1 to project.").arg(filePath)); + return false; + } + + return true; +} + +ModelNode CreateTexture::createTextureFromImage(const QString &assetPath, AddTextureMode mode) +{ + if (mode != AddTextureMode::Texture && mode != AddTextureMode::LightProbe) + return {}; + + ModelNode matLib = m_view->materialLibraryNode(); + if (!matLib.isValid()) + return {}; + + NodeMetaInfo metaInfo = m_view->model()->qtQuick3DTextureMetaInfo(); + + Utils::FilePath imagePath = ModelNodeOperations::getImagesDefaultDirectory() + .pathAppended(Utils::FilePath::fromString(assetPath).fileName()); + QString sourceVal = imagePath.relativePathFrom( + QmlDesigner::DocumentManager::currentFilePath()).toString(); + + ModelNode newTexNode = m_view->getTextureDefaultInstance(sourceVal); + if (!newTexNode.isValid()) { + newTexNode = m_view->createModelNode("QtQuick3D.Texture", + metaInfo.majorVersion(), + metaInfo.minorVersion()); + newTexNode.validId(); + VariantProperty sourceProp = newTexNode.variantProperty("source"); + sourceProp.setValue(sourceVal); + matLib.defaultNodeListProperty().reparentHere(newTexNode); + } + + return newTexNode; +} + +void CreateTexture::assignTextureAsLightProbe(const ModelNode &texture, int sceneId) +{ + ModelNode sceneEnvNode = resolveSceneEnv(sceneId); + QmlObjectNode sceneEnv = sceneEnvNode; + if (sceneEnv.isValid()) { + sceneEnv.setBindingProperty("lightProbe", texture.id()); + sceneEnv.setVariantProperty("backgroundMode", + QVariant::fromValue(Enumeration("SceneEnvironment", + "SkyBox"))); + } +} + +ModelNode CreateTexture::resolveSceneEnv(int sceneId) +{ + ModelNode activeSceneEnv; + ModelNode selectedNode = m_view->firstSelectedModelNode(); + + if (selectedNode.metaInfo().isQtQuick3DSceneEnvironment()) { + activeSceneEnv = selectedNode; + } else if (sceneId != -1) { + ModelNode activeScene = m_view->active3DSceneNode(); + if (activeScene.isValid()) { + QmlObjectNode view3D; + if (activeScene.metaInfo().isQtQuick3DView3D()) { + view3D = activeScene; + } else { + ModelNode sceneParent = activeScene.parentProperty().parentModelNode(); + if (sceneParent.metaInfo().isQtQuick3DView3D()) + view3D = sceneParent; + } + if (view3D.isValid()) + activeSceneEnv = m_view->modelNodeForId(view3D.expression("environment")); + } + } + + return activeSceneEnv; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/createtexture.h b/src/plugins/qmldesigner/components/createtexture.h new file mode 100644 index 00000000000..3898b67862f --- /dev/null +++ b/src/plugins/qmldesigner/components/createtexture.h @@ -0,0 +1,42 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +namespace QmlDesigner { + +class AbstractView; + +enum class AddTextureMode { Image, Texture, LightProbe }; + +class CreateTexture +{ +public: + CreateTexture(AbstractView *view, bool importFiles = false); + void execute(const QString &filePath, AddTextureMode mode, int sceneId); + ModelNode resolveSceneEnv(int sceneId); + void assignTextureAsLightProbe(const ModelNode &texture, int sceneId); + +private: + bool addFileToProject(const QString &filePath); + ModelNode createTextureFromImage(const QString &assetPath, AddTextureMode mode); + +private: + AbstractView *m_view = nullptr; + bool m_importFile = false; +}; + +class CreateTextures : public CreateTexture +{ +public: + using CreateTexture::CreateTexture; + void execute(const QStringList &filePaths, AddTextureMode mode, int sceneId) + { + for (const QString &path : filePaths) + CreateTexture::execute(path, mode, sceneId); + } +}; + +} diff --git a/src/plugins/qmldesigner/components/debugview/debugview.cpp b/src/plugins/qmldesigner/components/debugview/debugview.cpp index 286e1000f3e..527a8644569 100644 --- a/src/plugins/qmldesigner/components/debugview/debugview.cpp +++ b/src/plugins/qmldesigner/components/debugview/debugview.cpp @@ -247,6 +247,17 @@ QTextStream &operator<<(QTextStream &stream, AuxiliaryDataType type) } } // namespace +static QString rectFToString(const QRectF &rect) +{ + return QString::number(rect.x()) + " " + QString::number(rect.y()) + " " + + QString::number(rect.width()) + " " + QString::number(rect.height()); +} + +static QString sizeToString(const QSize &size) +{ + return QString::number(size.width()) + " " + QString::number(size.height()); +} + void DebugView::selectedNodesChanged(const QList &selectedNodes /*selectedNodeList*/, const QList & /*lastSelectedNodeList*/) { @@ -266,6 +277,10 @@ void DebugView::selectedNodesChanged(const QList &selectedNodes /*sel message << lineBreak; + message << lineBreak; + message << "metaInfo valid: " << selectedNode.metaInfo().isValid(); + message << lineBreak; + if (selectedNode.metaInfo().isValid()) { for (const NodeMetaInfo &metaInfo : selectedNode.metaInfo().classHierarchy()) message << metaInfo.typeName() << lineBreak; @@ -289,6 +304,42 @@ void DebugView::selectedNodesChanged(const QList &selectedNodes /*sel message << name << " "; message << lineBreak; + message << "Is QtQuickItem: " + << selectedNode.metaInfo().isBasedOn(model()->qtQuickItemMetaInfo()); + } + + message << lineBreak; + message << "Is item or window: " << QmlItemNode::isItemOrWindow(selectedNode); + message << lineBreak; + + message << lineBreak; + message << "Is graphical item: " << selectedNode.metaInfo().isGraphicalItem(); + message << lineBreak; + + message << lineBreak; + message << "Is valid object node: " << QmlItemNode::isValidQmlObjectNode(selectedNode); + message << lineBreak; + + message << "version: " + << QString::number(selectedNode.metaInfo().majorVersion()) + "." + + QString::number(selectedNode.metaInfo().minorVersion()); + + message << lineBreak; + + QmlItemNode itemNode(selectedNode); + if (itemNode.isValid()) { + message << lineBreak; + message << "Item Node"; + message << lineBreak; + message << "BR "; + message << rectFToString(itemNode.instanceBoundingRect()); + message << lineBreak; + message << "BR content "; + message << rectFToString(itemNode.instanceContentItemBoundingRect()); + message << lineBreak; + message << "PM "; + message << sizeToString(itemNode.instanceRenderPixmap().size()); + message << lineBreak; } auto auxiliaryData = selectedNode.auxiliaryData(); diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp index 6098e49fbcf..b7efef60d6a 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp @@ -2,26 +2,26 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 #include "edit3dview.h" + #include "backgroundcolorselection.h" +#include "designersettings.h" +#include "designmodecontext.h" #include "edit3dactions.h" #include "edit3dcanvas.h" #include "edit3dviewconfig.h" #include "edit3dwidget.h" #include "metainfo.h" #include "nodehints.h" +#include "nodeinstanceview.h" +#include "qmldesignerconstants.h" +#include "qmldesignericons.h" +#include "qmldesignerplugin.h" #include "seekerslider.h" #include #include -#include -#include -#include -#include -#include -#include -#include -#include +#include #include #include @@ -278,15 +278,19 @@ void Edit3DView::nodeAtPosReady(const ModelNode &modelNode, const QVector3D &pos setSelectedModelNode(modelNode); m_edit3DWidget->showContextMenu(m_contextMenuPos, modelNode, pos3d); } else if (m_nodeAtPosReqType == NodeAtPosReqType::MaterialDrop) { - const bool isModel = modelNode.metaInfo().isQtQuick3DModel(); - if (m_droppedMaterial.isValid() && modelNode.isValid() && isModel) { + bool isModel = modelNode.metaInfo().isQtQuick3DModel(); + if (m_droppedModelNode.isValid() && modelNode.isValid() && isModel) { executeInTransaction(__FUNCTION__, [&] { - assignMaterialTo3dModel(modelNode, m_droppedMaterial); + assignMaterialTo3dModel(modelNode, m_droppedModelNode); }); } } else if (m_nodeAtPosReqType == NodeAtPosReqType::BundleMaterialDrop) { emitCustomNotification("drop_bundle_material", {modelNode}); // To ContentLibraryView + } else if (m_nodeAtPosReqType == NodeAtPosReqType::TextureDrop) { + emitCustomNotification("apply_texture_to_model3D", {modelNode, m_droppedModelNode}); } + + m_droppedModelNode = {}; m_nodeAtPosReqType = NodeAtPosReqType::None; } @@ -842,7 +846,7 @@ void Edit3DView::startContextMenu(const QPoint &pos) void Edit3DView::dropMaterial(const ModelNode &matNode, const QPointF &pos) { m_nodeAtPosReqType = NodeAtPosReqType::MaterialDrop; - m_droppedMaterial = matNode; + m_droppedModelNode = matNode; emitView3DAction(View3DActionType::GetNodeAtPos, pos); } @@ -852,4 +856,11 @@ void Edit3DView::dropBundleMaterial(const QPointF &pos) emitView3DAction(View3DActionType::GetNodeAtPos, pos); } +void Edit3DView::dropTexture(const ModelNode &textureNode, const QPointF &pos) +{ + m_nodeAtPosReqType = NodeAtPosReqType::TextureDrop; + m_droppedModelNode = textureNode; + emitView3DAction(View3DActionType::GetNodeAtPos, pos); +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.h b/src/plugins/qmldesigner/components/edit3d/edit3dview.h index eaa881d5b1e..a14399e1c70 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.h @@ -8,14 +8,15 @@ #include #include +#include #include #include #include #include QT_BEGIN_NAMESPACE -class QInputEvent; class QAction; +class QInputEvent; QT_END_NAMESPACE namespace QmlDesigner { @@ -62,6 +63,7 @@ public: void startContextMenu(const QPoint &pos); void dropMaterial(const ModelNode &matNode, const QPointF &pos); void dropBundleMaterial(const QPointF &pos); + void dropTexture(const ModelNode &textureNode, const QPointF &pos); private slots: void onEntriesChanged(); @@ -70,6 +72,7 @@ private: enum class NodeAtPosReqType { BundleMaterialDrop, MaterialDrop, + TextureDrop, ContextMenu, None }; @@ -77,6 +80,7 @@ private: void createEdit3DWidget(); void checkImports(); void handleEntriesChanged(); + void showMaterialPropertiesView(); Edit3DAction *createSelectBackgroundColorAction(QAction *syncBackgroundColorAction); Edit3DAction *createGridColorSelectionAction(); @@ -112,7 +116,7 @@ private: SeekerSlider *m_seeker = nullptr; int particlemode; ModelCache m_canvasCache; - ModelNode m_droppedMaterial; + ModelNode m_droppedModelNode; NodeAtPosReqType m_nodeAtPosReqType; QPoint m_contextMenuPos; QTimer m_compressionTimer; diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index e347120ba34..ca57d229d3b 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -30,6 +30,8 @@ #include #include +#include +#include #include #include @@ -235,34 +237,7 @@ void Edit3DWidget::createContextMenu() bool Edit3DWidget::isPasteAvailable() const { - if (TimelineActions::clipboardContainsKeyframes()) - return false; - - auto pasteModel(DesignDocumentView::pasteToModel(view()->externalDependencies())); - if (!pasteModel) - return false; - - DesignDocumentView docView{view()->externalDependencies()}; - pasteModel->attachView(&docView); - auto rootNode = docView.rootModelNode(); - - if (rootNode.type() == "empty") - return false; - - QList allNodes; - if (rootNode.id() == "__multi__selection__") - allNodes << rootNode.directSubModelNodes(); - else - allNodes << rootNode; - - bool hasNon3DNode = std::any_of(allNodes.begin(), allNodes.end(), [](const ModelNode &node) { - return !node.metaInfo().isQtQuick3DNode(); - }); - - if (hasNon3DNode) - return false; - - return true; + return QApplication::clipboard()->text().startsWith(Constants::HEADER_3DPASTE_CONTENT); } // Called by the view to update the "create" sub-menu when the Quick3D entries are ready. @@ -426,7 +401,8 @@ void Edit3DWidget::dragEnterEvent(QDragEnterEvent *dragEnterEvent) ->viewManager().designerActionManager(); if (actionManager.externalDragHasSupportedAssets(dragEnterEvent->mimeData()) || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_MATERIAL) - || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_BUNDLE_MATERIAL)) { + || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_BUNDLE_MATERIAL) + || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_TEXTURE)) { dragEnterEvent->acceptProposedAction(); } } @@ -435,15 +411,19 @@ void Edit3DWidget::dropEvent(QDropEvent *dropEvent) { const QPointF pos = m_canvas->mapFrom(this, dropEvent->position()); - // handle dropping materials - if (dropEvent->mimeData()->hasFormat(Constants::MIME_TYPE_MATERIAL)) { - QByteArray data = dropEvent->mimeData()->data(Constants::MIME_TYPE_MATERIAL); - QDataStream stream(data); - qint32 internalId; - stream >> internalId; - - if (ModelNode matNode = m_view->modelNodeForInternalId(internalId)) - m_view->dropMaterial(matNode, pos); + // handle dropping materials and textures + if (dropEvent->mimeData()->hasFormat(Constants::MIME_TYPE_MATERIAL) + || dropEvent->mimeData()->hasFormat(Constants::MIME_TYPE_TEXTURE)) { + bool isMaterial = dropEvent->mimeData()->hasFormat(Constants::MIME_TYPE_MATERIAL); + QByteArray data = dropEvent->mimeData()->data(isMaterial + ? QString::fromLatin1(Constants::MIME_TYPE_MATERIAL) + : QString::fromLatin1(Constants::MIME_TYPE_TEXTURE)); + if (ModelNode dropNode = m_view->modelNodeForInternalId(data.toInt())) { + if (isMaterial) + m_view->dropMaterial(dropNode, pos); + else + m_view->dropTexture(dropNode, pos); + } return; } diff --git a/src/plugins/qmldesigner/components/eventlist/eventlistactions.cpp b/src/plugins/qmldesigner/components/eventlist/eventlistactions.cpp index a9ca7936aed..fd6485118a7 100644 --- a/src/plugins/qmldesigner/components/eventlist/eventlistactions.cpp +++ b/src/plugins/qmldesigner/components/eventlist/eventlistactions.cpp @@ -55,7 +55,7 @@ EventListAction::EventListAction() QObject::tr("Show Event List"), ComponentCoreConstants::eventListCategory, QKeySequence("Alt+e"), - 230, + 11, &handleAction, &eventListEnabled) {} @@ -77,7 +77,7 @@ AssignEventEditorAction::AssignEventEditorAction() QObject::tr("Assign Events to Actions"), ComponentCoreConstants::eventListCategory, QKeySequence("Alt+a"), - 220, + 21, &handleAssignEventActionOperation, &eventListEnabled) {} @@ -88,7 +88,7 @@ ConnectSignalAction::ConnectSignalAction() assignEventListIcon(), ComponentCoreConstants::eventListCategory, QKeySequence(), - 210, + 31, &handleAssignEventActionOperation) {} diff --git a/src/plugins/qmldesigner/components/eventlist/eventlistpluginview.cpp b/src/plugins/qmldesigner/components/eventlist/eventlistpluginview.cpp index 47b0043b571..f45d609d927 100644 --- a/src/plugins/qmldesigner/components/eventlist/eventlistpluginview.cpp +++ b/src/plugins/qmldesigner/components/eventlist/eventlistpluginview.cpp @@ -45,7 +45,7 @@ void EventListPluginView::registerActions() designerActionManager.addDesignerAction(new ActionGroup(tr("Event List"), ComponentCoreConstants::eventListCategory, - ComponentCoreConstants::priorityEventListCategory, + ComponentCoreConstants::Priorities::EventListCategory, &SelectionContextFunctors::always, &SelectionContextFunctors::always)); auto eventListAction = new EventListAction(); diff --git a/src/plugins/qmldesigner/components/formeditor/dragtool.cpp b/src/plugins/qmldesigner/components/formeditor/dragtool.cpp index fe345024182..b64ce3d1007 100644 --- a/src/plugins/qmldesigner/components/formeditor/dragtool.cpp +++ b/src/plugins/qmldesigner/components/formeditor/dragtool.cpp @@ -243,33 +243,15 @@ void DragTool::dropEvent(const QList &itemList, QGraphicsSceneD if (!effectPath.isEmpty()) { FormEditorItem *targetContainerFormEditorItem = targetContainerOrRootItem(itemList); if (targetContainerFormEditorItem) { - QmlItemNode parentQmlItemNode = targetContainerFormEditorItem->qmlItemNode(); - QString effectName = QFileInfo(effectPath).baseName(); - - if (!AssetsLibraryModel::isEffectQmlExist(effectName)) { - QMessageBox msgBox; - msgBox.setText("Effect " + effectName + " is empty"); - msgBox.setInformativeText("Do you want to edit " + effectName + "?"); - msgBox.setStandardButtons(QMessageBox::No |QMessageBox::Yes); - msgBox.setDefaultButton(QMessageBox::Yes); - msgBox.setIcon(QMessageBox::Question); - int ret = msgBox.exec(); - switch (ret) { - case QMessageBox::Yes: - ModelNodeOperations::openEffectMaker(effectPath); - break; - default: - break; - } - + if (!ModelNodeOperations::validateEffect(effectPath)) { event->ignore(); return; } - QmlItemNode::createQmlItemNodeForEffect(view(), parentQmlItemNode, effectName); - + bool layerEffect = ModelNodeOperations::useLayerEffect(); + QmlItemNode parentQmlItemNode = targetContainerFormEditorItem->qmlItemNode(); + QmlItemNode::createQmlItemNodeForEffect(view(), parentQmlItemNode, effectPath, layerEffect); view()->setSelectedModelNodes({parentQmlItemNode}); - view()->resetPuppet(); commitTransaction(); } diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.cpp index f17e280702e..6bfe64dd22a 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.cpp @@ -5,6 +5,7 @@ #include "formeditoritem.h" #include "formeditorwidget.h" #include "navigation2d.h" +#include #include #include @@ -44,9 +45,11 @@ FormEditorGraphicsView::FormEditorGraphicsView(QWidget *parent) connect(filter, &Navigation2dFilter::zoomIn, this, &FormEditorGraphicsView::zoomIn); connect(filter, &Navigation2dFilter::zoomOut, this, &FormEditorGraphicsView::zoomOut); - connect(filter, &Navigation2dFilter::panChanged, [this](const QPointF &direction) { - Navigation2dFilter::scroll(direction, horizontalScrollBar(), verticalScrollBar()); - }); + if (Utils::HostOsInfo::isMacHost()) { + connect(filter, &Navigation2dFilter::panChanged, [this](const QPointF &direction) { + Navigation2dFilter::scroll(direction, horizontalScrollBar(), verticalScrollBar()); + }); + } auto zoomChanged = &Navigation2dFilter::zoomChanged; connect(filter, zoomChanged, [this](double s, const QPointF &/*pos*/) { diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp index ec847d04245..331f7fbe97b 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp @@ -6,6 +6,7 @@ #include "designersettings.h" #include "formeditoritem.h" #include "formeditorscene.h" +#include "modelnodecontextmenu_helper.h" #include "qmldesignerconstants.h" #include "qmldesignericons.h" #include "qmldesignerplugin.h" @@ -67,47 +68,49 @@ FormEditorWidget::FormEditorWidget(FormEditorView *view) auto layoutActionGroup = new QActionGroup(this); layoutActionGroup->setExclusive(true); - m_noSnappingAction = layoutActionGroup->addAction(tr("No snapping.")); + m_noSnappingAction = layoutActionGroup->addAction(tr("No Snapping")); m_noSnappingAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_noSnappingAction->setCheckable(true); m_noSnappingAction->setChecked(true); - m_noSnappingAction->setIcon(Icons::NO_SNAPPING.icon()); - registerActionAsCommand(m_noSnappingAction, Constants::FORMEDITOR_NO_SNAPPING, QKeySequence(Qt::Key_T)); - m_snappingAndAnchoringAction = layoutActionGroup->addAction(tr("Snap to parent or sibling components and generate anchors.")); + registerActionAsCommand(m_noSnappingAction, + Constants::FORMEDITOR_NO_SNAPPING, + QKeySequence(Qt::Key_T), + ComponentCoreConstants::snappingCategory, + 1); + + m_snappingAndAnchoringAction = layoutActionGroup->addAction(tr("Snap with Anchors")); m_snappingAndAnchoringAction->setCheckable(true); m_snappingAndAnchoringAction->setChecked(true); - m_snappingAndAnchoringAction->setIcon(Icons::NO_SNAPPING_AND_ANCHORING.icon()); - registerActionAsCommand(m_snappingAndAnchoringAction, Constants::FORMEDITOR_NO_SNAPPING_AND_ANCHORING, QKeySequence(Qt::Key_W)); - m_snappingAction = layoutActionGroup->addAction(tr("Snap to parent or sibling components but do not generate anchors.")); + registerActionAsCommand(m_snappingAndAnchoringAction, + Constants::FORMEDITOR_NO_SNAPPING_AND_ANCHORING, + QKeySequence(Qt::Key_W), + ComponentCoreConstants::snappingCategory, + 2); + + m_snappingAction = layoutActionGroup->addAction(tr("Snap without Anchors")); m_snappingAction->setCheckable(true); m_snappingAction->setChecked(true); - m_snappingAction->setIcon(Icons::SNAPPING.icon()); - registerActionAsCommand(m_snappingAction, Constants::FORMEDITOR_SNAPPING, QKeySequence(Qt::Key_E)); + + registerActionAsCommand(m_snappingAction, + Constants::FORMEDITOR_SNAPPING, + QKeySequence(Qt::Key_E), + ComponentCoreConstants::snappingCategory, + 3); addActions(layoutActionGroup->actions()); - upperActions.append(layoutActionGroup->actions()); - auto separatorAction = new QAction(this); - separatorAction->setSeparator(true); - addAction(separatorAction); - upperActions.append(separatorAction); - - m_showBoundingRectAction = new QAction(Utils::Icons::BOUNDING_RECT.icon(), - tr("Show bounding rectangles and stripes for empty components."), - this); + m_showBoundingRectAction = new QAction(tr("Show Bounds"), this); m_showBoundingRectAction->setCheckable(true); m_showBoundingRectAction->setChecked(false); - registerActionAsCommand(m_showBoundingRectAction, Constants::FORMEDITOR_NO_SHOW_BOUNDING_RECTANGLE, QKeySequence(Qt::Key_A)); + registerActionAsCommand(m_showBoundingRectAction, + Constants::FORMEDITOR_NO_SHOW_BOUNDING_RECTANGLE, + QKeySequence(Qt::Key_A), + ComponentCoreConstants::rootCategory, + ComponentCoreConstants::Priorities::ShowBoundingRect); addAction(m_showBoundingRectAction.data()); - upperActions.append(m_showBoundingRectAction.data()); - - separatorAction = new QAction(this); - separatorAction->setSeparator(true); - addAction(separatorAction); - upperActions.append(separatorAction); m_rootWidthAction = new LineEditAction(tr("Override Width"), this); m_rootWidthAction->setToolTip(tr("Override width of root component.")); @@ -266,7 +269,11 @@ FormEditorWidget::FormEditorWidget(FormEditorView *view) connect(m_zoomSelectionAction.data(), &QAction::triggered, frameSelection); m_resetAction = new QAction(Utils::Icons::RESET_TOOLBAR.icon(), tr("Reset View"), this); - registerActionAsCommand(m_resetAction, Constants::FORMEDITOR_REFRESH, QKeySequence(Qt::Key_R)); + registerActionAsCommand(m_resetAction, + Constants::FORMEDITOR_REFRESH, + QKeySequence(Qt::Key_R), + ComponentCoreConstants::rootCategory, + ComponentCoreConstants::Priorities::ResetView); addAction(m_resetAction.data()); upperActions.append(m_resetAction.data()); @@ -334,12 +341,25 @@ void FormEditorWidget::changeBackgound(const QColor &color) } } -void FormEditorWidget::registerActionAsCommand(QAction *action, Utils::Id id, const QKeySequence &keysequence) +void FormEditorWidget::registerActionAsCommand( + QAction *action, Utils::Id id, const QKeySequence &, const QByteArray &category, int priority) { Core::Context context(Constants::C_QMLFORMEDITOR); Core::Command *command = Core::ActionManager::registerAction(action, id, context); - command->setDefaultKeySequence(keysequence); + + DesignerActionManager &designerActionManager = QmlDesignerPlugin::instance() + ->viewManager() + .designerActionManager(); + + designerActionManager.addCreatorCommand(command, category, priority); + + connect(command->action(), &QAction::enabledChanged, command, [command](bool b) { + command->action()->setVisible(b); + }); + + command->action()->setVisible(command->action()->isEnabled()); + command->augmentActionWithShortcutToolTip(action); } diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorwidget.h b/src/plugins/qmldesigner/components/formeditor/formeditorwidget.h index 7931af6ec51..413b5f16c24 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorwidget.h +++ b/src/plugins/qmldesigner/components/formeditor/formeditorwidget.h @@ -82,7 +82,11 @@ private: void changeRootItemWidth(const QString &widthText); void changeRootItemHeight(const QString &heightText); void changeBackgound(const QColor &color); - void registerActionAsCommand(QAction *action, Utils::Id id, const QKeySequence &keysequence); + void registerActionAsCommand(QAction *action, + Utils::Id id, + const QKeySequence &keysequence, + const QByteArray &category = {}, + int priority = 0); QPointer m_formEditorView; QPointer m_graphicsView; diff --git a/src/plugins/qmldesigner/components/formeditor/movetool.cpp b/src/plugins/qmldesigner/components/formeditor/movetool.cpp index 506bb0d85f3..8e10a9ccc5e 100644 --- a/src/plugins/qmldesigner/components/formeditor/movetool.cpp +++ b/src/plugins/qmldesigner/components/formeditor/movetool.cpp @@ -47,7 +47,8 @@ void MoveTool::clear() m_contentNotEditableIndicator.clear(); AbstractFormEditorTool::clear(); - view()->formEditorWidget()->graphicsView()->viewport()->unsetCursor(); + if (view()->formEditorWidget()->graphicsView()) + view()->formEditorWidget()->graphicsView()->viewport()->unsetCursor(); } void MoveTool::start() diff --git a/src/plugins/qmldesigner/components/integration/designdocumentview.cpp b/src/plugins/qmldesigner/components/integration/designdocumentview.cpp index d2f63010f6d..5513ca1664d 100644 --- a/src/plugins/qmldesigner/components/integration/designdocumentview.cpp +++ b/src/plugins/qmldesigner/components/integration/designdocumentview.cpp @@ -9,8 +9,10 @@ #include #include "designdocument.h" +#include #include #include +#include #include #include @@ -18,6 +20,7 @@ #include #include +#include #include namespace QmlDesigner { @@ -80,6 +83,18 @@ void DesignDocumentView::fromClipboard() // } } +static bool hasOnly3DNodes(const ModelNode &node) +{ + if (node.id() == "__multi__selection__") { + bool hasNon3DNode = Utils::anyOf(node.directSubModelNodes(), [](const ModelNode &node) { + return !node.metaInfo().isQtQuick3DNode(); + }); + + return !hasNon3DNode; + } + return node.metaInfo().isQtQuick3DNode(); +} + QString DesignDocumentView::toText() const { auto outputModel = Model::create("QtQuick.Rectangle", 1, 0, model()); @@ -110,7 +125,11 @@ QString DesignDocumentView::toText() const ModelNode rewriterNode(rewriterView->rootModelNode()); rewriterView->writeAuxiliaryData(); - return rewriterView->extractText({rewriterNode}).value(rewriterNode) + rewriterView->getRawAuxiliaryData(); + + QString paste3DHeader = hasOnly3DNodes(rewriterNode) ? QString(Constants::HEADER_3DPASTE_CONTENT) : QString(); + return paste3DHeader + + rewriterView->extractText({rewriterNode}).value(rewriterNode) + + rewriterView->getRawAuxiliaryData(); //get the text of the root item without imports } diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.cpp index b1ec214993f..c5f267627dc 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.cpp @@ -51,6 +51,11 @@ QString ItemLibraryItem::componentSource() const return m_itemLibraryEntry.customComponentSource(); } +QString ItemLibraryItem::toolTip() const +{ + return m_itemLibraryEntry.toolTip(); +} + bool ItemLibraryItem::setVisible(bool isVisible) { if (isVisible != m_isVisible) { diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.h index 58daac2e6e8..0f5d4878f51 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.h @@ -24,6 +24,7 @@ class ItemLibraryItem: public QObject Q_PROPERTY(bool itemUsable READ isUsable FINAL) Q_PROPERTY(QString itemRequiredImport READ requiredImport FINAL) Q_PROPERTY(QString itemComponentSource READ componentSource FINAL) + Q_PROPERTY(QString toolTip READ toolTip FINAL) public: ItemLibraryItem(const ItemLibraryEntry &itemLibraryEntry, bool isImported, QObject *parent); @@ -35,6 +36,7 @@ public: QString componentPath() const; QString requiredImport() const; QString componentSource() const; + QString toolTip() const; bool setVisible(bool isVisible); bool isVisible() const; diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp index 0bb20a27253..33d3b8a717d 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp @@ -149,13 +149,14 @@ void ItemLibraryView::updateImport3DSupport(const QVariantMap &supportMap) const QString &defaultDir, bool showDialog) -> AddFilesResult { Q_UNUSED(showDialog) + auto importDlg = new ItemLibraryAssetImportDialog(fileNames, defaultDir, m_importableExtensions3DMap, m_importOptions3DMap, {}, {}, Core::ICore::dialogParent()); int result = importDlg->exec(); - return result == QDialog::Accepted ? AddFilesResult::Succeeded : AddFilesResult::Cancelled; + return result == QDialog::Accepted ? AddFilesResult::succeeded() : AddFilesResult::cancelled(); }; auto add3DHandler = [&](const QString &group, const QString &ext) { diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp index ea2e1e47089..f7af499a6f0 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp @@ -171,18 +171,18 @@ void MaterialBrowserModel::setHasModelSelection(bool b) emit hasModelSelectionChanged(); } -bool MaterialBrowserModel::hasMaterialRoot() const +bool MaterialBrowserModel::hasMaterialLibrary() const { - return m_hasMaterialRoot; + return m_hasMaterialLibrary; } -void MaterialBrowserModel::setHasMaterialRoot(bool b) +void MaterialBrowserModel::setHasMaterialLibrary(bool b) { - if (m_hasMaterialRoot == b) + if (m_hasMaterialLibrary == b) return; - m_hasMaterialRoot = b; - emit hasMaterialRootChanged(); + m_hasMaterialLibrary = b; + emit hasMaterialLibraryChanged(); } QString MaterialBrowserModel::copiedMaterialType() const @@ -213,6 +213,11 @@ void MaterialBrowserModel::setSearchText(const QString &searchText) m_searchText = lowerSearchText; + refreshSearch(); +} + +void MaterialBrowserModel::refreshSearch() +{ bool isEmpty = false; // if selected material goes invisible, select nearest material @@ -256,9 +261,13 @@ void MaterialBrowserModel::setMaterials(const QList &materials, bool emit isEmptyChanged(); } - setHasQuick3DImport(hasQuick3DImport); + if (!m_searchText.isEmpty()) + refreshSearch(); + else + resetModel(); + updateSelectedMaterial(); - resetModel(); + setHasQuick3DImport(hasQuick3DImport); } void MaterialBrowserModel::removeMaterial(const ModelNode &material) @@ -315,6 +324,13 @@ ModelNode MaterialBrowserModel::materialAt(int idx) const return {}; } +ModelNode MaterialBrowserModel::selectedMaterial() const +{ + if (isValidIndex(m_selectedIndex)) + return m_materialList[m_selectedIndex]; + return {}; +} + void MaterialBrowserModel::resetModel() { beginResetModel(); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h index f850aaedb01..71a6407632a 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h @@ -21,7 +21,7 @@ class MaterialBrowserModel : public QAbstractListModel Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged) Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged) Q_PROPERTY(bool hasModelSelection READ hasModelSelection WRITE setHasModelSelection NOTIFY hasModelSelectionChanged) - Q_PROPERTY(bool hasMaterialRoot READ hasMaterialRoot WRITE setHasMaterialRoot NOTIFY hasMaterialRootChanged) + Q_PROPERTY(bool hasMaterialLibrary READ hasMaterialLibrary WRITE setHasMaterialLibrary NOTIFY hasMaterialLibraryChanged) Q_PROPERTY(QString copiedMaterialType READ copiedMaterialType WRITE setCopiedMaterialType NOTIFY copiedMaterialTypeChanged) Q_PROPERTY(QStringList defaultMaterialSections MEMBER m_defaultMaterialSections NOTIFY materialSectionsChanged) Q_PROPERTY(QStringList principledMaterialSections MEMBER m_principledMaterialSections NOTIFY materialSectionsChanged) @@ -37,6 +37,7 @@ public: QHash roleNames() const override; void setSearchText(const QString &searchText); + void refreshSearch(); bool hasQuick3DImport() const; void setHasQuick3DImport(bool b); @@ -44,8 +45,10 @@ public: bool hasModelSelection() const; void setHasModelSelection(bool b); - bool hasMaterialRoot() const; - void setHasMaterialRoot(bool b); + bool hasMaterialLibrary() const; + void setHasMaterialLibrary(bool b); + + bool isEmpty() const { return m_isEmpty; } QString copiedMaterialType() const; void setCopiedMaterialType(const QString &matType); @@ -58,6 +61,7 @@ public: void updateSelectedMaterial(); int materialIndex(const ModelNode &material) const; ModelNode materialAt(int idx) const; + ModelNode selectedMaterial() const; bool loadPropertyGroups(const QString &path); void unloadPropertyGroups(); @@ -87,7 +91,7 @@ signals: void isEmptyChanged(); void hasQuick3DImportChanged(); void hasModelSelectionChanged(); - void hasMaterialRootChanged(); + void hasMaterialLibraryChanged(); void copiedMaterialTypeChanged(); void materialSectionsChanged(); void selectedIndexChanged(int idx); @@ -115,11 +119,11 @@ private: QHash m_materialIndexHash; // internalId -> index QJsonObject m_propertyGroupsObj; - int m_selectedIndex = 0; + int m_selectedIndex = -1; bool m_isEmpty = true; bool m_hasQuick3DImport = false; bool m_hasModelSelection = false; - bool m_hasMaterialRoot = false; + bool m_hasMaterialLibrary = false; bool m_allPropsCopied = true; QString m_copiedMaterialType; }; diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp index b39bdbb1f94..f348580937d 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp @@ -3,13 +3,13 @@ #include "materialbrowsertexturesmodel.h" -#include -#include -#include -#include -#include -#include -#include "utils/qtcassert.h" +#include "designeractionmanager.h" +#include "designmodewidget.h" +#include "qmldesignerplugin.h" +#include "qmlobjectnode.h" +#include "variantproperty.h" + +#include namespace QmlDesigner { @@ -32,18 +32,46 @@ QVariant MaterialBrowserTexturesModel::data(const QModelIndex &index, int role) QTC_ASSERT(index.isValid() && index.row() < m_textureList.count(), return {}); QTC_ASSERT(roleNames().contains(role), return {}); - QByteArray roleName = roleNames().value(role); - if (roleName == "textureSource") { - QString source = m_textureList.at(index.row()).variantProperty("source").value().toString(); - return QUrl::fromLocalFile(DocumentManager::currentResourcePath().path() + '/' + source); + if (role == RoleTexSource) { + QString source = QmlObjectNode(m_textureList.at(index.row())).modelValue("source").toString(); + if (source.isEmpty()) + return {}; + if (Utils::FilePath::fromString(source).isAbsolutePath()) + return QVariant(source); + return QVariant(QmlDesignerPlugin::instance()->documentManager().currentDesignDocument() + ->fileName().absolutePath().pathAppended(source).cleanPath().toString()); } - if (roleName == "textureVisible") + if (role == RoleTexVisible) return isTextureVisible(index.row()); - if (roleName == "hasDynamicProperties") + if (role == RoleTexHasDynamicProps) return !m_textureList.at(index.row()).dynamicProperties().isEmpty(); + if (role == RoleTexInternalId) + return m_textureList.at(index.row()).internalId(); + + if (role == RoleTexToolTip) { + QString source = QmlObjectNode(m_textureList.at(index.row())).modelValue("source").toString(); + if (source.isEmpty()) + return tr("Texture has no source image."); + + const QString noData = tr("Texture has no data."); + + auto op = QmlDesignerPlugin::instance()->viewManager().designerActionManager() + .modelNodePreviewOperation(m_textureList.at(index.row())); + if (!op) + return noData; + + QVariantMap imgMap = op(m_textureList.at(index.row())).toMap(); + if (imgMap.isEmpty()) + return noData; + + return QLatin1String("%1\n%2\n%3").arg(imgMap.value("id").toString(), + source.split('/').last(), + imgMap.value("info").toString()); + } + return {}; } @@ -52,7 +80,7 @@ bool MaterialBrowserTexturesModel::isTextureVisible(int idx) const if (!isValidIndex(idx)) return false; - return m_searchText.isEmpty() || m_textureList.at(idx).variantProperty("objectName") + return m_searchText.isEmpty() || m_textureList.at(idx).variantProperty("source") .value().toString().contains(m_searchText, Qt::CaseInsensitive); } @@ -61,13 +89,14 @@ bool MaterialBrowserTexturesModel::isValidIndex(int idx) const return idx > -1 && idx < rowCount(); } - QHash MaterialBrowserTexturesModel::roleNames() const { static const QHash roles { - {Qt::UserRole + 1, "textureSource"}, - {Qt::UserRole + 2, "textureVisible"}, - {Qt::UserRole + 3, "hasDynamicProperties"} + {RoleTexHasDynamicProps, "hasDynamicProperties"}, + {RoleTexInternalId, "textureInternalId"}, + {RoleTexSource, "textureSource"}, + {RoleTexToolTip, "textureToolTip"}, + {RoleTexVisible, "textureVisible"} }; return roles; } @@ -86,6 +115,11 @@ void MaterialBrowserTexturesModel::setSearchText(const QString &searchText) m_searchText = lowerSearchText; + refreshSearch(); +} + +void MaterialBrowserTexturesModel::refreshSearch() +{ bool isEmpty = false; // if selected texture goes invisible, select nearest one @@ -154,11 +188,28 @@ void MaterialBrowserTexturesModel::removeTexture(const ModelNode &texture) } } +void MaterialBrowserTexturesModel::addNewTexture() +{ + emit addNewTextureTriggered(); +} + void MaterialBrowserTexturesModel::deleteSelectedTexture() { deleteTexture(m_selectedIndex); } +void MaterialBrowserTexturesModel::updateTextureSource(const ModelNode &texture) +{ + int idx = textureIndex(texture); + if (idx != -1) + emit dataChanged(index(idx, 0), index(idx, 0), {RoleTexSource, RoleTexToolTip}); +} + +void MaterialBrowserTexturesModel::updateAllTexturesSources() +{ + emit dataChanged(index(0, 0), index(rowCount() - 1, 0), {RoleTexSource, RoleTexToolTip}); +} + void MaterialBrowserTexturesModel::updateSelectedTexture() { selectTexture(m_selectedIndex, true); @@ -180,6 +231,39 @@ ModelNode MaterialBrowserTexturesModel::textureAt(int idx) const return {}; } +ModelNode MaterialBrowserTexturesModel::selectedTexture() const +{ + return textureAt(m_selectedIndex); +} + +bool MaterialBrowserTexturesModel::hasSingleModelSelection() const +{ + return m_hasSingleModelSelection; +} + +void MaterialBrowserTexturesModel::setHasSingleModelSelection(bool b) +{ + if (b == m_hasSingleModelSelection) + return; + + m_hasSingleModelSelection = b; + emit hasSingleModelSelectionChanged(); +} + +bool MaterialBrowserTexturesModel::hasSceneEnv() const +{ + return m_hasSceneEnv; +} + +void MaterialBrowserTexturesModel::setHasSceneEnv(bool b) +{ + if (b == m_hasSceneEnv) + return; + + m_hasSceneEnv = b; + emit hasSceneEnvChanged(); +} + void MaterialBrowserTexturesModel::resetModel() { beginResetModel(); @@ -216,4 +300,46 @@ void MaterialBrowserTexturesModel::deleteTexture(int idx) } } +void MaterialBrowserTexturesModel::applyToSelectedMaterial(qint64 internalId) +{ + int idx = m_textureIndexHash.value(internalId); + if (idx != -1) { + ModelNode tex = m_textureList.at(idx); + emit applyToSelectedMaterialTriggered(tex); + } +} + +void MaterialBrowserTexturesModel::applyToSelectedModel(qint64 internalId) +{ + int idx = m_textureIndexHash.value(internalId); + if (idx != -1) { + ModelNode tex = m_textureList.at(idx); + emit applyToSelectedModelTriggered(tex); + } +} + +void MaterialBrowserTexturesModel::openTextureEditor() +{ + QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("TextureEditor", true); +} + +void MaterialBrowserTexturesModel::updateSceneEnvState() +{ + emit updateSceneEnvStateRequested(); +} + +void MaterialBrowserTexturesModel::applyAsLightProbe(qint64 internalId) +{ + int idx = m_textureIndexHash.value(internalId); + if (idx != -1) { + ModelNode tex = m_textureList.at(idx); + emit applyAsLightProbeRequested(tex); + } +} + +void MaterialBrowserTexturesModel::updateModelSelectionState() +{ + emit updateModelSelectionStateRequested(); +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h index 4b5d1bd55bf..6056431019c 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h @@ -3,7 +3,7 @@ #pragma once -#include +#include "modelnode.h" #include #include @@ -16,6 +16,8 @@ class MaterialBrowserTexturesModel : public QAbstractListModel Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged) + Q_PROPERTY(bool hasSingleModelSelection READ hasSingleModelSelection NOTIFY hasSingleModelSelectionChanged) + Q_PROPERTY(bool hasSceneEnv READ hasSceneEnv NOTIFY hasSceneEnvChanged) public: MaterialBrowserTexturesModel(QObject *parent = nullptr); @@ -26,26 +28,52 @@ public: QHash roleNames() const override; void setSearchText(const QString &searchText); + void refreshSearch(); QList textures() const; void setTextures(const QList &textures); void removeTexture(const ModelNode &texture); void deleteSelectedTexture(); void updateSelectedTexture(); - int textureIndex(const ModelNode &material) const; + void updateTextureSource(const ModelNode &texture); + void updateAllTexturesSources(); + int textureIndex(const ModelNode &texture) const; ModelNode textureAt(int idx) const; + ModelNode selectedTexture() const; + + bool hasSingleModelSelection() const; + void setHasSingleModelSelection(bool b); + + bool hasSceneEnv() const; + void setHasSceneEnv(bool b); + + bool isEmpty() const { return m_isEmpty; } void resetModel(); Q_INVOKABLE void selectTexture(int idx, bool force = false); + Q_INVOKABLE void addNewTexture(); Q_INVOKABLE void duplicateTexture(int idx); Q_INVOKABLE void deleteTexture(int idx); + Q_INVOKABLE void applyToSelectedMaterial(qint64 internalId); + Q_INVOKABLE void applyToSelectedModel(qint64 internalId); + Q_INVOKABLE void openTextureEditor(); + Q_INVOKABLE void updateSceneEnvState(); + Q_INVOKABLE void updateModelSelectionState(); + Q_INVOKABLE void applyAsLightProbe(qint64 internalId); signals: void isEmptyChanged(); - void materialSectionsChanged(); + void hasSingleModelSelectionChanged(); void selectedIndexChanged(int idx); - void duplicateTextureTriggered(const QmlDesigner::ModelNode &material); + void duplicateTextureTriggered(const QmlDesigner::ModelNode &texture); + void applyToSelectedMaterialTriggered(const QmlDesigner::ModelNode &texture); + void applyToSelectedModelTriggered(const QmlDesigner::ModelNode &texture); + void addNewTextureTriggered(); + void updateSceneEnvStateRequested(); + void updateModelSelectionStateRequested(); + void hasSceneEnvChanged(); + void applyAsLightProbeRequested(const QmlDesigner::ModelNode &texture); private: bool isTextureVisible(int idx) const; @@ -53,11 +81,20 @@ private: QString m_searchText; QList m_textureList; - ModelNode m_copiedMaterial; QHash m_textureIndexHash; // internalId -> index int m_selectedIndex = 0; bool m_isEmpty = true; + bool m_hasSingleModelSelection = false; + bool m_hasSceneEnv = false; + + enum { + RoleTexHasDynamicProps = Qt::UserRole + 1, + RoleTexInternalId, + RoleTexSource, + RoleTexToolTip, + RoleTexVisible + }; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 0f05ed0b39b..1f4df699481 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -4,30 +4,48 @@ #include "materialbrowserview.h" #include "bindingproperty.h" -#include "materialbrowserwidget.h" +#include "createtexture.h" #include "materialbrowsermodel.h" #include "materialbrowsertexturesmodel.h" +#include "materialbrowserwidget.h" #include "nodeabstractproperty.h" #include "nodemetainfo.h" #include "qmlobjectnode.h" #include "variantproperty.h" -#include #include #include -#include #include #include -#include +#include +#include + +#include +#include + +#include +#include #include +#include #include #include namespace QmlDesigner { -MaterialBrowserView::MaterialBrowserView(ExternalDependenciesInterface &externalDependencies) +static QString propertyEditorResourcesPath() +{ +#ifdef SHARE_QML_PATH + if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) + return QLatin1String(SHARE_QML_PATH) + "/propertyEditorQmlSources"; +#endif + return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toString(); +} + +MaterialBrowserView::MaterialBrowserView(AsynchronousImageCache &imageCache, + ExternalDependenciesInterface &externalDependencies) : AbstractView{externalDependencies} + , m_imageCache(imageCache) { m_previewTimer.setSingleShot(true); connect(&m_previewTimer, &QTimer::timeout, this, &MaterialBrowserView::requestPreviews); @@ -44,14 +62,13 @@ bool MaterialBrowserView::hasWidget() const WidgetInfo MaterialBrowserView::widgetInfo() { if (m_widget.isNull()) { - m_widget = new MaterialBrowserWidget(this); + m_widget = new MaterialBrowserWidget(m_imageCache, this); auto matEditorContext = new Internal::MaterialBrowserContext(m_widget.data()); Core::ICore::addContextObject(matEditorContext); - MaterialBrowserModel *matBrowserModel = m_widget->materialBrowserModel().data(); - // custom notifications below are sent to the MaterialEditor + MaterialBrowserModel *matBrowserModel = m_widget->materialBrowserModel().data(); connect(matBrowserModel, &MaterialBrowserModel::selectedIndexChanged, this, [&] (int idx) { ModelNode matNode = m_widget->materialBrowserModel()->materialAt(idx); @@ -139,6 +156,56 @@ WidgetInfo MaterialBrowserView::widgetInfo() } }); }); + + // custom notifications below are sent to the TextureEditor + MaterialBrowserTexturesModel *texturesModel = m_widget->materialBrowserTexturesModel().data(); + connect(texturesModel, &MaterialBrowserTexturesModel::selectedIndexChanged, this, [&] (int idx) { + ModelNode texNode = m_widget->materialBrowserTexturesModel()->textureAt(idx); + emitCustomNotification("selected_texture_changed", {texNode}, {}); + }); + connect(texturesModel, &MaterialBrowserTexturesModel::duplicateTextureTriggered, this, + [&] (const ModelNode &texture) { + emitCustomNotification("duplicate_texture", {texture}); + }); + + connect(texturesModel, &MaterialBrowserTexturesModel::applyToSelectedMaterialTriggered, this, + [&] (const ModelNode &texture) { + if (!m_widget) + return; + const ModelNode material = m_widget->materialBrowserModel()->selectedMaterial(); + applyTextureToMaterial({material}, texture); + }); + + connect(texturesModel, &MaterialBrowserTexturesModel::applyToSelectedModelTriggered, this, + [&] (const ModelNode &texture) { + if (m_selectedModels.size() != 1) + return; + applyTextureToModel3D(m_selectedModels[0], texture); + }); + + connect(texturesModel, &MaterialBrowserTexturesModel::addNewTextureTriggered, this, [&] { + emitCustomNotification("add_new_texture"); + }); + + connect(texturesModel, &MaterialBrowserTexturesModel::updateSceneEnvStateRequested, this, [&]() { + ModelNode activeSceneEnv = CreateTexture(this).resolveSceneEnv(m_sceneId); + const bool sceneEnvExists = activeSceneEnv.isValid(); + m_widget->materialBrowserTexturesModel()->setHasSceneEnv(sceneEnvExists); + }); + + connect(texturesModel, &MaterialBrowserTexturesModel::updateModelSelectionStateRequested, this, [&]() { + bool hasModel = false; + if (m_selectedModels.size() == 1) + hasModel = getMaterialOfModel(m_selectedModels.at(0)).isValid(); + m_widget->materialBrowserTexturesModel()->setHasSingleModelSelection(hasModel); + }); + + connect(texturesModel, &MaterialBrowserTexturesModel::applyAsLightProbeRequested, this, + [&] (const ModelNode &texture) { + executeInTransaction(__FUNCTION__, [&] { + CreateTexture(this).assignTextureAsLightProbe(texture, m_sceneId); + }); + }); } return createWidgetInfo(m_widget.data(), @@ -153,8 +220,7 @@ void MaterialBrowserView::modelAttached(Model *model) AbstractView::modelAttached(model); m_widget->clearSearchFilter(); - m_widget->materialBrowserModel()->setHasMaterialRoot( - rootModelNode().metaInfo().isQtQuick3DMaterial()); + m_widget->materialBrowserModel()->setHasMaterialLibrary(false); m_hasQuick3DImport = model->hasImport("QtQuick3D"); // Project load is already very busy and may even trigger puppet reset, so let's wait a moment @@ -163,6 +229,8 @@ void MaterialBrowserView::modelAttached(Model *model) refreshModel(true); loadPropertyGroups(); // Needs the delay because it uses metaInfo }); + + m_sceneId = model->active3DSceneId(); } void MaterialBrowserView::refreshModel(bool updateImages) @@ -184,9 +252,9 @@ void MaterialBrowserView::refreshModel(bool updateImages) } } - m_widget->clearSearchFilter(); m_widget->materialBrowserModel()->setMaterials(materials, m_hasQuick3DImport); m_widget->materialBrowserTexturesModel()->setTextures(textures); + m_widget->materialBrowserModel()->setHasMaterialLibrary(matLib.isValid()); if (updateImages) { for (const ModelNode &node : std::as_const(materials)) @@ -212,6 +280,7 @@ bool MaterialBrowserView::isTexture(const ModelNode &node) const void MaterialBrowserView::modelAboutToBeDetached(Model *model) { m_widget->materialBrowserModel()->setMaterials({}, m_hasQuick3DImport); + m_widget->materialBrowserModel()->setHasMaterialLibrary(false); m_widget->clearPreviewCache(); if (m_propertyGroupsLoaded) { @@ -238,13 +307,8 @@ void MaterialBrowserView::selectedNodesChanged(const QList &selectedN if (selectedNodeList.size() > 1 || m_selectedModels.isEmpty()) return; - QmlObjectNode qmlObjNode(m_selectedModels.at(0)); - QString matExp = qmlObjNode.expression("materials"); - if (matExp.isEmpty()) - return; + ModelNode mat = getMaterialOfModel(m_selectedModels.at(0)); - QString matId = matExp.remove('[').remove(']').split(',', Qt::SkipEmptyParts).at(0); - ModelNode mat = modelNodeForId(matId); if (!mat.isValid()) return; @@ -259,14 +323,35 @@ void MaterialBrowserView::modelNodePreviewPixmapChanged(const ModelNode &node, c m_widget->updateMaterialPreview(node, pixmap); } +void MaterialBrowserView::nodeIdChanged(const ModelNode &node, [[maybe_unused]] const QString &newId, + [[maybe_unused]] const QString &oldId) +{ + if (isTexture(node)) + m_widget->materialBrowserTexturesModel()->updateTextureSource(node); +} + void MaterialBrowserView::variantPropertiesChanged(const QList &propertyList, [[maybe_unused]] PropertyChangeFlags propertyChange) { for (const VariantProperty &property : propertyList) { ModelNode node(property.parentModelNode()); - - if (isMaterial(node) && property.name() == "objectName") + if (isMaterial(node) && property.name() == "objectName") { m_widget->materialBrowserModel()->updateMaterialName(node); + } else if (property.name() == "source") { + QmlObjectNode selectedTex = m_widget->materialBrowserTexturesModel()->selectedTexture(); + if (isTexture(node)) + m_widget->materialBrowserTexturesModel()->updateTextureSource(node); + else if (selectedTex.propertyChangeForCurrentState() == node) + m_widget->materialBrowserTexturesModel()->updateTextureSource(selectedTex); + } + } +} + +void MaterialBrowserView::propertiesRemoved(const QList &propertyList) +{ + for (const AbstractProperty &prop : propertyList) { + if (isTexture(prop.parentModelNode()) && prop.name() == "source") + m_widget->materialBrowserTexturesModel()->updateTextureSource(prop.parentModelNode()); } } @@ -277,42 +362,56 @@ void MaterialBrowserView::nodeReparented(const ModelNode &node, { Q_UNUSED(propertyChange) + if (node.id() == Constants::MATERIAL_LIB_ID) + m_widget->materialBrowserModel()->setHasMaterialLibrary(true); + if (!isMaterial(node) && !isTexture(node)) return; ModelNode newParentNode = newPropertyParent.parentModelNode(); ModelNode oldParentNode = oldPropertyParent.parentModelNode(); - bool matAdded = newParentNode.id() == Constants::MATERIAL_LIB_ID; - bool matRemoved = oldParentNode.id() == Constants::MATERIAL_LIB_ID; + bool added = newParentNode.id() == Constants::MATERIAL_LIB_ID; + bool removed = oldParentNode.id() == Constants::MATERIAL_LIB_ID; - if (matAdded || matRemoved) { - if (matAdded && !m_puppetResetPending) { + if (!added && !removed) + return; + + refreshModel(removed); + + if (isMaterial(node)) { + if (added && !m_puppetResetPending) { // Workaround to fix various material issues all likely caused by QTBUG-103316 resetPuppet(); m_puppetResetPending = true; } - refreshModel(!matAdded); int idx = m_widget->materialBrowserModel()->materialIndex(node); m_widget->materialBrowserModel()->selectMaterial(idx); + m_widget->materialBrowserModel()->refreshSearch(); + } else { // is texture + int idx = m_widget->materialBrowserTexturesModel()->textureIndex(node); + m_widget->materialBrowserTexturesModel()->selectTexture(idx); + m_widget->materialBrowserTexturesModel()->refreshSearch(); } } void MaterialBrowserView::nodeAboutToBeRemoved(const ModelNode &removedNode) { - // removing the material editor node + // removing the material lib node if (removedNode.id() == Constants::MATERIAL_LIB_ID) { m_widget->materialBrowserModel()->setMaterials({}, m_hasQuick3DImport); + m_widget->materialBrowserModel()->setHasMaterialLibrary(false); m_widget->clearPreviewCache(); return; } - // not a material under the material editor - if (!isMaterial(removedNode) - || removedNode.parentProperty().parentModelNode().id() != Constants::MATERIAL_LIB_ID) { + // not under the material lib + if (removedNode.parentProperty().parentModelNode().id() != Constants::MATERIAL_LIB_ID) return; - } - m_widget->materialBrowserModel()->removeMaterial(removedNode); + if (isMaterial(removedNode)) + m_widget->materialBrowserModel()->removeMaterial(removedNode); + else if (isTexture(removedNode)) + m_widget->materialBrowserTexturesModel()->removeTexture(removedNode); } void MaterialBrowserView::nodeRemoved([[maybe_unused]] const ModelNode &removedNode, @@ -323,6 +422,7 @@ void MaterialBrowserView::nodeRemoved([[maybe_unused]] const ModelNode &removedN return; m_widget->materialBrowserModel()->updateSelectedMaterial(); + m_widget->materialBrowserTexturesModel()->updateSelectedTexture(); } void QmlDesigner::MaterialBrowserView::loadPropertyGroups() @@ -344,6 +444,25 @@ void MaterialBrowserView::requestPreviews() m_previewRequests.clear(); } +ModelNode MaterialBrowserView::getMaterialOfModel(const ModelNode &model) +{ + QmlObjectNode qmlObjNode(model); + QString matExp = qmlObjNode.expression("materials"); + if (matExp.isEmpty()) + return {}; + + const QStringList mats = matExp.remove('[').remove(']').split(',', Qt::SkipEmptyParts); + if (mats.isEmpty()) + return {}; + + for (const auto &matId : mats) { + ModelNode mat = modelNodeForId(matId); + if (mat.isValid()) + return mat; + } + return {}; +} + void MaterialBrowserView::importsChanged([[maybe_unused]] const QList &addedImports, [[maybe_unused]] const QList &removedImports) { @@ -372,15 +491,35 @@ void MaterialBrowserView::customNotification(const AbstractView *view, int idx = m_widget->materialBrowserModel()->materialIndex(nodeList.first()); if (idx != -1) m_widget->materialBrowserModel()->selectMaterial(idx); + } else if (identifier == "selected_texture_changed") { + int idx = m_widget->materialBrowserTexturesModel()->textureIndex(nodeList.first()); + if (idx != -1) { + m_widget->materialBrowserTexturesModel()->selectTexture(idx); + m_widget->materialBrowserTexturesModel()->refreshSearch(); + } } else if (identifier == "refresh_material_browser") { QTimer::singleShot(0, model(), [this]() { refreshModel(true); }); } else if (identifier == "delete_selected_material") { - m_widget->materialBrowserModel()->deleteSelectedMaterial(); + m_widget->deleteSelectedItem(); + } else if (identifier == "apply_texture_to_model3D") { + applyTextureToModel3D(nodeList.at(0), nodeList.at(1)); + } else if (identifier == "apply_texture_to_material") { + applyTextureToMaterial({nodeList.at(0)}, nodeList.at(1)); } } +void MaterialBrowserView::active3DSceneChanged(qint32 sceneId) +{ + m_sceneId = sceneId; +} + +void MaterialBrowserView::currentStateChanged([[maybe_unused]] const ModelNode &node) +{ + m_widget->materialBrowserTexturesModel()->updateAllTexturesSources(); +} + void MaterialBrowserView::instancesCompleted(const QVector &completedNodeList) { for (const ModelNode &node : completedNodeList) { @@ -415,4 +554,104 @@ void MaterialBrowserView::instancePropertyChanged(const QList materials; + if (hasId(matsProp.expression())) + materials.append(modelNodeForId(matsProp.expression())); + else + materials = matsProp.resolveToModelNodeList(); + + applyTextureToMaterial(materials, texture); +} + +void MaterialBrowserView::applyTextureToMaterial(const QList &materials, + const ModelNode &texture) +{ + if (materials.size() > 0) { + m_appliedTextureId = texture.id(); + m_textureModels.clear(); + QStringList materialsModel; + for (const ModelNode &mat : std::as_const(materials)) { + QString matName = mat.variantProperty("objectName").value().toString(); + materialsModel.append(QLatin1String("%1 (%2)").arg(matName, mat.id())); + QList texProps; + for (const PropertyMetaInfo &p : mat.metaInfo().properties()) { + if (p.propertyType().isQtQuick3DTexture()) + texProps.append(p.name()); + } + m_textureModels.insert(mat.id(), texProps); + } + + QString path = MaterialBrowserWidget::qmlSourcesPath() + "/ChooseMaterialProperty.qml"; + + m_chooseMatPropsView = new QQuickView; + m_chooseMatPropsView->setTitle(tr("Select a material property")); + m_chooseMatPropsView->setResizeMode(QQuickView::SizeRootObjectToView); + m_chooseMatPropsView->setMinimumSize({150, 100}); + m_chooseMatPropsView->setMaximumSize({600, 400}); + m_chooseMatPropsView->setWidth(450); + m_chooseMatPropsView->setHeight(300); + m_chooseMatPropsView->setFlags(Qt::Widget); + m_chooseMatPropsView->setModality(Qt::ApplicationModal); + m_chooseMatPropsView->engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); + m_chooseMatPropsView->rootContext()->setContextProperties({ + {"rootView", QVariant::fromValue(this)}, + {"materialsModel", QVariant::fromValue(materialsModel)}, + {"propertiesModel", QVariant::fromValue(m_textureModels.value(materials.at(0).id()))}, + }); + m_chooseMatPropsView->setSource(QUrl::fromLocalFile(path)); + m_chooseMatPropsView->installEventFilter(this); + m_chooseMatPropsView->show(); + } +} + +void MaterialBrowserView::updatePropsModel(const QString &matId) +{ + m_chooseMatPropsView->rootContext()->setContextProperty("propertiesModel", + QVariant::fromValue(m_textureModels.value(matId))); +} + +void MaterialBrowserView::applyTextureToProperty(const QString &matId, const QString &propName) +{ + QTC_ASSERT(!m_appliedTextureId.isEmpty(), return); + + QmlObjectNode mat = modelNodeForId(matId); + QTC_ASSERT(mat.isValid(), return); + + BindingProperty texProp = mat.bindingProperty(propName.toLatin1()); + QTC_ASSERT(texProp.isValid(), return); + + mat.setBindingProperty(propName.toLatin1(), m_appliedTextureId); + + closeChooseMatPropsView(); +} + +void MaterialBrowserView::closeChooseMatPropsView() +{ + m_chooseMatPropsView->close(); +} + +bool MaterialBrowserView::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Escape) { + if (obj == m_chooseMatPropsView) + m_chooseMatPropsView->close(); + } + } else if (event->type() == QEvent::Close) { + if (obj == m_chooseMatPropsView) { + m_appliedTextureId.clear(); + m_chooseMatPropsView->deleteLater(); + } + } + + return AbstractView::eventFilter(obj, event); +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h index f59cb2dbbcd..0d2ec42af0d 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h @@ -4,11 +4,16 @@ #pragma once #include "abstractview.h" +#include "createtexture.h" #include #include #include +QT_BEGIN_NAMESPACE +class QQuickView; +QT_END_NAMESPACE + namespace QmlDesigner { class MaterialBrowserWidget; @@ -18,7 +23,8 @@ class MaterialBrowserView : public AbstractView Q_OBJECT public: - MaterialBrowserView(ExternalDependenciesInterface &externalDependencies); + MaterialBrowserView(class AsynchronousImageCache &imageCache, + ExternalDependenciesInterface &externalDependencies); ~MaterialBrowserView() override; bool hasWidget() const override; @@ -30,7 +36,9 @@ public: void selectedNodesChanged(const QList &selectedNodeList, const QList &lastSelectedNodeList) override; void modelNodePreviewPixmapChanged(const ModelNode &node, const QPixmap &pixmap) override; + void nodeIdChanged(const ModelNode &node, const QString &newId, const QString &oldId) override; void variantPropertiesChanged(const QList &propertyList, PropertyChangeFlags propertyChange) override; + void propertiesRemoved(const QList &propertyList) override; void nodeReparented(const ModelNode &node, const NodeAbstractProperty &newPropertyParent, const NodeAbstractProperty &oldPropertyParent, AbstractView::PropertyChangeFlags propertyChange) override; @@ -42,6 +50,19 @@ public: const QList &nodeList, const QList &data) override; void instancesCompleted(const QVector &completedNodeList) override; void instancePropertyChanged(const QList > &propertyList) override; + void active3DSceneChanged(qint32 sceneId) override; + void currentStateChanged(const ModelNode &node) override; + + void applyTextureToModel3D(const QmlObjectNode &model3D, const ModelNode &texture); + void applyTextureToMaterial(const QList &materials, const ModelNode &texture); + + + Q_INVOKABLE void updatePropsModel(const QString &matId); + Q_INVOKABLE void applyTextureToProperty(const QString &matId, const QString &propName); + Q_INVOKABLE void closeChooseMatPropsView(); + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; private: void refreshModel(bool updateImages); @@ -49,7 +70,10 @@ private: bool isTexture(const ModelNode &node) const; void loadPropertyGroups(); void requestPreviews(); + ModelNode resolveSceneEnv(); + ModelNode getMaterialOfModel(const ModelNode &model); + AsynchronousImageCache &m_imageCache; QPointer m_widget; QList m_selectedModels; // selected 3D model nodes @@ -60,6 +84,10 @@ private: QTimer m_previewTimer; QSet m_previewRequests; + QPointer m_chooseMatPropsView; + QHash> m_textureModels; + QString m_appliedTextureId; + int m_sceneId = -1; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp index d9a3539d249..64d285f9222 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -18,6 +19,7 @@ #include #include +#include #include #include @@ -103,24 +105,30 @@ bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event) QMouseEvent *me = static_cast(event); if ((me->globalPos() - m_dragStartPoint).manhattanLength() > 20) { bool isMaterial = m_materialToDrag.isValid(); - QByteArray data; QMimeData *mimeData = new QMimeData; - QDataStream stream(&data, QIODevice::WriteOnly); - stream << m_materialToDrag.internalId(); - mimeData->setData(isMaterial ? QString::fromLatin1(Constants::MIME_TYPE_MATERIAL) - : QString::fromLatin1(Constants::MIME_TYPE_TEXTURE), - data); - mimeData->removeFormat("text/plain"); + QByteArray internalId; if (isMaterial) { + internalId.setNum(m_materialToDrag.internalId()); + mimeData->setData(Constants::MIME_TYPE_MATERIAL, internalId); model->startDrag(mimeData, m_previewImageProvider->requestPixmap( QString::number(m_materialToDrag.internalId()), nullptr, {128, 128})); } else { + internalId.setNum(m_textureToDrag.internalId()); + mimeData->setData(Constants::MIME_TYPE_TEXTURE, internalId); QString iconPath = QLatin1String("%1/%2") .arg(DocumentManager::currentResourcePath().path(), m_textureToDrag.variantProperty("source").value().toString()); - model->startDrag(mimeData, QPixmap(iconPath).scaled({128, 128})); + QPixmap pixmap; + const QString suffix = iconPath.split('.').last().toLower(); + if (suffix == "hdr") + pixmap = HdrImage{iconPath}.toPixmap(); + else + pixmap = Utils::StyleHelper::dpiSpecificImageFile(iconPath); + if (pixmap.isNull()) + pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/textureeditor/images/texture_default.png"); + model->startDrag(mimeData, pixmap.scaled({128, 128})); } m_materialToDrag = {}; m_textureToDrag = {}; @@ -134,13 +142,18 @@ bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event) return QObject::eventFilter(obj, event); } -MaterialBrowserWidget::MaterialBrowserWidget(MaterialBrowserView *view) +MaterialBrowserWidget::MaterialBrowserWidget(AsynchronousImageCache &imageCache, + MaterialBrowserView *view) : m_materialBrowserView(view) , m_materialBrowserModel(new MaterialBrowserModel(this)) , m_materialBrowserTexturesModel(new MaterialBrowserTexturesModel(this)) , m_quickWidget(new QQuickWidget(this)) , m_previewImageProvider(new PreviewImageProvider()) { + QImage defaultImage; + defaultImage.load(Utils::StyleHelper::dpiSpecificImageFile(":/textureeditor/images/texture_default.png")); + m_textureImageProvider = new PropertyEditorImageProvider(imageCache, defaultImage); + setWindowTitle(tr("Material Browser", "Title of material browser widget")); setMinimumWidth(120); @@ -160,6 +173,8 @@ MaterialBrowserWidget::MaterialBrowserWidget(MaterialBrowserView *view) }); m_quickWidget->engine()->addImageProvider("materialBrowser", m_previewImageProvider); + m_quickWidget->engine()->addImageProvider("materialBrowserTex", m_textureImageProvider); + Theme::setupTheme(m_quickWidget->engine()); m_quickWidget->installEventFilter(this); @@ -176,6 +191,16 @@ MaterialBrowserWidget::MaterialBrowserWidget(MaterialBrowserView *view) m_qmlSourceUpdateShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F8), this); connect(m_qmlSourceUpdateShortcut, &QShortcut::activated, this, &MaterialBrowserWidget::reloadQmlSource); + connect(m_materialBrowserModel, &MaterialBrowserModel::isEmptyChanged, this, [&] { + if (m_materialBrowserModel->isEmpty()) + focusMaterialSection(false); + }); + + connect(m_materialBrowserTexturesModel, &MaterialBrowserTexturesModel::isEmptyChanged, this, [&] { + if (m_materialBrowserTexturesModel->isEmpty()) + focusMaterialSection(true); + }); + QmlDesignerPlugin::trackWidgetFocusTime(this, Constants::EVENT_MATERIALBROWSER_TIME); reloadQmlSource(); @@ -189,6 +214,14 @@ void MaterialBrowserWidget::updateMaterialPreview(const ModelNode &node, const Q QMetaObject::invokeMethod(m_quickWidget->rootObject(), "refreshPreview", Q_ARG(QVariant, idx)); } +void MaterialBrowserWidget::deleteSelectedItem() +{ + if (m_materialSectionFocused) + m_materialBrowserModel->deleteSelectedMaterial(); + else + m_materialBrowserTexturesModel->deleteSelectedTexture(); +} + QList MaterialBrowserWidget::createToolBarWidgets() { return {}; @@ -227,6 +260,30 @@ void MaterialBrowserWidget::acceptBundleMaterialDrop() m_materialBrowserView->emitCustomNotification("drop_bundle_material", {}, {}); // To ContentLibraryView } +void MaterialBrowserWidget::acceptBundleTextureDrop() +{ + m_materialBrowserView->emitCustomNotification("drop_bundle_texture", {}, {}); // To ContentLibraryView +} + +void MaterialBrowserWidget::acceptTextureDropOnMaterial(int matIndex, const QString &texId) +{ + ModelNode mat = m_materialBrowserModel->materialAt(matIndex); + ModelNode tex = m_materialBrowserView->modelNodeForInternalId(texId.toInt()); + + if (mat.isValid() && tex.isValid()) { + m_materialBrowserModel->selectMaterial(matIndex); + m_materialBrowserView->applyTextureToMaterial({mat}, tex); + } +} + +void MaterialBrowserWidget::focusMaterialSection(bool focusMatSec) +{ + if (focusMatSec != m_materialSectionFocused) { + m_materialSectionFocused = focusMatSec; + emit materialSectionFocusedChanged(); + } +} + QString MaterialBrowserWidget::qmlSourcesPath() { #ifdef SHARE_QML_PATH @@ -254,6 +311,7 @@ void MaterialBrowserWidget::reloadQmlSource() void MaterialBrowserWidget::updateSearch() { m_materialBrowserModel->setSearchText(m_filterText); + m_materialBrowserTexturesModel->setSearchText(m_filterText); m_quickWidget->update(); } diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h index d1c0ef7c8a6..47930eee0ae 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h @@ -28,13 +28,16 @@ class MaterialBrowserView; class MaterialBrowserModel; class MaterialBrowserTexturesModel; class PreviewImageProvider; +class PropertyEditorImageProvider; class MaterialBrowserWidget : public QFrame { Q_OBJECT + Q_PROPERTY(bool materialSectionFocused MEMBER m_materialSectionFocused NOTIFY materialSectionFocusedChanged) + public: - MaterialBrowserWidget(MaterialBrowserView *view); + MaterialBrowserWidget(class AsynchronousImageCache &imageCache, MaterialBrowserView *view); ~MaterialBrowserWidget() = default; QList createToolBarWidgets(); @@ -46,16 +49,23 @@ public: QPointer materialBrowserModel() const; QPointer materialBrowserTexturesModel() const; void updateMaterialPreview(const ModelNode &node, const QPixmap &pixmap); + void deleteSelectedItem(); Q_INVOKABLE void handleSearchFilterChanged(const QString &filterText); Q_INVOKABLE void startDragMaterial(int index, const QPointF &mousePos); Q_INVOKABLE void startDragTexture(int index, const QPointF &mousePos); Q_INVOKABLE void acceptBundleMaterialDrop(); + Q_INVOKABLE void acceptBundleTextureDrop(); + Q_INVOKABLE void acceptTextureDropOnMaterial(int matIndex, const QString &texId); + Q_INVOKABLE void focusMaterialSection(bool focusMatSec); QQuickWidget *quickWidget() const; void clearPreviewCache(); +signals: + void materialSectionFocusedChanged(); + protected: bool eventFilter(QObject *obj, QEvent *event) override; @@ -70,6 +80,7 @@ private: QShortcut *m_qmlSourceUpdateShortcut = nullptr; PreviewImageProvider *m_previewImageProvider = nullptr; + PropertyEditorImageProvider *m_textureImageProvider = nullptr; Core::IContext *m_context = nullptr; QString m_filterText; @@ -77,6 +88,8 @@ private: ModelNode m_materialToDrag; ModelNode m_textureToDrag; QPoint m_dragStartPoint; + + bool m_materialSectionFocused = true; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp index 7566bd60bcc..3f788e7f3fd 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp @@ -30,7 +30,7 @@ MaterialEditorContextObject::MaterialEditorContextObject(QQmlContext *context, Q : QObject(parent) , m_qmlContext(context) { - qmlRegisterUncreatableType("ToolBarAction", 1, 0, "ToolBarAction", "Enum type"); + qmlRegisterUncreatableType("MaterialToolBarAction", 1, 0, "ToolBarAction", "Enum type"); } QQmlComponent *MaterialEditorContextObject::specificQmlComponent() @@ -227,18 +227,18 @@ void MaterialEditorContextObject::setHasQuick3DImport(bool b) emit hasQuick3DImportChanged(); } -bool MaterialEditorContextObject::hasMaterialRoot() const +bool MaterialEditorContextObject::hasMaterialLibrary() const { - return m_hasMaterialRoot; + return m_hasMaterialLibrary; } -void MaterialEditorContextObject::setHasMaterialRoot(bool b) +void MaterialEditorContextObject::setHasMaterialLibrary(bool b) { - if (b == m_hasMaterialRoot) + if (b == m_hasMaterialLibrary) return; - m_hasMaterialRoot = b; - emit hasMaterialRootChanged(); + m_hasMaterialLibrary = b; + emit hasMaterialLibraryChanged(); } bool MaterialEditorContextObject::hasModelSelection() const diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.h b/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.h index af155511338..39aeff83326 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.h +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.h @@ -38,7 +38,7 @@ class MaterialEditorContextObject : public QObject Q_PROPERTY(bool hasActiveTimeline READ hasActiveTimeline NOTIFY hasActiveTimelineChanged) Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged) Q_PROPERTY(bool hasModelSelection READ hasModelSelection WRITE setHasModelSelection NOTIFY hasModelSelectionChanged) - Q_PROPERTY(bool hasMaterialRoot READ hasMaterialRoot WRITE setHasMaterialRoot NOTIFY hasMaterialRootChanged) + Q_PROPERTY(bool hasMaterialLibrary READ hasMaterialLibrary WRITE setHasMaterialLibrary NOTIFY hasMaterialLibraryChanged) Q_PROPERTY(QQmlPropertyMap *backendValues READ backendValues WRITE setBackendValues NOTIFY backendValuesChanged) @@ -93,8 +93,8 @@ public: bool hasQuick3DImport() const; void setHasQuick3DImport(bool b); - bool hasMaterialRoot() const; - void setHasMaterialRoot(bool b); + bool hasMaterialLibrary() const; + void setHasMaterialLibrary(bool b); bool hasModelSelection() const; void setHasModelSelection(bool b); @@ -132,7 +132,7 @@ signals: void hasAliasExportChanged(); void hasActiveTimelineChanged(); void hasQuick3DImportChanged(); - void hasMaterialRootChanged(); + void hasMaterialLibraryChanged(); void hasModelSelectionChanged(); private: @@ -161,7 +161,7 @@ private: bool m_aliasExport = false; bool m_hasActiveTimeline = false; bool m_hasQuick3DImport = false; - bool m_hasMaterialRoot = false; + bool m_hasMaterialLibrary = false; bool m_hasModelSelection = false; ModelNode m_selectedMaterial; diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp index b1594f80f93..8e2c5fe9fc0 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp @@ -61,6 +61,8 @@ MaterialEditorView::MaterialEditorView(ExternalDependenciesInterface &externalDe if (model() && model()->rewriterView() && !model()->rewriterView()->hasIncompleteTypeInformation() && model()->rewriterView()->errors().isEmpty()) { ensureMaterialLibraryNode(); + if (m_qmlBackEnd && m_qmlBackEnd->contextObject()) + m_qmlBackEnd->contextObject()->setHasMaterialLibrary(materialLibraryNode().isValid()); m_ensureMatLibTimer.stop(); } }); @@ -534,7 +536,7 @@ void MaterialEditorView::setupQmlBackend() QString specificQmlData; QString currentTypeName; - if (m_selectedMaterial.isValid() && m_hasQuick3DImport) { + if (m_selectedMaterial.isValid() && m_hasQuick3DImport && (materialLibraryNode().isValid() || m_hasMaterialRoot)) { qmlPaneUrl = QUrl::fromLocalFile(materialEditorResourcesPath() + "/MaterialEditorPane.qml"); TypeName diffClassName; @@ -587,7 +589,7 @@ void MaterialEditorView::setupQmlBackend() currentQmlBackend->widget()->installEventFilter(this); currentQmlBackend->contextObject()->setHasQuick3DImport(m_hasQuick3DImport); - currentQmlBackend->contextObject()->setHasMaterialRoot(m_hasMaterialRoot); + currentQmlBackend->contextObject()->setHasMaterialLibrary(materialLibraryNode().isValid()); currentQmlBackend->contextObject()->setSpecificQmlData(specificQmlData); currentQmlBackend->contextObject()->setCurrentType(currentTypeName); @@ -781,6 +783,7 @@ void MaterialEditorView::modelAboutToBeDetached(Model *model) AbstractView::modelAboutToBeDetached(model); m_dynamicPropertiesModel->reset(); m_qmlBackEnd->materialEditorTransaction()->end(); + m_qmlBackEnd->contextObject()->setHasMaterialLibrary(false); } void MaterialEditorView::propertiesRemoved(const QList &propertyList) @@ -825,6 +828,12 @@ void MaterialEditorView::variantPropertiesChanged(const QList & changed = true; } + if (!changed && node.metaInfo().isQtQuick3DTexture() + && m_selectedMaterial.bindingProperties().size() > 0) { + // update preview when editing texture properties if the material has binding properties + changed = true; + } + dynamicPropertiesModel()->dispatchPropertyChanges(property); } if (changed) @@ -1097,6 +1106,21 @@ void MaterialEditorView::customNotification([[maybe_unused]] const AbstractView } } +void MaterialEditorView::nodeReparented(const ModelNode &node, + [[maybe_unused]] const NodeAbstractProperty &newPropertyParent, + [[maybe_unused]] const NodeAbstractProperty &oldPropertyParent, + [[maybe_unused]] PropertyChangeFlags propertyChange) +{ + if (node.id() == Constants::MATERIAL_LIB_ID && m_qmlBackEnd && m_qmlBackEnd->contextObject()) + m_qmlBackEnd->contextObject()->setHasMaterialLibrary(true); +} + +void MaterialEditorView::nodeAboutToBeRemoved(const ModelNode &removedNode) +{ + if (removedNode.id() == Constants::MATERIAL_LIB_ID && m_qmlBackEnd && m_qmlBackEnd->contextObject()) + m_qmlBackEnd->contextObject()->setHasMaterialLibrary(false); +} + void QmlDesigner::MaterialEditorView::highlightSupportedProperties(bool highlight) { if (!m_selectedMaterial.isValid()) @@ -1118,16 +1142,17 @@ void QmlDesigner::MaterialEditorView::highlightSupportedProperties(bool highligh void MaterialEditorView::dragStarted(QMimeData *mimeData) { - if (!mimeData->hasFormat(Constants::MIME_TYPE_ASSETS)) - return; + if (mimeData->hasFormat(Constants::MIME_TYPE_ASSETS)) { + const QString assetPath = QString::fromUtf8(mimeData->data(Constants::MIME_TYPE_ASSETS)).split(',')[0]; + QString assetType = AssetsLibraryWidget::getAssetTypeAndData(assetPath).first; - const QString assetPath = QString::fromUtf8(mimeData->data(Constants::MIME_TYPE_ASSETS)).split(',')[0]; - QString assetType = AssetsLibraryWidget::getAssetTypeAndData(assetPath).first; + if (assetType != Constants::MIME_TYPE_ASSET_IMAGE) // currently only image assets have dnd-supported properties + return; - if (assetType != Constants::MIME_TYPE_ASSET_IMAGE) // currently only image assets have dnd-supported properties - return; - - highlightSupportedProperties(); + highlightSupportedProperties(); + } else if (mimeData->hasFormat(Constants::MIME_TYPE_TEXTURE)) { + highlightSupportedProperties(); + } } void MaterialEditorView::dragEnded() @@ -1149,7 +1174,7 @@ bool MaterialEditorView::eventFilter(QObject *obj, QEvent *event) if (m_qmlBackEnd && m_qmlBackEnd->widget() == obj) QMetaObject::invokeMethod(m_qmlBackEnd->widget()->rootObject(), "closeContextMenu"); } - return QObject::eventFilter(obj, event); + return AbstractView::eventFilter(obj, event); } void MaterialEditorView::reloadQml() diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h index 5c0a59d3a0e..c0880c738bc 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h @@ -62,6 +62,10 @@ public: void importsChanged(const QList &addedImports, const QList &removedImports) override; void customNotification(const AbstractView *view, const QString &identifier, const QList &nodeList, const QList &data) override; + void nodeReparented(const ModelNode &node, const NodeAbstractProperty &newPropertyParent, + const NodeAbstractProperty &oldPropertyParent, + AbstractView::PropertyChangeFlags propertyChange) override; + void nodeAboutToBeRemoved(const ModelNode &removedNode) override; void dragStarted(QMimeData *mimeData) override; void dragEnded() override; diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp index a407cf129b3..66b6f640faf 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp @@ -146,16 +146,25 @@ static bool isInLayoutable(NodeAbstractProperty &parentProperty) static void reparentModelNodeToNodeProperty(NodeAbstractProperty &parentProperty, const ModelNode &modelNode) { try { + if (parentProperty.parentModelNode().type().startsWith("Effects.")) + return; + if (!modelNode.hasParentProperty() || parentProperty != modelNode.parentProperty()) { if (isInLayoutable(parentProperty)) { removePosition(modelNode); parentProperty.reparentHere(modelNode); } else { if (QmlItemNode::isValidQmlItemNode(modelNode)) { - QPointF scenePosition = QmlItemNode(modelNode).instanceScenePosition(); - parentProperty.reparentHere(modelNode); - if (!scenePosition.isNull()) - setScenePosition(modelNode, scenePosition); + if (modelNode.hasParentProperty() && modelNode.parentProperty().name() == "layer.effect") { + parentProperty = parentProperty.parentModelNode().nodeAbstractProperty("layer.effect"); + QmlItemNode::placeEffectNode(parentProperty, modelNode, true); + } else { + QPointF scenePosition = QmlItemNode(modelNode).instanceScenePosition(); + parentProperty.reparentHere(modelNode); + if (!scenePosition.isNull()) + setScenePosition(modelNode, scenePosition); + } + } else { parentProperty.reparentHere(modelNode); } @@ -597,6 +606,9 @@ bool NavigatorTreeModel::dropMimeData(const QMimeData *mimeData, } else if (assetType == Constants::MIME_TYPE_ASSET_TEXTURE3D) { currNode = handleItemLibraryTexture3dDrop(assetPath, targetProperty, rowModelIndex, moveNodesAfter); + } else if (assetType == Constants::MIME_TYPE_ASSET_EFFECT) { + currNode = handleItemLibraryEffectDrop(assetPath, rowModelIndex); + moveNodesAfter = false; } if (currNode.isValid()) @@ -1003,6 +1015,24 @@ ModelNode NavigatorTreeModel::handleItemLibraryTexture3dDrop(const QString &tex3 return newModelNode; } +ModelNode NavigatorTreeModel::handleItemLibraryEffectDrop(const QString &effectPath, const QModelIndex &rowModelIndex) +{ + QTC_ASSERT(m_view, return {}); + + ModelNode targetNode(modelNodeForIndex(rowModelIndex)); + ModelNode newModelNode; + + if (targetNode.hasParentProperty() && targetNode.parentProperty().name() == "layer.effect") + return newModelNode; + + if (ModelNodeOperations::validateEffect(effectPath)) { + bool layerEffect = ModelNodeOperations::useLayerEffect(); + newModelNode = QmlItemNode::createQmlItemNodeForEffect(m_view, targetNode, effectPath, layerEffect); + } + + return newModelNode; +} + bool NavigatorTreeModel::dropAsImage3dTexture(const ModelNode &targetNode, const NodeAbstractProperty &targetProp, const QString &imagePath, diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h index 85741c0ff00..e67bcc8849c 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h @@ -106,6 +106,7 @@ private: const QModelIndex &rowModelIndex); ModelNode handleItemLibraryTexture3dDrop(const QString &tex3DPath, NodeAbstractProperty targetProperty, const QModelIndex &rowModelIndex, bool &outMoveNodesAfter); + ModelNode handleItemLibraryEffectDrop(const QString &effectPath, const QModelIndex &rowModelIndex); bool dropAsImage3dTexture(const ModelNode &targetNode, const NodeAbstractProperty &targetProp, const QString &imagePath, ModelNode &newNode, bool &outMoveNodesAfter); ModelNode createTextureNode(const NodeAbstractProperty &targetProp, const QString &imagePath); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp index 10f08b542ae..396dac9039f 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp @@ -424,6 +424,20 @@ void PropertyEditorContextObject::setHasMultiSelection(bool b) emit hasMultiSelectionChanged(); } +void PropertyEditorContextObject::setInsightEnabled(bool value) +{ + if (value != m_insightEnabled) { + m_insightEnabled = value; + emit insightEnabledChanged(); + } +} + +void PropertyEditorContextObject::setInsightCategories(const QStringList &categories) +{ + m_insightCategories = categories; + emit insightCategoriesChanged(); +} + void PropertyEditorContextObject::setSpecificsUrl(const QUrl &newSpecificsUrl) { if (newSpecificsUrl == m_specificsUrl) @@ -581,6 +595,14 @@ bool PropertyEditorContextObject::isBlocked(const QString &propName) const return false; } +void PropertyEditorContextObject::verifyInsightImport() +{ + Import import = Import::createLibraryImport("QtInsightTracker", "1.0"); + + if (!m_model->hasImport(import)) + m_model->changeImports({import}, {}); +} + void EasingCurveEditor::registerDeclarativeType() { qmlRegisterType("HelperWidgets", 2, 0, "EasingCurveEditor"); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.h index 23a2bba47fb..402b2d9bfa2 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.h @@ -49,6 +49,9 @@ class PropertyEditorContextObject : public QObject Q_PROPERTY(bool hasMultiSelection READ hasMultiSelection WRITE setHasMultiSelection NOTIFY hasMultiSelectionChanged) + Q_PROPERTY(bool insightEnabled MEMBER m_insightEnabled NOTIFY insightEnabledChanged) + Q_PROPERTY(QStringList insightCategories MEMBER m_insightCategories NOTIFY insightCategoriesChanged) + public: PropertyEditorContextObject(QObject *parent = nullptr); @@ -87,6 +90,8 @@ public: Q_INVOKABLE bool isBlocked(const QString &propName) const; + Q_INVOKABLE void verifyInsightImport(); + QString activeDragSuffix() const; void setActiveDragSuffix(const QString &suffix); @@ -111,6 +116,9 @@ public: void setHasMultiSelection(bool); + void setInsightEnabled(bool value); + void setInsightCategories(const QStringList &categories); + signals: void specificsUrlChanged(); void specificQmlDataChanged(); @@ -129,6 +137,9 @@ signals: void activeDragSuffixChanged(); void hasMultiSelectionChanged(); + void insightEnabledChanged(); + void insightCategoriesChanged(); + public slots: void setSpecificsUrl(const QUrl &newSpecificsUrl); @@ -180,6 +191,9 @@ private: QString m_activeDragSuffix; bool m_hasMultiSelection = false; + + bool m_insightEnabled = false; + QStringList m_insightCategories; }; class EasingCurveEditor : public QObject diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorimageprovider.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorimageprovider.cpp index 90dd2caaebe..e7ff48450d1 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorimageprovider.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorimageprovider.cpp @@ -25,18 +25,25 @@ QQuickImageResponse *PropertyEditorImageProvider::requestImageResponse(const QSt return m_smallImageCacheProvider.requestImageResponse("#" + id.split('.').first(), requestedSize); - QImage image; - auto response = std::make_unique(image); + auto response = std::make_unique(m_smallImageCacheProvider.defaultImage()); QMetaObject::invokeMethod( response.get(), - [response = QPointer(response.get()), image, suffix, id] { - if (AssetsLibraryModel::supportedImageSuffixes().contains(suffix)) - response->setImage(QImage(Utils::StyleHelper::dpiSpecificImageFile(id))); - else if (AssetsLibraryModel::supportedTexture3DSuffixes().contains(suffix)) - response->setImage(HdrImage{id}.image()); - else - response->abort(); + [response = QPointer(response.get()), suffix, id, requestedSize] { + if (AssetsLibraryModel::supportedImageSuffixes().contains(suffix)) { + QImage image = QImage(Utils::StyleHelper::dpiSpecificImageFile(id)); + if (!image.isNull()) { + response->setImage(image.scaled(requestedSize, Qt::KeepAspectRatio)); + return; + } + } else if (AssetsLibraryModel::supportedTexture3DSuffixes().contains(suffix)) { + HdrImage hdr{id}; + if (!hdr.image().isNull()) { + response->setImage(hdr.image().scaled(requestedSize, Qt::KeepAspectRatio)); + return; + } + } + response->setImage(response->image().scaled(requestedSize, Qt::KeepAspectRatio)); }, Qt::QueuedConnection); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp index 3606f626a54..09af5cb0a0f 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp @@ -171,6 +171,17 @@ QVariant properDefaultLayoutAttachedProperties(const QmlObjectNode &qmlObjectNod return QVariant(); } + +QVariant properDefaultInsightAttachedProperties(const QmlObjectNode &qmlObjectNode, + const PropertyName &propertyName) +{ + const QVariant value = qmlObjectNode.modelValue("InsightCategory." + propertyName); + + if (value.isValid()) + return value; + + return QString(); +} } // namespace void PropertyEditorQmlBackend::setupLayoutAttachedProperties(const QmlObjectNode &qmlObjectNode, PropertyEditorView *propertyEditor) @@ -188,6 +199,16 @@ void PropertyEditorQmlBackend::setupLayoutAttachedProperties(const QmlObjectNode } } +void PropertyEditorQmlBackend::setupInsightAttachedProperties(const QmlObjectNode &qmlObjectNode, + PropertyEditorView *propertyEditor) +{ + const PropertyName propertyName = "category"; + createPropertyEditorValue(qmlObjectNode, + "InsightCategory." + propertyName, + properDefaultInsightAttachedProperties(qmlObjectNode, propertyName), + propertyEditor); +} + void PropertyEditorQmlBackend::setupAuxiliaryProperties(const QmlObjectNode &qmlObjectNode, PropertyEditorView *propertyEditor) { @@ -262,9 +283,9 @@ void PropertyEditorQmlBackend::setupAuxiliaryProperties(const QmlObjectNode &qml } void PropertyEditorQmlBackend::createPropertyEditorValue(const QmlObjectNode &qmlObjectNode, - const PropertyName &name, - const QVariant &value, - PropertyEditorView *propertyEditor) + const PropertyName &name, + const QVariant &value, + PropertyEditorView *propertyEditor) { PropertyName propertyName(name); propertyName.replace('.', '_'); @@ -397,6 +418,7 @@ void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const Q propertyEditor); } setupLayoutAttachedProperties(qmlObjectNode, propertyEditor); + setupInsightAttachedProperties(qmlObjectNode, propertyEditor); setupAuxiliaryProperties(qmlObjectNode, propertyEditor); // model node @@ -888,6 +910,14 @@ void PropertyEditorQmlBackend::setValueforLayoutAttachedProperties(const QmlObje } } +void PropertyEditorQmlBackend::setValueforInsightAttachedProperties(const QmlObjectNode &qmlObjectNode, + const PropertyName &name) +{ + PropertyName propertyName = name; + propertyName.replace("InsightCategory.", ""); + setValue(qmlObjectNode, name, properDefaultInsightAttachedProperties(qmlObjectNode, propertyName)); +} + void PropertyEditorQmlBackend::setValueforAuxiliaryProperties(const QmlObjectNode &qmlObjectNode, AuxiliaryDataKeyView key) { diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h index dd176bb9e84..1334ff55f2f 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h @@ -57,10 +57,16 @@ public: void emitSelectionToBeChanged(); void emitSelectionChanged(); - void setValueforLayoutAttachedProperties(const QmlObjectNode &qmlObjectNode, const PropertyName &name); + void setValueforLayoutAttachedProperties(const QmlObjectNode &qmlObjectNode, + const PropertyName &name); + void setValueforInsightAttachedProperties(const QmlObjectNode &qmlObjectNode, + const PropertyName &name); void setValueforAuxiliaryProperties(const QmlObjectNode &qmlObjectNode, AuxiliaryDataKeyView key); - void setupLayoutAttachedProperties(const QmlObjectNode &qmlObjectNode, PropertyEditorView *propertyEditor); + void setupLayoutAttachedProperties(const QmlObjectNode &qmlObjectNode, + PropertyEditorView *propertyEditor); + void setupInsightAttachedProperties(const QmlObjectNode &qmlObjectNode, + PropertyEditorView *propertyEditor); void setupAuxiliaryProperties(const QmlObjectNode &qmlObjectNode, PropertyEditorView *propertyEditor); static NodeMetaInfo findCommonAncestor(const ModelNode &node); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp index 45eb5b69b4c..6bca126ae69 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp @@ -2,28 +2,23 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 #include "propertyeditorvalue.h" -#include "variantproperty.h" -#include "documentmanager.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "abstractview.h" +#include "bindingproperty.h" +#include "designermcumanager.h" +#include "documentmanager.h" +#include "nodelistproperty.h" +#include "nodemetainfo.h" +#include "nodeproperty.h" +#include "qmlitemnode.h" +#include "qmlobjectnode.h" +#include "variantproperty.h" #include #include -#include #include -#include - -//using namespace QmlDesigner; +#include PropertyEditorValue::PropertyEditorValue(QObject *parent) : QObject(parent), @@ -498,27 +493,40 @@ bool PropertyEditorValue::idListReplace(int idx, const QString &value) return true; } -void PropertyEditorValue::commitDrop(const QString &path) +void PropertyEditorValue::commitDrop(const QString &dropData) { if (m_modelNode.metaInfo().isQtQuick3DMaterial() && m_modelNode.metaInfo().property(m_name).propertyType().isQtQuick3DTexture()) { - // create a texture node - QmlDesigner::NodeMetaInfo metaInfo = m_modelNode.view()->model()->metaInfo("QtQuick3D.Texture"); - QmlDesigner::ModelNode texture = m_modelNode.view()->createModelNode("QtQuick3D.Texture", - metaInfo.majorVersion(), - metaInfo.minorVersion()); - texture.validId(); - m_modelNode.view()->rootModelNode().defaultNodeListProperty().reparentHere(texture); - // TODO: group textures under 1 node (just like materials) + m_modelNode.view()->executeInTransaction(__FUNCTION__, [&] { + QmlDesigner::ModelNode texture = m_modelNode.view()->modelNodeForInternalId(dropData.toInt()); + if (!texture || !texture.metaInfo().isQtQuick3DTexture()) { + Utils::FilePath imagePath = Utils::FilePath::fromString(dropData); + Utils::FilePath currFilePath = QmlDesigner::DocumentManager::currentFilePath(); + QString sourceVal = imagePath.relativePathFrom(currFilePath).toString(); + texture = m_modelNode.view()->getTextureDefaultInstance(sourceVal); - // set texture source - Utils::FilePath imagePath = Utils::FilePath::fromString(path); - Utils::FilePath currFilePath = QmlDesigner::DocumentManager::currentFilePath(); - QmlDesigner::VariantProperty srcProp = texture.variantProperty("source"); - srcProp.setValue(imagePath.relativePathFrom(currFilePath).toString()); + if (!texture.isValid()) { + // create a texture node + QmlDesigner::NodeMetaInfo metaInfo = m_modelNode.view()->model()->metaInfo("QtQuick3D.Texture"); + texture = m_modelNode.view()->createModelNode("QtQuick3D.Texture", metaInfo.majorVersion(), + metaInfo.minorVersion()); + texture.validId(); + m_modelNode.view()->materialLibraryNode().defaultNodeListProperty().reparentHere(texture); + } - // assign the texture to the property - setExpressionWithEmit(texture.id()); + // set texture source + QmlDesigner::VariantProperty srcProp = texture.variantProperty("source"); + srcProp.setValue(sourceVal); + + QTimer::singleShot(0, this, [this, texture]() { + if (m_modelNode.isValid() && texture.isValid() && m_modelNode.view()) + m_modelNode.view()->emitCustomNotification("selected_texture_changed", {texture}); + }); + } + + // assign the texture to the property + setExpressionWithEmit(texture.id()); + }); } m_modelNode.view()->model()->endDrag(); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h index e7199170dab..0680ceb9935 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h @@ -125,7 +125,7 @@ public: Q_INVOKABLE bool idListAdd(const QString &value); Q_INVOKABLE bool idListRemove(int idx); Q_INVOKABLE bool idListReplace(int idx, const QString &value); - Q_INVOKABLE void commitDrop(const QString &path); + Q_INVOKABLE void commitDrop(const QString &dropData); public slots: void resetValue(); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp index 5788f3b3a52..1d93f26f01a 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp @@ -7,9 +7,10 @@ #include "propertyeditortransaction.h" #include "propertyeditorvalue.h" +#include +#include #include #include -#include #include #include @@ -48,6 +49,11 @@ static bool propertyIsAttachedLayoutProperty(const PropertyName &propertyName) return propertyName.contains("Layout."); } +static bool propertyIsAttachedInsightProperty(const PropertyName &propertyName) +{ + return propertyName.contains("InsightCategory."); +} + PropertyEditorView::PropertyEditorView(AsynchronousImageCache &imageCache, ExternalDependenciesInterface &externalDependencies) : AbstractView(externalDependencies) @@ -164,7 +170,8 @@ void PropertyEditorView::changeValue(const QString &name) if (auto property = metaInfo.property(propertyName)) { castedValue = property.castedValue(value->value()); - } else if (propertyIsAttachedLayoutProperty(propertyName)) { + } else if (propertyIsAttachedLayoutProperty(propertyName) + || propertyIsAttachedInsightProperty(propertyName)) { castedValue = value->value(); } else { qWarning() << "PropertyEditor:" << propertyName << "cannot be casted (metainfo)"; @@ -500,6 +507,13 @@ void PropertyEditorView::setupQmlBackend() m_qmlBackEndForCurrentType = currentQmlBackend; + if (rootModelNode().hasAuxiliaryData(insightEnabledProperty)) + m_qmlBackEndForCurrentType->contextObject()->setInsightEnabled( + rootModelNode().auxiliaryData(insightEnabledProperty)->toBool()); + + if (rootModelNode().hasAuxiliaryData(insightCategoriesProperty)) + m_qmlBackEndForCurrentType->contextObject()->setInsightCategories( + rootModelNode().auxiliaryData(insightCategoriesProperty)->toStringList()); } void PropertyEditorView::commitVariantValueToModel(const PropertyName &propertyName, const QVariant &value) @@ -641,6 +655,11 @@ void PropertyEditorView::propertiesRemoved(const QList& proper } } + if (propertyIsAttachedInsightProperty(property.name())) { + m_qmlBackEndForCurrentType->setValueforInsightAttachedProperties(m_selectedNode, + property.name()); + } + if ("width" == property.name() || "height" == property.name()) { const QmlItemNode qmlItemNode = m_selectedNode; if (qmlItemNode.isInLayout()) @@ -662,7 +681,12 @@ void PropertyEditorView::variantPropertiesChanged(const QList& ModelNode node(property.parentModelNode()); if (propertyIsAttachedLayoutProperty(property.name())) - m_qmlBackEndForCurrentType->setValueforLayoutAttachedProperties(m_selectedNode, property.name()); + m_qmlBackEndForCurrentType->setValueforLayoutAttachedProperties(m_selectedNode, + property.name()); + + if (propertyIsAttachedInsightProperty(property.name())) + m_qmlBackEndForCurrentType->setValueforInsightAttachedProperties(m_selectedNode, + property.name()); if (node == m_selectedNode || QmlObjectNode(m_selectedNode).propertyChangeForCurrentState() == node) { if ( QmlObjectNode(m_selectedNode).modelNode().property(property.name()).isBindingProperty()) @@ -701,9 +725,8 @@ void PropertyEditorView::bindingPropertiesChanged(const QList& void PropertyEditorView::auxiliaryDataChanged(const ModelNode &node, [[maybe_unused]] AuxiliaryDataKeyView key, - const QVariant &) + const QVariant &data) { - if (noValidSelection()) return; @@ -711,6 +734,12 @@ void PropertyEditorView::auxiliaryDataChanged(const ModelNode &node, return; m_qmlBackEndForCurrentType->setValueforAuxiliaryProperties(m_selectedNode, key); + + if (key == insightEnabledProperty) + m_qmlBackEndForCurrentType->contextObject()->setInsightEnabled(data.toBool()); + + if (key == insightCategoriesProperty) + m_qmlBackEndForCurrentType->contextObject()->setInsightCategories(data.toStringList()); } void PropertyEditorView::instanceInformationsChanged(const QMultiHash &informationChangedHash) @@ -747,6 +776,12 @@ void PropertyEditorView::select() m_qmlBackEndForCurrentType->emitSelectionToBeChanged(); delayedResetView(); + + auto nodes = selectedModelNodes(); + + for (const auto &n : nodes) { + n.metaInfo().isFileComponent(); + } } void PropertyEditorView::setSelelectedModelNode() diff --git a/src/plugins/qmldesigner/components/sourcetool/sourcetool.cpp b/src/plugins/qmldesigner/components/sourcetool/sourcetool.cpp index 5732235b508..cfae70b4cc3 100644 --- a/src/plugins/qmldesigner/components/sourcetool/sourcetool.cpp +++ b/src/plugins/qmldesigner/components/sourcetool/sourcetool.cpp @@ -40,59 +40,8 @@ bool modelNodeHasUrlSource(const QmlDesigner::ModelNode &modelNode) namespace QmlDesigner { -class SourceToolAction : public AbstractAction -{ -public: - SourceToolAction() : AbstractAction(QCoreApplication::translate("SourceToolAction","Change Source URL...")) - { - const Utils::Icon prevIcon({ - {":/utils/images/fileopen.png", Utils::Theme::OutputPanes_NormalMessageTextColor}}, Utils::Icon::MenuTintedStyle); - - action()->setIcon(prevIcon.icon()); - } - - QByteArray category() const override - { - return QByteArray(); - } - - QByteArray menuId() const override - { - return "SourceTool"; - } - - int priority() const override - { - return CustomActionsPriority; - } - - Type type() const override - { - return FormEditorAction; - } - -protected: - bool isVisible(const SelectionContext &selectionContext) const override - { - if (selectionContext.singleNodeIsSelected()) - return modelNodeHasUrlSource(selectionContext.currentSingleSelectedNode()); - return false; - } - - bool isEnabled(const SelectionContext &selectionContext) const override - { - return isVisible(selectionContext); - } -}; - - SourceTool::SourceTool() { - auto sourceToolAction = new SourceToolAction; - QmlDesignerPlugin::instance()->designerActionManager().addDesignerAction(sourceToolAction); - connect(sourceToolAction->action(), &QAction::triggered, [=]() { - view()->changeCurrentToolTo(this); - }); } SourceTool::~SourceTool() = default; diff --git a/src/plugins/qmldesigner/components/stateseditornew/stateseditorview.cpp b/src/plugins/qmldesigner/components/stateseditornew/stateseditorview.cpp index e005d6243db..c263a510966 100644 --- a/src/plugins/qmldesigner/components/stateseditornew/stateseditorview.cpp +++ b/src/plugins/qmldesigner/components/stateseditornew/stateseditorview.cpp @@ -105,6 +105,8 @@ void StatesEditorView::setActiveStatesGroupNode(const ModelNode &modelNode) m_activeStatesGroupNode = modelNode; resetModel(); + checkForStatesAvailability(); + emit m_statesEditorModel->activeStateGroupChanged(); emit m_statesEditorModel->activeStateGroupIndexChanged(); } @@ -229,6 +231,16 @@ void StatesEditorView::cloneState(int nodeId) int from = newNode.parentProperty().indexOf(newNode); int to = stateNode.parentProperty().indexOf(stateNode) + 1; + // When duplicating an extended state the new state needs to be added after the extend group. + if (!modelState.hasExtend()) { + auto modelNodeList = activeStatesGroupNode().nodeListProperty("states").toModelNodeList(); + for (; to != modelNodeList.count(); ++to) { + QmlModelState currentState(modelNodeList.at(to)); + if (!currentState.isValid() || currentState.isBaseState() || !currentState.hasExtend()) + break; + } + } + executeInTransaction("moveState", [this, &newState, from, to]() { activeStatesGroupNode().nodeListProperty("states").slide(from, to); setCurrentState(newState); @@ -420,8 +432,10 @@ void StatesEditorView::resetStateGroups() void StatesEditorView::checkForStatesAvailability() { if (m_statesEditorWidget) { - const bool isVisual = QmlVisualNode::isValidQmlVisualNode(activeStatesGroupNode()); - m_statesEditorWidget->showAddNewStatesButton(isVisual); + const bool isVisual = activeStatesGroupNode().metaInfo().isBasedOn( + model()->qtQuickItemMetaInfo(), model()->qtQuick3DNodeMetaInfo()); + const bool isRoot = activeStatesGroupNode().isRootNode(); + m_statesEditorWidget->showAddNewStatesButton(isVisual || !isRoot); } } @@ -714,6 +728,9 @@ void StatesEditorView::modelAttached(Model *model) resetModel(); resetStateGroups(); + + emit m_statesEditorModel->activeStateGroupChanged(); + emit m_statesEditorModel->activeStateGroupIndexChanged(); } void StatesEditorView::modelAboutToBeDetached(Model *model) diff --git a/src/plugins/qmldesigner/components/stateseditornew/stateseditorwidget.cpp b/src/plugins/qmldesigner/components/stateseditornew/stateseditorwidget.cpp index ebf98d458c4..c47f19e4cb2 100644 --- a/src/plugins/qmldesigner/components/stateseditornew/stateseditorwidget.cpp +++ b/src/plugins/qmldesigner/components/stateseditornew/stateseditorwidget.cpp @@ -43,9 +43,10 @@ #include +#include #include -#include #include +#include #include #include @@ -141,6 +142,7 @@ void StatesEditorWidget::showEvent(QShowEvent *event) { QQuickWidget::showEvent(event); update(); + QMetaObject::invokeMethod(rootObject(), "showEvent"); } void StatesEditorWidget::focusOutEvent(QFocusEvent *focusEvent) diff --git a/src/plugins/qmldesigner/components/texttool/texttool.cpp b/src/plugins/qmldesigner/components/texttool/texttool.cpp index 596ef44efb9..ffd2875ca78 100644 --- a/src/plugins/qmldesigner/components/texttool/texttool.cpp +++ b/src/plugins/qmldesigner/components/texttool/texttool.cpp @@ -28,56 +28,8 @@ namespace QmlDesigner { -class TextToolAction : public AbstractAction -{ -public: - TextToolAction() : AbstractAction(QCoreApplication::translate("TextToolAction","Edit Text")) {} - - QByteArray category() const override - { - return QByteArray(); - } - - QByteArray menuId() const override - { - return "TextTool"; - } - - int priority() const override - { - return CustomActionsPriority; - } - - Type type() const override - { - return ContextMenuAction; - } - -protected: - bool isVisible(const SelectionContext &selectionContext) const override - { - if (selectionContext.scenePosition().isNull()) - return false; - - if (selectionContext.singleNodeIsSelected()) - return selectionContext.currentSingleSelectedNode().metaInfo().hasProperty("text"); - - return false; - } - - bool isEnabled(const SelectionContext &selectionContext) const override - { - return isVisible(selectionContext); - } -}; - TextTool::TextTool() { - auto textToolAction = new TextToolAction; - QmlDesignerPlugin::instance()->designerActionManager().addDesignerAction(textToolAction); - connect(textToolAction->action(), &QAction::triggered, [=]() { - view()->changeCurrentToolTo(this); - }); } TextTool::~TextTool() = default; diff --git a/src/plugins/qmldesigner/components/textureeditor/images/texture_default.png b/src/plugins/qmldesigner/components/textureeditor/images/texture_default.png new file mode 100644 index 00000000000..32ad4e3eda6 Binary files /dev/null and b/src/plugins/qmldesigner/components/textureeditor/images/texture_default.png differ diff --git a/src/plugins/qmldesigner/components/textureeditor/images/texture_default@2x.png b/src/plugins/qmldesigner/components/textureeditor/images/texture_default@2x.png new file mode 100644 index 00000000000..f1ed622ca58 Binary files /dev/null and b/src/plugins/qmldesigner/components/textureeditor/images/texture_default@2x.png differ diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditor.qrc b/src/plugins/qmldesigner/components/textureeditor/textureeditor.qrc new file mode 100644 index 00000000000..680fe7d82b0 --- /dev/null +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditor.qrc @@ -0,0 +1,6 @@ + + + images/texture_default.png + images/texture_default@2x.png + + diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp new file mode 100644 index 00000000000..690e78db0a5 --- /dev/null +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp @@ -0,0 +1,345 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "textureeditorcontextobject.h" + +#include "abstractview.h" +#include "documentmanager.h" +#include "model.h" +#include "qmldesignerplugin.h" +#include "qmlobjectnode.h" +#include "qmltimeline.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace QmlDesigner { + +TextureEditorContextObject::TextureEditorContextObject(QQmlContext *context, QObject *parent) + : QObject(parent) + , m_qmlContext(context) +{ + qmlRegisterUncreatableType("TextureToolBarAction", 1, 0, "ToolBarAction", "Enum type"); +} + +QQmlComponent *TextureEditorContextObject::specificQmlComponent() +{ + if (m_specificQmlComponent) + return m_specificQmlComponent; + + m_specificQmlComponent = new QQmlComponent(m_qmlContext->engine(), this); + m_specificQmlComponent->setData(m_specificQmlData.toUtf8(), QUrl::fromLocalFile("specifics.qml")); + + return m_specificQmlComponent; +} + +QString TextureEditorContextObject::convertColorToString(const QVariant &color) +{ + QString colorString; + QColor theColor; + if (color.canConvert(QVariant::Color)) { + theColor = color.value(); + } else if (color.canConvert(QVariant::Vector3D)) { + auto vec = color.value(); + theColor = QColor::fromRgbF(vec.x(), vec.y(), vec.z()); + } + + colorString = theColor.name(); + + if (theColor.alpha() != 255) { + QString hexAlpha = QString("%1").arg(theColor.alpha(), 2, 16, QLatin1Char('0')); + colorString.remove(0, 1); + colorString.prepend(hexAlpha); + colorString.prepend(QStringLiteral("#")); + } + return colorString; +} + +// TODO: this method is used by the ColorEditor helper widget, check if at all needed? +QColor TextureEditorContextObject::colorFromString(const QString &colorString) +{ + return colorString; +} + +void TextureEditorContextObject::insertKeyframe(const QString &propertyName) +{ + QTC_ASSERT(m_model && m_model->rewriterView(), return); + QTC_ASSERT(m_selectedTexture.isValid(), return); + + // Ideally we should not missuse the rewriterView + // If we add more code here we have to forward the material editor view + RewriterView *rewriterView = m_model->rewriterView(); + + QmlTimeline timeline = rewriterView->currentTimeline(); + + QTC_ASSERT(timeline.isValid(), return); + + rewriterView->executeInTransaction("TextureEditorContextObject::insertKeyframe", [&] { + timeline.insertKeyframe(m_selectedTexture, propertyName.toUtf8()); + }); +} + +int TextureEditorContextObject::majorVersion() const +{ + return m_majorVersion; +} + +void TextureEditorContextObject::setMajorVersion(int majorVersion) +{ + if (m_majorVersion == majorVersion) + return; + + m_majorVersion = majorVersion; + + emit majorVersionChanged(); +} + +QString TextureEditorContextObject::activeDragSuffix() const +{ + return m_activeDragSuffix; +} + +void TextureEditorContextObject::setActiveDragSuffix(const QString &suffix) +{ + if (m_activeDragSuffix != suffix) { + m_activeDragSuffix = suffix; + emit activeDragSuffixChanged(); + } +} + +bool TextureEditorContextObject::hasActiveTimeline() const +{ + return m_hasActiveTimeline; +} + +void TextureEditorContextObject::setHasActiveTimeline(bool b) +{ + if (b == m_hasActiveTimeline) + return; + + m_hasActiveTimeline = b; + emit hasActiveTimelineChanged(); +} + +bool TextureEditorContextObject::hasQuick3DImport() const +{ + return m_hasQuick3DImport; +} + +void TextureEditorContextObject::setHasQuick3DImport(bool b) +{ + if (b == m_hasQuick3DImport) + return; + + m_hasQuick3DImport = b; + emit hasQuick3DImportChanged(); +} + +bool TextureEditorContextObject::hasMaterialLibrary() const +{ + return m_hasMaterialLibrary; +} + +void TextureEditorContextObject::setHasMaterialLibrary(bool b) +{ + if (b == m_hasMaterialLibrary) + return; + + m_hasMaterialLibrary = b; + emit hasMaterialLibraryChanged(); +} + +bool TextureEditorContextObject::hasSingleModelSelection() const +{ + return m_hasSingleModelSelection; +} + +void TextureEditorContextObject::setHasSingleModelSelection(bool b) +{ + if (b == m_hasSingleModelSelection) + return; + + m_hasSingleModelSelection = b; + emit hasSingleModelSelectionChanged(); +} + +void TextureEditorContextObject::setSelectedMaterial(const ModelNode &matNode) +{ + m_selectedTexture = matNode; +} + +void TextureEditorContextObject::setSpecificsUrl(const QUrl &newSpecificsUrl) +{ + if (newSpecificsUrl == m_specificsUrl) + return; + + m_specificsUrl = newSpecificsUrl; + emit specificsUrlChanged(); +} + +void TextureEditorContextObject::setSpecificQmlData(const QString &newSpecificQmlData) +{ + if (newSpecificQmlData == m_specificQmlData) + return; + + m_specificQmlData = newSpecificQmlData; + + delete m_specificQmlComponent; + m_specificQmlComponent = nullptr; + + emit specificQmlComponentChanged(); + emit specificQmlDataChanged(); +} + +void TextureEditorContextObject::setStateName(const QString &newStateName) +{ + if (newStateName == m_stateName) + return; + + m_stateName = newStateName; + emit stateNameChanged(); +} + +void TextureEditorContextObject::setAllStateNames(const QStringList &allStates) +{ + if (allStates == m_allStateNames) + return; + + m_allStateNames = allStates; + emit allStateNamesChanged(); +} + +void TextureEditorContextObject::setIsBaseState(bool newIsBaseState) +{ + if (newIsBaseState == m_isBaseState) + return; + + m_isBaseState = newIsBaseState; + emit isBaseStateChanged(); +} + +void TextureEditorContextObject::setSelectionChanged(bool newSelectionChanged) +{ + if (newSelectionChanged == m_selectionChanged) + return; + + m_selectionChanged = newSelectionChanged; + emit selectionChangedChanged(); +} + +void TextureEditorContextObject::setBackendValues(QQmlPropertyMap *newBackendValues) +{ + if (newBackendValues == m_backendValues) + return; + + m_backendValues = newBackendValues; + emit backendValuesChanged(); +} + +void TextureEditorContextObject::setModel(Model *model) +{ + m_model = model; +} + +void TextureEditorContextObject::triggerSelectionChanged() +{ + setSelectionChanged(!m_selectionChanged); +} + +void TextureEditorContextObject::setHasAliasExport(bool hasAliasExport) +{ + if (m_aliasExport == hasAliasExport) + return; + + m_aliasExport = hasAliasExport; + emit hasAliasExportChanged(); +} + +void TextureEditorContextObject::hideCursor() +{ + if (QApplication::overrideCursor()) + return; + + QApplication::setOverrideCursor(QCursor(Qt::BlankCursor)); + + if (QWidget *w = QApplication::activeWindow()) + m_lastPos = QCursor::pos(w->screen()); +} + +void TextureEditorContextObject::restoreCursor() +{ + if (!QApplication::overrideCursor()) + return; + + QApplication::restoreOverrideCursor(); + + if (QWidget *w = QApplication::activeWindow()) + QCursor::setPos(w->screen(), m_lastPos); +} + +void TextureEditorContextObject::holdCursorInPlace() +{ + if (!QApplication::overrideCursor()) + return; + + if (QWidget *w = QApplication::activeWindow()) + QCursor::setPos(w->screen(), m_lastPos); +} + +int TextureEditorContextObject::devicePixelRatio() +{ + if (QWidget *w = QApplication::activeWindow()) + return w->devicePixelRatio(); + + return 1; +} + +QStringList TextureEditorContextObject::allStatesForId(const QString &id) +{ + if (m_model && m_model->rewriterView()) { + const QmlObjectNode node = m_model->rewriterView()->modelNodeForId(id); + if (node.isValid()) + return node.allStateNames(); + } + + return {}; +} + +bool TextureEditorContextObject::isBlocked(const QString &propName) const +{ + if (!m_selectedTexture.isValid()) + return false; + + if (!m_model || !m_model->rewriterView()) + return false; + + if (QmlObjectNode(m_selectedTexture).isBlocked(propName.toUtf8())) + return true; + + return false; +} + +void TextureEditorContextObject::goIntoComponent() +{ + QTC_ASSERT(m_model, return); + DocumentManager::goIntoComponent(m_selectedTexture); +} + +QString TextureEditorContextObject::resolveResourcePath(const QString &path) +{ + if (Utils::FilePath::fromString(path).isAbsolutePath()) + return path; + return DocumentManager::currentResourcePath().path() + '/' + path; +} + +} // QmlDesigner diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.h b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.h new file mode 100644 index 00000000000..56249382724 --- /dev/null +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.h @@ -0,0 +1,166 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace QmlDesigner { + +class Model; + +class TextureEditorContextObject : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QUrl specificsUrl READ specificsUrl WRITE setSpecificsUrl NOTIFY specificsUrlChanged) + Q_PROPERTY(QString specificQmlData READ specificQmlData WRITE setSpecificQmlData NOTIFY specificQmlDataChanged) + Q_PROPERTY(QQmlComponent *specificQmlComponent READ specificQmlComponent NOTIFY specificQmlComponentChanged) + + Q_PROPERTY(QString stateName READ stateName WRITE setStateName NOTIFY stateNameChanged) + Q_PROPERTY(QStringList allStateNames READ allStateNames WRITE setAllStateNames NOTIFY allStateNamesChanged) + + Q_PROPERTY(bool isBaseState READ isBaseState WRITE setIsBaseState NOTIFY isBaseStateChanged) + Q_PROPERTY(bool selectionChanged READ selectionChanged WRITE setSelectionChanged NOTIFY selectionChangedChanged) + + Q_PROPERTY(int majorVersion READ majorVersion WRITE setMajorVersion NOTIFY majorVersionChanged) + + Q_PROPERTY(bool hasAliasExport READ hasAliasExport NOTIFY hasAliasExportChanged) + Q_PROPERTY(bool hasActiveTimeline READ hasActiveTimeline NOTIFY hasActiveTimelineChanged) + Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged) + Q_PROPERTY(bool hasSingleModelSelection READ hasSingleModelSelection WRITE setHasSingleModelSelection + NOTIFY hasSingleModelSelectionChanged) + Q_PROPERTY(bool hasMaterialLibrary READ hasMaterialLibrary WRITE setHasMaterialLibrary NOTIFY hasMaterialLibraryChanged) + + Q_PROPERTY(QQmlPropertyMap *backendValues READ backendValues WRITE setBackendValues NOTIFY backendValuesChanged) + + Q_PROPERTY(QString activeDragSuffix READ activeDragSuffix NOTIFY activeDragSuffixChanged) + +public: + TextureEditorContextObject(QQmlContext *context, QObject *parent = nullptr); + + QUrl specificsUrl() const { return m_specificsUrl; } + QString specificQmlData() const {return m_specificQmlData; } + QQmlComponent *specificQmlComponent(); + QString stateName() const { return m_stateName; } + QStringList allStateNames() const { return m_allStateNames; } + + bool isBaseState() const { return m_isBaseState; } + bool selectionChanged() const { return m_selectionChanged; } + + QQmlPropertyMap *backendValues() const { return m_backendValues; } + + Q_INVOKABLE QString convertColorToString(const QVariant &color); + Q_INVOKABLE QColor colorFromString(const QString &colorString); + + Q_INVOKABLE void insertKeyframe(const QString &propertyName); + + Q_INVOKABLE void hideCursor(); + Q_INVOKABLE void restoreCursor(); + Q_INVOKABLE void holdCursorInPlace(); + + Q_INVOKABLE int devicePixelRatio(); + + Q_INVOKABLE QStringList allStatesForId(const QString &id); + + Q_INVOKABLE bool isBlocked(const QString &propName) const; + Q_INVOKABLE void goIntoComponent(); + Q_INVOKABLE QString resolveResourcePath(const QString &path); + + enum ToolBarAction { + ApplyToSelected, + AddNewTexture, + DeleteCurrentTexture, + OpenMaterialBrowser + }; + Q_ENUM(ToolBarAction) + + int majorVersion() const; + void setMajorVersion(int majorVersion); + + bool hasActiveTimeline() const; + void setHasActiveTimeline(bool b); + + bool hasQuick3DImport() const; + void setHasQuick3DImport(bool b); + + bool hasMaterialLibrary() const; + void setHasMaterialLibrary(bool b); + + bool hasSingleModelSelection() const; + void setHasSingleModelSelection(bool b); + + QString activeDragSuffix() const; + void setActiveDragSuffix(const QString &suffix); + + bool hasAliasExport() const { return m_aliasExport; } + + void setSelectedMaterial(const ModelNode &matNode); + + void setSpecificsUrl(const QUrl &newSpecificsUrl); + void setSpecificQmlData(const QString &newSpecificQmlData); + void setStateName(const QString &newStateName); + void setAllStateNames(const QStringList &allStates); + void setIsBaseState(bool newIsBaseState); + void setSelectionChanged(bool newSelectionChanged); + void setBackendValues(QQmlPropertyMap *newBackendValues); + void setModel(Model *model); + + void triggerSelectionChanged(); + void setHasAliasExport(bool hasAliasExport); + +signals: + void specificsUrlChanged(); + void specificQmlDataChanged(); + void specificQmlComponentChanged(); + void stateNameChanged(); + void allStateNamesChanged(); + void isBaseStateChanged(); + void selectionChangedChanged(); + void backendValuesChanged(); + void majorVersionChanged(); + void hasAliasExportChanged(); + void hasActiveTimelineChanged(); + void hasQuick3DImportChanged(); + void hasMaterialLibraryChanged(); + void hasSingleModelSelectionChanged(); + void activeDragSuffixChanged(); + +private: + QUrl m_specificsUrl; + QString m_specificQmlData; + QQmlComponent *m_specificQmlComponent = nullptr; + QQmlContext *m_qmlContext = nullptr; + + QString m_stateName; + QStringList m_allStateNames; + + int m_majorVersion = 1; + + QQmlPropertyMap *m_backendValues = nullptr; + Model *m_model = nullptr; + + QPoint m_lastPos; + + bool m_isBaseState = false; + bool m_selectionChanged = false; + bool m_aliasExport = false; + bool m_hasActiveTimeline = false; + bool m_hasQuick3DImport = false; + bool m_hasMaterialLibrary = false; + bool m_hasSingleModelSelection = false; + + ModelNode m_selectedTexture; + + QString m_activeDragSuffix; +}; + +} // QmlDesigner diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditordynamicpropertiesproxymodel.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditordynamicpropertiesproxymodel.cpp new file mode 100644 index 00000000000..e6f9c64c740 --- /dev/null +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditordynamicpropertiesproxymodel.cpp @@ -0,0 +1,22 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "textureeditordynamicpropertiesproxymodel.h" + +#include "dynamicpropertiesmodel.h" +#include "textureeditorview.h" + +using namespace QmlDesigner; + +TextureEditorDynamicPropertiesProxyModel::TextureEditorDynamicPropertiesProxyModel(QObject *parent) + : DynamicPropertiesProxyModel(parent) +{ + if (TextureEditorView::instance()) + initModel(TextureEditorView::instance()->dynamicPropertiesModel()); +} + +void TextureEditorDynamicPropertiesProxyModel::registerDeclarativeType() +{ + DynamicPropertiesProxyModel::registerDeclarativeType(); + qmlRegisterType("HelperWidgets", 2, 0, "TextureEditorDynamicPropertiesModel"); +} diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditordynamicpropertiesproxymodel.h b/src/plugins/qmldesigner/components/textureeditor/textureeditordynamicpropertiesproxymodel.h new file mode 100644 index 00000000000..5cd81b751f2 --- /dev/null +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditordynamicpropertiesproxymodel.h @@ -0,0 +1,16 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "dynamicpropertiesproxymodel.h" + +class TextureEditorDynamicPropertiesProxyModel : public DynamicPropertiesProxyModel +{ + Q_OBJECT + +public: + explicit TextureEditorDynamicPropertiesProxyModel(QObject *parent = nullptr); + + static void registerDeclarativeType(); +}; diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp new file mode 100644 index 00000000000..c3bcdd7d5e7 --- /dev/null +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp @@ -0,0 +1,282 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "textureeditorqmlbackend.h" + +#include "bindingproperty.h" +#include "documentmanager.h" +#include "nodemetainfo.h" +#include "propertyeditorimageprovider.h" +#include "propertyeditorvalue.h" +#include "qmldesignerconstants.h" +#include "qmlobjectnode.h" +#include "qmltimeline.h" +#include "textureeditortransaction.h" +#include "textureeditorcontextobject.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static QObject *variantToQObject(const QVariant &value) +{ + if (value.userType() == QMetaType::QObjectStar || value.userType() > QMetaType::User) + return *(QObject **)value.constData(); + + return nullptr; +} + +namespace QmlDesigner { + +TextureEditorQmlBackend::TextureEditorQmlBackend(TextureEditorView *textureEditor, AsynchronousImageCache &imageCache) + : m_view(new QQuickWidget) + , m_textureEditorTransaction(new TextureEditorTransaction(textureEditor)) + , m_contextObject(new TextureEditorContextObject(m_view->rootContext())) +{ + QImage defaultImage; + defaultImage.load(Utils::StyleHelper::dpiSpecificImageFile(":/textureeditor/images/texture_default.png")); + m_textureEditorImageProvider = new PropertyEditorImageProvider(imageCache, defaultImage); + m_view->setResizeMode(QQuickWidget::SizeRootObjectToView); + m_view->engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); + m_view->engine()->addImageProvider("qmldesigner_thumbnails", m_textureEditorImageProvider); + m_contextObject->setBackendValues(&m_backendValuesPropertyMap); + m_contextObject->setModel(textureEditor->model()); + context()->setContextObject(m_contextObject.data()); + + QObject::connect(&m_backendValuesPropertyMap, &DesignerPropertyMap::valueChanged, + textureEditor, &TextureEditorView::changeValue); +} + +TextureEditorQmlBackend::~TextureEditorQmlBackend() +{ +} + +PropertyName TextureEditorQmlBackend::auxNamePostFix(const PropertyName &propertyName) +{ + return propertyName + "__AUX"; +} + +void TextureEditorQmlBackend::createPropertyEditorValue(const QmlObjectNode &qmlObjectNode, + const PropertyName &name, + const QVariant &value, + TextureEditorView *textureEditor) +{ + PropertyName propertyName(name); + propertyName.replace('.', '_'); + auto valueObject = qobject_cast(variantToQObject(backendValuesPropertyMap().value(QString::fromUtf8(propertyName)))); + if (!valueObject) { + valueObject = new PropertyEditorValue(&backendValuesPropertyMap()); + QObject::connect(valueObject, &PropertyEditorValue::valueChanged, &backendValuesPropertyMap(), &DesignerPropertyMap::valueChanged); + QObject::connect(valueObject, &PropertyEditorValue::expressionChanged, textureEditor, &TextureEditorView::changeExpression); + QObject::connect(valueObject, &PropertyEditorValue::exportPropertyAsAliasRequested, textureEditor, &TextureEditorView::exportPropertyAsAlias); + QObject::connect(valueObject, &PropertyEditorValue::removeAliasExportRequested, textureEditor, &TextureEditorView::removeAliasExport); + backendValuesPropertyMap().insert(QString::fromUtf8(propertyName), QVariant::fromValue(valueObject)); + } + valueObject->setName(name); + valueObject->setModelNode(qmlObjectNode); + + if (qmlObjectNode.propertyAffectedByCurrentState(name) && !(qmlObjectNode.modelNode().property(name).isBindingProperty())) + valueObject->setValue(qmlObjectNode.modelValue(name)); + else + valueObject->setValue(value); + + if (propertyName != "id" && qmlObjectNode.currentState().isBaseState() + && qmlObjectNode.modelNode().property(propertyName).isBindingProperty()) { + valueObject->setExpression(qmlObjectNode.modelNode().bindingProperty(propertyName).expression()); + } else { + if (qmlObjectNode.hasBindingProperty(name)) + valueObject->setExpression(qmlObjectNode.expression(name)); + else + valueObject->setExpression(qmlObjectNode.instanceValue(name).toString()); + } +} + +void TextureEditorQmlBackend::setValue(const QmlObjectNode &, const PropertyName &name, const QVariant &value) +{ + // Vector*D values need to be split into their subcomponents + if (value.type() == QVariant::Vector2D) { + const char *suffix[2] = {"_x", "_y"}; + auto vecValue = value.value(); + for (int i = 0; i < 2; ++i) { + PropertyName subPropName(name.size() + 2, '\0'); + subPropName.replace(0, name.size(), name); + subPropName.replace(name.size(), 2, suffix[i]); + auto propertyValue = qobject_cast(variantToQObject(m_backendValuesPropertyMap.value(QString::fromUtf8(subPropName)))); + if (propertyValue) + propertyValue->setValue(QVariant(vecValue[i])); + } + } else if (value.type() == QVariant::Vector3D) { + const char *suffix[3] = {"_x", "_y", "_z"}; + auto vecValue = value.value(); + for (int i = 0; i < 3; ++i) { + PropertyName subPropName(name.size() + 2, '\0'); + subPropName.replace(0, name.size(), name); + subPropName.replace(name.size(), 2, suffix[i]); + auto propertyValue = qobject_cast(variantToQObject(m_backendValuesPropertyMap.value(QString::fromUtf8(subPropName)))); + if (propertyValue) + propertyValue->setValue(QVariant(vecValue[i])); + } + } else if (value.type() == QVariant::Vector4D) { + const char *suffix[4] = {"_x", "_y", "_z", "_w"}; + auto vecValue = value.value(); + for (int i = 0; i < 4; ++i) { + PropertyName subPropName(name.size() + 2, '\0'); + subPropName.replace(0, name.size(), name); + subPropName.replace(name.size(), 2, suffix[i]); + auto propertyValue = qobject_cast( + variantToQObject(m_backendValuesPropertyMap.value(QString::fromUtf8(subPropName)))); + if (propertyValue) + propertyValue->setValue(QVariant(vecValue[i])); + } + } else { + PropertyName propertyName = name; + propertyName.replace('.', '_'); + auto propertyValue = qobject_cast(variantToQObject(m_backendValuesPropertyMap.value(QString::fromUtf8(propertyName)))); + if (propertyValue) + propertyValue->setValue(value); + } +} + +QQmlContext *TextureEditorQmlBackend::context() const +{ + return m_view->rootContext(); +} + +TextureEditorContextObject *TextureEditorQmlBackend::contextObject() const +{ + return m_contextObject.data(); +} + +QQuickWidget *TextureEditorQmlBackend::widget() const +{ + return m_view; +} + +void TextureEditorQmlBackend::setSource(const QUrl &url) +{ + m_view->setSource(url); +} + +Internal::QmlAnchorBindingProxy &TextureEditorQmlBackend::backendAnchorBinding() +{ + return m_backendAnchorBinding; +} + +DesignerPropertyMap &TextureEditorQmlBackend::backendValuesPropertyMap() +{ + return m_backendValuesPropertyMap; +} + +TextureEditorTransaction *TextureEditorQmlBackend::textureEditorTransaction() const +{ + return m_textureEditorTransaction.data(); +} + +PropertyEditorValue *TextureEditorQmlBackend::propertyValueForName(const QString &propertyName) +{ + return qobject_cast(variantToQObject(backendValuesPropertyMap().value(propertyName))); +} + +void TextureEditorQmlBackend::setup(const QmlObjectNode &selectedTextureNode, const QString &stateName, + const QUrl &qmlSpecificsFile, TextureEditorView *textureEditor) +{ + if (selectedTextureNode.isValid()) { + m_contextObject->setModel(textureEditor->model()); + + for (const auto &property : selectedTextureNode.modelNode().metaInfo().properties()) { + createPropertyEditorValue(selectedTextureNode, + property.name(), + selectedTextureNode.instanceValue(property.name()), + textureEditor); + } + + // model node + m_backendModelNode.setup(selectedTextureNode.modelNode()); + context()->setContextProperty("modelNodeBackend", &m_backendModelNode); + context()->setContextProperty("hasTexture", QVariant(true)); + + // className + auto valueObject = qobject_cast(variantToQObject( + m_backendValuesPropertyMap.value(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY))); + if (!valueObject) + valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap); + valueObject->setName(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY); + valueObject->setModelNode(selectedTextureNode.modelNode()); + valueObject->setValue(m_backendModelNode.simplifiedTypeName()); + QObject::connect(valueObject, + &PropertyEditorValue::valueChanged, + &backendValuesPropertyMap(), + &DesignerPropertyMap::valueChanged); + m_backendValuesPropertyMap.insert(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY, + QVariant::fromValue(valueObject)); + + // anchors + m_backendAnchorBinding.setup(selectedTextureNode.modelNode()); + context()->setContextProperties( + QVector{ + {{"anchorBackend"}, QVariant::fromValue(&m_backendAnchorBinding)}, + {{"transaction"}, QVariant::fromValue(m_textureEditorTransaction.data())} + } + ); + + contextObject()->setSpecificsUrl(qmlSpecificsFile); + contextObject()->setStateName(stateName); + + QStringList stateNames = selectedTextureNode.allStateNames(); + stateNames.prepend("base state"); + contextObject()->setAllStateNames(stateNames); + contextObject()->setSelectedMaterial(selectedTextureNode); + contextObject()->setIsBaseState(selectedTextureNode.isInBaseState()); + contextObject()->setHasAliasExport(selectedTextureNode.isAliasExported()); + contextObject()->setHasActiveTimeline(QmlTimeline::hasActiveTimeline(selectedTextureNode.view())); + + contextObject()->setSelectionChanged(false); + + NodeMetaInfo metaInfo = selectedTextureNode.modelNode().metaInfo(); + contextObject()->setMajorVersion(metaInfo.isValid() ? metaInfo.majorVersion() : -1); + } else { + context()->setContextProperty("hasTexture", QVariant(false)); + } +} + +QString TextureEditorQmlBackend::propertyEditorResourcesPath() +{ +#ifdef SHARE_QML_PATH + if (Utils::qtcEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) + return QLatin1String(SHARE_QML_PATH) + "/propertyEditorQmlSources"; +#endif + return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toString(); +} + +void TextureEditorQmlBackend::emitSelectionToBeChanged() +{ + m_backendModelNode.emitSelectionToBeChanged(); +} + +void TextureEditorQmlBackend::emitSelectionChanged() +{ + m_backendModelNode.emitSelectionChanged(); +} + +void TextureEditorQmlBackend::setValueforAuxiliaryProperties(const QmlObjectNode &qmlObjectNode, + AuxiliaryDataKeyView key) +{ + const PropertyName propertyName = auxNamePostFix(PropertyName(key.name)); + setValue(qmlObjectNode, propertyName, qmlObjectNode.modelNode().auxiliaryDataWithDefault(key)); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h new file mode 100644 index 00000000000..2d0131cc9fc --- /dev/null +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h @@ -0,0 +1,71 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "designerpropertymap.h" +#include "qmlanchorbindingproxy.h" +#include "qmlmodelnodeproxy.h" + +#include + +class PropertyEditorValue; + +QT_BEGIN_NAMESPACE +class QQuickWidget; +QT_END_NAMESPACE + +namespace QmlDesigner { + +class PropertyEditorImageProvider; +class TextureEditorContextObject; +class TextureEditorImageProvider; +class TextureEditorTransaction; +class TextureEditorView; + +class TextureEditorQmlBackend +{ + Q_DISABLE_COPY(TextureEditorQmlBackend) + +public: + TextureEditorQmlBackend(TextureEditorView *materialEditor, + class AsynchronousImageCache &imageCache); + ~TextureEditorQmlBackend(); + + void setup(const QmlObjectNode &selectedTextureNode, const QString &stateName, const QUrl &qmlSpecificsFile, + TextureEditorView *textureEditor); + void setValue(const QmlObjectNode &fxObjectNode, const PropertyName &name, const QVariant &value); + + QQmlContext *context() const; + TextureEditorContextObject *contextObject() const; + QQuickWidget *widget() const; + void setSource(const QUrl &url); + Internal::QmlAnchorBindingProxy &backendAnchorBinding(); + DesignerPropertyMap &backendValuesPropertyMap(); + TextureEditorTransaction *textureEditorTransaction() const; + + PropertyEditorValue *propertyValueForName(const QString &propertyName); + + static QString propertyEditorResourcesPath(); + + void emitSelectionToBeChanged(); + void emitSelectionChanged(); + + void setValueforAuxiliaryProperties(const QmlObjectNode &qmlObjectNode, AuxiliaryDataKeyView key); + +private: + void createPropertyEditorValue(const QmlObjectNode &qmlObjectNode, + const PropertyName &name, const QVariant &value, + TextureEditorView *textureEditor); + PropertyName auxNamePostFix(const PropertyName &propertyName); + + QQuickWidget *m_view = nullptr; + Internal::QmlAnchorBindingProxy m_backendAnchorBinding; + QmlModelNodeProxy m_backendModelNode; + DesignerPropertyMap m_backendValuesPropertyMap; + QScopedPointer m_textureEditorTransaction; + QScopedPointer m_contextObject; + PropertyEditorImageProvider *m_textureEditorImageProvider = nullptr; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditortransaction.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditortransaction.cpp new file mode 100644 index 00000000000..91ca4efb40a --- /dev/null +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditortransaction.cpp @@ -0,0 +1,48 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "textureeditortransaction.h" + +#include + +namespace QmlDesigner { + +TextureEditorTransaction::TextureEditorTransaction(TextureEditorView *textureEditor) + : QObject(textureEditor), + m_textureEditor(textureEditor) +{ +} + +void TextureEditorTransaction::start() +{ + if (!m_textureEditor->model()) + return; + if (m_rewriterTransaction.isValid()) + m_rewriterTransaction.commit(); + m_rewriterTransaction = m_textureEditor->beginRewriterTransaction(QByteArrayLiteral("MaterialEditorTransaction::start")); + m_timerId = startTimer(10000); +} + +void TextureEditorTransaction::end() +{ + if (m_rewriterTransaction.isValid() && m_textureEditor->model()) { + killTimer(m_timerId); + m_rewriterTransaction.commit(); + } +} + +bool TextureEditorTransaction::active() const +{ + return m_rewriterTransaction.isValid(); +} + +void TextureEditorTransaction::timerEvent(QTimerEvent *timerEvent) +{ + if (timerEvent->timerId() != m_timerId) + return; + killTimer(timerEvent->timerId()); + if (m_rewriterTransaction.isValid()) + m_rewriterTransaction.commit(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditortransaction.h b/src/plugins/qmldesigner/components/textureeditor/textureeditortransaction.h new file mode 100644 index 00000000000..6c543aebcbc --- /dev/null +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditortransaction.h @@ -0,0 +1,31 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "textureeditorview.h" + +namespace QmlDesigner { + +class TextureEditorTransaction : public QObject +{ + Q_OBJECT + +public: + TextureEditorTransaction(TextureEditorView *textureEditor); + + Q_INVOKABLE void start(); + Q_INVOKABLE void end(); + + Q_INVOKABLE bool active() const; + +protected: + void timerEvent(QTimerEvent *event) override; + +private: + TextureEditorView *m_textureEditor = nullptr; + RewriterTransaction m_rewriterTransaction; + int m_timerId = -1; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp new file mode 100644 index 00000000000..6ff4f43cfef --- /dev/null +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp @@ -0,0 +1,897 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "textureeditorview.h" + +#include "textureeditorqmlbackend.h" +#include "textureeditorcontextobject.h" +#include "textureeditordynamicpropertiesproxymodel.h" +#include "propertyeditorvalue.h" +#include "textureeditortransaction.h" +#include "assetslibrarywidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace QmlDesigner { + +TextureEditorView::TextureEditorView(AsynchronousImageCache &imageCache, + ExternalDependenciesInterface &externalDependencies) + : AbstractView{externalDependencies} + , m_imageCache(imageCache) + , m_stackedWidget(new QStackedWidget) + , m_dynamicPropertiesModel(new Internal::DynamicPropertiesModel(true, this)) +{ + m_updateShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F12), m_stackedWidget); + connect(m_updateShortcut, &QShortcut::activated, this, &TextureEditorView::reloadQml); + + m_ensureMatLibTimer.callOnTimeout([this] { + if (model() && model()->rewriterView() && !model()->rewriterView()->hasIncompleteTypeInformation() + && model()->rewriterView()->errors().isEmpty()) { + ensureMaterialLibraryNode(); + if (m_qmlBackEnd && m_qmlBackEnd->contextObject()) + m_qmlBackEnd->contextObject()->setHasMaterialLibrary(materialLibraryNode().isValid()); + m_ensureMatLibTimer.stop(); + } + }); + + m_stackedWidget->setStyleSheet(Theme::replaceCssColors( + QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css")))); + m_stackedWidget->setMinimumWidth(250); + QmlDesignerPlugin::trackWidgetFocusTime(m_stackedWidget, Constants::EVENT_TEXTUREEDITOR_TIME); + + TextureEditorDynamicPropertiesProxyModel::registerDeclarativeType(); +} + +TextureEditorView::~TextureEditorView() +{ + qDeleteAll(m_qmlBackendHash); +} + +// from texture editor to model +void TextureEditorView::changeValue(const QString &name) +{ + PropertyName propertyName = name.toUtf8(); + + if (propertyName.isNull() || locked() || noValidSelection() || propertyName == "id" + || propertyName == Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY) { + return; + } + + PropertyName underscoreName(propertyName); + underscoreName.replace('.', '_'); + PropertyEditorValue *value = m_qmlBackEnd->propertyValueForName(QString::fromLatin1(underscoreName)); + + if (!value) + return; + + if (propertyName.endsWith("__AUX")) { + commitAuxValueToModel(propertyName, value->value()); + return; + } + + const NodeMetaInfo metaInfo = m_selectedTexture.metaInfo(); + + QVariant castedValue; + + if (auto property = metaInfo.property(propertyName)) { + castedValue = property.castedValue(value->value()); + } else { + qWarning() << __FUNCTION__ << propertyName << "cannot be casted (metainfo)"; + return; + } + + if (value->value().isValid() && !castedValue.isValid()) { + qWarning() << __FUNCTION__ << propertyName << "not properly casted (metainfo)"; + return; + } + + bool propertyTypeUrl = false; + + if (auto property = metaInfo.property(propertyName)) { + if (property.propertyType().isUrl()) { + // turn absolute local file paths into relative paths + propertyTypeUrl = true; + QString filePath = castedValue.toUrl().toString(); + QFileInfo fi(filePath); + if (fi.exists() && fi.isAbsolute()) { + QDir fileDir(QFileInfo(model()->fileUrl().toLocalFile()).absolutePath()); + castedValue = QUrl(fileDir.relativeFilePath(filePath)); + } + } + } + + if (name == "state" && castedValue.toString() == "base state") + castedValue = ""; + + if (castedValue.type() == QVariant::Color) { + QColor color = castedValue.value(); + QColor newColor = QColor(color.name()); + newColor.setAlpha(color.alpha()); + castedValue = QVariant(newColor); + } + + if (!value->value().isValid() || (propertyTypeUrl && value->value().toString().isEmpty())) { // reset + removePropertyFromModel(propertyName); + } else { + // QVector*D(0, 0, 0) detects as null variant though it is valid value + if (castedValue.isValid() + && (!castedValue.isNull() || castedValue.type() == QVariant::Vector2D + || castedValue.type() == QVariant::Vector3D + || castedValue.type() == QVariant::Vector4D)) { + commitVariantValueToModel(propertyName, castedValue); + } + } +} + +static bool isTrueFalseLiteral(const QString &expression) +{ + return (expression.compare("false", Qt::CaseInsensitive) == 0) + || (expression.compare("true", Qt::CaseInsensitive) == 0); +} + +void TextureEditorView::changeExpression(const QString &propertyName) +{ + PropertyName name = propertyName.toUtf8(); + + if (name.isNull() || locked() || noValidSelection()) + return; + + executeInTransaction("TextureEditorView::changeExpression", [this, name] { + PropertyName underscoreName(name); + underscoreName.replace('.', '_'); + + QmlObjectNode qmlObjectNode(m_selectedTexture); + PropertyEditorValue *value = m_qmlBackEnd->propertyValueForName(QString::fromLatin1(underscoreName)); + + if (!value) { + qWarning() << __FUNCTION__ << "no value for " << underscoreName; + return; + } + + if (auto property = m_selectedTexture.metaInfo().property(name)) { + auto propertyTypeName = property.propertyType().typeName(); + if (propertyTypeName == "QColor") { + if (QColor(value->expression().remove('"')).isValid()) { + qmlObjectNode.setVariantProperty(name, QColor(value->expression().remove('"'))); + return; + } + } else if (propertyTypeName == "bool") { + if (isTrueFalseLiteral(value->expression())) { + if (value->expression().compare("true", Qt::CaseInsensitive) == 0) + qmlObjectNode.setVariantProperty(name, true); + else + qmlObjectNode.setVariantProperty(name, false); + return; + } + } else if (propertyTypeName == "int") { + bool ok; + int intValue = value->expression().toInt(&ok); + if (ok) { + qmlObjectNode.setVariantProperty(name, intValue); + return; + } + } else if (propertyTypeName == "qreal") { + bool ok; + qreal realValue = value->expression().toDouble(&ok); + if (ok) { + qmlObjectNode.setVariantProperty(name, realValue); + return; + } + } else if (propertyTypeName == "QVariant") { + bool ok; + qreal realValue = value->expression().toDouble(&ok); + if (ok) { + qmlObjectNode.setVariantProperty(name, realValue); + return; + } else if (isTrueFalseLiteral(value->expression())) { + if (value->expression().compare("true", Qt::CaseInsensitive) == 0) + qmlObjectNode.setVariantProperty(name, true); + else + qmlObjectNode.setVariantProperty(name, false); + return; + } + } + } + + if (value->expression().isEmpty()) { + value->resetValue(); + return; + } + + if (qmlObjectNode.expression(name) != value->expression() || !qmlObjectNode.propertyAffectedByCurrentState(name)) + qmlObjectNode.setBindingProperty(name, value->expression()); + }); +} + +void TextureEditorView::exportPropertyAsAlias(const QString &name) +{ + if (name.isNull() || locked() || noValidSelection()) + return; + + executeInTransaction("TextureEditorView::exportPopertyAsAlias", [this, name] { + const QString id = m_selectedTexture.validId(); + QString upperCasePropertyName = name; + upperCasePropertyName.replace(0, 1, upperCasePropertyName.at(0).toUpper()); + QString aliasName = id + upperCasePropertyName; + aliasName.replace(".", ""); //remove all dots + + PropertyName propertyName = aliasName.toUtf8(); + if (rootModelNode().hasProperty(propertyName)) { + Core::AsynchronousMessageBox::warning(tr("Cannot Export Property as Alias"), + tr("Property %1 does already exist for root component.").arg(aliasName)); + return; + } + rootModelNode().bindingProperty(propertyName).setDynamicTypeNameAndExpression("alias", id + "." + name); + }); +} + +void TextureEditorView::removeAliasExport(const QString &name) +{ + if (name.isNull() || locked() || noValidSelection()) + return; + + executeInTransaction("TextureEditorView::removeAliasExport", [this, name] { + const QString id = m_selectedTexture.validId(); + + const QList bindingProps = rootModelNode().bindingProperties(); + for (const BindingProperty &property : bindingProps) { + if (property.expression() == (id + "." + name)) { + rootModelNode().removeProperty(property.name()); + break; + } + } + }); +} + +bool TextureEditorView::locked() const +{ + return m_locked; +} + +void TextureEditorView::currentTimelineChanged(const ModelNode &) +{ + m_qmlBackEnd->contextObject()->setHasActiveTimeline(QmlTimeline::hasActiveTimeline(this)); +} + +Internal::DynamicPropertiesModel *TextureEditorView::dynamicPropertiesModel() const +{ + return m_dynamicPropertiesModel; +} + +TextureEditorView *TextureEditorView::instance() +{ + static TextureEditorView *s_instance = nullptr; + + if (s_instance) + return s_instance; + + const auto views = QmlDesignerPlugin::instance()->viewManager().views(); + for (auto *view : views) { + TextureEditorView *myView = qobject_cast(view); + if (myView) + s_instance = myView; + } + + QTC_ASSERT(s_instance, return nullptr); + return s_instance; +} + +void TextureEditorView::timerEvent(QTimerEvent *timerEvent) +{ + if (m_timerId == timerEvent->timerId()) + resetView(); +} + +void TextureEditorView::resetView() +{ + if (!model()) + return; + + m_locked = true; + + if (m_timerId) + killTimer(m_timerId); + + setupQmlBackend(); + + if (m_qmlBackEnd) + m_qmlBackEnd->emitSelectionChanged(); + + m_locked = false; + + if (m_timerId) + m_timerId = 0; +} + +// static +QString TextureEditorView::textureEditorResourcesPath() +{ +#ifdef SHARE_QML_PATH + if (Utils::qtcEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) + return QLatin1String(SHARE_QML_PATH) + "/textureEditorQmlSource"; +#endif + return Core::ICore::resourcePath("qmldesigner/textureEditorQmlSource").toString(); +} + +void TextureEditorView::applyTextureToSelectedModel(const ModelNode &texture) +{ + if (!m_selectedModel.isValid()) + return; + + QTC_ASSERT(texture.isValid(), return); + + emitCustomNotification("apply_texture_to_model3D", {m_selectedModel, m_selectedTexture}); +} + +void TextureEditorView::handleToolBarAction(int action) +{ + QTC_ASSERT(m_hasQuick3DImport, return); + + switch (action) { + case TextureEditorContextObject::ApplyToSelected: { + applyTextureToSelectedModel(m_selectedTexture); + break; + } + + case TextureEditorContextObject::AddNewTexture: { + if (!model()) + break; + executeInTransaction("TextureEditorView:handleToolBarAction", [&] { + ModelNode matLib = materialLibraryNode(); + if (!matLib.isValid()) + return; + + NodeMetaInfo metaInfo = model()->metaInfo("QtQuick3D.Texture"); + ModelNode newTextureNode = createModelNode("QtQuick3D.Texture", metaInfo.majorVersion(), + metaInfo.minorVersion()); + newTextureNode.validId(); + matLib.defaultNodeListProperty().reparentHere(newTextureNode); + }); + break; + } + + case TextureEditorContextObject::DeleteCurrentTexture: { + if (m_selectedTexture.isValid()) + m_selectedTexture.destroy(); + break; + } + + case TextureEditorContextObject::OpenMaterialBrowser: { + QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("MaterialBrowser", true); + break; + } + } +} + +void TextureEditorView::setupQmlBackend() +{ + QUrl qmlPaneUrl; + QUrl qmlSpecificsUrl; + QString specificQmlData; + + if (m_selectedTexture.isValid() && m_hasQuick3DImport && (materialLibraryNode().isValid() || m_hasTextureRoot)) { + qmlPaneUrl = QUrl::fromLocalFile(textureEditorResourcesPath() + "/TextureEditorPane.qml"); + + TypeName diffClassName; + if (NodeMetaInfo metaInfo = m_selectedTexture.metaInfo()) { + diffClassName = metaInfo.typeName(); + for (const NodeMetaInfo &metaInfo : metaInfo.classHierarchy()) { + if (PropertyEditorQmlBackend::checkIfUrlExists(qmlSpecificsUrl)) + break; + qmlSpecificsUrl = PropertyEditorQmlBackend::getQmlFileUrl(metaInfo.typeName() + + "Specifics", metaInfo); + diffClassName = metaInfo.typeName(); + } + + if (diffClassName != m_selectedTexture.type()) { + specificQmlData = PropertyEditorQmlBackend::templateGeneration(metaInfo, + model()->metaInfo( + diffClassName), + m_selectedTexture); + } + } + } else { + qmlPaneUrl = QUrl::fromLocalFile(textureEditorResourcesPath() + "/EmptyTextureEditorPane.qml"); + } + + TextureEditorQmlBackend *currentQmlBackend = m_qmlBackendHash.value(qmlPaneUrl.toString()); + + QString currentStateName = currentState().isBaseState() ? currentState().name() : "invalid state"; + + if (!currentQmlBackend) { + currentQmlBackend = new TextureEditorQmlBackend(this, m_imageCache); + + m_stackedWidget->addWidget(currentQmlBackend->widget()); + m_qmlBackendHash.insert(qmlPaneUrl.toString(), currentQmlBackend); + + currentQmlBackend->setup(m_selectedTexture, currentStateName, qmlSpecificsUrl, this); + + currentQmlBackend->setSource(qmlPaneUrl); + + QObject *rootObj = currentQmlBackend->widget()->rootObject(); + QObject::connect(rootObj, SIGNAL(toolBarAction(int)), this, SLOT(handleToolBarAction(int))); + } else { + currentQmlBackend->setup(m_selectedTexture, currentStateName, qmlSpecificsUrl, this); + } + + currentQmlBackend->widget()->installEventFilter(this); + currentQmlBackend->contextObject()->setHasQuick3DImport(m_hasQuick3DImport); + currentQmlBackend->contextObject()->setHasMaterialLibrary(materialLibraryNode().isValid()); + currentQmlBackend->contextObject()->setSpecificQmlData(specificQmlData); + bool hasValidSelection = QmlObjectNode(m_selectedModel).hasBindingProperty("materials"); + currentQmlBackend->contextObject()->setHasSingleModelSelection(hasValidSelection); + + m_qmlBackEnd = currentQmlBackend; + + if (m_hasTextureRoot) + m_dynamicPropertiesModel->setSelectedNode(m_selectedTexture); + else + m_dynamicPropertiesModel->reset(); + + m_stackedWidget->setCurrentWidget(m_qmlBackEnd->widget()); +} + +void TextureEditorView::commitVariantValueToModel(const PropertyName &propertyName, const QVariant &value) +{ + m_locked = true; + executeInTransaction("TextureEditorView:commitVariantValueToModel", [&] { + QmlObjectNode(m_selectedTexture).setVariantProperty(propertyName, value); + }); + m_locked = false; +} + +void TextureEditorView::commitAuxValueToModel(const PropertyName &propertyName, const QVariant &value) +{ + m_locked = true; + + PropertyName name = propertyName; + name.chop(5); + + try { + if (value.isValid()) + m_selectedTexture.setAuxiliaryData(AuxiliaryDataType::Document, name, value); + else + m_selectedTexture.removeAuxiliaryData(AuxiliaryDataType::Document, name); + } + catch (const Exception &e) { + e.showException(); + } + m_locked = false; +} + +void TextureEditorView::removePropertyFromModel(const PropertyName &propertyName) +{ + m_locked = true; + executeInTransaction("MaterialEditorView:removePropertyFromModel", [&] { + QmlObjectNode(m_selectedTexture).removeProperty(propertyName); + }); + m_locked = false; +} + +bool TextureEditorView::noValidSelection() const +{ + QTC_ASSERT(m_qmlBackEnd, return true); + return !QmlObjectNode::isValidQmlObjectNode(m_selectedTexture); +} + +void TextureEditorView::modelAttached(Model *model) +{ + AbstractView::modelAttached(model); + + m_locked = true; + + m_hasQuick3DImport = model->hasImport("QtQuick3D"); + m_hasTextureRoot = rootModelNode().metaInfo().isQtQuick3DTexture(); + + if (m_hasTextureRoot) { + m_selectedTexture = rootModelNode(); + } else if (m_hasQuick3DImport) { + // Creating the material library node on model attach causes errors as long as the type + // information is not complete yet, so we keep checking until type info is complete. + m_ensureMatLibTimer.start(500); + } + + if (!m_setupCompleted) { + reloadQml(); + m_setupCompleted = true; + } + resetView(); + + m_locked = false; +} + +void TextureEditorView::modelAboutToBeDetached(Model *model) +{ + AbstractView::modelAboutToBeDetached(model); + m_dynamicPropertiesModel->reset(); + m_qmlBackEnd->textureEditorTransaction()->end(); + m_qmlBackEnd->contextObject()->setHasMaterialLibrary(false); +} + +void TextureEditorView::propertiesRemoved(const QList &propertyList) +{ + if (noValidSelection()) + return; + + for (const AbstractProperty &property : propertyList) { + ModelNode node(property.parentModelNode()); + + if (node.isRootNode()) + m_qmlBackEnd->contextObject()->setHasAliasExport(QmlObjectNode(m_selectedTexture).isAliasExported()); + + if (node == m_selectedTexture || QmlObjectNode(m_selectedTexture).propertyChangeForCurrentState() == node) { + // TODO: workaround for bug QDS-8539. To be removed once it is fixed. + if (node.metaInfo().property(property.name()).propertyType().isUrl()) { + resetPuppet(); + } else { + setValue(m_selectedTexture, property.name(), + QmlObjectNode(m_selectedTexture).instanceValue(property.name())); + } + } + + if (property.name() == "materials" && (node == m_selectedModel + || QmlObjectNode(m_selectedModel).propertyChangeForCurrentState() == node)) { + m_qmlBackEnd->contextObject()->setHasSingleModelSelection(false); + } + + dynamicPropertiesModel()->dispatchPropertyChanges(property); + } +} + +void TextureEditorView::variantPropertiesChanged(const QList &propertyList, PropertyChangeFlags /*propertyChange*/) +{ + if (noValidSelection()) + return; + + for (const VariantProperty &property : propertyList) { + ModelNode node(property.parentModelNode()); + if (node == m_selectedTexture || QmlObjectNode(m_selectedTexture).propertyChangeForCurrentState() == node) { + if (property.isDynamic()) + m_dynamicPropertiesModel->variantPropertyChanged(property); + if (m_selectedTexture.property(property.name()).isBindingProperty()) + setValue(m_selectedTexture, property.name(), QmlObjectNode(m_selectedTexture).instanceValue(property.name())); + else + setValue(m_selectedTexture, property.name(), QmlObjectNode(m_selectedTexture).modelValue(property.name())); + } + + dynamicPropertiesModel()->dispatchPropertyChanges(property); + } +} + +void TextureEditorView::bindingPropertiesChanged(const QList &propertyList, PropertyChangeFlags /*propertyChange*/) +{ + if (noValidSelection()) + return; + + for (const BindingProperty &property : propertyList) { + ModelNode node(property.parentModelNode()); + + if (property.isAliasExport()) + m_qmlBackEnd->contextObject()->setHasAliasExport(QmlObjectNode(m_selectedTexture).isAliasExported()); + + if (node == m_selectedTexture || QmlObjectNode(m_selectedTexture).propertyChangeForCurrentState() == node) { + if (property.isDynamic()) + m_dynamicPropertiesModel->bindingPropertyChanged(property); + if (QmlObjectNode(m_selectedTexture).modelNode().property(property.name()).isBindingProperty()) + setValue(m_selectedTexture, property.name(), QmlObjectNode(m_selectedTexture).instanceValue(property.name())); + else + setValue(m_selectedTexture, property.name(), QmlObjectNode(m_selectedTexture).modelValue(property.name())); + } + + if (property.name() == "materials" && (node == m_selectedModel + || QmlObjectNode(m_selectedModel).propertyChangeForCurrentState() == node)) { + bool hasMaterials = QmlObjectNode(m_selectedModel).hasBindingProperty("materials"); + m_qmlBackEnd->contextObject()->setHasSingleModelSelection(hasMaterials); + } + + dynamicPropertiesModel()->dispatchPropertyChanges(property); + } +} + +void TextureEditorView::auxiliaryDataChanged(const ModelNode &node, + AuxiliaryDataKeyView key, + const QVariant &) +{ + + if (noValidSelection() || !node.isSelected()) + return; + + m_qmlBackEnd->setValueforAuxiliaryProperties(m_selectedTexture, key); +} + +void TextureEditorView::propertiesAboutToBeRemoved(const QList &propertyList) +{ + for (const auto &property : propertyList) { + if (property.isBindingProperty()) + m_dynamicPropertiesModel->bindingRemoved(property.toBindingProperty()); + else if (property.isVariantProperty()) + m_dynamicPropertiesModel->variantRemoved(property.toVariantProperty()); + } +} + +void TextureEditorView::nodeReparented(const ModelNode &node, + [[maybe_unused]] const NodeAbstractProperty &newPropertyParent, + [[maybe_unused]] const NodeAbstractProperty &oldPropertyParent, + [[maybe_unused]] PropertyChangeFlags propertyChange) +{ + if (node.id() == Constants::MATERIAL_LIB_ID && m_qmlBackEnd && m_qmlBackEnd->contextObject()) + m_qmlBackEnd->contextObject()->setHasMaterialLibrary(true); +} + +void TextureEditorView::nodeAboutToBeRemoved(const ModelNode &removedNode) +{ + if (removedNode.id() == Constants::MATERIAL_LIB_ID && m_qmlBackEnd && m_qmlBackEnd->contextObject()) + m_qmlBackEnd->contextObject()->setHasMaterialLibrary(false); +} + +bool TextureEditorView::hasWidget() const +{ + return true; +} + +WidgetInfo TextureEditorView::widgetInfo() +{ + return createWidgetInfo(m_stackedWidget, + "TextureEditor", + WidgetInfo::RightPane, + 0, + tr("Texture Editor")); +} + +void TextureEditorView::selectedNodesChanged(const QList &selectedNodeList, + [[maybe_unused]] const QList &lastSelectedNodeList) +{ + m_selectedModel = {}; + + if (selectedNodeList.size() == 1 && selectedNodeList.at(0).metaInfo().isQtQuick3DModel()) + m_selectedModel = selectedNodeList.at(0); + + bool hasValidSelection = QmlObjectNode(m_selectedModel).hasBindingProperty("materials"); + m_qmlBackEnd->contextObject()->setHasSingleModelSelection(hasValidSelection); +} + +void TextureEditorView::currentStateChanged(const ModelNode &node) +{ + QmlModelState newQmlModelState(node); + Q_ASSERT(newQmlModelState.isValid()); + resetView(); +} + +void TextureEditorView::instancePropertyChanged(const QList> &propertyList) +{ + if (!m_selectedTexture.isValid() || !m_qmlBackEnd) + return; + + m_locked = true; + + for (const QPair &propertyPair : propertyList) { + const ModelNode modelNode = propertyPair.first; + const QmlObjectNode qmlObjectNode(modelNode); + const PropertyName propertyName = propertyPair.second; + + if (qmlObjectNode.isValid() && modelNode == m_selectedTexture && qmlObjectNode.currentState().isValid()) { + const AbstractProperty property = modelNode.property(propertyName); + if (!modelNode.hasProperty(propertyName) || modelNode.property(property.name()).isBindingProperty()) + setValue(modelNode, property.name(), qmlObjectNode.instanceValue(property.name())); + else + setValue(modelNode, property.name(), qmlObjectNode.modelValue(property.name())); + } + } + + m_locked = false; +} + +void TextureEditorView::importsChanged([[maybe_unused]] const QList &addedImports, + [[maybe_unused]] const QList &removedImports) +{ + m_hasQuick3DImport = model()->hasImport("QtQuick3D"); + m_qmlBackEnd->contextObject()->setHasQuick3DImport(m_hasQuick3DImport); + + if (m_hasQuick3DImport) + m_ensureMatLibTimer.start(500); + + resetView(); +} + +void TextureEditorView::duplicateTexture(const ModelNode &texture) +{ + QTC_ASSERT(texture.isValid(), return); + + if (!model()) + return; + + TypeName matType = texture.type(); + QmlObjectNode sourceTexture(texture); + ModelNode duplicateTextureNode; + QList dynamicProps; + + executeInTransaction(__FUNCTION__, [&] { + ModelNode matLib = materialLibraryNode(); + if (!matLib.isValid()) + return; + + // create the duplicate texture + NodeMetaInfo metaInfo = model()->metaInfo(matType); + QmlObjectNode duplicateTex = createModelNode(matType, metaInfo.majorVersion(), metaInfo.minorVersion()); + + duplicateTextureNode = duplicateTex .modelNode(); + duplicateTextureNode.validId(); + + // sync properties. Only the base state is duplicated. + const QList props = texture.properties(); + for (const AbstractProperty &prop : props) { + if (prop.name() == "objectName" || prop.name() == "data") + continue; + + if (prop.isVariantProperty()) { + if (prop.isDynamic()) { + dynamicProps.append(prop); + } else { + duplicateTextureNode.variantProperty(prop.name()) + .setValue(prop.toVariantProperty().value()); + } + } else if (prop.isBindingProperty()) { + if (prop.isDynamic()) { + dynamicProps.append(prop); + } else { + duplicateTextureNode.bindingProperty(prop.name()) + .setExpression(prop.toBindingProperty().expression()); + } + } + } + + matLib.defaultNodeListProperty().reparentHere(duplicateTex); + }); + + // For some reason, creating dynamic properties in the same transaction doesn't work, so + // let's do it in separate transaction. + // TODO: Fix the issue and merge transactions (QDS-8094) + if (!dynamicProps.isEmpty()) { + executeInTransaction(__FUNCTION__, [&] { + for (const AbstractProperty &prop : std::as_const(dynamicProps)) { + if (prop.isVariantProperty()) { + duplicateTextureNode.variantProperty(prop.name()) + .setDynamicTypeNameAndValue(prop.dynamicTypeName(), + prop.toVariantProperty().value()); + } else if (prop.isBindingProperty()) { + duplicateTextureNode.bindingProperty(prop.name()) + .setDynamicTypeNameAndExpression(prop.dynamicTypeName(), + prop.toBindingProperty().expression()); + } + } + }); + } +} + +void TextureEditorView::customNotification([[maybe_unused]] const AbstractView *view, + const QString &identifier, + const QList &nodeList, + [[maybe_unused]] const QList &data) +{ + if (identifier == "selected_texture_changed") { + if (!m_hasTextureRoot) { + m_selectedTexture = nodeList.first(); + m_dynamicPropertiesModel->setSelectedNode(m_selectedTexture); + QTimer::singleShot(0, this, &TextureEditorView::resetView); + } + } else if (identifier == "apply_texture_to_selected_model") { + applyTextureToSelectedModel(nodeList.first()); + } else if (identifier == "add_new_texture") { + handleToolBarAction(TextureEditorContextObject::AddNewTexture); + } else if (identifier == "duplicate_texture") { + duplicateTexture(nodeList.first()); + } +} + +void QmlDesigner::TextureEditorView::highlightSupportedProperties(bool highlight) +{ + if (!m_selectedTexture.isValid()) + return; + + DesignerPropertyMap &propMap = m_qmlBackEnd->backendValuesPropertyMap(); + const QStringList propNames = propMap.keys(); + NodeMetaInfo metaInfo = m_selectedTexture.metaInfo(); + QTC_ASSERT(metaInfo.isValid(), return); + + for (const QString &propName : propNames) { + if (metaInfo.property(propName.toUtf8()).propertyType().isQtQuick3DTexture()) { + QObject *propEditorValObj = propMap.value(propName).value(); + PropertyEditorValue *propEditorVal = qobject_cast(propEditorValObj); + propEditorVal->setHasActiveDrag(highlight); + } else if (metaInfo.property(propName.toUtf8()).propertyType().isUrl()) { + QObject *propEditorValObj = propMap.value(propName).value(); + PropertyEditorValue *propEditorVal = qobject_cast(propEditorValObj); + if (propEditorVal) + propEditorVal->setHasActiveDrag(highlight); + } + } +} + +void TextureEditorView::dragStarted(QMimeData *mimeData) +{ + if (!mimeData->hasFormat(Constants::MIME_TYPE_ASSETS)) + return; + + const QString assetPath = QString::fromUtf8(mimeData->data(Constants::MIME_TYPE_ASSETS)).split(',')[0]; + QString assetType = AssetsLibraryWidget::getAssetTypeAndData(assetPath).first; + + if (assetType != Constants::MIME_TYPE_ASSET_IMAGE) // currently only image assets have dnd-supported properties + return; + + highlightSupportedProperties(); + + const QString suffix = "*." + assetPath.split('.').last().toLower(); + m_qmlBackEnd->contextObject()->setActiveDragSuffix(suffix); +} + +void TextureEditorView::dragEnded() +{ + highlightSupportedProperties(false); + m_qmlBackEnd->contextObject()->setActiveDragSuffix(""); +} + +// from model to texture editor +void TextureEditorView::setValue(const QmlObjectNode &qmlObjectNode, const PropertyName &name, const QVariant &value) +{ + m_locked = true; + m_qmlBackEnd->setValue(qmlObjectNode, name, value); + m_locked = false; +} + +bool TextureEditorView::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::FocusOut) { + if (m_qmlBackEnd && m_qmlBackEnd->widget() == obj) + QMetaObject::invokeMethod(m_qmlBackEnd->widget()->rootObject(), "closeContextMenu"); + } + return QObject::eventFilter(obj, event); +} + +void TextureEditorView::reloadQml() +{ + m_qmlBackendHash.clear(); + while (QWidget *widget = m_stackedWidget->widget(0)) { + m_stackedWidget->removeWidget(widget); + delete widget; + } + m_qmlBackEnd = nullptr; + + resetView(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.h b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.h new file mode 100644 index 00000000000..39586cd8425 --- /dev/null +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.h @@ -0,0 +1,129 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QShortcut; +class QStackedWidget; +class QTimer; +class QColorDialog; +QT_END_NAMESPACE + +namespace QmlDesigner { + +class ModelNode; +class TextureEditorQmlBackend; + +namespace Internal { +class DynamicPropertiesModel; +} + +class TextureEditorView : public AbstractView +{ + Q_OBJECT + +public: + TextureEditorView(class AsynchronousImageCache &imageCache, + ExternalDependenciesInterface &externalDependencies); + ~TextureEditorView() override; + + bool hasWidget() const override; + WidgetInfo widgetInfo() override; + + void selectedNodesChanged(const QList &selectedNodeList, + const QList &lastSelectedNodeList) override; + + void propertiesRemoved(const QList &propertyList) override; + + void modelAttached(Model *model) override; + void modelAboutToBeDetached(Model *model) override; + + void variantPropertiesChanged(const QList &propertyList, PropertyChangeFlags propertyChange) override; + void bindingPropertiesChanged(const QList &propertyList, PropertyChangeFlags propertyChange) override; + void auxiliaryDataChanged(const ModelNode &node, + AuxiliaryDataKeyView key, + const QVariant &data) override; + void propertiesAboutToBeRemoved(const QList &propertyList) override; + void nodeReparented(const ModelNode &node, const NodeAbstractProperty &newPropertyParent, + const NodeAbstractProperty &oldPropertyParent, + AbstractView::PropertyChangeFlags propertyChange) override; + void nodeAboutToBeRemoved(const ModelNode &removedNode) override; + + void resetView(); + void currentStateChanged(const ModelNode &node) override; + void instancePropertyChanged(const QList > &propertyList) override; + + void importsChanged(const QList &addedImports, const QList &removedImports) override; + void customNotification(const AbstractView *view, const QString &identifier, + const QList &nodeList, const QList &data) override; + + void dragStarted(QMimeData *mimeData) override; + void dragEnded() override; + + void changeValue(const QString &name); + void changeExpression(const QString &name); + void exportPropertyAsAlias(const QString &name); + void removeAliasExport(const QString &name); + + bool locked() const; + + void currentTimelineChanged(const ModelNode &node) override; + + Internal::DynamicPropertiesModel *dynamicPropertiesModel() const; + + static TextureEditorView *instance(); + +public slots: + void handleToolBarAction(int action); + +protected: + void timerEvent(QTimerEvent *event) override; + void setValue(const QmlObjectNode &fxObjectNode, const PropertyName &name, const QVariant &value); + bool eventFilter(QObject *obj, QEvent *event) override; + +private: + static QString textureEditorResourcesPath(); + + void reloadQml(); + void highlightSupportedProperties(bool highlight = true); + + void applyTextureToSelectedModel(const ModelNode &texture); + + void setupQmlBackend(); + + void commitVariantValueToModel(const PropertyName &propertyName, const QVariant &value); + void commitAuxValueToModel(const PropertyName &propertyName, const QVariant &value); + void removePropertyFromModel(const PropertyName &propertyName); + void duplicateTexture(const ModelNode &texture); + + bool noValidSelection() const; + + AsynchronousImageCache &m_imageCache; + ModelNode m_selectedTexture; + QTimer m_ensureMatLibTimer; + QShortcut *m_updateShortcut = nullptr; + int m_timerId = 0; + QStackedWidget *m_stackedWidget = nullptr; + ModelNode m_selectedModel; + QHash m_qmlBackendHash; + TextureEditorQmlBackend *m_qmlBackEnd = nullptr; + bool m_locked = false; + bool m_setupCompleted = false; + bool m_hasQuick3DImport = false; + bool m_hasTextureRoot = false; + bool m_initializingPreviewData = false; + + QPointer m_colorDialog; + QPointer m_itemLibraryInfo; + Internal::DynamicPropertiesModel *m_dynamicPropertiesModel = nullptr; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineconstants.h b/src/plugins/qmldesigner/components/timelineeditor/timelineconstants.h index 6324372d5b7..38c16e41bbc 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelineconstants.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelineconstants.h @@ -20,7 +20,6 @@ const int timelineBounds = 8; const int timelineLeftOffset = 10; const char timelineCategory[] = "Timeline"; -const int priorityTimelineCategory = 110; const char timelineCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Timeline"); const char timelineCopyKeyframesDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", @@ -28,7 +27,7 @@ const char timelineCopyKeyframesDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerCo const char timelinePasteKeyframesDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Paste Keyframes"); const char timelineInsertKeyframesDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", - "Add Keyframes at Current Frame"); + "Add Keyframe"); const char timelineDeleteKeyframesDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Delete All Keyframes"); diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp index c3aa2a18283..6249ce94484 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp @@ -584,7 +584,7 @@ void TimelineView::registerActions() actionManager.addDesignerAction(new ActionGroup(TimelineConstants::timelineCategoryDisplayName, TimelineConstants::timelineCategory, - TimelineConstants::priorityTimelineCategory, + ComponentCoreConstants::Priorities::TimelineCategory, timelineEnabled, &SelectionContextFunctors::always)); @@ -594,7 +594,7 @@ void TimelineView::registerActions() {}, TimelineConstants::timelineCategory, QKeySequence(), - 160, + 3, deleteKeyframes, timelineHasKeyframes)); @@ -604,7 +604,7 @@ void TimelineView::registerActions() {}, TimelineConstants::timelineCategory, QKeySequence(), - 140, + 1, insertKeyframes, timelineHasKeyframes)); @@ -614,7 +614,7 @@ void TimelineView::registerActions() {}, TimelineConstants::timelineCategory, QKeySequence(), - 120, + 4, copyKeyframes, timelineHasKeyframes)); @@ -624,7 +624,7 @@ void TimelineView::registerActions() {}, TimelineConstants::timelineCategory, QKeySequence(), - 100, + 5, pasteKeyframes, timelineHasClipboard)); } diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.cpp b/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.cpp index be176760491..d762299f1cb 100644 --- a/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.cpp +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.cpp @@ -14,6 +14,7 @@ #include #include +#include #include namespace QmlDesigner { @@ -103,7 +104,11 @@ void ImageCacheCollector::start(Utils::SmallStringView name, auto callback = [=, captureCallback = std::move(captureCallback)](const QImage &image) { if (nullImageHandling == ImageCacheCollectorNullImageHandling::CaptureNullImage || !image.isNull()) { - QSize smallImageSize = image.size().scaled(QSize{96, 96}.boundedTo(image.size()), + QSize targetSize {96, 96}; + const qreal ratio = qGuiApp->devicePixelRatio(); + if (ratio > 1.0) + targetSize *= qRound(ratio); + QSize smallImageSize = image.size().scaled(targetSize.boundedTo(image.size()), Qt::KeepAspectRatio); QImage smallImage = image.isNull() ? QImage{} : image.scaled(smallImageSize, diff --git a/src/plugins/qmldesigner/designercore/imagecache/smallimagecacheprovider.h b/src/plugins/qmldesigner/designercore/imagecache/smallimagecacheprovider.h index 1a608c626c9..54b3b63f1d5 100644 --- a/src/plugins/qmldesigner/designercore/imagecache/smallimagecacheprovider.h +++ b/src/plugins/qmldesigner/designercore/imagecache/smallimagecacheprovider.h @@ -20,6 +20,7 @@ public: QQuickTextureFactory *textureFactory() const override; void setImage(const QImage &image); + QImage image() const { return m_image; } void abort(); @@ -37,6 +38,7 @@ public: QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override; + QImage defaultImage() const { return m_defaultImage; } private: AsynchronousImageCache &m_cache; diff --git a/src/plugins/qmldesigner/designercore/include/abstractview.h b/src/plugins/qmldesigner/designercore/include/abstractview.h index 2afc56e6e0a..55f7ddcf9d0 100644 --- a/src/plugins/qmldesigner/designercore/include/abstractview.h +++ b/src/plugins/qmldesigner/designercore/include/abstractview.h @@ -230,6 +230,7 @@ public: ModelNode materialLibraryNode(); ModelNode active3DSceneNode(); void assignMaterialTo3dModel(const ModelNode &modelNode, const ModelNode &materialNode = {}); + ModelNode getTextureDefaultInstance(const QString &source); const NodeInstanceView *nodeInstanceView() const; RewriterView *rewriterView() const; diff --git a/src/plugins/qmldesigner/designercore/include/auxiliarydataproperties.h b/src/plugins/qmldesigner/designercore/include/auxiliarydataproperties.h index 41889045f2e..665ec0bcb42 100644 --- a/src/plugins/qmldesigner/designercore/include/auxiliarydataproperties.h +++ b/src/plugins/qmldesigner/designercore/include/auxiliarydataproperties.h @@ -81,6 +81,12 @@ inline constexpr AuxiliaryDataKeyDefaultValue areaFillColorProperty{AuxiliaryDat inline constexpr AuxiliaryDataKeyDefaultValue fillColorProperty{AuxiliaryDataType::Document, "fillColor", QColor{0, 0, 0, 0}}; +inline constexpr AuxiliaryDataKeyDefaultValue insightEnabledProperty{AuxiliaryDataType::Temporary, + "insightEnabled", + false}; +inline constexpr AuxiliaryDataKeyDefaultValue insightCategoriesProperty{AuxiliaryDataType::Temporary, + "insightCategories", + {}}; inline constexpr AuxiliaryDataKeyView uuidProperty{AuxiliaryDataType::Document, "uuid"}; inline constexpr AuxiliaryDataKeyView active3dSceneProperty{AuxiliaryDataType::Temporary, "active3dScene"}; diff --git a/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h b/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h index cd9d37ec679..3126dc00537 100644 --- a/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h +++ b/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h @@ -47,6 +47,7 @@ public: QString requiredImport() const; QString customComponentSource() const; QStringList extraFilePaths() const; + QString toolTip() const; using Property = QmlDesigner::PropertyContainer; @@ -62,6 +63,7 @@ public: void setCategory(const QString &category); void setQmlPath(const QString &qml); void setRequiredImport(const QString &requiredImport); + void setToolTip(const QString &tooltip); void addHints(const QHash &hints); void setCustomComponentSource(const QString &source); void addExtraFilePath(const QString &extraFile); diff --git a/src/plugins/qmldesigner/designercore/include/model.h b/src/plugins/qmldesigner/designercore/include/model.h index e22dbe70b6e..336a5b2d280 100644 --- a/src/plugins/qmldesigner/designercore/include/model.h +++ b/src/plugins/qmldesigner/designercore/include/model.h @@ -149,6 +149,7 @@ public: QString generateIdFromName(const QString &name, const QString &fallbackId = "element") const; void setActive3DSceneId(qint32 sceneId); + qint32 active3DSceneId() const; void startDrag(QMimeData *mimeData, const QPixmap &icon); void endDrag(); diff --git a/src/plugins/qmldesigner/designercore/include/qmlitemnode.h b/src/plugins/qmldesigner/designercore/include/qmlitemnode.h index 61c38dd5042..a0d12104446 100644 --- a/src/plugins/qmldesigner/designercore/include/qmlitemnode.h +++ b/src/plugins/qmldesigner/designercore/include/qmlitemnode.h @@ -60,9 +60,19 @@ public: const QPointF &position, NodeAbstractProperty parentproperty, bool executeInTransaction = true); - static void createQmlItemNodeForEffect(AbstractView *view, - const QmlItemNode &parentNode, - const QString &effectName); + + static QmlItemNode createQmlItemNodeForEffect(AbstractView *view, + QmlItemNode parentQmlItemNode, + const QString &effectPath, + bool isLayerEffect); + static QmlItemNode createQmlItemNodeForEffect(AbstractView *view, + NodeAbstractProperty parentProperty, + const QString &effectPath, + bool isLayerEffect); + static void placeEffectNode(NodeAbstractProperty &parentProperty, + const QmlItemNode &effectNode, + bool isLayerEffect); + QList children() const; QList resources() const; QList allDirectSubNodes() const; diff --git a/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h b/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h index 5dd4d32ecaa..0d47b7e36bc 100644 --- a/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h +++ b/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h @@ -69,6 +69,7 @@ public: QVariant modelValue(const PropertyName &name) const; bool isTranslatableText(const PropertyName &name) const; QString stripedTranslatableText(const PropertyName &name) const; + BindingProperty bindingProperty(const PropertyName &name) const; QString expression(const PropertyName &name) const; bool isInBaseState() const; bool timelineIsActive() const; diff --git a/src/plugins/qmldesigner/designercore/include/rewriterview.h b/src/plugins/qmldesigner/designercore/include/rewriterview.h index d95d811261c..56dc01c215d 100644 --- a/src/plugins/qmldesigner/designercore/include/rewriterview.h +++ b/src/plugins/qmldesigner/designercore/include/rewriterview.h @@ -167,6 +167,9 @@ public: void resetPossibleImports(); + bool possibleImportsEnabled() const; + void setPossibleImportsEnabled(bool b); + signals: void modelInterfaceProjectUpdated(); @@ -212,6 +215,7 @@ private: //variables bool m_restoringAuxData = false; bool m_modelAttachPending = false; bool m_allowComponentRoot = false; + bool m_possibleImportsEnabled = true; mutable QHash m_canonicalIntModelNode; mutable QHash m_canonicalModelNodeInt; diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp index 67bbc93a10f..661b303a940 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp @@ -1908,6 +1908,7 @@ QVariant NodeInstanceView::previewImageDataForImageNode(const ModelNode &modelNo const int dim = Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * ratio; imageData.pixmap = originalPixmap.scaled(dim, dim, Qt::KeepAspectRatio); imageData.pixmap.setDevicePixelRatio(ratio); + imageData.time = modified; double imgSize = double(imageFi.size()); static QStringList units({::QmlDesigner::NodeInstanceView::tr("B"), diff --git a/src/plugins/qmldesigner/designercore/metainfo/itemlibraryinfo.cpp b/src/plugins/qmldesigner/designercore/metainfo/itemlibraryinfo.cpp index a96fac3ad09..ffdf282ce1e 100644 --- a/src/plugins/qmldesigner/designercore/metainfo/itemlibraryinfo.cpp +++ b/src/plugins/qmldesigner/designercore/metainfo/itemlibraryinfo.cpp @@ -3,6 +3,7 @@ #include "itemlibraryinfo.h" #include "nodemetainfo.h" +#include "qregularexpression.h" #include @@ -32,6 +33,7 @@ public: QHash hints; QString customComponentSource; QStringList extraFilePaths; + QString toolTip; }; } // namespace Internal @@ -96,6 +98,11 @@ QStringList ItemLibraryEntry::extraFilePaths() const return m_data->extraFilePaths; } +QString ItemLibraryEntry::toolTip() const +{ + return m_data->toolTip; +} + int ItemLibraryEntry::majorVersion() const { return m_data->majorVersion; @@ -165,6 +172,16 @@ void ItemLibraryEntry::setRequiredImport(const QString &requiredImport) m_data->requiredImport = requiredImport; } +void ItemLibraryEntry::setToolTip(const QString &tooltip) +{ + static QRegularExpression regularExpressionPattern(QLatin1String("^qsTr\\(\"(.*)\"\\)$")); + const QRegularExpressionMatch match = regularExpressionPattern.match(tooltip); + if (match.hasMatch()) + m_data->toolTip = match.captured(1); + else + m_data->toolTip = tooltip; +} + void ItemLibraryEntry::addHints(const QHash &hints) { Utils::addToHash(&m_data->hints, hints); diff --git a/src/plugins/qmldesigner/designercore/metainfo/metainforeader.cpp b/src/plugins/qmldesigner/designercore/metainfo/metainforeader.cpp index 862db6095b6..91fb7efab91 100644 --- a/src/plugins/qmldesigner/designercore/metainfo/metainforeader.cpp +++ b/src/plugins/qmldesigner/designercore/metainfo/metainforeader.cpp @@ -266,6 +266,8 @@ void MetaInfoReader::readItemLibraryEntryProperty(const QString &name, const QVa setVersion(value.toString()); } else if (name == QStringLiteral("requiredImport")) { m_currentEntry.setRequiredImport(value.toString()); + } else if (name == QStringLiteral("toolTip")) { + m_currentEntry.setToolTip(value.toString()); } else { addError(::QmlDesigner::Internal::MetaInfoReader::tr( "Unknown property for ItemLibraryEntry %1") diff --git a/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp b/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp index 927b762ffec..cdda880f84b 100644 --- a/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp +++ b/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp @@ -149,7 +149,7 @@ static QString qualifiedTypeNameForContext(const ObjectValue *objectValue, if (!cImport.valid()) break; for (const Export &e : std::as_const(cImport.possibleExports)) { - if (e.pathRequired.isEmpty() || vContext.paths.contains(e.pathRequired)) { + if (e.pathRequired.isEmpty() || vContext.paths.count(e.pathRequired) > 0) { switch (e.exportName.type) { case ImportType::Library: { diff --git a/src/plugins/qmldesigner/designercore/model/abstractview.cpp b/src/plugins/qmldesigner/designercore/model/abstractview.cpp index 8f937f97958..20d39362a0e 100644 --- a/src/plugins/qmldesigner/designercore/model/abstractview.cpp +++ b/src/plugins/qmldesigner/designercore/model/abstractview.cpp @@ -806,8 +806,11 @@ void AbstractView::changeRootNodeType(const TypeName &type, int majorVersion, in void AbstractView::ensureMaterialLibraryNode() { ModelNode matLib = modelNodeForId(Constants::MATERIAL_LIB_ID); - if (matLib.isValid() || rootModelNode().metaInfo().isQtQuick3DMaterial()) + if (matLib.isValid() + || (!rootModelNode().metaInfo().isQtQuick3DNode() + && !rootModelNode().metaInfo().isQtQuickItem())) { return; + } executeInTransaction(__FUNCTION__, [&] { // Create material library node @@ -912,6 +915,30 @@ void AbstractView::assignMaterialTo3dModel(const ModelNode &modelNode, const Mod modelMatsProp.setExpression(newMaterialNode.id()); } +ModelNode AbstractView::getTextureDefaultInstance(const QString &source) +{ + ModelNode matLib = materialLibraryNode(); + if (!matLib.isValid()) + return {}; + + const QList matLibNodes = matLib.directSubModelNodes(); + for (const ModelNode &tex : matLibNodes) { + if (tex.isValid() && tex.metaInfo().isQtQuick3DTexture()) { + const QList props = tex.properties(); + if (props.size() != 1) + continue; + const AbstractProperty &prop = props[0]; + if (prop.name() == "source" && prop.isVariantProperty() + && prop.toVariantProperty().value().toString() == source) { + return tex; + } + } + } + + return {}; +} + + ModelNode AbstractView::currentStateNode() const { if (model()) diff --git a/src/plugins/qmldesigner/designercore/model/model.cpp b/src/plugins/qmldesigner/designercore/model/model.cpp index f3d8f403d61..0909afabac1 100644 --- a/src/plugins/qmldesigner/designercore/model/model.cpp +++ b/src/plugins/qmldesigner/designercore/model/model.cpp @@ -1596,6 +1596,14 @@ void Model::setActive3DSceneId(qint32 sceneId) d->notifyActive3DSceneIdChanged(sceneId); } +qint32 Model::active3DSceneId() const +{ + auto sceneId = d->rootNode()->auxiliaryData(active3dSceneProperty); + if (sceneId) + return sceneId->toInt(); + return -1; +} + void Model::startDrag(QMimeData *mimeData, const QPixmap &icon) { d->notifyDragStarted(mimeData); diff --git a/src/plugins/qmldesigner/designercore/model/qmlconnections.cpp b/src/plugins/qmldesigner/designercore/model/qmlconnections.cpp index 7ba31a8fcc0..aae9528eb64 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlconnections.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlconnections.cpp @@ -99,7 +99,7 @@ QList QmlConnections::signalProperties() const ModelNode QmlConnections::createQmlConnections(AbstractView *view) { - NodeMetaInfo nodeMetaInfo = view->model()->metaInfo("QtQuick.Connections"); + NodeMetaInfo nodeMetaInfo = view->model()->qtQuickConnectionsMetaInfo(); return view->createModelNode("QtQuick.Connections", nodeMetaInfo.majorVersion(), nodeMetaInfo.minorVersion()); diff --git a/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp b/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp index ca2bb3c5a51..4570e5fc432 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp @@ -69,8 +69,10 @@ QmlItemNode QmlItemNode::createQmlItemNodeFromImage(AbstractView *view, const QS auto doCreateQmlItemNodeFromImage = [=, &newQmlItemNode, &parentproperty]() { NodeMetaInfo metaInfo = view->model()->metaInfo("QtQuick.Image"); QList > propertyPairList; - propertyPairList.append({PropertyName("x"), QVariant(qRound(position.x()))}); - propertyPairList.append({PropertyName("y"), QVariant(qRound(position.y()))}); + if (const int intX = qRound(position.x())) + propertyPairList.append({PropertyName("x"), QVariant(intX)}); + if (const int intY = qRound(position.y())) + propertyPairList.append({PropertyName("y"), QVariant(intY)}); QString relativeImageName = imageName; @@ -131,8 +133,10 @@ QmlItemNode QmlItemNode::createQmlItemNodeFromFont(AbstractView *view, auto doCreateQmlItemNodeFromFont = [=, &newQmlItemNode, &parentproperty]() { NodeMetaInfo metaInfo = view->model()->metaInfo("QtQuick.Text"); QList> propertyPairList; - propertyPairList.append({PropertyName("x"), QVariant(qRound(position.x()))}); - propertyPairList.append({PropertyName("y"), QVariant(qRound(position.y()))}); + if (const int intX = qRound(position.x())) + propertyPairList.append({PropertyName("x"), QVariant(intX)}); + if (const int intY = qRound(position.y())) + propertyPairList.append({PropertyName("y"), QVariant(intY)}); propertyPairList.append({PropertyName("font.family"), QVariant(fontFamily)}); propertyPairList.append({PropertyName("font.pointSize"), 20}); propertyPairList.append({PropertyName("text"), QVariant(fontFamily)}); @@ -154,42 +158,61 @@ QmlItemNode QmlItemNode::createQmlItemNodeFromFont(AbstractView *view, return newQmlItemNode; } -static bool useLayerEffect() +QmlItemNode QmlItemNode::createQmlItemNodeForEffect(AbstractView *view, + QmlItemNode parentQmlItemNode, + const QString &effectPath, + bool isLayerEffect) { - QSettings *settings = Core::ICore::settings(); - const QString layerEffectEntry = "QML/Designer/UseLayerEffect"; + if (!parentQmlItemNode.isValid()) + parentQmlItemNode = QmlItemNode(view->rootModelNode()); - return settings->value(layerEffectEntry, true).toBool(); + NodeAbstractProperty parentProperty = isLayerEffect + ? parentQmlItemNode.nodeAbstractProperty("layer.effect") + : parentQmlItemNode.defaultNodeAbstractProperty(); + + return createQmlItemNodeForEffect(view, parentProperty, effectPath, isLayerEffect); } -void QmlItemNode::createQmlItemNodeForEffect(AbstractView *view, - const QmlItemNode &parentNode, - const QString &effectName) +QmlItemNode QmlItemNode::createQmlItemNodeForEffect(AbstractView *view, + NodeAbstractProperty parentProperty, + const QString &effectPath, + bool isLayerEffect) { QmlItemNode newQmlItemNode; - const bool layerEffect = useLayerEffect(); + auto createEffectNode = [=, &newQmlItemNode, &parentProperty]() { + const QString effectName = QFileInfo(effectPath).baseName(); + Import import = Import::createLibraryImport("Effects." + effectName, "1.0"); + try { + if (!view->model()->hasImport(import, true, true)) + view->model()->changeImports({import}, {}); + } catch (const Exception &) { + QTC_ASSERT(false, return); + } - QmlDesigner::Import import = Import::createLibraryImport("Effects." + effectName, "1.0"); - try { - if (!view->model()->hasImport(import, true, true)) - view->model()->changeImports({import}, {}); - } catch (const Exception &) { - QTC_ASSERT(false, return); + TypeName type(effectName.toUtf8()); + newQmlItemNode = QmlItemNode(view->createModelNode(type, -1, -1)); + + placeEffectNode(parentProperty, newQmlItemNode, isLayerEffect); + }; + + view->executeInTransaction("QmlItemNode::createQmlItemNodeFromEffect", createEffectNode); + return newQmlItemNode; +} + +void QmlItemNode::placeEffectNode(NodeAbstractProperty &parentProperty, const QmlItemNode &effectNode, bool isLayerEffect) { + if (isLayerEffect && !parentProperty.isEmpty()) { // already contains a node + ModelNode oldEffect = parentProperty.toNodeProperty().modelNode(); + QmlObjectNode(oldEffect).destroy(); } - TypeName type(effectName.toUtf8()); - newQmlItemNode = QmlItemNode(view->createModelNode(type, 1, 0)); - NodeAbstractProperty parentProperty = layerEffect - ? parentNode.nodeAbstractProperty("layer.effect") - : parentNode.defaultNodeAbstractProperty(); - parentProperty.reparentHere(newQmlItemNode); + parentProperty.reparentHere(effectNode); - if (!layerEffect) { - newQmlItemNode.modelNode().bindingProperty("source").setExpression("parent"); - newQmlItemNode.modelNode().bindingProperty("anchors.fill").setExpression("parent"); + if (!isLayerEffect) { + effectNode.modelNode().bindingProperty("source").setExpression("parent"); + effectNode.modelNode().bindingProperty("anchors.fill").setExpression("parent"); } else { - parentNode.modelNode().variantProperty("layer.enabled").setValue(true); + parentProperty.parentModelNode().variantProperty("layer.enabled").setValue(true); } } diff --git a/src/plugins/qmldesigner/designercore/model/qmlobjectnode.cpp b/src/plugins/qmldesigner/designercore/model/qmlobjectnode.cpp index c2e6ce107dc..b64cf2612ee 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlobjectnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlobjectnode.cpp @@ -256,23 +256,28 @@ QString QmlObjectNode::stripedTranslatableText(const PropertyName &name) const return instanceValue(name).toString(); } -QString QmlObjectNode::expression(const PropertyName &name) const +BindingProperty QmlObjectNode::bindingProperty(const PropertyName &name) const { if (!isValid()) return {}; if (currentState().isBaseState()) - return modelNode().bindingProperty(name).expression(); + return modelNode().bindingProperty(name); if (!currentState().hasPropertyChanges(modelNode())) - return modelNode().bindingProperty(name).expression(); + return modelNode().bindingProperty(name); QmlPropertyChanges propertyChanges(currentState().propertyChanges(modelNode())); if (!propertyChanges.modelNode().hasProperty(name)) - return modelNode().bindingProperty(name).expression(); + return modelNode().bindingProperty(name); - return propertyChanges.modelNode().bindingProperty(name).expression(); + return propertyChanges.modelNode().bindingProperty(name); +} + +QString QmlObjectNode::expression(const PropertyName &name) const +{ + return bindingProperty(name).expression(); } /*! diff --git a/src/plugins/qmldesigner/designercore/model/qmlstate.cpp b/src/plugins/qmldesigner/designercore/model/qmlstate.cpp index bba6d9c1d3a..e620cd948c2 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlstate.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlstate.cpp @@ -254,17 +254,31 @@ QmlModelState QmlModelState::duplicate(const QString &name) const if (!isValid()) return {}; -// QmlModelState newState(stateGroup().addState(name)); QmlModelState newState(createQmlState(view(), {{PropertyName("name"), QVariant(name)}})); + + if (hasExtend()) + newState.setExtend(extend()); + const QList nodes = modelNode().nodeListProperty("changes").toModelNodeList(); for (const ModelNode &childNode : nodes) { - ModelNode newModelNode(view()->createModelNode(childNode.type(), childNode.majorVersion(), childNode.minorVersion())); + ModelNode newModelNode(view()->createModelNode(childNode.type(), + childNode.majorVersion(), + childNode.minorVersion())); + + for (const BindingProperty &bindingProperty : childNode.bindingProperties()) { + auto property = newModelNode.bindingProperty(bindingProperty.name()); + property.setExpression(bindingProperty.expression()); + } + const QList bindingProperties = childNode.bindingProperties(); for (const BindingProperty &bindingProperty : bindingProperties) - newModelNode.bindingProperty(bindingProperty.name()).setExpression(bindingProperty.expression()); - const QList variantProperties = childNode.variantProperties(); - for (const VariantProperty &variantProperty : variantProperties) - newModelNode.variantProperty(variantProperty.name()).setValue(variantProperty.value()); + newModelNode.bindingProperty(bindingProperty.name()) + .setExpression(bindingProperty.expression()); + + for (const VariantProperty &variantProperty : childNode.variantProperties()) { + auto property = newModelNode.variantProperty(variantProperty.name()); + property.setValue(variantProperty.value()); + } newState.modelNode().nodeListProperty("changes").reparentHere(newModelNode); } diff --git a/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp b/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp index 36edea4e9ea..8d08eaa8069 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp @@ -209,14 +209,19 @@ void QmlVisualNode::setDoubleProperty(const PropertyName &name, double value) void QmlVisualNode::setPosition(const QmlVisualNode::Position &position) { - if (!isValid()) + if (!modelNode().isValid()) return; - setDoubleProperty("x", position.x()); - setDoubleProperty("y", position.y()); + if (!qFuzzyIsNull(position.x()) || modelNode().hasProperty("x")) + setDoubleProperty("x", position.x()); + if (!qFuzzyIsNull(position.y()) || modelNode().hasProperty("y")) + setDoubleProperty("y", position.y()); - if (position.is3D() && modelNode().metaInfo().isQtQuick3DNode()) + if (position.is3D() + && (!qFuzzyIsNull(position.z()) || modelNode().hasProperty("z")) + && modelNode().metaInfo().isQtQuick3DNode()) { setDoubleProperty("z", position.z()); + } } QmlVisualNode::Position QmlVisualNode::position() const @@ -566,11 +571,19 @@ QList > QmlVisualNode::Position::propertyPairList( { QList > propertyPairList; - propertyPairList.append({"x", QVariant(qRound(x()))}); - propertyPairList.append({"y", QVariant(qRound(y()))}); - - if (m_is3D) - propertyPairList.append({"z", QVariant(z())}); + if (m_is3D) { + if (!qFuzzyIsNull(x())) + propertyPairList.append({"x", QVariant{x()}}); + if (!qFuzzyIsNull(y())) + propertyPairList.append({"y", QVariant{y()}}); + if (!qFuzzyIsNull(z())) + propertyPairList.append({"z", QVariant{z()}}); + } else { + if (const int intX = qRound(x())) + propertyPairList.append({"x", QVariant(intX)}); + if (const int intY = qRound(y())) + propertyPairList.append({"y", QVariant(intY)}); + } return propertyPairList; } diff --git a/src/plugins/qmldesigner/designercore/model/rewriterview.cpp b/src/plugins/qmldesigner/designercore/model/rewriterview.cpp index 1a09a070fd3..4a9ab62814c 100644 --- a/src/plugins/qmldesigner/designercore/model/rewriterview.cpp +++ b/src/plugins/qmldesigner/designercore/model/rewriterview.cpp @@ -693,6 +693,16 @@ void RewriterView::resetPossibleImports() m_textToModelMerger->clearPossibleImportKeys(); } +bool RewriterView::possibleImportsEnabled() const +{ + return m_possibleImportsEnabled; +} + +void RewriterView::setPossibleImportsEnabled(bool b) +{ + m_possibleImportsEnabled = b; +} + Internal::ModelNodePositionStorage *RewriterView::positionStorage() const { return m_positionStorage.data(); @@ -1005,8 +1015,10 @@ QString RewriterView::pathForImport(const Import &import) QStringList RewriterView::importDirectories() const { - return Utils::transform(m_textToModelMerger->vContext().paths, - [](const Utils::FilePath &p) { return p.toString(); }); + const QList list(m_textToModelMerger->vContext().paths.begin(), + m_textToModelMerger->vContext().paths.end()); + + return Utils::transform(list, [](const Utils::FilePath &p) { return p.toString(); }); } QSet > RewriterView::qrcMapping() const diff --git a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp index 9857451a4e8..8070319a2d4 100644 --- a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp +++ b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp @@ -54,7 +54,8 @@ namespace { bool isSupportedAttachedProperties(const QString &propertyName) { - return propertyName.startsWith(QLatin1String("Layout.")); + return propertyName.startsWith(QLatin1String("Layout.")) + || propertyName.startsWith(QLatin1String("InsightCategory.")); } QStringList supportedVersionsList() @@ -498,8 +499,6 @@ public: qDebug() << Q_FUNC_INFO; qDebug() << astTypeNode->name.toString() << typeName; qDebug() << metaInfo.isValid() << metaInfo.typeName(); - if (metaInfo.isValid()) - qDebug() << metaInfo.superClasses().front().typeName(); } typeName = QString::fromUtf8(metaInfo.typeName()); @@ -986,6 +985,9 @@ static QList generatePossibleLibraryImports(const QHashpossibleImportsEnabled()) + return; + static QUrl lastProjectUrl; auto &externalDependencies = m_rewriterView->externalDependencies(); auto projectUrl = externalDependencies.projectUrl(); diff --git a/src/plugins/qmldesigner/generateresource.cpp b/src/plugins/qmldesigner/generateresource.cpp index a201f411003..1ffa42ff47e 100644 --- a/src/plugins/qmldesigner/generateresource.cpp +++ b/src/plugins/qmldesigner/generateresource.cpp @@ -3,12 +3,13 @@ #include -#include #include +#include #include #include -#include #include +#include +#include #include #include @@ -217,13 +218,11 @@ static bool runRcc(const CommandLine &command, const FilePath &workingDir, void GenerateResource::generateMenuEntry(QObject *parent) { - Core::ActionContainer *menu = - Core::ActionManager::actionContainer(Core::Constants::M_FILE); - const Core::Context projectContext(QmlProjectManager::Constants::QML_PROJECT_ID); // ToDo: move this to QtCreator and add tr to the string then auto action = new QAction(QCoreApplication::translate("QmlDesigner::GenerateResource", - "Generate QRC Resource File"), parent); + "Generate QRC Resource File..."), + parent); action->setEnabled(ProjectExplorer::SessionManager::startupProject() != nullptr); // todo make it more intelligent when it gets enabled QObject::connect(ProjectExplorer::SessionManager::instance(), @@ -330,7 +329,8 @@ void GenerateResource::generateMenuEntry(QObject *parent) // ToDo: move this to QtCreator and add tr to the string then auto rccAction = new QAction(QCoreApplication::translate("QmlDesigner::GenerateResource", - "Generate Deployable Package"), parent); + "Generate Deployable Package..."), + parent); rccAction->setEnabled(ProjectExplorer::SessionManager::startupProject() != nullptr); QObject::connect(ProjectExplorer::SessionManager::instance(), &ProjectExplorer::SessionManager::startupProjectChanged, [rccAction]() { @@ -339,7 +339,7 @@ void GenerateResource::generateMenuEntry(QObject *parent) Core::Command *cmd2 = Core::ActionManager::registerAction(rccAction, "QmlProject.CreateRCCResource"); - QObject::connect(rccAction, &QAction::triggered, [] () { + QObject::connect(rccAction, &QAction::triggered, []() { auto currentProject = ProjectExplorer::SessionManager::startupProject(); QTC_ASSERT(currentProject, return); const FilePath projectPath = currentProject->projectFilePath().parentDir(); @@ -465,9 +465,19 @@ void GenerateResource::generateMenuEntry(QObject *parent) projectPath, resourceFileName.path())) { return; } + + Core::AsynchronousMessageBox::information( + QCoreApplication::translate("QmlDesigner::GenerateResource", + "Success"), + QCoreApplication::translate("QmlDesigner::GenerateResource", + "Successfully generated deployable package\n %1") + .arg(resourceFileName.toString())); }); - menu->addAction(cmd, Core::Constants::G_FILE_EXPORT); - menu->addAction(cmd2, Core::Constants::G_FILE_EXPORT); + + Core::ActionContainer *exportMenu = Core::ActionManager::actionContainer( + QmlProjectManager::Constants::EXPORT_MENU); + exportMenu->addAction(cmd, QmlProjectManager::Constants::G_EXPORT_GENERATE); + exportMenu->addAction(cmd2, QmlProjectManager::Constants::G_EXPORT_GENERATE); } } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/puppetenvironmentbuilder.cpp b/src/plugins/qmldesigner/puppetenvironmentbuilder.cpp index 38d2c403409..5c3fd5547df 100644 --- a/src/plugins/qmldesigner/puppetenvironmentbuilder.cpp +++ b/src/plugins/qmldesigner/puppetenvironmentbuilder.cpp @@ -7,6 +7,8 @@ #include +#include + #include #include #include @@ -40,7 +42,9 @@ Utils::FilePath pathForBinPuppet(ProjectExplorer::Target *target) QtSupport::QtVersion *currentQtVersion = QtSupport::QtKitAspect::qtVersion(target->kit()); if (currentQtVersion) - return currentQtVersion->binPath().pathAppended("qml2puppet").withExecutableSuffix(); + return currentQtVersion->binPath() + .pathAppended(QString{"qml2puppet-"} + Core::Constants::IDE_VERSION_LONG) + .withExecutableSuffix(); return {}; } diff --git a/src/plugins/qmldesigner/qmldesignerconstants.h b/src/plugins/qmldesigner/qmldesignerconstants.h index 7b32ea297b9..cb682355af7 100644 --- a/src/plugins/qmldesigner/qmldesignerconstants.h +++ b/src/plugins/qmldesigner/qmldesignerconstants.h @@ -8,6 +8,7 @@ namespace Constants { const char C_BACKSPACE[] = "QmlDesigner.Backspace"; const char C_DELETE[] = "QmlDesigner.Delete"; +const char C_DUPLICATE[] = "QmlDesigner.Duplicate"; // Context const char C_QMLDESIGNER[] = "QmlDesigner::QmlDesignerMain"; @@ -113,6 +114,7 @@ const char EVENT_TRANSITIONEDITOR_TIME[] = "transitionEditor"; const char EVENT_CURVEDITOR_TIME[] = "curveEditor"; const char EVENT_STATESEDITOR_TIME[] = "statesEditor"; const char EVENT_TEXTEDITOR_TIME[] = "textEditor"; +const char EVENT_TEXTUREEDITOR_TIME[] = "textureEditor"; const char EVENT_PROPERTYEDITOR_TIME[] = "propertyEditor"; const char EVENT_ASSETSLIBRARY_TIME[] = "assetsLibrary"; const char EVENT_ITEMLIBRARY_TIME[] = "itemLibrary"; @@ -125,6 +127,9 @@ const char EVENT_INSIGHT_TIME[] = "insight"; const char PROPERTY_EDITOR_CLASSNAME_PROPERTY[] = "__classNamePrivateInternal"; +// Copy/Paste Headers +const char HEADER_3DPASTE_CONTENT[] = "// __QmlDesigner.Editor3D.Paste__ \n"; + namespace Internal { enum { debug = 0 }; } diff --git a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp index 2ef0a0cc860..d7c314a085d 100644 --- a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp +++ b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp @@ -65,7 +65,7 @@ QmlPreviewAction::QmlPreviewAction() : ModelNodeAction(livePreviewId, QmlPreviewWidgetPlugin::tr("Show Live Preview"), ComponentCoreConstants::qmlPreviewCategory, QKeySequence("Alt+p"), - 20, + 1, &handleAction, &SelectionContextFunctors::always) { diff --git a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewplugin.cpp b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewplugin.cpp index 0b526338de7..fb7296ec947 100644 --- a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewplugin.cpp +++ b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewplugin.cpp @@ -40,7 +40,7 @@ QmlPreviewWidgetPlugin::QmlPreviewWidgetPlugin() designerActionManager.addDesignerAction(new ActionGroup( QString(), ComponentCoreConstants::qmlPreviewCategory, - ComponentCoreConstants::priorityQmlPreviewCategory, + ComponentCoreConstants::Priorities::QmlPreviewCategory, &SelectionContextFunctors::always)); s_previewPlugin = getPreviewPlugin(); diff --git a/src/plugins/qmldesigner/qtquickplugin/quick.metainfo b/src/plugins/qmldesigner/qtquickplugin/quick.metainfo index 6420b398651..ec420e27200 100644 --- a/src/plugins/qmldesigner/qtquickplugin/quick.metainfo +++ b/src/plugins/qmldesigner/qtquickplugin/quick.metainfo @@ -16,6 +16,7 @@ MetaInfo { Property { name: "width"; type: "int"; value: 200; } Property { name: "height"; type: "int"; value: 200; } + toolTip: qsTr("Groups several visual items.") } } @@ -32,6 +33,7 @@ MetaInfo { Property { name: "width"; type: "int"; value: 200; } Property { name: "height"; type: "int"; value: 200; } Property { name: "color"; type: "QColor"; value: "#ffffff"; } + toolTip: qsTr("A rectangle with an optional border.") } } @@ -47,6 +49,7 @@ MetaInfo { Property { name: "font.pixelSize"; type: "int"; value: 12; } Property { name: "text"; type: "binding"; value: "qsTr(\"Text\")"; } + toolTip: qsTr("A read-only text label.") } } @@ -64,6 +67,7 @@ MetaInfo { Property { name: "height"; type: "int"; value: 20; } Property { name: "font.pixelSize"; type: "int"; value: 12; } Property { name: "text"; type: "binding"; value: "qsTr(\"Text Edit\")"; } + toolTip: qsTr("A multi-line block of editable text.") } } @@ -81,6 +85,7 @@ MetaInfo { Property { name: "height"; type: "int"; value: 20; } Property { name: "font.pixelSize"; type: "int"; value: 12; } Property { name: "text"; type: "binding"; value: "qsTr(\"Text Input\")"; } + toolTip: qsTr("An editable line of text.") } } @@ -96,6 +101,7 @@ MetaInfo { Property { name: "width"; type: "int"; value: 100; } Property { name: "height"; type: "int"; value: 100; } + toolTip: qsTr("An area with mouse functionality.") } } @@ -113,6 +119,7 @@ MetaInfo { Property { name: "height"; type: "int"; value: 100; } Property { name: "source"; type: "QUrl"; value:"qrc:/qtquickplugin/images/template_image.png"; } Property { name: "fillMode"; type: "enum"; value: "Image.PreserveAspectFit"; } + toolTip: qsTr("Displays an image.") } } @@ -129,6 +136,7 @@ MetaInfo { Property { name: "width"; type: "int"; value: 100; } Property { name: "height"; type: "int"; value: 100; } Property { name: "source"; type: "QUrl"; value:"qrc:/qtquickplugin/images/template_image.png"; } + toolTip: qsTr("Animates a series of images.") } } @@ -145,6 +153,7 @@ MetaInfo { Property { name: "width"; type: "int"; value: 100; } Property { name: "height"; type: "int"; value: 100; } Property { name: "source"; type: "QUrl"; value:"qrc:/qtquickplugin/images/template_image.png"; } + toolTip: qsTr("A responsive border based on an image.") } } @@ -160,6 +169,7 @@ MetaInfo { Property { name: "width"; type: "int"; value: 300; } Property { name: "height"; type: "int"; value: 300; } + toolTip: qsTr("An area for keeping dragable objects.") } } @@ -174,6 +184,7 @@ MetaInfo { version: "2.0" QmlSource { source: ":/qtquickplugin/source/gridviewv2.qml" } + toolTip: qsTr("Organizes dynamic data sets in a grid.") } } @@ -188,6 +199,7 @@ MetaInfo { version: "2.0" QmlSource { source: ":/qtquickplugin/source/listviewv2.qml" } + toolTip: qsTr("Organizes dynamic data sets in a list.") } } @@ -202,6 +214,7 @@ MetaInfo { version: "2.0" QmlSource { source: ":/qtquickplugin/source/pathviewv2.qml" } + toolTip: qsTr("Organizes dynamic data sets along a path.") } } @@ -217,6 +230,7 @@ MetaInfo { Property { name: "width"; type: "int"; value: 100; } Property { name: "height"; type: "int"; value: 100; } + toolTip: qsTr("A scope to focus on a specific text element.") } } @@ -232,6 +246,8 @@ MetaInfo { Property { name: "width"; type: "int"; value: 200; } Property { name: "height"; type: "int"; value: 400; } + + toolTip: qsTr("Organizes items in a column.") } } @@ -244,6 +260,7 @@ MetaInfo { category: "c.Qt Quick - Positioner" libraryIcon: ":/qtquickplugin/images/row-positioner-icon.png" version: "2.0" + toolTip: qsTr("Organizes items in a row.") Property { name: "width"; type: "int"; value: 200; } Property { name: "height"; type: "int"; value: 400; } @@ -262,6 +279,7 @@ MetaInfo { Property { name: "width"; type: "int"; value: 400; } Property { name: "height"; type: "int"; value: 400; } + toolTip: qsTr("Organizes items in a fixed grid.") } } @@ -277,6 +295,7 @@ MetaInfo { Property { name: "width"; type: "int"; value: 400; } Property { name: "height"; type: "int"; value: 400; } + toolTip: qsTr("Organizes items in free-flowing rows.") } } @@ -351,6 +370,7 @@ MetaInfo { category: "d.Qt Quick - Animation" libraryIcon: ":/qtquickplugin/images/item-icon.png" version: "2.0" + toolTip: qsTr("Animates changes in property values.") } } @@ -370,6 +390,7 @@ MetaInfo { category: "d.Qt Quick - Animation" libraryIcon: ":/qtquickplugin/images/item-icon.png" version: "2.0" + toolTip: qsTr("Provides a pause between animations.") } } @@ -388,6 +409,7 @@ MetaInfo { category: "d.Qt Quick - Animation" libraryIcon: ":/qtquickplugin/images/item-icon.png" version: "2.0" + toolTip: qsTr("Runs animations one after the other.") } } @@ -406,6 +428,7 @@ MetaInfo { category: "d.Qt Quick - Animation" libraryIcon: ":/qtquickplugin/images/item-icon.png" version: "2.0" + toolTip: qsTr("Runs animations together at the same time.") } } @@ -425,6 +448,7 @@ MetaInfo { category: "d.Qt Quick - Animation" libraryIcon: ":/qtquickplugin/images/item-icon.png" version: "2.0" + toolTip: qsTr("Provides an immediate property change during animations.") } } @@ -444,6 +468,7 @@ MetaInfo { category: "d.Qt Quick - Animation" libraryIcon: ":/qtquickplugin/images/item-icon.png" version: "2.0" + toolTip: qsTr("Runs a script during animation.") } } @@ -463,6 +488,7 @@ MetaInfo { category: "d.Qt Quick - Animation" libraryIcon: ":/qtquickplugin/images/item-icon.png" version: "2.0" + toolTip: qsTr("Animates the color of an item.") } } @@ -484,6 +510,7 @@ MetaInfo { version: "2.0" Property { name: "to"; type: "int"; value: 0; } Property { name: "from"; type: "int"; value: 0; } + toolTip: qsTr("Animates a numerical property of an item.") } } @@ -503,6 +530,7 @@ MetaInfo { category: "d.Qt Quick - Animation" libraryIcon: ":/qtquickplugin/images/timer-24px.png" version: "2.0" + toolTip: qsTr(" Triggers an action at a given time.") } } @@ -522,6 +550,7 @@ MetaInfo { version: "2.0" QmlSource { source: ":/qtquickplugin/source/component.qml" } + toolTip: qsTr("Allows you to define components inline, within a QML document.") } } @@ -556,6 +585,7 @@ MetaInfo { version: "2.0" Property { name: "width"; type: "int"; value: 200; } Property { name: "height"; type: "int"; value: 200; } + toolTip: qsTr("Allows you to load components dynamically.") } } @@ -573,6 +603,7 @@ MetaInfo { category: "e.Qt Quick - Component" libraryIcon: ":/qtquickplugin/images/repeater-icon.png" version: "2.0" + toolTip: qsTr("Creates a number of copies of the same item.") } } diff --git a/src/plugins/qmldesigner/shortcutmanager.cpp b/src/plugins/qmldesigner/shortcutmanager.cpp index 22f91f95438..326977c4af5 100644 --- a/src/plugins/qmldesigner/shortcutmanager.cpp +++ b/src/plugins/qmldesigner/shortcutmanager.cpp @@ -19,6 +19,8 @@ #include #include +#include + #include #include @@ -29,6 +31,7 @@ #include +#include "modelnodecontextmenu_helper.h" #include "qmldesignerconstants.h" #include "qmldesignerplugin.h" @@ -46,6 +49,7 @@ ShortCutManager::ShortCutManager() m_cutAction(tr("Cu&t")), m_copyAction(tr("&Copy")), m_pasteAction(tr("&Paste")), + m_duplicateAction(tr("&Duplicate")), m_selectAllAction(tr("Select &All")), m_escapeAction(this) { @@ -61,7 +65,6 @@ void ShortCutManager::registerActions(const Core::Context &qmlDesignerMainContex Q_UNUSED(qmlDesignerMaterialBrowserContext) Core::ActionContainer *editMenu = Core::ActionManager::actionContainer(Core::Constants::M_EDIT); - Core::ActionContainer *fileMenu = Core::ActionManager::actionContainer(Core::Constants::M_FILE); connect(&m_undoAction, &QAction::triggered, this, &ShortCutManager::undo); @@ -69,6 +72,8 @@ void ShortCutManager::registerActions(const Core::Context &qmlDesignerMainContex connect(&m_deleteAction, &QAction::triggered, this, &ShortCutManager::deleteSelected); + connect(&m_duplicateAction, &QAction::triggered, this, &ShortCutManager::duplicateSelected); + connect(&m_cutAction, &QAction::triggered, this, &ShortCutManager::cutSelected); connect(&m_copyAction, &QAction::triggered, this, &ShortCutManager::copySelected); @@ -101,7 +106,11 @@ void ShortCutManager::registerActions(const Core::Context &qmlDesignerMainContex connect(&m_exportAsImageAction, &QAction::triggered, [] { QmlDesignerPlugin::instance()->viewManager().exportAsImage(); }); - fileMenu->addAction(command, Core::Constants::G_FILE_SAVE); + + Core::ActionContainer *exportMenu = Core::ActionManager::actionContainer( + QmlProjectManager::Constants::EXPORT_MENU); + + exportMenu->addAction(command, QmlProjectManager::Constants::G_EXPORT_CONVERT); //Close Editor Core::ActionManager::registerAction(&m_closeCurrentEditorAction, Core::Constants::CLOSE, qmlDesignerMainContext); @@ -121,10 +130,13 @@ void ShortCutManager::registerActions(const Core::Context &qmlDesignerMainContex // Undo / Redo command = Core::ActionManager::registerAction(&m_undoAction, Core::Constants::UNDO, qmlDesignerMainContext); - designerActionManager.addCreatorCommand(command, ComponentCoreConstants::editCategory, 310, Utils::Icons::UNDO_TOOLBAR.icon()); + command->setDefaultKeySequence(QKeySequence::Undo); + designerActionManager.addCreatorCommand(command, ComponentCoreConstants::editCategory, 1, Utils::Icons::UNDO_TOOLBAR.icon()); command = Core::ActionManager::registerAction(&m_redoAction, Core::Constants::REDO, qmlDesignerMainContext); - designerActionManager.addCreatorCommand(command, ComponentCoreConstants::editCategory, 300, Utils::Icons::REDO_TOOLBAR.icon()); + command->setDefaultKeySequence(QKeySequence::Redo); + designerActionManager.addCreatorCommand(command, ComponentCoreConstants::editCategory, 2, Utils::Icons::REDO_TOOLBAR.icon()); + designerActionManager.addDesignerAction(new SeperatorDesignerAction(ComponentCoreConstants::editCategory, 10)); //Edit Menu m_deleteAction.setIcon(QIcon::fromTheme(QLatin1String("edit-cut"), Utils::Icons::EDIT_CLEAR_TOOLBAR.icon())); @@ -135,28 +147,34 @@ void ShortCutManager::registerActions(const Core::Context &qmlDesignerMainContex command->setAttribute(Core::Command::CA_Hide); // don't show delete in other modes if (!Utils::HostOsInfo::isMacHost()) editMenu->addAction(command, Core::Constants::G_EDIT_COPYPASTE); - designerActionManager.addCreatorCommand(command, ComponentCoreConstants::editCategory, 280); + designerActionManager.addCreatorCommand(command, ComponentCoreConstants::editCategory, 14); Core::ActionManager::registerAction(&m_cutAction, Core::Constants::CUT, qmlDesignerFormEditorContext); Core::ActionManager::registerAction(&m_cutAction, Core::Constants::CUT, qmlDesignerEditor3DContext); command = Core::ActionManager::registerAction(&m_cutAction, Core::Constants::CUT, qmlDesignerNavigatorContext); command->setDefaultKeySequence(QKeySequence::Cut); editMenu->addAction(command, Core::Constants::G_EDIT_COPYPASTE); - designerActionManager.addCreatorCommand(command, ComponentCoreConstants::editCategory, 260, Utils::Icons::CUT_TOOLBAR.icon()); + designerActionManager.addCreatorCommand(command, ComponentCoreConstants::editCategory, 13, Utils::Icons::CUT_TOOLBAR.icon()); Core::ActionManager::registerAction(&m_copyAction, Core::Constants::COPY, qmlDesignerFormEditorContext); Core::ActionManager::registerAction(&m_copyAction, Core::Constants::COPY, qmlDesignerEditor3DContext); command = Core::ActionManager::registerAction(&m_copyAction, Core::Constants::COPY, qmlDesignerNavigatorContext); command->setDefaultKeySequence(QKeySequence::Copy); editMenu->addAction(command, Core::Constants::G_EDIT_COPYPASTE); - designerActionManager.addCreatorCommand(command, ComponentCoreConstants::editCategory, 250, Utils::Icons::COPY_TOOLBAR.icon()); + designerActionManager.addCreatorCommand(command, ComponentCoreConstants::editCategory, 11, Utils::Icons::COPY_TOOLBAR.icon()); Core::ActionManager::registerAction(&m_pasteAction, Core::Constants::PASTE, qmlDesignerFormEditorContext); Core::ActionManager::registerAction(&m_pasteAction, Core::Constants::PASTE, qmlDesignerEditor3DContext); command = Core::ActionManager::registerAction(&m_pasteAction, Core::Constants::PASTE, qmlDesignerNavigatorContext); command->setDefaultKeySequence(QKeySequence::Paste); editMenu->addAction(command, Core::Constants::G_EDIT_COPYPASTE); - designerActionManager.addCreatorCommand(command, ComponentCoreConstants::editCategory, 240, Utils::Icons::PASTE_TOOLBAR.icon()); + designerActionManager.addCreatorCommand(command, ComponentCoreConstants::editCategory, 12, Utils::Icons::PASTE_TOOLBAR.icon()); + + Core::ActionManager::registerAction(&m_duplicateAction, Constants::C_DUPLICATE, qmlDesignerFormEditorContext); + Core::ActionManager::registerAction(&m_duplicateAction, Constants::C_DUPLICATE, qmlDesignerEditor3DContext); + command = Core::ActionManager::registerAction(&m_duplicateAction, Constants::C_DUPLICATE, qmlDesignerMainContext); + editMenu->addAction(command, Core::Constants::G_EDIT_COPYPASTE); + designerActionManager.addCreatorCommand(command, ComponentCoreConstants::editCategory, 15); Core::ActionManager::registerAction(&m_selectAllAction, Core::Constants::SELECTALL, qmlDesignerFormEditorContext); command = Core::ActionManager::registerAction(&m_selectAllAction, Core::Constants::SELECTALL, qmlDesignerNavigatorContext); @@ -253,6 +271,12 @@ void ShortCutManager::copySelected() currentDesignDocument()->copySelected(); } +void ShortCutManager::duplicateSelected() +{ + if (currentDesignDocument()) + currentDesignDocument()->duplicateSelected(); +} + void ShortCutManager::paste() { if (currentDesignDocument()) diff --git a/src/plugins/qmldesigner/shortcutmanager.h b/src/plugins/qmldesigner/shortcutmanager.h index 564a2dbbd7e..63c724934c2 100644 --- a/src/plugins/qmldesigner/shortcutmanager.h +++ b/src/plugins/qmldesigner/shortcutmanager.h @@ -41,6 +41,7 @@ private: void deleteSelected(); void cutSelected(); void copySelected(); + void duplicateSelected(); void paste(); void selectAll(); void undoAvailable(bool isAvailable); @@ -61,6 +62,7 @@ private: QAction m_cutAction; QAction m_copyAction; QAction m_pasteAction; + QAction m_duplicateAction; QAction m_selectAllAction; QAction m_escapeAction; diff --git a/src/plugins/qmldesigner/utils/designersettings.cpp b/src/plugins/qmldesigner/utils/designersettings.cpp index 23df15e0470..797a904b177 100644 --- a/src/plugins/qmldesigner/utils/designersettings.cpp +++ b/src/plugins/qmldesigner/utils/designersettings.cpp @@ -85,7 +85,8 @@ void DesignerSettings::fromSettings(QSettings *settings) restoreValue(settings, DesignerSettingsKey::EDIT3DVIEW_GRID_COLOR, "#aaaaaa"); restoreValue(settings, DesignerSettingsKey::SMOOTH_RENDERING, false); restoreValue(settings, DesignerSettingsKey::SHOW_DEBUG_SETTINGS, false); - restoreValue(settings, DesignerSettingsKey::OLD_STATES_EDITOR, true); + restoreValue(settings, DesignerSettingsKey::OLD_STATES_EDITOR, false); + restoreValue(settings, DesignerSettingsKey::EDITOR_ZOOM_FACTOR, 1.0); settings->endGroup(); settings->endGroup(); diff --git a/src/plugins/qmldesigner/utils/designersettings.h b/src/plugins/qmldesigner/utils/designersettings.h index 32fd7dd2987..49942e79509 100644 --- a/src/plugins/qmldesigner/utils/designersettings.h +++ b/src/plugins/qmldesigner/utils/designersettings.h @@ -53,7 +53,8 @@ const char ALWAYS_DESIGN_MODE[] = "AlwaysDesignMode"; const char DISABLE_ITEM_LIBRARY_UPDATE_TIMER[] = "DisableItemLibraryUpdateTimer"; const char ASK_BEFORE_DELETING_ASSET[] = "AskBeforeDeletingAsset"; const char SMOOTH_RENDERING[] = "SmoothRendering"; -const char OLD_STATES_EDITOR[] = "OldStatesEditor"; +const char OLD_STATES_EDITOR[] = "ForceOldStatesEditor"; +const char EDITOR_ZOOM_FACTOR[] = "EditorZoomFactor"; } class QMLDESIGNERUTILS_EXPORT DesignerSettings diff --git a/src/plugins/qmldesigner/utils/hdrimage.cpp b/src/plugins/qmldesigner/utils/hdrimage.cpp index 4c1c49b08e6..291784efd0b 100644 --- a/src/plugins/qmldesigner/utils/hdrimage.cpp +++ b/src/plugins/qmldesigner/utils/hdrimage.cpp @@ -19,6 +19,8 @@ typedef unsigned char RGBE[4]; #define B 2 #define E 3 +constexpr float GAMMA = 1.f / 2.2f; + QByteArray fileToByteArray(QString const &filename) { QFile file(filename); @@ -38,7 +40,11 @@ float convertComponent(int exponent, int val) { float v = val / (256.0f); float d = powf(2.0f, (float)exponent - 128.0f); - return v * d; + float converted = v * d; + + // Apply gamma correction to brighten the image. + // This will approximately match the default (i.e. linear) tonemapping of SceneEnvironment + return powf(converted, GAMMA); } void decrunchScanline(const char *&p, const char *pEnd, RGBE *scanline, int w) diff --git a/src/plugins/qmljstools/qmljsmodelmanager.cpp b/src/plugins/qmljstools/qmljsmodelmanager.cpp index 66b92ebabe0..149897b7520 100644 --- a/src/plugins/qmljstools/qmljsmodelmanager.cpp +++ b/src/plugins/qmljstools/qmljsmodelmanager.cpp @@ -281,7 +281,7 @@ void ModelManager::delayedInitialization() ViewerContext qbsVContext; qbsVContext.language = Dialect::QmlQbs; - qbsVContext.paths.append(ICore::resourcePath("qbs")); + qbsVContext.paths.insert(ICore::resourcePath("qbs")); setDefaultVContext(qbsVContext); } diff --git a/src/plugins/qmlprojectmanager/cmakegen/cmakeprojectconverter.cpp b/src/plugins/qmlprojectmanager/cmakegen/cmakeprojectconverter.cpp index 159e7f5fad5..4cd3a586de6 100644 --- a/src/plugins/qmlprojectmanager/cmakegen/cmakeprojectconverter.cpp +++ b/src/plugins/qmlprojectmanager/cmakegen/cmakeprojectconverter.cpp @@ -13,6 +13,8 @@ #include #include +#include + #include #include #include @@ -24,7 +26,7 @@ namespace QmlProjectManager { namespace GenerateCmake { const QString MENU_ITEM_CONVERT = QCoreApplication::translate("QmlDesigner::CmakeProjectConverter", - "Export as Latest Project Format"); + "Export as Latest Project Format..."); const QString ERROR_TITLE = QCoreApplication::translate("QmlDesigner::CmakeProjectConverter", "Creating Project"); const QString SUCCESS_TITLE = QCoreApplication::translate("QmlDesigner::CmakeProjectConverter", @@ -36,12 +38,12 @@ const QString SUCCESS_TEXT = QCoreApplication::translate("QmlDesigner::CmakeProj void CmakeProjectConverter::generateMenuEntry(QObject *parent) { - Core::ActionContainer *menu = - Core::ActionManager::actionContainer(Core::Constants::M_FILE); + Core::ActionContainer *exportMenu = Core::ActionManager::actionContainer( + QmlProjectManager::Constants::EXPORT_MENU); auto action = new QAction(MENU_ITEM_CONVERT, parent); QObject::connect(action, &QAction::triggered, CmakeProjectConverter::onConvertProject); Core::Command *cmd = Core::ActionManager::registerAction(action, "QmlProject.ConvertToCmakeProject"); - menu->addAction(cmd, Core::Constants::G_FILE_EXPORT); + exportMenu->addAction(cmd, QmlProjectManager::Constants::G_EXPORT_CONVERT); action->setEnabled(isProjectConvertable(ProjectExplorer::SessionManager::startupProject())); QObject::connect(ProjectExplorer::SessionManager::instance(), diff --git a/src/plugins/qmlprojectmanager/cmakegen/generatecmakelists.cpp b/src/plugins/qmlprojectmanager/cmakegen/generatecmakelists.cpp index 74b37db8c95..b239ca75ccc 100644 --- a/src/plugins/qmlprojectmanager/cmakegen/generatecmakelists.cpp +++ b/src/plugins/qmlprojectmanager/cmakegen/generatecmakelists.cpp @@ -14,18 +14,19 @@ #include #include +#include #include #include -#include #include #include +#include #include -#include #include #include #include +#include using namespace Utils; using namespace QmlProjectManager::GenerateCmake::Constants; @@ -56,16 +57,27 @@ enum ProjectDirectoryError { }; const QString MENU_ITEM_GENERATE = QCoreApplication::translate("QmlDesigner::GenerateCmake", - "Generate CMake Build Files"); + "Generate CMake Build Files..."); void generateMenuEntry(QObject *parent) { - Core::ActionContainer *menu = - Core::ActionManager::actionContainer(Core::Constants::M_FILE); + Core::ActionContainer *menu = Core::ActionManager::actionContainer(Core::Constants::M_FILE); + + Core::ActionContainer *exportMenu = Core::ActionManager::createMenu( + QmlProjectManager::Constants::EXPORT_MENU); + + exportMenu->menu()->setTitle( + QCoreApplication::translate("QmlDesigner::GenerateCmake", "Export Project")); + menu->addMenu(exportMenu, Core::Constants::G_FILE_EXPORT); + + exportMenu->appendGroup(QmlProjectManager::Constants::G_EXPORT_GENERATE); + exportMenu->appendGroup(QmlProjectManager::Constants::G_EXPORT_CONVERT); + exportMenu->addSeparator(QmlProjectManager::Constants::G_EXPORT_CONVERT); + auto action = new QAction(MENU_ITEM_GENERATE, parent); QObject::connect(action, &QAction::triggered, GenerateCmake::onGenerateCmakeLists); Core::Command *cmd = Core::ActionManager::registerAction(action, "QmlProject.CreateCMakeLists"); - menu->addAction(cmd, Core::Constants::G_FILE_EXPORT); + exportMenu->addAction(cmd, QmlProjectManager::Constants::G_EXPORT_GENERATE); action->setEnabled(false); QObject::connect(ProjectExplorer::SessionManager::instance(), diff --git a/src/plugins/qmlprojectmanager/qmlprojectmanagerconstants.h b/src/plugins/qmlprojectmanager/qmlprojectmanagerconstants.h index 43b266d9995..dd4d7a97899 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectmanagerconstants.h +++ b/src/plugins/qmlprojectmanager/qmlprojectmanagerconstants.h @@ -16,5 +16,9 @@ const char USE_MULTILANGUAGE_KEY[] = "QmlProjectManager.QmlRunConfiguration.UseM const char LAST_USED_LANGUAGE[] = "QmlProjectManager.QmlRunConfiguration.LastUsedLanguage"; const char USER_ENVIRONMENT_CHANGES_KEY[] = "QmlProjectManager.QmlRunConfiguration.UserEnvironmentChanges"; +const char EXPORT_MENU[] = "QmlDesigner.ExportMenu"; +const char G_EXPORT_GENERATE[] = "QmlDesigner.Group.GenerateProject"; +const char G_EXPORT_CONVERT[] = "QmlDesigner.Group.ConvertProject"; + } // namespace Constants } // namespace QmlProjectManager diff --git a/src/plugins/studiowelcome/studiowelcomeplugin.cpp b/src/plugins/studiowelcome/studiowelcomeplugin.cpp index a819a88eb10..7ceedd631fd 100644 --- a/src/plugins/studiowelcome/studiowelcomeplugin.cpp +++ b/src/plugins/studiowelcome/studiowelcomeplugin.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -72,6 +73,16 @@ static bool useNewWelcomePage() return settings->value(newWelcomePageEntry, false).toBool(); } +static void openOpenProjectDialog() +{ + const FilePath path = Core::DocumentManager::useProjectsDirectory() + ? Core::DocumentManager::projectsDirectory() + : FilePath(); + const FilePaths files = Core::DocumentManager::getOpenFileNames("*.qmlproject", path); + if (!files.isEmpty()) + Core::ICore::openFiles(files, Core::ICore::None); +} + const char DO_NOT_SHOW_SPLASHSCREEN_AGAIN_KEY[] = "StudioSplashScreen"; const char DETAILED_USAGE_STATISTICS[] = "DetailedUsageStatistics"; @@ -81,7 +92,8 @@ const char CRASH_REPORTER_SETTING[] = "CrashReportingEnabled"; const char EXAMPLES_DOWNLOAD_PATH[] = "StudioWelcome/ExamplesDownloadPath"; -QPointer s_view = nullptr; +QPointer s_viewWindow = nullptr; +QPointer s_viewWidget = nullptr; static StudioWelcomePlugin *s_pluginInstance = nullptr; std::unique_ptr makeUserFeedbackSettings() @@ -194,16 +206,14 @@ public: Q_INVOKABLE void createProject() { - QTimer::singleShot(0, []() { + QTimer::singleShot(0, this, []() { ProjectExplorer::ProjectExplorerPlugin::openNewProjectDialog(); }); } Q_INVOKABLE void openProject() { - QTimer::singleShot(0, []() { - ProjectExplorer::ProjectExplorerPlugin::openOpenProjectDialog(); - }); + QTimer::singleShot(0, this, []() { openOpenProjectDialog(); }); } Q_INVOKABLE void openProjectAt(int row) @@ -246,7 +256,7 @@ public: const QString qmlFile = QFileInfo(projectFile).dir().absolutePath() + "/" + formFile; // This timer should be replaced with a signal send from project loading - QTimer::singleShot(1000, [qmlFile](){ + QTimer::singleShot(1000, this, [qmlFile]() { Core::EditorManager::openEditor(Utils::FilePath::fromString(qmlFile)); }); } @@ -441,22 +451,13 @@ private: void StudioWelcomePlugin::closeSplashScreen() { - if (!s_view.isNull()) { - const bool doNotShowAgain = s_view->rootObject()->property("doNotShowAgain").toBool(); - if (doNotShowAgain) - Utils::CheckableMessageBox::doNotAskAgain(Core::ICore::settings(), - DO_NOT_SHOW_SPLASHSCREEN_AGAIN_KEY); + Utils::CheckableMessageBox::doNotAskAgain(Core::ICore::settings(), + DO_NOT_SHOW_SPLASHSCREEN_AGAIN_KEY); + if (!s_viewWindow.isNull()) + s_viewWindow->deleteLater(); - s_view->deleteLater(); - } -} - -void StudioWelcomePlugin::showSystemSettings() -{ - Core::ICore::infoBar()->removeInfo("WarnCrashReporting"); - Core::ICore::infoBar()->globallySuppressInfo("WarnCrashReporting"); - - Core::ICore::showOptionsDialog(Core::Constants::SETTINGS_ID_SYSTEM); + if (!s_viewWidget.isNull()) + s_viewWidget->deleteLater(); } StudioWelcomePlugin::StudioWelcomePlugin() @@ -529,47 +530,83 @@ void StudioWelcomePlugin::extensionsInitialized() if (showSplashScreen()) { connect(Core::ICore::instance(), &Core::ICore::coreOpened, this, [this] { - s_view = new QQuickWidget(Core::ICore::dialogParent()); - s_view->setResizeMode(QQuickWidget::SizeRootObjectToView); - s_view->setWindowFlag(Qt::SplashScreen, true); - s_view->setWindowModality(Qt::ApplicationModal); - s_view->engine()->addImportPath("qrc:/studiofonts"); + if (Utils::HostOsInfo::isMacHost()) { + s_viewWindow = new QQuickView(Core::ICore::mainWindow()->windowHandle()); + + s_viewWindow->setFlag(Qt::FramelessWindowHint); + + s_viewWindow->engine()->addImportPath("qrc:/studiofonts"); #ifdef QT_DEBUG - s_view->engine()->addImportPath(QLatin1String(STUDIO_QML_PATH) + "splashscreen/imports"); - s_view->setSource( - QUrl::fromLocalFile(QLatin1String(STUDIO_QML_PATH) + "splashscreen/main.qml")); + s_viewWindow->engine()->addImportPath(QLatin1String(STUDIO_QML_PATH) + + "splashscreen/imports"); + s_viewWindow->setSource( + QUrl::fromLocalFile(QLatin1String(STUDIO_QML_PATH) + "splashscreen/main.qml")); #else - s_view->engine()->addImportPath("qrc:/qml/splashscreen/imports"); - s_view->setSource(QUrl("qrc:/qml/splashscreen/main.qml")); + s_viewWindow->engine()->addImportPath("qrc:/qml/splashscreen/imports"); + s_viewWindow->setSource(QUrl("qrc:/qml/splashscreen/main.qml")); #endif + QTC_ASSERT(s_viewWindow->rootObject(), + qWarning() << "The StudioWelcomePlugin has a runtime depdendency on " + "qt/qtquicktimeline."; + return ); - QTC_ASSERT(s_view->rootObject(), - qWarning() << "The StudioWelcomePlugin has a runtime depdendency on " - "qt/qtquicktimeline."; - return ); + connect(s_viewWindow->rootObject(), + SIGNAL(closeClicked()), + this, + SLOT(closeSplashScreen())); - connect(s_view->rootObject(), SIGNAL(closeClicked()), this, SLOT(closeSplashScreen())); - connect(s_view->rootObject(), - SIGNAL(configureClicked()), - this, - SLOT(showSystemSettings())); + auto mainWindow = Core::ICore::mainWindow()->windowHandle(); + s_viewWindow->setPosition((mainWindow->width() - s_viewWindow->width()) / 2, + (mainWindow->height() - s_viewWindow->height()) / 2); - s_view->show(); - s_view->raise(); - s_view->setFocus(); + Core::ICore::mainWindow()->setEnabled(false); + connect(s_viewWindow, &QObject::destroyed, []() { + if (Core::ICore::mainWindow()) + Core::ICore::mainWindow()->setEnabled(true); + }); + + s_viewWindow->show(); + s_viewWindow->requestActivate(); + } else { + s_viewWidget = new QQuickWidget(Core::ICore::dialogParent()); + + s_viewWidget->setWindowFlag(Qt::SplashScreen, true); + + s_viewWidget->setWindowModality(Qt::ApplicationModal); + s_viewWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); + s_viewWidget->engine()->addImportPath("qrc:/studiofonts"); +#ifdef QT_DEBUG + s_viewWidget->engine()->addImportPath(QLatin1String(STUDIO_QML_PATH) + + "splashscreen/imports"); + s_viewWidget->setSource( + QUrl::fromLocalFile(QLatin1String(STUDIO_QML_PATH) + "splashscreen/main.qml")); +#else + s_viewWidget->engine()->addImportPath("qrc:/qml/splashscreen/imports"); + s_viewWidget->setSource(QUrl("qrc:/qml/splashscreen/main.qml")); +#endif + + QTC_ASSERT(s_viewWidget->rootObject(), + qWarning() << "The StudioWelcomePlugin has a runtime depdendency on " + "qt/qtquicktimeline."; + return ); + + connect(s_viewWidget->rootObject(), + SIGNAL(closeClicked()), + this, + SLOT(closeSplashScreen())); + + s_viewWidget->show(); + s_viewWidget->raise(); + s_viewWidget->setFocus(); + } }); } } bool StudioWelcomePlugin::delayedInitialize() { - if (s_view.isNull()) - return false; - - QTC_ASSERT(s_view->rootObject(), return true); - - return false; + return true; } Utils::FilePath StudioWelcomePlugin::defaultExamplesPath() diff --git a/src/plugins/studiowelcome/studiowelcomeplugin.h b/src/plugins/studiowelcome/studiowelcomeplugin.h index 6368e5dd8d7..88a8b0abbb2 100644 --- a/src/plugins/studiowelcome/studiowelcomeplugin.h +++ b/src/plugins/studiowelcome/studiowelcomeplugin.h @@ -45,7 +45,6 @@ class StudioWelcomePlugin final : public ExtensionSystem::IPlugin public slots: void closeSplashScreen(); - void showSystemSettings(); public: StudioWelcomePlugin(); diff --git a/src/tools/qml2puppet/CMakeLists.txt b/src/tools/qml2puppet/CMakeLists.txt index c1aeb5c54fa..074099be894 100644 --- a/src/tools/qml2puppet/CMakeLists.txt +++ b/src/tools/qml2puppet/CMakeLists.txt @@ -14,7 +14,7 @@ set(CMAKE_CXX_EXTENSIONS OFF) if (NOT QT_CREATOR_API_DEFINED) # standalone build - set(DESTINATION DESTINATION $ENV{QDS_PUPPET_DESTINATION}) + set(DESTINATION DESTINATION .) include(QtCreatorIDEBranding) include(QtCreatorAPI) endif() @@ -39,13 +39,7 @@ add_qtc_executable(qml2puppet qmlpuppet.qrc ) -set(qml2puppet_suffix $ENV{QDS_PUPPET_VERSION}) -if (NOT qml2puppet_suffix) - set(qml2puppet_suffix ${IDE_VERSION}) -endif() -extend_qtc_executable(qml2puppet - PROPERTIES OUTPUT_NAME qml2puppet-${qml2puppet_suffix} -) +set_target_properties(qml2puppet PROPERTIES OUTPUT_NAME qml2puppet-${IDE_VERSION}) extend_qtc_executable(qml2puppet CONDITION Qt5_VERSION VERSION_GREATER_EQUAL 6.0.0 diff --git a/src/tools/qml2puppet/qml2puppet/editor3d/gridgeometry.cpp b/src/tools/qml2puppet/qml2puppet/editor3d/gridgeometry.cpp index d2f04ef38f5..6bbe2365447 100644 --- a/src/tools/qml2puppet/qml2puppet/editor3d/gridgeometry.cpp +++ b/src/tools/qml2puppet/qml2puppet/editor3d/gridgeometry.cpp @@ -5,6 +5,10 @@ #include "gridgeometry.h" +#if QT_VERSION_MAJOR == 6 && QT_VERSION_MINOR == 4 +#include +#endif + namespace QmlDesigner { namespace Internal { @@ -82,6 +86,34 @@ void GridGeometry::doUpdateGeometry() QVector3D(vertexPtr[lastIndex][0], vertexPtr[lastIndex][1], 0.0)); } +#if QT_VERSION_MAJOR == 6 && QT_VERSION_MINOR == 4 +QSSGRenderGraphObject *GridGeometry::updateSpatialNode(QSSGRenderGraphObject *node) +{ + if (!node) { + markAllDirty(); + auto geometryNode = new QSSGRenderGeometry(); + node = geometryNode; + emit geometryNodeDirty(); + + // This is a work around for the issue of incorrect geometry objects getting matched for + // cached mesh data in QSSGBufferManager::loadRenderMesh in QtQuick3D in 6.4 (see QDS-8516). + // Each setting of stride value increments the generation id of the geometry node. + // By incrementing generation id by different amounts for each grid geometry node we have, + // we can ensure QSSGBufferManager cache never matches wrong mesh data. + // The cache should be cleared of old objects after they are unused for one frame, + // and we use 4 grid objects in total, so max of 8 different generation ids should ensure no + // invalid cache matches. + static int dirtyCount = 0; + if (++dirtyCount > 8) + dirtyCount = 0; + for (int i = 0; i < dirtyCount; ++i) + geometryNode->setStride(stride()); + } + + return QQuick3DGeometry::updateSpatialNode(node); +} +#endif + void GridGeometry::fillVertexData(QByteArray &vertexData) { const int numSubdivs = 1; // number of subdivision lines (i.e. lines between main grid lines) diff --git a/src/tools/qml2puppet/qml2puppet/editor3d/gridgeometry.h b/src/tools/qml2puppet/qml2puppet/editor3d/gridgeometry.h index 376c9d3bf4b..6df03a38efe 100644 --- a/src/tools/qml2puppet/qml2puppet/editor3d/gridgeometry.h +++ b/src/tools/qml2puppet/qml2puppet/editor3d/gridgeometry.h @@ -39,6 +39,9 @@ signals: protected: void doUpdateGeometry() override; +#if QT_VERSION_MAJOR == 6 && QT_VERSION_MINOR == 4 + QSSGRenderGraphObject *updateSpatialNode(QSSGRenderGraphObject *node) override; +#endif private: void fillVertexData(QByteArray &vertexData); diff --git a/src/tools/qml2puppet/qml2puppet/iconrenderer/iconrenderer.cpp b/src/tools/qml2puppet/qml2puppet/iconrenderer/iconrenderer.cpp index 453fa24473e..f672f4e2f55 100644 --- a/src/tools/qml2puppet/qml2puppet/iconrenderer/iconrenderer.cpp +++ b/src/tools/qml2puppet/qml2puppet/iconrenderer/iconrenderer.cpp @@ -129,7 +129,7 @@ void IconRenderer::setupRender() void IconRenderer::startCreateIcon() { #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - m_designerSupport.refFromEffectItem(m_containerItem, false); + m_designerSupport->refFromEffectItem(m_containerItem, false); #endif QQuickDesignerSupportItems::disableNativeTextRendering(m_containerItem); @@ -202,7 +202,7 @@ void IconRenderer::render(const QString &fileName) QRect rect(QPoint(), m_contentItem->size().toSize()); QImage renderImage; #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - renderImage = m_designerSupport.renderImageForItem(m_containerItem, rect, rect.size()); + renderImage = m_designerSupport->renderImageForItem(m_containerItem, rect, rect.size()); #else m_renderControl->polishItems(); m_renderControl->beginFrame(); diff --git a/src/tools/qml2puppet/qml2puppet/iconrenderer/iconrenderer.h b/src/tools/qml2puppet/qml2puppet/iconrenderer/iconrenderer.h index 856811b767f..a585cc8ce56 100644 --- a/src/tools/qml2puppet/qml2puppet/iconrenderer/iconrenderer.h +++ b/src/tools/qml2puppet/qml2puppet/iconrenderer/iconrenderer.h @@ -6,6 +6,8 @@ #include #include +#include + QT_BEGIN_NAMESPACE class QQuickWindow; class QQuickItem; diff --git a/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp b/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp index 70d2571e847..dbbe90c2f35 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp +++ b/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "servernodeinstance.h" #include "childrenchangeeventfilter.h" @@ -427,6 +428,9 @@ void Qt5InformationNodeInstanceServer::getNodeAtPos(const QPointF &pos) if (!qFuzzyCompare(planePos.z(), -1.f) && qAbs(planePos.x()) < limit && qAbs(planePos.y()) < limit) pos3d = {planePos.x(), 0, planePos.y()}; } + if (auto rootScene = qobject_cast(m_active3DScene)) + pos3d = rootScene->sceneTransform().inverted().map(pos3d); + QVariantList data; data.append(instanceId); data.append(pos3d); diff --git a/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp b/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp index bc0f7821620..e1029b3dda0 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp +++ b/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp @@ -14,6 +14,10 @@ #include "qt5testnodeinstanceserver.h" #include "quickitemnodeinstance.h" +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + #include +#endif + #if defined(Q_OS_UNIX) #include #elif defined(Q_OS_WIN) @@ -41,7 +45,7 @@ Qt5NodeInstanceClientProxy::Qt5NodeInstanceClientProxy(QObject *parent) : Internal::QuickItemNodeInstance::enableUnifiedRenderPath(true); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) else - DesignerSupport::activateDesignerWindowManager(); + QQuickDesignerSupport::activateDesignerWindowManager(); #endif if (QCoreApplication::arguments().at(1) == QLatin1String("--readcapturedstream")) { diff --git a/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceserver.cpp b/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceserver.cpp index c7bf859daa2..14672a21953 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceserver.cpp +++ b/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceserver.cpp @@ -45,6 +45,7 @@ namespace QmlDesigner { Qt5NodeInstanceServer::Qt5NodeInstanceServer(NodeInstanceClientInterface *nodeInstanceClient) : NodeInstanceServer(nodeInstanceClient) + , m_designerSupport(new QQuickDesignerSupport) { if (!ViewConfig::isParticleViewMode()) QQuickDesignerSupport::activateDesignerMode(); diff --git a/src/tools/qml2puppet/qml2puppet/instances/servernodeinstance.cpp b/src/tools/qml2puppet/qml2puppet/instances/servernodeinstance.cpp index 8ea0eaf8061..e7229f06a6e 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/servernodeinstance.cpp +++ b/src/tools/qml2puppet/qml2puppet/instances/servernodeinstance.cpp @@ -285,7 +285,15 @@ ServerNodeInstance ServerNodeInstance::create(NodeInstanceServer *nodeInstanceSe if (object == nullptr) object = new QQuickItem; } else { - object = Internal::ObjectNodeInstance::createPrimitive("QtQml/QtObject", 2, 0, nodeInstanceServer->context()); + object = Internal::ObjectNodeInstance::createPrimitive("QML/QtObject", + 1, + 0, + nodeInstanceServer->context()); + if (object == nullptr) //Fallback for Qt 5 + object = Internal::ObjectNodeInstance::createPrimitive("QtQml/QtObject", + 2, + 0, + nodeInstanceServer->context()); } } diff --git a/src/tools/qml2puppet/qmlprivategate/qmlprivategate.cpp b/src/tools/qml2puppet/qmlprivategate/qmlprivategate.cpp index 5d36ff8755b..b3e9af82ddd 100644 --- a/src/tools/qml2puppet/qmlprivategate/qmlprivategate.cpp +++ b/src/tools/qml2puppet/qmlprivategate/qmlprivategate.cpp @@ -41,6 +41,10 @@ #include #endif +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + #include +#endif + namespace QmlDesigner { namespace Internal { @@ -52,10 +56,10 @@ bool isPropertyBlackListed(const QmlDesigner::PropertyName &propertyName) return QQuickDesignerSupportProperties::isPropertyBlackListed(propertyName); } -#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) static void addToPropertyNameListIfNotBlackListed( - PropertyNameList *propertyNameList, const QQuickQQuickDesignerSupport::PropertyName &propertyName) + PropertyNameList *propertyNameList, const QQuickDesignerSupport::PropertyName &propertyName) { if (!QQuickDesignerSupportProperties::isPropertyBlackListed(propertyName)) propertyNameList->append(propertyName); @@ -66,7 +70,7 @@ PropertyNameList allPropertyNamesInline(QObject *object, QObjectList *inspectedObjects = nullptr, int depth = 0) { - QQuickQQuickDesignerSupport::PropertyNameList propertyNameList; + QQuickDesignerSupport::PropertyNameList propertyNameList; QObjectList localObjectList; @@ -100,7 +104,7 @@ PropertyNameList allPropertyNamesInline(QObject *object, propertyNameList.append( allPropertyNamesInline(childObject, baseName - + QQuickQQuickDesignerSupport::PropertyName( + + QQuickDesignerSupport::PropertyName( metaProperty.name()) + '.', inspectedObjects, @@ -110,18 +114,18 @@ PropertyNameList allPropertyNamesInline(QObject *object, = QQmlGadgetPtrWrapper::instance(qmlEngine(object), metaProperty.userType())) { valueType->setValue(metaProperty.read(object)); propertyNameList.append(baseName - + QQuickQQuickDesignerSupport::PropertyName(metaProperty.name())); + + QQuickDesignerSupport::PropertyName(metaProperty.name())); propertyNameList.append( allPropertyNamesInline(valueType, baseName - + QQuickQQuickDesignerSupport::PropertyName(metaProperty.name()) + + QQuickDesignerSupport::PropertyName(metaProperty.name()) + '.', inspectedObjects, depth + 1)); } else { addToPropertyNameListIfNotBlackListed(&propertyNameList, baseName - + QQuickQQuickDesignerSupport::PropertyName( + + QQuickDesignerSupport::PropertyName( metaProperty.name())); } } diff --git a/tests/auto/qml/codemodel/importscheck/tst_importscheck.cpp b/tests/auto/qml/codemodel/importscheck/tst_importscheck.cpp index 45d5f51577e..8f300db02f8 100644 --- a/tests/auto/qml/codemodel/importscheck/tst_importscheck.cpp +++ b/tests/auto/qml/codemodel/importscheck/tst_importscheck.cpp @@ -58,7 +58,7 @@ void scanDirectory(const QString &dir) ModelManagerInterface::instance(), false); ModelManagerInterface::instance()->test_joinAllThreads(); ViewerContext vCtx; - vCtx.paths.append(dirPath); + vCtx.paths.insert(dirPath); Snapshot snap = ModelManagerInterface::instance()->snapshot(); ImportDependencies *iDeps = snap.importDependencies(); @@ -176,7 +176,10 @@ void tst_ImportCheck::test() ModelManagerInterface::instance(), false); ModelManagerInterface::instance()->test_joinAllThreads(); ViewerContext vCtx; - vCtx.paths.append(pathPaths); + + for (const Utils::FilePath &path : pathPaths) + vCtx.paths.insert(path); + Snapshot snap = ModelManagerInterface::instance()->snapshot(); ImportDependencies *iDeps = snap.importDependencies(); diff --git a/tests/unit/mockup/qmldesigner/designercore/include/nodemetainfo.h b/tests/unit/mockup/qmldesigner/designercore/include/nodemetainfo.h index 9a350961032..fe8abea17b6 100644 --- a/tests/unit/mockup/qmldesigner/designercore/include/nodemetainfo.h +++ b/tests/unit/mockup/qmldesigner/designercore/include/nodemetainfo.h @@ -76,6 +76,8 @@ public: bool isQtQuick3DModel() const { return {}; } bool isQtQuick3DMaterial() const { return {}; } bool isQtQuickLoader() const { return {}; } + bool isQtQuickItem() const { return {}; } + bool isQtQuick3DTexture() const { return {}; } QString importDirectoryPath() const { return {}; }