diff --git a/README.md b/README.md index 98504b7c751..b72234370ea 100644 --- a/README.md +++ b/README.md @@ -934,6 +934,34 @@ SQLite (https://www.sqlite.org) is in the Public Domain. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +### QrCodeGenerator + + The QML Designer plugin uses QR Code Generator for Design Viewer integration. + + https://github.com/alex-spataru/Qt-QrCodeGenerator + + MIT License + + Copyright (c) 2023 Alex Spataru + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + ### cmake The CMake project manager uses the CMake lexer code for parsing CMake files diff --git a/dist/branding/qtdesignstudio/QtCreatorIDEBranding.cmake b/dist/branding/qtdesignstudio/QtCreatorIDEBranding.cmake index 5877838a481..59b9fdf3138 100644 --- a/dist/branding/qtdesignstudio/QtCreatorIDEBranding.cmake +++ b/dist/branding/qtdesignstudio/QtCreatorIDEBranding.cmake @@ -1,6 +1,6 @@ -set(IDE_VERSION "4.3.0") # The IDE version. -set(IDE_VERSION_COMPAT "4.3.0") # The IDE Compatibility version. -set(IDE_VERSION_DISPLAY "4.3.0") # The IDE display version. +set(IDE_VERSION "4.4.0") # The IDE version. +set(IDE_VERSION_COMPAT "4.4.0") # The IDE Compatibility version. +set(IDE_VERSION_DISPLAY "4.4.0") # The IDE display version. set(IDE_COPYRIGHT_YEAR "2023") # The IDE current copyright year. set(IDE_SETTINGSVARIANT "QtProject") # The IDE settings variation. diff --git a/doc/qtcreator/images/extraimages/images/KDxnMQzgmIY.jpg b/doc/qtcreator/images/extraimages/images/KDxnMQzgmIY.jpg new file mode 100644 index 00000000000..de226c2b1a3 Binary files /dev/null and b/doc/qtcreator/images/extraimages/images/KDxnMQzgmIY.jpg differ diff --git a/doc/qtcreator/images/extraimages/qtdesignstudio-extraimages.qdocconf b/doc/qtcreator/images/extraimages/qtdesignstudio-extraimages.qdocconf index 7a4f2d4dcee..6eead9aa2cd 100644 --- a/doc/qtcreator/images/extraimages/qtdesignstudio-extraimages.qdocconf +++ b/doc/qtcreator/images/extraimages/qtdesignstudio-extraimages.qdocconf @@ -2,7 +2,6 @@ images/commercial.png \ images/SsFWyUeAA_4.jpg \ images/9ihYeC0YJ0M.jpg \ - images/RfEYO-5Mw6s.jpg \ images/yOUdg1o2KJM.jpg \ images/DVWd_xMMgvg.jpg \ images/Ed8WS03C-Vk.jpg \ @@ -11,6 +10,5 @@ images/w1yhDl93YI0.jpg \ images/pEETxSxYazg.jpg \ images/V3Po15bNErw.jpg \ - images/bMXeeQw6BYs.jpg \ - images/u3kZJjlk3CY.jpg \ - images/9MqUCP6JLCQ.jpg + images/9MqUCP6JLCQ.jpg \ + images/KDxnMQzgmIY.jpg diff --git a/doc/qtcreator/src/overview/creator-acknowledgements.qdoc b/doc/qtcreator/src/overview/creator-acknowledgements.qdoc index 5c7a8371e05..57d07f4d0d6 100644 --- a/doc/qtcreator/src/overview/creator-acknowledgements.qdoc +++ b/doc/qtcreator/src/overview/creator-acknowledgements.qdoc @@ -897,6 +897,18 @@ \include license-mit.qdocinc + \li \b QrCodeGenerator + + The QML Designer plugin uses QR Code Generator for Design Viewer integration. + + \list + \li \l https://github.com/alex-spataru/Qt-QrCodeGenerator + \endlist + + Distributed under the MIT license. + + \include license-mit.qdocinc + \li \b cmake The CMake project manager uses the CMake lexer code for parsing CMake files. diff --git a/doc/qtdesignstudio/examples/doc/3DsceneTutorial.qdoc b/doc/qtdesignstudio/examples/doc/3DsceneTutorial.qdoc index 78a3490113f..37340999157 100644 --- a/doc/qtdesignstudio/examples/doc/3DsceneTutorial.qdoc +++ b/doc/qtdesignstudio/examples/doc/3DsceneTutorial.qdoc @@ -41,6 +41,8 @@ Besides the 3D model, the 3D scene also has the default camera and the default directional light. + \include run-tutorial-project.qdocinc + \section1 Adding Materials to the 3D Models First, use materials from \uicontrol {Content Library} on the ball bearing. diff --git a/doc/qtdesignstudio/examples/doc/StateTransitions.qdoc b/doc/qtdesignstudio/examples/doc/StateTransitions.qdoc index 2c2729d77ad..e7d4f318a0b 100644 --- a/doc/qtdesignstudio/examples/doc/StateTransitions.qdoc +++ b/doc/qtdesignstudio/examples/doc/StateTransitions.qdoc @@ -30,6 +30,8 @@ All assets you need for this tutorial are included in the Car Demo project. + \include run-tutorial-project.qdocinc + \section1 Creating States First, you create the different states. In this tutorial, you create four @@ -121,16 +123,19 @@ \list 1 \li Go to the \uicontrol Connections view. \li In \uicontrol{Navigator}, select \e button_side and in - \uicontrol {Connections}, select \inlineimage icons/plus.png - . - This creates a new connection with \e button_side as the target. - \li Set \uicontrol{Signal Handler} to \uicontrol onClicked. - \li Set \uicontrol Actions to \e {Change state to side}. + \uicontrol {Connections}, select the \inlineimage icons/plus.png + button to open the connection setup options. + \li Set \uicontrol Signal to \c clicked, \uicontrol Action to + \c {Change State}, \uicontrol {State Group} to \c rectangle and + \uicontrol State to \c side in the respective + drop-down menus. + \li Select the \inlineimage icons/close.png + button to close the connection setup options. \li Repeat steps 2 to 4 for the next three buttons and set them to go to their corresponding states. \endlist - \image state-transition-connections.png + \image state-transition-connections.webp Now you can preview and try the transitions to see how the UI moves between the states when you select the buttons. diff --git a/doc/qtdesignstudio/examples/doc/animationTutorial.qdoc b/doc/qtdesignstudio/examples/doc/animationTutorial.qdoc index d5b71b91bdb..66fb2009670 100644 --- a/doc/qtdesignstudio/examples/doc/animationTutorial.qdoc +++ b/doc/qtdesignstudio/examples/doc/animationTutorial.qdoc @@ -52,6 +52,8 @@ This tutorial requires that you know the basics of \QDS, see \l{Getting Started}. + \include run-tutorial-project.qdocinc + \section1 Creating a Timeline Animation First, you create an animation where the ball bearing continuously rotates diff --git a/doc/qtdesignstudio/examples/doc/images/loginui3-connections.png b/doc/qtdesignstudio/examples/doc/images/loginui3-connections.png deleted file mode 100644 index 6ebf2f0e470..00000000000 Binary files a/doc/qtdesignstudio/examples/doc/images/loginui3-connections.png and /dev/null differ diff --git a/doc/qtdesignstudio/examples/doc/images/loginui3-connections.webp b/doc/qtdesignstudio/examples/doc/images/loginui3-connections.webp new file mode 100644 index 00000000000..bc7199ec52a Binary files /dev/null and b/doc/qtdesignstudio/examples/doc/images/loginui3-connections.webp differ diff --git a/doc/qtdesignstudio/examples/doc/images/state-transition-connections.png b/doc/qtdesignstudio/examples/doc/images/state-transition-connections.png deleted file mode 100644 index c4c429378d4..00000000000 Binary files a/doc/qtdesignstudio/examples/doc/images/state-transition-connections.png and /dev/null differ diff --git a/doc/qtdesignstudio/examples/doc/images/state-transition-connections.webp b/doc/qtdesignstudio/examples/doc/images/state-transition-connections.webp new file mode 100644 index 00000000000..f52122638b1 Binary files /dev/null and b/doc/qtdesignstudio/examples/doc/images/state-transition-connections.webp differ diff --git a/doc/qtdesignstudio/examples/doc/images/washingmachineui-connections-presets.png b/doc/qtdesignstudio/examples/doc/images/washingmachineui-connections-presets.png deleted file mode 100644 index 59c5c61315f..00000000000 Binary files a/doc/qtdesignstudio/examples/doc/images/washingmachineui-connections-presets.png and /dev/null differ diff --git a/doc/qtdesignstudio/examples/doc/images/washingmachineui-connections.png b/doc/qtdesignstudio/examples/doc/images/washingmachineui-connections.png deleted file mode 100644 index 04f3acad87e..00000000000 Binary files a/doc/qtdesignstudio/examples/doc/images/washingmachineui-connections.png and /dev/null differ diff --git a/doc/qtdesignstudio/examples/doc/images/washingmachineui-connections.webp b/doc/qtdesignstudio/examples/doc/images/washingmachineui-connections.webp new file mode 100644 index 00000000000..997a7771de2 Binary files /dev/null and b/doc/qtdesignstudio/examples/doc/images/washingmachineui-connections.webp differ diff --git a/doc/qtdesignstudio/examples/doc/loginui1.qdoc b/doc/qtdesignstudio/examples/doc/loginui1.qdoc index afb7c50556e..773c2eaaac3 100644 --- a/doc/qtdesignstudio/examples/doc/loginui1.qdoc +++ b/doc/qtdesignstudio/examples/doc/loginui1.qdoc @@ -252,7 +252,7 @@ \image loginui1-entry-field-styled.jpg "Modified button in the 2D view" - \note Do not edit the the value of \uicontrol Text in the \uicontrol Character + \note Do not edit the value of \uicontrol Text in the \uicontrol Character property, because this will break the connection, and later you won't be able to change the text in \uicontrol {Button Content} > \uicontrol Text. diff --git a/doc/qtdesignstudio/examples/doc/loginui3.qdoc b/doc/qtdesignstudio/examples/doc/loginui3.qdoc index 59501c199dd..c66d2b7e484 100644 --- a/doc/qtdesignstudio/examples/doc/loginui3.qdoc +++ b/doc/qtdesignstudio/examples/doc/loginui3.qdoc @@ -135,17 +135,22 @@ \uicontrol {Connections} to open the \uicontrol Connections view. \li Select \e createAccount in \uicontrol Navigator. \li In the \uicontrol Connections tab, select the \inlineimage icons/plus.png - button to add the action that the \c onClicked signal handler of - \e createAccount should apply. - \li Double-click the value \uicontrol Action column and select - \uicontrol {Change state to createAccount} in the drop-down menu. - \note Or, you can right-click the \e createAccount button in \l Navigator. - Then select \uicontrol {Connections} > \uicontrol {Add signal handler} > - \uicontrol {clicked} > \uicontrol {Change State to createAccount}. - \image loginui3-connections.png "Connections tab" + button to open the connection setup options. + \li Set \uicontrol Signal to \c clicked, \uicontrol Action to + \c {Change State}, \uicontrol {State Group} to \c rectangle and + \uicontrol State to \c createAccount in the respective + drop-down menus. + \li Select the \inlineimage icons/close.png + button to close the connection setup options. + + \image loginui3-connections.webp "Connections tab" + \li Select \uicontrol File > \uicontrol Save or press \key {Ctrl+S} to save your changes. + \note Or, you can right-click the \e createAccount button in \l Navigator. + Then select \uicontrol {Connections} > \uicontrol {Add signal handler} > + \uicontrol {clicked} > \uicontrol {Change State to createAccount}. \endlist In the live preview, you can now click the \uicontrol {Create Account} diff --git a/doc/qtdesignstudio/examples/doc/multilanguage.qdoc b/doc/qtdesignstudio/examples/doc/multilanguage.qdoc index 5bb3dd56450..8689960cbb8 100644 --- a/doc/qtdesignstudio/examples/doc/multilanguage.qdoc +++ b/doc/qtdesignstudio/examples/doc/multilanguage.qdoc @@ -20,14 +20,13 @@ \l{https://git.qt.io/public-demos/qtdesign-studio/-/tree/master/tutorial%20projects/multi-language%20tutorial/Loginui2}{here} before you start. - Download the project and open the \e loginui2.qmlproject file in \QDS - to get started. - This project consists of a login page with a couple of text elements. Additionally, you will use a JSON translation file in this tutorial. Download it from \l{https://git.qt.io/public-demos/qtdesign-studio/-/tree/master/tutorial%20projects/multi-language}{here}. + \include run-tutorial-project.qdocinc + \section1 JSON Translation File The JSON translation file you are using in this project has the following diff --git a/doc/qtdesignstudio/examples/doc/washingMachineUI.qdoc b/doc/qtdesignstudio/examples/doc/washingMachineUI.qdoc index a003d922d47..634ea666e06 100644 --- a/doc/qtdesignstudio/examples/doc/washingMachineUI.qdoc +++ b/doc/qtdesignstudio/examples/doc/washingMachineUI.qdoc @@ -40,10 +40,23 @@ We use the \uicontrol {\QMCU Application} project template to create an application for MCUs, which support only a subset of the preset - \l{glossary-component}{components}. We select \uicontrol File > - \uicontrol {New Project} > \uicontrol {\QMCU Application} > - \uicontrol Choose, and follow the instructions of the wizard to create our - project. + \l{glossary-component}{components}. + + To create an MCU project: + + \list 1 + \li Select \uicontrol {File} > \uicontrol {New Project}. + \li In the \uicontrol {Presets} tab, select the \uicontrol {\QMCU} preset. + \li In the \uicontrol {Details} tab: + \list + \li Select the path for the project files. You can move the project + folders later. + \li Set the screen size to match the device screen, which also enables + previewing on the desktop. You can change the screen size later in + \l {Properties}. + \endlist + \li Select \uicontrol {Create} to create the project. + \endlist This way, only the components and properties supported on MCUs are visible in \l Components and \l Properties, and we won't accidentally @@ -193,10 +206,12 @@ Then, we select the mouse area for the start button, \e startMA, in \uicontrol Navigator. On the \uicontrol Connections tab in the \l {Connections} view, we select the \inlineimage icons/plus.png - (\uicontrol Add) button to connect the \c onClicked() signal handler - of the button to the \c startClicked() signal. + (\uicontrol Add) button. We set \uicontrol Signal to \c clicked, + \uicontrol Action to \c {Call Function} and \uicontrol Item to + \c startClicked. Next, we select the \inlineimage icons/close.png + button to close the connection setup options. - \image washingmachineui-connections.png "Connections view" + \image washingmachineui-connections.webp "Connections view" Then, in \e ApplicationView.qml, we specify that the \c startClicked() signal changes the application state to \e presets: diff --git a/doc/qtdesignstudio/images/qmldesigner-bindings.png b/doc/qtdesignstudio/images/qmldesigner-bindings.png deleted file mode 100644 index 3ac0964f76e..00000000000 Binary files a/doc/qtdesignstudio/images/qmldesigner-bindings.png and /dev/null differ diff --git a/doc/qtdesignstudio/images/qmldesigner-dynamicprops.png b/doc/qtdesignstudio/images/qmldesigner-dynamicprops.png deleted file mode 100644 index 565dc26b0e6..00000000000 Binary files a/doc/qtdesignstudio/images/qmldesigner-dynamicprops.png and /dev/null differ diff --git a/doc/qtdesignstudio/images/qtquick-component-signal.webp b/doc/qtdesignstudio/images/qtquick-component-signal.webp new file mode 100644 index 00000000000..7256ef97d63 Binary files /dev/null and b/doc/qtdesignstudio/images/qtquick-component-signal.webp differ diff --git a/doc/qtdesignstudio/images/qtquick-connection-editor-assignment.png b/doc/qtdesignstudio/images/qtquick-connection-editor-assignment.png deleted file mode 100644 index 60c9831184d..00000000000 Binary files a/doc/qtdesignstudio/images/qtquick-connection-editor-assignment.png and /dev/null differ diff --git a/doc/qtdesignstudio/images/qtquick-connection-editor-assignment.webp b/doc/qtdesignstudio/images/qtquick-connection-editor-assignment.webp new file mode 100644 index 00000000000..e2306339bf2 Binary files /dev/null and b/doc/qtdesignstudio/images/qtquick-connection-editor-assignment.webp differ diff --git a/doc/qtdesignstudio/images/studio-project-export-advanced-options.webp b/doc/qtdesignstudio/images/studio-project-export-advanced-options.webp new file mode 100644 index 00000000000..33be8549a70 Binary files /dev/null and b/doc/qtdesignstudio/images/studio-project-export-advanced-options.webp differ diff --git a/doc/qtdesignstudio/images/studio-project-export-advanced.webp b/doc/qtdesignstudio/images/studio-project-export-advanced.webp new file mode 100644 index 00000000000..d6f1189093e Binary files /dev/null and b/doc/qtdesignstudio/images/studio-project-export-advanced.webp differ diff --git a/doc/qtdesignstudio/images/studio-project-export.webp b/doc/qtdesignstudio/images/studio-project-export.webp new file mode 100644 index 00000000000..8847dd945f8 Binary files /dev/null and b/doc/qtdesignstudio/images/studio-project-export.webp differ diff --git a/doc/qtdesignstudio/images/studio-project-structure.png b/doc/qtdesignstudio/images/studio-project-structure.png deleted file mode 100644 index 347b74b0a36..00000000000 Binary files a/doc/qtdesignstudio/images/studio-project-structure.png and /dev/null differ diff --git a/doc/qtdesignstudio/src/components/qtquick-component-instances.qdoc b/doc/qtdesignstudio/src/components/qtquick-component-instances.qdoc index 0cdced17c76..3fa33460569 100644 --- a/doc/qtdesignstudio/src/components/qtquick-component-instances.qdoc +++ b/doc/qtdesignstudio/src/components/qtquick-component-instances.qdoc @@ -28,19 +28,19 @@ in ways that are not supported in \QDS by default, you can define custom properties on the \uicontrol {Properties} tab in the \l {Connections} view. - \image qmldesigner-dynamicprops.png "Connections View Properties tab" + \image add-updated-local-custom-property.webp "Connections View Properties tab" For more information, see \l{Specifying Custom Properties}. \li To enable users to interact with the component instances, connect the instances to signals on the \uicontrol Connections tab in the \uicontrol {Connections} view. For example, you can specify what happens when a component instance is clicked. For more information, see \l{Connecting Components to Signals}. - \image qmldesigner-connections.png "Connections View Connections tab" + \image qmldesigner-connections.webp "Connections View Connections tab" \li To dynamically change the behavior of a component instance when another component instance changes, create bindings between them on the \uicontrol Bindings tab in the \uicontrol {Connections} view. For more information, see \l{Adding Bindings Between Properties}. - \image qmldesigner-bindings.png "Connections view Bindings tab" + \image qmldesigner-bindings.webp "Connections view Bindings tab" \li Add states to apply sets of changes to the property values of one or several component instances in the \uicontrol States view. For more information, see \l{Working with States}. diff --git a/doc/qtdesignstudio/src/components/qtquick-components.qdoc b/doc/qtdesignstudio/src/components/qtquick-components.qdoc index edd28485db3..f512b8f67d4 100644 --- a/doc/qtdesignstudio/src/components/qtquick-components.qdoc +++ b/doc/qtdesignstudio/src/components/qtquick-components.qdoc @@ -72,7 +72,7 @@ the \l{UI Files}{UI files} (.ui.qml), while developers should work on the corresponding implementation files (.qml) to define their programmatic behaviors or JavaScript. This enables iteration from - both the design and development side of the process without the the + both the design and development side of the process without the risk of overwriting each other's work. \endlist */ diff --git a/doc/qtdesignstudio/src/components/qtquick-positioning.qdoc b/doc/qtdesignstudio/src/components/qtquick-positioning.qdoc index 1eb55ebd771..6c91124d697 100644 --- a/doc/qtdesignstudio/src/components/qtquick-positioning.qdoc +++ b/doc/qtdesignstudio/src/components/qtquick-positioning.qdoc @@ -421,7 +421,7 @@ The child components of grid layout components are arranged according to the \uicontrol Flow property. When the direction of a flow is set to \uicontrol LeftToRight, child components are positioned next to to each - other until the the number of columns specified in the + other until the number of columns specified in the \uicontrol {Columns & Rows} field is reached. Then, the auto-positioning wraps back to the beginning of the next row. diff --git a/doc/qtdesignstudio/src/components/qtquick-preset-components.qdoc b/doc/qtdesignstudio/src/components/qtquick-preset-components.qdoc index 875d0f9ea67..19ee8e60ddc 100644 --- a/doc/qtdesignstudio/src/components/qtquick-preset-components.qdoc +++ b/doc/qtdesignstudio/src/components/qtquick-preset-components.qdoc @@ -73,19 +73,6 @@ \note Using 3D components will affect the performance of your UI. Do not use 3D components if the same results can be achieved using 2D components. - \section2 Videos About 3D Components - - The following video shows you how to add the components included in the - \uicontrol {Qt Quick 3D} module, such as 3D models, cameras, and lights, - to your scene: - - \youtube u3kZJjlk3CY - - The following video shows you how to use the custom shader utilities, 3D - effects, and materials: - - \youtube bMXeeQw6BYs - The following video shows you how to combine 2D and 3D components: \youtube w1yhDl93YI0 diff --git a/doc/qtdesignstudio/src/components/qtquick-text.qdoc b/doc/qtdesignstudio/src/components/qtquick-text.qdoc index 0bb541f1c31..0e8695b5101 100644 --- a/doc/qtdesignstudio/src/components/qtquick-text.qdoc +++ b/doc/qtdesignstudio/src/components/qtquick-text.qdoc @@ -206,7 +206,7 @@ Text can be either in plain text or rich text format, depending on the value you set in the \uicontrol Format field. If you select - \uicontrol AutoText and the the first line of text contains an HTML tag, + \uicontrol AutoText and the first line of text contains an HTML tag, the text is treated as rich text. Rich text supports a subset of HTML 4 described on the \l {Supported HTML Subset}. Note that plain text offers better performance than rich text. diff --git a/doc/qtdesignstudio/src/developers/studio-designer-developer-workflow.qdoc b/doc/qtdesignstudio/src/developers/studio-designer-developer-workflow.qdoc index 146501328c2..74ffff54688 100644 --- a/doc/qtdesignstudio/src/developers/studio-designer-developer-workflow.qdoc +++ b/doc/qtdesignstudio/src/developers/studio-designer-developer-workflow.qdoc @@ -31,76 +31,58 @@ \e CMakeLists.txt file as the project file. This enables you to share your project as a fully working C++ application with developers. - If you add or remove QML files in \QDS, you have to regenerate the - \e CMakeLists.txt project configuration file by selecting \uicontrol File - > \uicontrol {Export Project} > \uicontrol {Generate CMake Build Files}. - If you use Git, you can clone an example project \l{https://git.qt.io/public-demos/qtdesign-studio/-/tree/master/playground/AuroraCluster0} {here}. - The following image shows the example project structure and contents in the - \l Projects and \l {File System} views in \QDS and Qt Creator: + \section1 Exporting a \QDS Project - \image studio-project-structure.png "\QDS project in \QDS and Qt Creator views" - - \section1 Converting Project Structure for CMake - - \QDS can generate \e CMakeLists.txt and other related files to use with - Qt Creator and to compile into an executable application but only if the - project has a certain folder structure. If you have a \QDS QML project that - doesn't have the CMake configuration, follow these steps to convert its - file structure to the correct format. + \QDS uses a different project format than Qt Creator. \QDS does not build the project, + it uses a pre-compiled \l{QML runtime} to run the project. To export a \QDS project for the + Qt Creator, follow the process: \list 1 - \li Create a folder named \e content in the project's folder. This folder contains the - application's main module. - \li Move all QML files of the project's main module to the \e content folder. If your project - has multiple modules, place the other modules in the \e imports or - \e asset_imports folder. - \li If your project's main module has resource folders such as \e fonts or \e {images}, move - them to the \e content folder. - \li Create a folder named \e src in the project's folder. This folder contains C++ code for - compiling the project. - \li If your project doesn't have an \e imports folder for other QML modules, create it - now even if you do not have other modules. The CMake file generator expects it. - \li In the project's \e .qmlproject file: - \list - \li Add \e "." in importPaths. For example: - \code - importPaths: [ "imports", "asset_imports", "." ] - \endcode - \li Change mainFile to \e "content/App.qml": - \code - mainFile: "content/App.qml" - \endcode - \endlist - \li In the \e content folder, create a file named \e App.qml and add the following content: + \li Open the project you want to export in \QDS. + \li Select \uicontrol {File} > \uicontrol {Export Project} > \uicontrol {Generate CMake Build Files}. + \image studio-project-export.webp "Export the \QDS project for Qt Creator" - \qml - import QtQuick - import QtQuick.Window - import YourImportModuleHere - Window { - width: Constants.width - height: Constants.height - visible: true - title: "YourWindowTitleHere" - { - } - } - \endqml + \li Select \uicontrol {Details} to access the \l {Advanced Options}. + \image studio-project-export-advanced.webp "Access Advanced Options in the project exporter" - \li In \e{App.qml}, modify imported modules, window dimensions, window title, and main QML - class appropriately. + \note The project exporter has default settings selected. This works better if the project + is combined with an existing Qt project. - \note This template assumes that your project has a module named \e YourImportModuleHere in - the \a imports folder containing a singleton class named \a Constants. - This isn't mandatory. - - \li Generate CMake files and C++ source files that are used to compile the application into - an executable file by selecting \uicontrol File > \uicontrol {Export Project} > - \uicontrol {Generate CMake Build Files}. + \li Select all the options here. This allows to export the + complete project. So, it can be compiled as a stand-alone application. + \image studio-project-export-advanced-options.webp "Select all the options in the project exporter" + \note If you copy this export on top of the existing Qt Creator project + it overwrites the existing project. Hence, the default selected options in + the exporter only exports the QML-specific items. You get a list of + warnings at the bottom part of the exporter that denotes exactly which parts + of the project gets overwritten. \endlist + + \section1 Using the Exported Project in Qt Creator + + After exporting the project from the \QDS, you have to open it from Qt Creator. + + If you have used any version before \QDS 4.0 to create the project, manually include this code + in the \l {CMakeLists.txt} file so the exported project works in Qt Creator. + + \code + set(BUILD_QDS_COMPONENTS ON CACHE BOOL "Build design studio components") + + set(CMAKE_INCLUDE_CURRENT_DIR ON) + + if (${BUILD_QDS_COMPONENTS}) + include(${CMAKE_CURRENT_SOURCE_DIR}/qmlcomponents) + endif () + + include(${CMAKE_CURRENT_SOURCE_DIR}/qmlmodules) + \endcode + + \note If you have created the project with the \QDS version 4.0 or above, you already have this code in + \l {CMakeLists.txt} by default. + */ diff --git a/doc/qtdesignstudio/src/mcus/qtdesignstudio-compatibility-with-mcu-sdks.qdoc b/doc/qtdesignstudio/src/mcus/qtdesignstudio-compatibility-with-mcu-sdks.qdoc index 9dad2fd4057..de94f59d805 100644 --- a/doc/qtdesignstudio/src/mcus/qtdesignstudio-compatibility-with-mcu-sdks.qdoc +++ b/doc/qtdesignstudio/src/mcus/qtdesignstudio-compatibility-with-mcu-sdks.qdoc @@ -16,8 +16,14 @@ \li \QDS Version \li \QMCU SDK Version \row - \li 4.0 or later - \li 2.4 or later + \li 4.3 or later + \li 2.6 or later + \row + \li 4.2 or later + \li 2.5 + \row + \li 4.0 up to 4.1 + \li 2.4 \row \li 3.8 up to 3.9 \li 2.3 diff --git a/doc/qtdesignstudio/src/mcus/qtdesignstudio-features-on-mcu-projects.qdoc b/doc/qtdesignstudio/src/mcus/qtdesignstudio-features-on-mcu-projects.qdoc index 89c1051cf90..196edd941bc 100644 --- a/doc/qtdesignstudio/src/mcus/qtdesignstudio-features-on-mcu-projects.qdoc +++ b/doc/qtdesignstudio/src/mcus/qtdesignstudio-features-on-mcu-projects.qdoc @@ -25,7 +25,8 @@ \li \b - \li A scene in the \uicontrol 2D view is rendered by the regular Qt Quick and QML, and not as \QUL and \QMCU, so some imperfections or inaccuracies - can occur. + can occur. Note that the default font used in \QDS preview and \QUL are + different, and the developer must confirm both fonts are the same. \row \li \l 3D \li \b - @@ -71,15 +72,19 @@ \li \b - \li \b - \li The \uicontrol Connections view displays all signal handlers in the - current file but it doesn't filter available signals, so you can still - see and select signals that are available in Qt Quick, but not in \QUL. + current file, but it doesn't filter available signals, so you can still + see and select signals available in Qt Quick, but not in \QUL. + The same also applies if \uicontrol Action is set to \uicontrol{Call Function} + and \uicontrol Item is set to \uicontrol Qt. See the component documentation + to filter available signal/function. \row \li \l {States} \li \b X \li \b - \li \b - \li The feature is fully supported as such, but there are some - limitations listed in \l {\QMCU Known Issues or Limitations}. + limitations such as StateGroup and the ones listed in + \l {\QMCU Known Issues or Limitations}. \row \li \l {Transitions} \li \b X @@ -89,9 +94,11 @@ \row \li \l {Translations} \li \b - - \li \b - \li \b X \li \b - + \li The \uicontrol Translations view previews with regular Qt Quick instead + of \QUL, and it can be inaccurate in calculating the text overflow in some translations. + Also, the developer needs to configure the \QUL project to use \QDS translations (.ts) files. \row \li \l {Timeline} \li \b X @@ -100,11 +107,11 @@ \li \b - \row \li \l {Curves} - \li \b - \li \b X \li \b - - \li Linear interpolation works, but \QMCU does not support the - \c easing.bezierCurve property of a keyframe. + \li \b - + \li Linear interpolation works, and \QMCU supports the \c easing.bezierCurve property + of a keyframe in \QMCU 2.6 or higher. \row \li \l Code \li \b X diff --git a/doc/qtdesignstudio/src/overviews/qtquick-annotations.qdoc b/doc/qtdesignstudio/src/overviews/qtquick-annotations.qdoc index aeaf304bfcf..ee21695251c 100644 --- a/doc/qtdesignstudio/src/overviews/qtquick-annotations.qdoc +++ b/doc/qtdesignstudio/src/overviews/qtquick-annotations.qdoc @@ -41,7 +41,7 @@ \image qtquick-annotation-editor.png "Annotation Editor" \li The \uicontrol {Selected Item} field displays the ID of the component. - \li In the the \uicontrol Name field, enter a free-form text that + \li In the \uicontrol Name field, enter a free-form text that describes the component. \li In the \uicontrol Title field, enter the text to display in the tab for this comment. diff --git a/doc/qtdesignstudio/src/prototyping/qtquick-live-preview-android.qdoc b/doc/qtdesignstudio/src/prototyping/qtquick-live-preview-android.qdoc index c4cdec1ea5f..69840eb0b2d 100644 --- a/doc/qtdesignstudio/src/prototyping/qtquick-live-preview-android.qdoc +++ b/doc/qtdesignstudio/src/prototyping/qtquick-live-preview-android.qdoc @@ -132,7 +132,7 @@ \section2 Set the AVD as the Device in the Android Kit - Next, you need to set the AVD as the Android device kit. You do this under the the + Next, you need to set the AVD as the Android device kit. You do this under the \uicontrol Kits tab. If the \uicontrol Kits list is empty, restart \QDS. \image qtds-options-kits.png diff --git a/doc/qtdesignstudio/src/qtdesignstudio-terms.qdoc b/doc/qtdesignstudio/src/qtdesignstudio-terms.qdoc index d4dcb9a75ee..9c517f9846c 100644 --- a/doc/qtdesignstudio/src/qtdesignstudio-terms.qdoc +++ b/doc/qtdesignstudio/src/qtdesignstudio-terms.qdoc @@ -57,7 +57,7 @@ component height is adjusted automatically. Similarly, the opacity of a component can be bound to the opacity of its parent component. - \image qtquick-connection-editor-assignment.png "Binding Editor" + \image qtquick-connection-editor-assignment.webp "Binding Editor" Property bindings are created implicitly whenever a property is assigned a JavaScript expression. @@ -115,7 +115,7 @@ is to create \l{glossary-binding}{bindings} between the values of their \l{glossary-property}{properties}. - \image qmldesigner-connections.png "The Connections view" + \image qmldesigner-connections.webp "The Connections view" Read more about connections: @@ -211,9 +211,9 @@ the application. For example, the \l {Mouse Area} component has a \c clicked signal that is emitted whenever the mouse is clicked within the area. Since the signal name is \c clicked, the signal handler for receiving this signal - is named \c onClicked. + is named \c onClicked. Then it performs the defined \uicontrol {Action}. - \image washingmachineui-connections.png "Connections view, Connections tab" + \image qtquick-component-signal.webp "Component signal" Further, a signal is automatically emitted when the value of a \l{glossary-property}{property} changes. diff --git a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-particles.qdoc b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-particles.qdoc index b9f5463c83d..85201a857e1 100644 --- a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-particles.qdoc +++ b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-particles.qdoc @@ -792,7 +792,7 @@ in particle size, specify values for \uicontrol {Particle scale variation} and \uicontrol {Particle end scale variation}. - \uicontrol {Depth bias} specifies the the depth bias of the emitter. Depth + \uicontrol {Depth bias} specifies the depth bias of the emitter. Depth bias is added to the object's distance from camera when sorting objects. This can be used to force the rendering order of objects that are located close to each other if it might otherwise change between frames. Negative @@ -1046,7 +1046,7 @@ \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 + \uicontrol Duration defines the duration of the scaling cycle in milliseconds. \uicontrol {Easing Curve} defines the diff --git a/doc/qtdesignstudio/src/qtquickdesigner-components/qtdesignstudio-logic-helpers.qdoc b/doc/qtdesignstudio/src/qtquickdesigner-components/qtdesignstudio-logic-helpers.qdoc index e616f82c972..c928fff58c6 100644 --- a/doc/qtdesignstudio/src/qtquickdesigner-components/qtdesignstudio-logic-helpers.qdoc +++ b/doc/qtdesignstudio/src/qtquickdesigner-components/qtdesignstudio-logic-helpers.qdoc @@ -285,7 +285,7 @@ \uicontrol {Input 01} field to the value of the \uicontrol {Below min} field of the minimum-maximum mapper for the bad value range. For the \e overValueAnd operator, we bind it to the value of the - \uicontrol {Above max} field of the the same mapper. + \uicontrol {Above max} field of the same mapper. \image studio-logic-helper-combining-example-ao2.png "Under value minimum-maximum mapper Input 01" diff --git a/doc/qtdesignstudio/src/run-tutorial-project.qdocinc b/doc/qtdesignstudio/src/run-tutorial-project.qdocinc new file mode 100644 index 00000000000..3be68b2d69f --- /dev/null +++ b/doc/qtdesignstudio/src/run-tutorial-project.qdocinc @@ -0,0 +1,4 @@ +\section1 Running the Tutorial Project + +To open the tutorial project in \QDS, open the \e{.qmlproject} file located +in the root folder of the downloaded project. diff --git a/doc/qtdesignstudio/src/views/qtquick-connection-editor-signals.qdoc b/doc/qtdesignstudio/src/views/qtquick-connection-editor-signals.qdoc index e175a84d4fd..fe7e83141da 100644 --- a/doc/qtdesignstudio/src/views/qtquick-connection-editor-signals.qdoc +++ b/doc/qtdesignstudio/src/views/qtquick-connection-editor-signals.qdoc @@ -112,6 +112,13 @@ \li Open the \uicontrol {Manual Code Edit} window from the \uicontrol {Connections} view and write JavaScript expressions with components and logical expressions manually. + + \note If you create a conditional expression by selecting options from the + drop-down menus in the \uicontrol {Connection} view, you can only create a single + level \e {if-else} expression. For nested level \e {if-else} expressions, + use the \uicontrol {Manual Code Edit}. + + \image qmldesigner-connections-ConditionalAction-Manual.webp \endlist \section2 Action Properties @@ -152,11 +159,7 @@ \li N/A \endtable - \note If you create a conditional expression by selecting options from drop-down menus in - the \uicontrol {Connection} view, you can only create a single - level {if-else} expression. For nested level \e {if-elseif-else} expressions, - you have to use the \uicontrol {Manual Code Edit}. - - \image qmldesigner-connections-ConditionalAction-Manual.webp + Watch this video for practical examples of the \uicontrol {Connection} view workflow: + \youtube KDxnMQzgmIY */ diff --git a/doc/qtdesignstudio/src/views/qtquick-designer.qdoc b/doc/qtdesignstudio/src/views/qtquick-designer.qdoc index 60e1dc191f6..dbf07849f0b 100644 --- a/doc/qtdesignstudio/src/views/qtquick-designer.qdoc +++ b/doc/qtdesignstudio/src/views/qtquick-designer.qdoc @@ -25,10 +25,6 @@ You can move the views anywhere on the screen and save them as \e workspaces, as instructed in \l {Managing Workspaces}. - To learn more about using the design views, see the following video: - - \youtube RfEYO-5Mw6s - \section1 Summary of Design Views In addition to the summary of design views, the table below includes an MCU diff --git a/doc/qtdesignstudio/src/views/qtquick-form-editor.qdoc b/doc/qtdesignstudio/src/views/qtquick-form-editor.qdoc index fc6d8c0223c..3ac30273920 100644 --- a/doc/qtdesignstudio/src/views/qtquick-form-editor.qdoc +++ b/doc/qtdesignstudio/src/views/qtquick-form-editor.qdoc @@ -45,7 +45,7 @@ \li \l{Previewing Component Size} \row \li \inlineimage icons/canvas-color.png - \li Sets the color of the the \uicontrol {2D} view working area. + \li Sets the color of the \uicontrol {2D} view working area. \li \l{Setting Canvas Color} \row \li \inlineimage icons/zoomIn.png diff --git a/share/qtcreator/qmldesigner/assetsLibraryQmlSources/AssetDelegate.qml b/share/qtcreator/qmldesigner/assetsLibraryQmlSources/AssetDelegate.qml index edaf038ac8d..96690fd9690 100644 --- a/share/qtcreator/qmldesigner/assetsLibraryQmlSources/AssetDelegate.qml +++ b/share/qtcreator/qmldesigner/assetsLibraryQmlSources/AssetDelegate.qml @@ -32,12 +32,7 @@ TreeViewDelegate { readonly property int __dirItemHeight: 21 implicitHeight: root.__isDirectory ? root.__dirItemHeight : root.__fileItemHeight - implicitWidth: { - if (root.assetsView.verticalScrollBar.scrollBarVisible) - return root.assetsView.width - root.indentation - root.assetsView.verticalScrollBar.width - else - return root.assetsView.width - root.indentation - } + implicitWidth: root.assetsView.width leftMargin: root.__isDirectory ? 0 : thumbnailImage.width @@ -88,7 +83,7 @@ TreeViewDelegate { background: Rectangle { id: bg - x: root.indentation * root.depth + x: root.indentation * (root.depth - 1) width: root.implicitWidth - bg.x color: { diff --git a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionDetailsEditDelegate.qml b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionDetailsEditDelegate.qml new file mode 100644 index 00000000000..44058419be8 --- /dev/null +++ b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionDetailsEditDelegate.qml @@ -0,0 +1,224 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import CollectionDetails 1.0 as CollectionDetails +import HelperWidgets 2.0 as HelperWidgets +import StudioControls 1.0 as StudioControls +import StudioHelpers as StudioHelpers +import StudioTheme 1.0 as StudioTheme +import QtQuick.Templates as T + +Item { + id: root + required property var columnType + + property var __modifier : textEditor + property bool __changesAccepted: true + + TableView.onCommit: { + if (root.__changesAccepted) + edit = __modifier.editor.editValue + } + + Component.onCompleted: { + __changesAccepted = true + if (edit && edit !== "") + root.__modifier.editor.editValue = edit + } + + onActiveFocusChanged: { + if (root.activeFocus) + root.__modifier.editor.forceActiveFocus() + } + + Connections { + id: modifierFocusConnection + + target: root.__modifier.editor + + function onActiveFocusChanged() { + if (!modifierFocusConnection.target.activeFocus) + root.TableView.commit() + } + } + + EditorPopup { + id: textEditor + + editor: textField + + StudioControls.TextField { + id: textField + + property alias editValue: textField.text + + actionIndicator.visible: false + translationIndicatorVisible: false + + onRejected: root.__changesAccepted = false + } + } + + EditorPopup { + id: numberEditor + + editor: numberField + + StudioControls.RealSpinBox { + id: numberField + + property alias editValue: numberField.realValue + + actionIndicator.visible: false + realFrom: -9e9 + realTo: 9e9 + realStepSize: 1.0 + decimals: 6 + } + } + + EditorPopup { + id: boolEditor + + editor: boolField + + StudioControls.CheckBox { + id: boolField + + property alias editValue: boolField.checked + + actionIndicatorVisible: false + } + } + + EditorPopup { + id: colorEditor + + editor: colorPicker + + implicitHeight: colorPicker.height + topPadding + bottomPadding + implicitWidth: colorPicker.width + leftPadding + rightPadding + padding: 8 + + StudioHelpers.ColorBackend { + id: colorBackend + } + + StudioControls.ColorEditorPopup { + id: colorPicker + + property alias editValue: colorBackend.color + color: colorBackend.color + + width: 200 + + Keys.onEnterPressed: colorPicker.focus = false + + onActivateColor: function(color) { + colorBackend.activateColor(color) + } + } + + background: Rectangle { + color: StudioTheme.Values.themeControlBackgroundInteraction + border.color: StudioTheme.Values.themeInteraction + border.width: StudioTheme.Values.border + } + } + + component EditorPopup: T.Popup { + id: editorPopup + + required property Item editor + + implicitHeight: contentHeight + implicitWidth: contentWidth + + enabled: visible + visible: false + + Connections { + target: editorPopup.editor + + function onActiveFocusChanged() { + if (!editorPopup.editor.activeFocus) + editorPopup.close() + } + } + + Connections { + target: editorPopup.editor.Keys + + function onEscapePressed() { + root.__changesAccepted = false + editorPopup.close() + } + } + } + + states: [ + State { + name: "default" + when: columnType !== CollectionDetails.DataType.Boolean + && columnType !== CollectionDetails.DataType.Color + && columnType !== CollectionDetails.DataType.Number + + PropertyChanges { + target: root + __modifier: textEditor + } + + PropertyChanges { + target: textEditor + visible: true + focus: true + } + }, + State { + name: "number" + when: columnType === CollectionDetails.DataType.Number + + PropertyChanges { + target: root + __modifier: numberEditor + } + + PropertyChanges { + target: numberEditor + visible: true + focus: true + } + }, + State { + name: "bool" + when: columnType === CollectionDetails.DataType.Boolean + + PropertyChanges { + target: root + __modifier: boolEditor + } + + PropertyChanges { + target: boolEditor + visible: true + focus: true + } + }, + State { + name: "color" + when: columnType === CollectionDetails.DataType.Color + + PropertyChanges { + target: root + __modifier: colorEditor + } + + PropertyChanges { + target: colorEditor + visible: true + focus: true + } + } + ] +} diff --git a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionDetailsToolbar.qml b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionDetailsToolbar.qml new file mode 100644 index 00000000000..93839872ba2 --- /dev/null +++ b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionDetailsToolbar.qml @@ -0,0 +1,286 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt.labs.platform as PlatformWidgets +import HelperWidgets 2.0 as HelperWidgets +import StudioControls 1.0 as StudioControls +import StudioTheme 1.0 as StudioTheme +import CollectionEditorBackend + +Item { + id: root + + property real iconHeight: 2 * StudioTheme.Values.bigFont + required property var model + required property var backend + property int selectedRow: -1 + + implicitHeight: container.height + + function addNewColumn() { + addColumnDialog.popUp(root.model.columnCount()) + } + + function addNewRow() { + root.model.insertRow(root.model.rowCount()) + } + + RowLayout { + id: container + width: parent.width + + spacing: StudioTheme.Values.sectionRowSpacing + + RowLayout { + id: leftSideToolbar + + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + spacing: StudioTheme.Values.sectionRowSpacing + + IconButton { + icon: StudioTheme.Constants.addcolumnleft_medium + tooltip: qsTr("Add property left %1").arg(leftSideToolbar.topPadding) + enabled: root.model.selectedColumn > -1 + onClicked: addColumnDialog.popUp(root.model.selectedColumn - 1) + } + + IconButton { + icon: StudioTheme.Constants.addcolumnright_medium + tooltip: qsTr("Add property right") + enabled: root.model.selectedColumn > -1 + onClicked: addColumnDialog.popUp(root.model.selectedColumn + 1) + } + + IconButton { + icon: StudioTheme.Constants.deletecolumn_medium + tooltip: qsTr("Delete selected property") + enabled: root.model.selectedColumn > -1 + onClicked: root.model.removeColumn(root.model.selectedColumn) + } + + Item { // spacer + implicitWidth: StudioTheme.Values.toolbarSpacing + implicitHeight: 1 + } + + IconButton { + icon: StudioTheme.Constants.addrowbelow_medium + tooltip: qsTr("Insert row below") + enabled: root.model.selectedRow > -1 + onClicked: root.model.insertRow(root.model.selectedRow + 1) + } + + IconButton { + icon: StudioTheme.Constants.addrowabove_medium + tooltip: qsTr("Insert row above") + enabled: root.model.selectedRow > -1 + onClicked: root.model.insertRow(root.model.selectedRow) + } + + IconButton { + icon: StudioTheme.Constants.deleterow_medium + tooltip: qsTr("Delete selected row") + enabled: root.model.selectedRow > -1 + onClicked: root.model.removeRow(root.model.selectedRow) + } + } + + Item { // spacer + Layout.minimumHeight: 1 + Layout.fillWidth: true + } + + RowLayout { + id: rightSideToolbar + spacing: StudioTheme.Values.sectionRowSpacing + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + + IconButton { + icon: StudioTheme.Constants.updateContent_medium + tooltip: qsTr("Update existing file with changes") + enabled: root.model.collectionName !== "" + onClicked: + { + if (root.backend.selectedSourceAddress().indexOf("json") !== -1) + root.model.exportCollection(root.backend.selectedSourceAddress(), root.model.collectionName, "JSON") + else + root.model.exportCollection(root.backend.selectedSourceAddress(), root.model.collectionName, "CSV") + } + } + + IconButton { + icon: StudioTheme.Constants.export_medium + tooltip: qsTr("Export the model to a new file") + enabled: root.model.collectionName !== "" + onClicked: exportMenu.popup() + } + + StudioControls.Menu { + id: exportMenu + + StudioControls.MenuItem { + text: qsTr("Export as JSON") + onTriggered: + { + fileDialog.defaultSuffix = "json" + fileDialog.open() + } + } + + StudioControls.MenuItem { + text: qsTr("Export as CSV") + onTriggered: + { + fileDialog.defaultSuffix = "csv" + fileDialog.open() + } + } + } + } + } + + + PlatformWidgets.FileDialog { + id: fileDialog + fileMode: PlatformWidgets.FileDialog.SaveFile + onAccepted: + { + var fileAddress = file.toString() + + if (fileAddress.indexOf("json") !== -1) + root.model.exportCollection(fileAddress, root.model.collectionName, "JSON") + else if (fileAddress.indexOf("csv") !== -1) + root.model.exportCollection(fileAddress, root.model.collectionName, "CSV") + + fileDialog.reject() + } + } + + component IconButton: HelperWidgets.IconButton { + Layout.preferredHeight: root.iconHeight + Layout.preferredWidth: root.iconHeight + radius: StudioTheme.Values.smallRadius + iconSize: StudioTheme.Values.bigFont + } + + component Spacer: Item { + implicitWidth: 1 + implicitHeight: StudioTheme.Values.columnGap + } + + RegularExpressionValidator { + id: nameValidator + regularExpression: /^\w+$/ + } + + StudioControls.Dialog { + id: addColumnDialog + + property int clickedIndex: -1 + property bool nameIsValid + + title: qsTr("Add Column") + + function popUp(index) + { + addColumnDialog.clickedIndex = index + columnName.text = "" + addedPropertyType.currentIndex = addedPropertyType.find("String") + + addColumnDialog.open() + } + + function addColumnName() { + if (addColumnDialog.nameIsValid) { + root.model.addColumn(addColumnDialog.clickedIndex, columnName.text, addedPropertyType.currentText) + addColumnDialog.accept() + } else { + addColumnDialog.reject() + } + } + + contentItem: ColumnLayout { + spacing: 2 + + Text { + text: qsTr("Column name:") + color: StudioTheme.Values.themeTextColor + } + + StudioControls.TextField { + id: columnName + + Layout.fillWidth: true + + actionIndicator.visible: false + translationIndicator.visible: false + validator: nameValidator + + Keys.onEnterPressed: addColumnDialog.addColumnName() + Keys.onReturnPressed: addColumnDialog.addColumnName() + Keys.onEscapePressed: addColumnDialog.reject() + + onTextChanged: { + addColumnDialog.nameIsValid = (columnName.text !== "" + && !root.model.isPropertyAvailable(columnName.text)) + } + } + + Spacer { implicitHeight: StudioTheme.Values.controlLabelGap } + + Label { + Layout.fillWidth: true + + text: qsTr("The model already contains \"%1\"!").arg(columnName.text) + visible: columnName.text !== "" && !addColumnDialog.nameIsValid + + color: StudioTheme.Values.themeTextColor + wrapMode: Label.WordWrap + padding: 5 + + background: Rectangle { + color: "transparent" + border.width: StudioTheme.Values.border + border.color: StudioTheme.Values.themeWarning + } + } + + Spacer {} + + Text { + text: qsTr("Type:") + color: StudioTheme.Values.themeTextColor + } + + StudioControls.ComboBox { + id: addedPropertyType + + Layout.fillWidth: true + + model: root.model.typesList() + actionIndicatorVisible: false + } + + Spacer {} + + RowLayout { + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + spacing: StudioTheme.Values.sectionRowSpacing + + HelperWidgets.Button { + enabled: addColumnDialog.nameIsValid + text: qsTr("Add") + onClicked: addColumnDialog.addColumnName() + } + + HelperWidgets.Button { + text: qsTr("Cancel") + onClicked: addColumnDialog.reject() + } + } + } + } +} diff --git a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionDetailsView.qml b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionDetailsView.qml new file mode 100644 index 00000000000..5b88ebc2619 --- /dev/null +++ b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionDetailsView.qml @@ -0,0 +1,434 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import HelperWidgets 2.0 as HelperWidgets +import StudioTheme 1.0 as StudioTheme +import StudioControls 1.0 as StudioControls + +Rectangle { + id: root + + required property var model + required property var backend + required property var sortedModel + + implicitWidth: 300 + implicitHeight: 400 + + color: StudioTheme.Values.themeControlBackground + + ColumnLayout { + id: topRow + + visible: collectionNameText.text !== "" + + spacing: 0 + anchors { + fill: parent + topMargin: 10 + leftMargin: 15 + rightMargin: 15 + bottomMargin: 10 + } + + Text { + id: collectionNameText + + leftPadding: 8 + rightPadding: 8 + topPadding: 3 + bottomPadding: 3 + + color: StudioTheme.Values.themeTextColor + text: root.model.collectionName + font.pixelSize: StudioTheme.Values.baseFontSize + elide: Text.ElideRight + } + + Item { // spacer + implicitWidth: 1 + implicitHeight: 10 + } + + CollectionDetailsToolbar { + id: toolbar + model: root.model + backend: root.backend + Layout.fillWidth: true + Layout.minimumWidth: implicitWidth + } + + Item { // spacer + implicitWidth: 1 + implicitHeight: 5 + } + + GridLayout { + columns: 3 + rowSpacing: 1 + columnSpacing: 1 + + Layout.fillWidth: true + Layout.fillHeight: true + Layout.maximumWidth: parent.width + + Rectangle { + clip: true + visible: !tableView.model.isEmpty + color: StudioTheme.Values.themeControlBackgroundInteraction + border.color: StudioTheme.Values.themeControlBackgroundInteraction + border.width: 2 + + Layout.preferredWidth: rowIdView.width + Layout.preferredHeight: headerView.height + Layout.minimumWidth: rowIdView.width + Layout.minimumHeight: headerView.height + + Text { + anchors.fill: parent + font: headerTextMetrics.font + text: "#" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: StudioTheme.Values.themeTextColor + } + } + + HorizontalHeaderView { + id: headerView + + property real topPadding: 5 + property real bottomPadding: 5 + + Layout.preferredHeight: headerTextMetrics.height + topPadding + bottomPadding + Layout.columnSpan: 2 + syncView: tableView + clip: true + + delegate: HeaderDelegate { + id: horizontalHeaderItem + + selectedItem: tableView.model.selectedColumn + color: StudioTheme.Values.themeControlBackgroundInteraction + + function getGlobalBottomLeft() { + return mapToGlobal(0, horizontalHeaderItem.height) + } + + MouseArea { + anchors.fill: parent + anchors.margins: 5 + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: (mouse) => { + tableView.model.selectColumn(index) + + if (mouse.button === Qt.RightButton) + headerMenu.popIndex(index, horizontalHeaderItem.getGlobalBottomLeft()) + } + } + } + + StudioControls.Menu { + id: headerMenu + + property int clickedHeader: -1 + property point initialPosition + + function popIndex(clickedIndex, clickedRect) + { + headerMenu.clickedHeader = clickedIndex + headerMenu.initialPosition = clickedRect + headerMenu.popup() + } + + onClosed: { + headerMenu.clickedHeader = -1 + } + + StudioControls.MenuItem { + text: qsTr("Edit") + onTriggered: editProperyDialog.editProperty(headerMenu.clickedHeader, headerMenu.initialPosition) + } + + StudioControls.MenuItem { + text: qsTr("Delete") + onTriggered: deleteColumnDialog.popUp(headerMenu.clickedHeader) + } + + StudioControls.MenuItem { + text: qsTr("Sort Ascending") + onTriggered: sortedModel.sort(headerMenu.clickedHeader, Qt.AscendingOrder) + } + + StudioControls.MenuItem { + text: qsTr("Sort Descending") + onTriggered: sortedModel.sort(headerMenu.clickedHeader, Qt.DescendingOrder) + } + } + } + + VerticalHeaderView { + id: rowIdView + + syncView: tableView + clip: true + + Layout.preferredHeight: tableView.height + Layout.rowSpan: 2 + Layout.alignment: Qt.AlignTop + Qt.AlignLeft + + delegate: HeaderDelegate { + selectedItem: tableView.model.selectedRow + color: StudioTheme.Values.themeControlBackgroundHover + + MouseArea { + anchors.fill: parent + anchors.margins: 5 + acceptedButtons: Qt.LeftButton + onClicked: tableView.model.selectRow(index) + } + } + } + + TableView { + id: tableView + + model: root.sortedModel + clip: true + + Layout.preferredWidth: tableView.contentWidth + Layout.preferredHeight: tableView.contentHeight + Layout.minimumWidth: 100 + Layout.minimumHeight: 20 + Layout.maximumWidth: root.width + + delegate: Rectangle { + id: itemCell + implicitWidth: 100 + implicitHeight: itemText.height + border.width: 1 + + Text { + id: itemText + + text: display ? display : "" + + width: parent.width + leftPadding: 5 + topPadding: 3 + bottomPadding: 3 + font.pixelSize: StudioTheme.Values.baseFontSize + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + + TableView.editDelegate: CollectionDetailsEditDelegate { + anchors { + top: itemText.top + left: itemText.left + } + } + + states: [ + State { + name: "default" + when: !itemSelected + + PropertyChanges { + target: itemCell + color: StudioTheme.Values.themeControlBackground + border.color: StudioTheme.Values.themeControlBackgroundInteraction + } + + PropertyChanges { + target: itemText + color: StudioTheme.Values.themePlaceholderTextColorInteraction + } + }, + State { + name: "selected" + when: itemSelected + + PropertyChanges { + target: itemCell + color: StudioTheme.Values.themeControlBackgroundInteraction + border.color: StudioTheme.Values.themeControlBackground + } + + PropertyChanges { + target: itemText + color: StudioTheme.Values.themeInteraction + } + } + ] + } + } + + HelperWidgets.IconButton { + id: addColumnContainer + + iconSize:16 + Layout.preferredWidth: 24 + Layout.preferredHeight: tableView.height + Layout.minimumHeight: 24 + Layout.alignment: Qt.AlignLeft + Qt.AlignVCenter + + icon: StudioTheme.Constants.create_medium + tooltip: "Add Column" + + onClicked: toolbar.addNewColumn() + } + + HelperWidgets.IconButton { + id: addRowContainer + + iconSize:16 + Layout.preferredWidth: tableView.width + Layout.preferredHeight: 24 + Layout.minimumWidth: 24 + Layout.alignment: Qt.AlignTop + Qt.AlignHCenter + + icon: StudioTheme.Constants.create_medium + tooltip: "Add Row" + + onClicked: toolbar.addNewRow() + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + } + } + + Text { + anchors.fill: parent + text: qsTr("Select a model to continue") + visible: !topRow.visible + textFormat: Text.RichText + color: StudioTheme.Values.themeTextColor + font.pixelSize: StudioTheme.Values.mediumFontSize + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + } + + TextMetrics { + id: headerTextMetrics + + font.pixelSize: StudioTheme.Values.baseFontSize + text: "Xq" + } + + component HeaderDelegate: Rectangle { + id: headerItem + + required property int selectedItem + property alias horizontalAlignment: headerText.horizontalAlignment + property alias verticalAlignment: headerText.verticalAlignment + + implicitWidth: headerText.implicitWidth + implicitHeight: headerText.implicitHeight + border.width: 1 + clip: true + + Text { + id: headerText + + topPadding: headerView.topPadding + bottomPadding: headerView.bottomPadding + leftPadding: 5 + rightPadding: 5 + text: display + font: headerTextMetrics.font + color: StudioTheme.Values.themeTextColor + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + anchors.fill: parent + elide: Text.ElideRight + } + + states: [ + State { + name: "default" + when: index !== selectedItem + PropertyChanges { + target: headerItem + border.color: StudioTheme.Values.themeControlBackgroundInteraction + } + + PropertyChanges { + target: headerText + font.bold: false + } + }, + State { + name: "selected" + when: index === selectedItem + + PropertyChanges { + target: headerItem + border.color: StudioTheme.Values.themeControlBackground + } + + PropertyChanges { + target: headerText + font.bold: true + } + } + ] + } + + EditPropertyDialog { + id: editProperyDialog + model: root.model + } + + StudioControls.Dialog { + id: deleteColumnDialog + + property int clickedIndex: -1 + + title: qsTr("Delete Column") + width: 400 + + onAccepted: { + root.model.removeColumn(clickedIndex) + } + + function popUp(index) + { + deleteColumnDialog.clickedIndex = index + deleteColumnDialog.open() + } + + contentItem: ColumnLayout { + spacing: StudioTheme.Values.sectionColumnSpacing + + Text { + text: qsTr("Are you sure that you want to delete column \"%1\"?").arg( + root.model.headerData( + deleteColumnDialog.clickedIndex, Qt.Horizontal)) + color: StudioTheme.Values.themeTextColor + } + + RowLayout { + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + spacing: StudioTheme.Values.sectionRowSpacing + + HelperWidgets.Button { + text: qsTr("Delete") + onClicked: deleteColumnDialog.accept() + } + + HelperWidgets.Button { + text: qsTr("Cancel") + onClicked: deleteColumnDialog.reject() + } + } + } + } +} diff --git a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionItem.qml b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionItem.qml index a92cdf065d8..589a1433965 100644 --- a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionItem.qml +++ b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionItem.qml @@ -3,7 +3,7 @@ import QtQuick import QtQuick.Controls -import Qt.labs.platform as PlatformWidgets +import QtQuick.Layouts import HelperWidgets 2.0 as HelperWidgets import StudioControls 1.0 as StudioControls import StudioTheme as StudioTheme @@ -12,9 +12,10 @@ Item { id: root implicitWidth: 300 - implicitHeight: innerRect.height + 6 + implicitHeight: innerRect.height + 3 property color textColor + property string sourceType signal selectItem(int itemIndex) signal deleteItem() @@ -23,7 +24,7 @@ Item { id: boundingRect anchors.centerIn: root - width: root.width - 24 + width: parent.width height: nameHolder.height clip: true @@ -47,21 +48,23 @@ Item { anchors.fill: parent } - Row { - width: parent.width - threeDots.width - leftPadding: 20 + RowLayout { + width: parent.width Text { id: moveTool property StudioTheme.ControlStyle style: StudioTheme.Values.viewBarButtonStyle - width: moveTool.style.squareControlSize.width - height: nameHolder.height + Layout.preferredWidth: moveTool.style.squareControlSize.width + Layout.preferredHeight: nameHolder.height + Layout.leftMargin: 12 + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter text: StudioTheme.Constants.dragmarks font.family: StudioTheme.Constants.iconFont.family font.pixelSize: moveTool.style.baseIconFontSize + color: root.textColor horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } @@ -69,9 +72,12 @@ Item { Text { id: nameHolder + Layout.fillWidth: true + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + text: collectionName font.pixelSize: StudioTheme.Values.baseFontSize - color: textColor + color: root.textColor leftPadding: 5 topPadding: 8 rightPadding: 8 @@ -79,82 +85,92 @@ Item { elide: Text.ElideMiddle verticalAlignment: Text.AlignVCenter } - } - Text { - id: threeDots + Text { + id: threeDots - text: StudioTheme.Constants.more_medium - font.family: StudioTheme.Constants.iconFont.family - font.pixelSize: StudioTheme.Values.baseIconFontSize - color: textColor - anchors.right: boundingRect.right - anchors.verticalCenter: parent.verticalCenter - rightPadding: 12 - topPadding: nameHolder.topPadding - bottomPadding: nameHolder.bottomPadding - verticalAlignment: Text.AlignVCenter + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + text: StudioTheme.Constants.more_medium + font.family: StudioTheme.Constants.iconFont.family + font.pixelSize: StudioTheme.Values.baseIconFontSize + color: root.textColor + rightPadding: 12 + topPadding: nameHolder.topPadding + bottomPadding: nameHolder.bottomPadding + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.RightButton + Qt.LeftButton - onClicked: (event) => { - collectionMenu.open() - event.accepted = true + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton | Qt.LeftButton + onClicked: collectionMenu.popup() } } } } - PlatformWidgets.Menu { + StudioControls.Menu { id: collectionMenu - PlatformWidgets.MenuItem { + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + + StudioControls.MenuItem { text: qsTr("Delete") shortcut: StandardKey.Delete onTriggered: deleteDialog.open() } - PlatformWidgets.MenuItem { + StudioControls.MenuItem { text: qsTr("Rename") shortcut: StandardKey.Replace onTriggered: renameDialog.open() } } + component Spacer: Item { + implicitWidth: 1 + implicitHeight: StudioTheme.Values.columnGap + } + StudioControls.Dialog { id: deleteDialog - title: qsTr("Deleting whole collection") + title: qsTr("Deleting the model") + clip: true - contentItem: Column { + contentItem: ColumnLayout { spacing: 2 Text { - text: qsTr("Are you sure that you want to delete collection \"" + collectionName + "\"?") + Layout.fillWidth: true + + wrapMode: Text.WordWrap color: StudioTheme.Values.themeTextColor + text: { + if (root.sourceType === "json") { + qsTr("Are you sure that you want to delete model \"%1\"?" + + "\nThe model will be deleted permanently.").arg(collectionName) + } else if (root.sourceType === "csv") { + qsTr("Are you sure that you want to delete model \"%1\"?" + + "\nThe model will be removed from the project " + + "but the file will not be deleted.").arg(collectionName) + } + } } - Item { // spacer - width: 1 - height: 20 - } + Spacer {} - Row { - anchors.right: parent.right - spacing: 10 + RowLayout { + spacing: StudioTheme.Values.sectionRowSpacing + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter HelperWidgets.Button { - id: btnDelete - anchors.verticalCenter: parent.verticalCenter - text: qsTr("Delete") - onClicked: root.deleteItem(index) + onClicked: root.deleteItem() } HelperWidgets.Button { text: qsTr("Cancel") - anchors.verticalCenter: parent.verticalCenter onClicked: deleteDialog.reject() } } @@ -164,7 +180,7 @@ Item { StudioControls.Dialog { id: renameDialog - title: qsTr("Rename collection") + title: qsTr("Rename model") onAccepted: { if (newNameField.text !== "") @@ -175,7 +191,7 @@ Item { newNameField.text = collectionName } - contentItem: Column { + contentItem: ColumnLayout { spacing: 2 Text { @@ -183,60 +199,57 @@ Item { color: StudioTheme.Values.themeTextColor } - Row { - spacing: 10 - Text { - text: qsTr("New name:") - color: StudioTheme.Values.themeTextColor - } + Spacer {} - StudioControls.TextField { - id: newNameField + Text { + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + text: qsTr("New name:") + color: StudioTheme.Values.themeTextColor + } - anchors.verticalCenter: parent.verticalCenter - actionIndicator.visible: false - translationIndicator.visible: false - validator: newNameValidator + StudioControls.TextField { + id: newNameField - Keys.onEnterPressed: renameDialog.accept() - Keys.onReturnPressed: renameDialog.accept() - Keys.onEscapePressed: renameDialog.reject() + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + Layout.fillWidth: true - onTextChanged: { - btnRename.enabled = newNameField.text !== "" - } + actionIndicator.visible: false + translationIndicator.visible: false + validator: newNameValidator + + Keys.onEnterPressed: renameDialog.accept() + Keys.onReturnPressed: renameDialog.accept() + Keys.onEscapePressed: renameDialog.reject() + + onTextChanged: { + btnRename.enabled = newNameField.text !== "" } } - Item { // spacer - width: 1 - height: 20 - } + Spacer {} - Row { - anchors.right: parent.right - spacing: 10 + RowLayout { + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + spacing: StudioTheme.Values.sectionRowSpacing HelperWidgets.Button { id: btnRename - anchors.verticalCenter: parent.verticalCenter text: qsTr("Rename") onClicked: renameDialog.accept() } HelperWidgets.Button { text: qsTr("Cancel") - anchors.verticalCenter: parent.verticalCenter onClicked: renameDialog.reject() } } } } - HelperWidgets.RegExpValidator { + RegularExpressionValidator { id: newNameValidator - regExp: /^\w+$/ + regularExpression: /^\w+$/ } states: [ @@ -277,12 +290,12 @@ Item { PropertyChanges { target: innerRect opacity: 1 - color: StudioTheme.Values.themeControlBackgroundInteraction + color: StudioTheme.Values.themeIconColorSelected } PropertyChanges { target: root - textColor: StudioTheme.Values.themeIconColorSelected + textColor: StudioTheme.Values.themeTextSelectedTextColor } } ] diff --git a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionView.qml b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionView.qml index e6fddf33ee4..70eb32c9c86 100644 --- a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionView.qml +++ b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionView.qml @@ -3,6 +3,7 @@ import QtQuick import QtQuick.Controls +import QtQuick.Layouts import QtQuickDesignerTheme 1.0 import HelperWidgets 2.0 as HelperWidgets import StudioTheme 1.0 as StudioTheme @@ -14,7 +15,8 @@ Item { property var rootView: CollectionEditorBackend.rootView property var model: CollectionEditorBackend.model - property var singleCollectionModel: CollectionEditorBackend.singleCollectionModel + property var collectionDetailsModel: CollectionEditorBackend.collectionDetailsModel + property var collectionDetailsSortFilterModel: CollectionEditorBackend.collectionDetailsSortFilterModel function showWarning(title, message) { warningDialog.title = title @@ -40,6 +42,7 @@ Item { id: newCollection backendValue: root.rootView + sourceModel: root.model anchors.centerIn: parent } @@ -50,57 +53,63 @@ Item { message: "" } - Rectangle { - id: collectionsRect + GridLayout { + id: grid + readonly property bool isHorizontal: width >= 500 - color: StudioTheme.Values.themeToolbarBackground - width: 300 - height: root.height + anchors.fill: parent + columns: isHorizontal ? 3 : 1 - Column { - width: parent.width + ColumnLayout { + id: collectionsSideBar - Rectangle { - width: parent.width - height: StudioTheme.Values.height + 5 - color: StudioTheme.Values.themeToolbarBackground + Layout.alignment: Qt.AlignTop | Qt.AlignLeft + Layout.minimumWidth: 300 + Layout.fillWidth: !grid.isHorizontal + + RowLayout { + spacing: StudioTheme.Values.sectionRowSpacing + Layout.fillWidth: true + Layout.preferredHeight: 50 Text { - id: collectionText + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + Layout.fillWidth: true - anchors.verticalCenter: parent.verticalCenter - text: qsTr("Collections") - font.pixelSize: StudioTheme.Values.mediumIconFont + text: qsTr("Data Models") + font.pixelSize: StudioTheme.Values.baseFontSize color: StudioTheme.Values.themeTextColor leftPadding: 15 } - Row { - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - rightPadding: 12 - spacing: 2 + IconTextButton { + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter - HelperWidgets.IconButton { - icon: StudioTheme.Constants.downloadjson_large - tooltip: qsTr("Import Json") + icon: StudioTheme.Constants.import_medium + text: qsTr("JSON") + tooltip: qsTr("Import JSON") + radius: StudioTheme.Values.smallRadius - onClicked: jsonImporter.open() - } + onClicked: jsonImporter.open() + } - HelperWidgets.IconButton { - icon: StudioTheme.Constants.downloadcsv_large - tooltip: qsTr("Import CSV") + IconTextButton { + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter - onClicked: csvImporter.open() - } + icon: StudioTheme.Constants.import_medium + text: qsTr("CSV") + tooltip: qsTr("Import CSV") + radius: StudioTheme.Values.smallRadius + + onClicked: csvImporter.open() } } - Rectangle { // Collections - width: parent.width + Rectangle { // Model Groups + Layout.fillWidth: true color: StudioTheme.Values.themeBackgroundColorNormal - height: 330 + Layout.minimumHeight: 150 + Layout.preferredHeight: sourceListView.contentHeight MouseArea { anchors.fill: parent @@ -114,41 +123,46 @@ Item { ListView { id: sourceListView - width: parent.width - height: contentHeight + anchors.fill: parent model: root.model delegate: ModelSourceItem { + implicitWidth: sourceListView.width onDeleteItem: root.model.removeRow(index) + hasSelectedTarget: root.rootView.targetNodeSelected + onAssignToSelected: root.rootView.assignSourceNodeToSelectedItem(sourceNode) } - } } - Rectangle { - width: parent.width - height: addCollectionButton.height - color: StudioTheme.Values.themeBackgroundColorNormal + HelperWidgets.IconButton { + id: addCollectionButton - IconTextButton { - id: addCollectionButton + iconSize:16 + Layout.fillWidth: true + Layout.minimumWidth: 24 + Layout.alignment: Qt.AlignTop | Qt.AlignHCenter - anchors.centerIn: parent - text: qsTr("Add new collection") - icon: StudioTheme.Constants.create_medium - onClicked: newCollection.open() - } + tooltip: qsTr("Add a new model") + icon: StudioTheme.Constants.create_medium + onClicked: newCollection.open() } } - } - SingleCollectionView { - model: root.singleCollectionModel - anchors { - left: collectionsRect.right - right: parent.right - top: parent.top - bottom: parent.bottom + Rectangle { // Splitter + Layout.fillWidth: !grid.isHorizontal + Layout.fillHeight: grid.isHorizontal + Layout.minimumWidth: 2 + Layout.minimumHeight: 2 + color: "black" + } + + CollectionDetailsView { + model: root.collectionDetailsModel + backend: root.model + sortedModel: root.collectionDetailsSortFilterModel + Layout.fillHeight: true + Layout.fillWidth: true } } } diff --git a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CsvImport.qml b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CsvImport.qml index 5323d6759e9..6bcd6e97a3a 100644 --- a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CsvImport.qml +++ b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CsvImport.qml @@ -3,6 +3,7 @@ import QtQuick import QtQuick.Controls +import QtQuick.Layouts import QtQuickDesignerTheme 1.0 import Qt.labs.platform as PlatformWidgets import HelperWidgets 2.0 as HelperWidgets @@ -32,9 +33,9 @@ StudioControls.Dialog { fileName.text = "" } - HelperWidgets.RegExpValidator { + RegularExpressionValidator { id: fileNameValidator - regExp: /^(\w[^*> 0) + root.expanded = !root.expanded || sourceIsSelected; + } + + ColumnLayout { id: wholeColumn + width: parent.width + spacing: 0 Item { id: boundingRect - anchors.centerIn: root - width: root.width - 24 - height: nameHolder.height + Layout.fillWidth: true + Layout.preferredHeight: nameHolder.height + Layout.leftMargin: 6 clip: true MouseArea { @@ -48,8 +59,7 @@ Item { } onDoubleClicked: (event) => { - if (collectionListView.count > 0) - root.expanded = !root.expanded; + root.toggleExpanded() } } @@ -58,26 +68,27 @@ Item { anchors.fill: parent } - Row { - width: parent.width - threeDots.width - leftPadding: 20 + RowLayout { + width: parent.width Text { id: expandButton property StudioTheme.ControlStyle style: StudioTheme.Values.viewBarButtonStyle - width: expandButton.style.squareControlSize.width - height: nameHolder.height + Layout.preferredWidth: expandButton.style.squareControlSize.width + Layout.preferredHeight: nameHolder.height + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter text: StudioTheme.Constants.startNode font.family: StudioTheme.Constants.iconFont.family - font.pixelSize: expandButton.style.baseIconFontSize + font.pixelSize: 6 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter color: textColor rotation: root.expanded ? 90 : 0 + visible: collectionListView.count > 0 Behavior on rotation { SpringAnimation { spring: 2; damping: 0.2 } @@ -85,18 +96,17 @@ Item { MouseArea { anchors.fill: parent - acceptedButtons: Qt.RightButton + Qt.LeftButton - onClicked: (event) => { - root.expanded = !root.expanded - event.accepted = true - } + acceptedButtons: Qt.RightButton | Qt.LeftButton + onClicked: root.toggleExpanded() } - visible: collectionListView.count > 0 } Text { id: nameHolder + Layout.fillWidth: true + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + text: sourceName font.pixelSize: StudioTheme.Values.baseFontSize color: textColor @@ -105,30 +115,29 @@ Item { rightPadding: 8 bottomPadding: 8 elide: Text.ElideMiddle + horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter } - } - Text { - id: threeDots + Text { + id: threeDots - text: StudioTheme.Constants.more_medium - font.family: StudioTheme.Constants.iconFont.family - font.pixelSize: StudioTheme.Values.baseIconFontSize - color: textColor - anchors.right: boundingRect.right - anchors.verticalCenter: parent.verticalCenter - rightPadding: 12 - topPadding: nameHolder.topPadding - bottomPadding: nameHolder.bottomPadding - verticalAlignment: Text.AlignVCenter + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.RightButton + Qt.LeftButton - onClicked: (event) => { - collectionMenu.popup() - event.accepted = true + text: StudioTheme.Constants.more_medium + font.family: StudioTheme.Constants.iconFont.family + font.pixelSize: StudioTheme.Values.baseIconFontSize + color: textColor + rightPadding: 12 + topPadding: nameHolder.topPadding + bottomPadding: nameHolder.bottomPadding + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton | Qt.LeftButton + onClicked: collectionMenu.popup() } } } @@ -137,18 +146,20 @@ Item { ListView { id: collectionListView - width: parent.width - height: root.expanded ? contentHeight : 0 - model: collections + Layout.fillWidth: true + Layout.preferredHeight: root.expanded ? contentHeight : 0 + Layout.leftMargin: 6 + model: internalModels clip: true - Behavior on height { + Behavior on Layout.preferredHeight { NumberAnimation {duration: 500} } delegate: CollectionItem { - width: parent.width - onDeleteItem: root.model.removeRow(index) + width: collectionListView.width + sourceType: collectionListView.model.sourceType + onDeleteItem: collectionListView.model.removeRow(index) } } } @@ -156,6 +167,8 @@ Item { StudioControls.Menu { id: collectionMenu + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + StudioControls.MenuItem { text: qsTr("Delete") shortcut: StandardKey.Delete @@ -167,6 +180,17 @@ Item { shortcut: StandardKey.Replace onTriggered: renameDialog.open() } + + StudioControls.MenuItem { + text: qsTr("Assign to the selected node") + enabled: root.hasSelectedTarget + onTriggered: root.assignToSelected() + } + } + + component Spacer: Item { + implicitWidth: 1 + implicitHeight: StudioTheme.Values.sectionColumnSpacing } StudioControls.Dialog { @@ -174,22 +198,17 @@ Item { title: qsTr("Deleting source") - contentItem: Column { - spacing: 2 + contentItem: ColumnLayout { + spacing: StudioTheme.Values.sectionColumnSpacing Text { text: qsTr("Are you sure that you want to delete source \"" + sourceName + "\"?") color: StudioTheme.Values.themeTextColor } - Item { // spacer - width: 1 - height: 20 - } - - Row { - anchors.right: parent.right - spacing: 10 + RowLayout { + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + spacing: StudioTheme.Values.sectionRowSpacing HelperWidgets.Button { id: btnDelete @@ -220,7 +239,7 @@ Item { newNameField.text = sourceName } - contentItem: Column { + contentItem: ColumnLayout { spacing: 2 Text { @@ -228,38 +247,35 @@ Item { color: StudioTheme.Values.themeTextColor } - Row { - spacing: 10 - Text { - text: qsTr("New name:") - color: StudioTheme.Values.themeTextColor - } + Spacer {} - StudioControls.TextField { - id: newNameField + Text { + text: qsTr("New name:") + color: StudioTheme.Values.themeTextColor + } - actionIndicator.visible: false - translationIndicator.visible: false - validator: newNameValidator + StudioControls.TextField { + id: newNameField - Keys.onEnterPressed: renameDialog.accept() - Keys.onReturnPressed: renameDialog.accept() - Keys.onEscapePressed: renameDialog.reject() + Layout.fillWidth: true + actionIndicator.visible: false + translationIndicator.visible: false + validator: newNameValidator - onTextChanged: { - btnRename.enabled = newNameField.text !== "" - } + Keys.onEnterPressed: renameDialog.accept() + Keys.onReturnPressed: renameDialog.accept() + Keys.onEscapePressed: renameDialog.reject() + + onTextChanged: { + btnRename.enabled = newNameField.text !== "" } } - Item { // spacer - width: 1 - height: 20 - } + Spacer {} - Row { - anchors.right: parent.right - spacing: 10 + RowLayout { + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + spacing: StudioTheme.Values.sectionRowSpacing HelperWidgets.Button { id: btnRename @@ -276,9 +292,9 @@ Item { } } - HelperWidgets.RegExpValidator { + RegularExpressionValidator { id: newNameValidator - regExp: /^\w+$/ + regularExpression: /^\w+$/ } states: [ @@ -325,6 +341,12 @@ Item { PropertyChanges { target: root textColor: StudioTheme.Values.themeIconColorSelected + expanded: true + } + + PropertyChanges { + target: expandButton + enabled: false } } ] diff --git a/share/qtcreator/qmldesigner/collectionEditorQmlSource/NewCollectionDialog.qml b/share/qtcreator/qmldesigner/collectionEditorQmlSource/NewCollectionDialog.qml index b0cf950fd7a..ee5dba02706 100644 --- a/share/qtcreator/qmldesigner/collectionEditorQmlSource/NewCollectionDialog.qml +++ b/share/qtcreator/qmldesigner/collectionEditorQmlSource/NewCollectionDialog.qml @@ -3,23 +3,37 @@ import QtQuick import QtQuick.Controls +import QtQuick.Layouts import QtQuickDesignerTheme 1.0 +import Qt.labs.platform as PlatformWidgets import HelperWidgets 2.0 as HelperWidgets import StudioControls 1.0 as StudioControls import StudioTheme as StudioTheme +import CollectionEditor 1.0 StudioControls.Dialog { id: root - title: qsTr("Add a new Collection") + enum SourceType { NewJson, NewCsv, ExistingCollection, NewCollectionToJson } + + required property var backendValue + required property var sourceModel + + readonly property alias collectionType: typeMode.collectionType + readonly property bool isValid: collectionName.isValid + && jsonCollections.isValid + && newCollectionPath.isValid + + title: qsTr("Add a new Model") anchors.centerIn: parent closePolicy: Popup.CloseOnEscape modal: true - required property var backendValue - onOpened: { - collectionName.text = "Collection" + collectionName.text = qsTr("Model") + updateType() + updateJsonSourceIndex() + updateCollectionExists() } onRejected: { @@ -27,65 +41,252 @@ StudioControls.Dialog { } onAccepted: { - if (collectionName.text !== "") - root.backendValue.addCollection(collectionName.text) + if (root.isValid) { + root.backendValue.addCollection(collectionName.text, + root.collectionType, + newCollectionPath.text, + jsonCollections.currentValue) + + } } - contentItem: Column { - spacing: 10 - Row { - spacing: 10 + function updateType() { + newCollectionPath.text = "" + if (typeMode.currentValue === NewCollectionDialog.SourceType.NewJson) { + newCollectionFileDialog.nameFilters = ["JSON Files (*.json)"] + newCollectionFileDialog.fileMode = PlatformWidgets.FileDialog.SaveFile + newCollectionPath.enabled = true + jsonCollections.enabled = false + typeMode.collectionType = "json" + } else if (typeMode.currentValue === NewCollectionDialog.SourceType.NewCsv) { + newCollectionFileDialog.nameFilters = ["Comma-Separated Values (*.csv)"] + newCollectionFileDialog.fileMode = PlatformWidgets.FileDialog.SaveFile + newCollectionPath.enabled = true + jsonCollections.enabled = false + typeMode.collectionType = "csv" + } else if (typeMode.currentValue === NewCollectionDialog.SourceType.ExistingCollection) { + newCollectionFileDialog.nameFilters = ["All Model Group Files (*.json *.csv)", + "JSON Files (*.json)", + "Comma-Separated Values (*.csv)"] + newCollectionFileDialog.fileMode = PlatformWidgets.FileDialog.OpenFile + newCollectionPath.enabled = true + jsonCollections.enabled = false + typeMode.collectionType = "existing" + } else if (typeMode.currentValue === NewCollectionDialog.SourceType.NewCollectionToJson) { + newCollectionFileDialog.nameFilters = [""] + newCollectionPath.enabled = false + jsonCollections.enabled = true + typeMode.collectionType = "json" + } + } + + function updateJsonSourceIndex() { + if (!jsonCollections.enabled) { + jsonCollections.currentIndex = -1 + return + } + + if (jsonCollections.currentIndex === -1 && jsonCollections.model.rowCount()) + jsonCollections.currentIndex = 0 + } + + function updateCollectionExists() { + collectionName.alreadyExists = sourceModel.collectionExists(jsonCollections.currentValue, + collectionName.text) + } + + component NameField: Text { + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + horizontalAlignment: Qt.AlignRight + verticalAlignment: Qt.AlignCenter + color: StudioTheme.Values.themeTextColor + font.family: StudioTheme.Constants.font.family + font.pixelSize: StudioTheme.Values.baseIconFontSize + } + + component ErrorField: Text { + Layout.columnSpan: 2 + color: StudioTheme.Values.themeError + font.family: StudioTheme.Constants.font.family + font.pixelSize: StudioTheme.Values.baseIconFontSize + } + + component Spacer: Item { + Layout.minimumWidth: 1 + Layout.preferredHeight: StudioTheme.Values.columnGap + } + + contentItem: ColumnLayout { + spacing: 5 + + NameField { + text: qsTr("Type") + } + + StudioControls.ComboBox { + id: typeMode + + property string collectionType + + Layout.minimumWidth: 300 + Layout.fillWidth: true + + model: ListModel { + ListElement { text: qsTr("New JSON model group"); value: NewCollectionDialog.SourceType.NewJson} + ListElement { text: qsTr("New CSV model"); value: NewCollectionDialog.SourceType.NewCsv} + ListElement { text: qsTr("Import an existing model group"); value: NewCollectionDialog.SourceType.ExistingCollection} + ListElement { text: qsTr("Add a model to an available JSON model group"); value: NewCollectionDialog.SourceType.NewCollectionToJson} + } + + textRole: "text" + valueRole: "value" + actionIndicatorVisible: false + + onCurrentValueChanged: root.updateType() + } + + Spacer {} + + RowLayout { + visible: newCollectionPath.enabled + + NameField { + text: qsTr("File location") + visible: newCollectionPath.enabled + } + Text { - text: qsTr("Collection name: ") - anchors.verticalCenter: parent.verticalCenter - color: StudioTheme.Values.themeTextColor + id: newCollectionPath + + readonly property bool isValid: !newCollectionPath.enabled || newCollectionPath.text !== "" + + Layout.fillWidth: true + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + elide: Text.ElideRight + font.family: StudioTheme.Constants.font.family + font.pixelSize: StudioTheme.Values.baseIconFontSize + color: StudioTheme.Values.themePlaceholderTextColor } - StudioControls.TextField { - id: collectionName + HelperWidgets.Button { + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + text: qsTr("Select") - anchors.verticalCenter: parent.verticalCenter - actionIndicator.visible: false - translationIndicator.visible: false - validator: HelperWidgets.RegExpValidator { - regExp: /^\w+$/ + onClicked: newCollectionFileDialog.open() + + PlatformWidgets.FileDialog { + id: newCollectionFileDialog + + title: qsTr("Select source file") + fileMode: PlatformWidgets.FileDialog.OpenFile + acceptLabel: newCollectionFileDialog.fileMode === PlatformWidgets.FileDialog.OpenFile + ? qsTr("Open") + : qsTr("Add") + + onAccepted: newCollectionPath.text = newCollectionFileDialog.currentFile } - - Keys.onEnterPressed: btnCreate.onClicked() - Keys.onReturnPressed: btnCreate.onClicked() - Keys.onEscapePressed: root.reject() } } - Text { - id: fieldErrorText - color: StudioTheme.Values.themeTextColor - anchors.right: parent.right - text: qsTr("Collection name can not be empty") - visible: collectionName.text === "" + ErrorField { + visible: !newCollectionPath.isValid + text: qsTr("Select a file to continue") } - Item { // spacer - width: 1 - height: 20 + Spacer { visible: newCollectionPath.enabled } + + NameField { + text: qsTr("JSON model group") + visible: jsonCollections.enabled } - Row { - anchors.right: parent.right - spacing: 10 + StudioControls.ComboBox { + id: jsonCollections + + readonly property bool isValid: !jsonCollections.enabled || jsonCollections.currentIndex !== -1 + + Layout.fillWidth: true + + implicitWidth: 300 + textRole: "sourceName" + valueRole: "sourceNode" + visible: jsonCollections.enabled + actionIndicatorVisible: false + + model: CollectionJsonSourceFilterModel { + sourceModel: root.sourceModel + onRowsInserted: root.updateJsonSourceIndex() + onModelReset: root.updateJsonSourceIndex() + onRowsRemoved: root.updateJsonSourceIndex() + } + + onEnabledChanged: root.updateJsonSourceIndex() + onCurrentValueChanged: root.updateCollectionExists() + } + + ErrorField { + visible: !jsonCollections.isValid + text: qsTr("Add a JSON resource to continue") + } + + Spacer {visible: jsonCollections.visible } + + NameField { + text: qsTr("The model name") + visible: collectionName.enabled + } + + StudioControls.TextField { + id: collectionName + + readonly property bool isValid: !collectionName.enabled + || (collectionName.text !== "" && !collectionName.alreadyExists) + property bool alreadyExists + + Layout.fillWidth: true + + visible: collectionName.enabled + actionIndicator.visible: false + translationIndicator.visible: false + validator: RegularExpressionValidator { + regularExpression: /^\w+$/ + } + + Keys.onEnterPressed: btnCreate.onClicked() + Keys.onReturnPressed: btnCreate.onClicked() + Keys.onEscapePressed: root.reject() + + onTextChanged: root.updateCollectionExists() + } + + ErrorField { + text: qsTr("The model name can not be empty") + visible: collectionName.enabled && collectionName.text === "" + } + + ErrorField { + text: qsTr("The model name already exists %1").arg(collectionName.text) + visible: collectionName.enabled && collectionName.alreadyExists + } + + Spacer { visible: collectionName.visible } + + RowLayout { + spacing: StudioTheme.Values.sectionRowSpacing + Layout.alignment: Qt.AlignRight | Qt.AlignBottom HelperWidgets.Button { id: btnCreate - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter text: qsTr("Create") - enabled: collectionName.text !== "" + enabled: root.isValid onClicked: root.accept() } HelperWidgets.Button { + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter text: qsTr("Cancel") - anchors.verticalCenter: parent.verticalCenter onClicked: root.reject() } } diff --git a/share/qtcreator/qmldesigner/collectionEditorQmlSource/SingleCollectionView.qml b/share/qtcreator/qmldesigner/collectionEditorQmlSource/SingleCollectionView.qml deleted file mode 100644 index 8ad8a63e916..00000000000 --- a/share/qtcreator/qmldesigner/collectionEditorQmlSource/SingleCollectionView.qml +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -import QtQuick -import QtQuick.Controls -import StudioTheme 1.0 as StudioTheme - -Rectangle { - id: root - - required property var model - - property alias leftPadding: topRow.leftPadding - property real rightPadding: topRow.rightPadding - - color: StudioTheme.Values.themeBackgroundColorAlternate - - Column { - id: topRow - - spacing: 0 - width: parent.width - leftPadding: 20 - rightPadding: 0 - topPadding: 5 - - Text { - id: collectionNameText - - leftPadding: 8 - rightPadding: 8 - topPadding: 3 - bottomPadding: 3 - - color: StudioTheme.Values.themeTextColor - text: root.model.collectionName - font.pixelSize: StudioTheme.Values.mediumIconFont - elide: Text.ElideRight - - Rectangle { - anchors.fill: parent - z: parent.z - 1 - color: StudioTheme.Values.themeBackgroundColorNormal - } - } - - Item { // spacer - width: 1 - height: 10 - } - - HorizontalHeaderView { - id: headerView - - property real topPadding: 5 - property real bottomPadding: 5 - - height: headerMetrics.height + topPadding + bottomPadding - - syncView: tableView - clip: true - - delegate: Rectangle { - implicitWidth: 100 - implicitHeight: headerText.height - color: StudioTheme.Values.themeControlBackground - border.width: 2 - border.color: StudioTheme.Values.themeControlOutline - clip: true - - Text { - id: headerText - - topPadding: headerView.topPadding - bottomPadding: headerView.bottomPadding - leftPadding: 5 - rightPadding: 5 - text: display - font.pixelSize: headerMetrics.font - color: StudioTheme.Values.themeIdleGreen - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - anchors.centerIn: parent - elide: Text.ElideRight - } - } - - TextMetrics { - id: headerMetrics - - font.pixelSize: StudioTheme.Values.baseFontSize - text: "Xq" - } - } - } - - TableView { - id: tableView - - anchors { - top: topRow.bottom - left: parent.left - right: parent.right - bottom: parent.bottom - leftMargin: root.leftPadding - rightMargin: root.rightPadding - } - - model: root.model - - delegate: Rectangle { - implicitWidth: 100 - implicitHeight: itemText.height - color: StudioTheme.Values.themeControlBackground - border.width: 1 - border.color: StudioTheme.Values.themeControlOutline - - Text { - id: itemText - - text: display - width: parent.width - leftPadding: 5 - topPadding: 3 - bottomPadding: 3 - font.pixelSize: StudioTheme.Values.baseFontSize - color: StudioTheme.Values.themeTextColor - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - } - } - } -} diff --git a/share/qtcreator/qmldesigner/connectionseditor/BindingsDialog.qml b/share/qtcreator/qmldesigner/connectionseditor/BindingsDialog.qml index 8830e298df3..93970c5af83 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/BindingsDialog.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/BindingsDialog.qml @@ -2,11 +2,13 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick -import StudioTheme 1.0 as StudioTheme +import StudioTheme as StudioTheme +import StudioControls as StudioControls import HelperWidgets as HelperWidgets -PopupDialog { +StudioControls.PopupDialog { property alias backend: form.backend + titleBar: Row { spacing: 30 // TODO anchors.fill: parent @@ -16,6 +18,7 @@ PopupDialog { text: qsTr("Owner") font.pixelSize: StudioTheme.Values.myFontSize anchors.verticalCenter: parent.verticalCenter + HelperWidgets.ToolTipArea { anchors.fill: parent tooltip: qsTr("The owner of the property") @@ -27,14 +30,10 @@ PopupDialog { font.pixelSize: StudioTheme.Values.myFontSize anchors.verticalCenter: parent.verticalCenter text: form.backend.targetNode - } - } BindingsDialogForm { id: form - y: 32 - height: 160 } } diff --git a/share/qtcreator/qmldesigner/connectionseditor/BindingsDialogForm.qml b/share/qtcreator/qmldesigner/connectionseditor/BindingsDialogForm.qml index 749add63c43..9d1d74901e9 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/BindingsDialogForm.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/BindingsDialogForm.qml @@ -1,6 +1,6 @@ - // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + import QtQuick import QtQuick.Controls import StudioControls as StudioControls @@ -10,12 +10,11 @@ Column { id: root readonly property real horizontalSpacing: 10 - readonly property real verticalSpacing: 16 + readonly property real verticalSpacing: 12 readonly property real columnWidth: (root.width - root.horizontalSpacing) / 2 property var backend - y: StudioTheme.Values.popupMargin width: parent.width spacing: root.verticalSpacing @@ -53,8 +52,8 @@ Column { PopupLabel { width: root.columnWidth text: backend.targetNode + anchors.verticalCenter: parent.verticalCenter } - } Row { diff --git a/share/qtcreator/qmldesigner/connectionseditor/BindingsListView.qml b/share/qtcreator/qmldesigner/connectionseditor/BindingsListView.qml index 83db20646e0..b50e1fbb5c5 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/BindingsListView.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/BindingsListView.qml @@ -15,12 +15,6 @@ ListView { property bool adsFocus: false - // Temporarily remove due to dockwidget focus issue - //onAdsFocusChanged: { - // if (!root.adsFocus) - // dialog.close() - //} - clip: true interactive: true highlightMoveDuration: 0 @@ -43,9 +37,7 @@ ListView { && verticalScrollBar.isNeeded } - onVisibleChanged: { - dialog.hide() - } + onVisibleChanged: dialog.close() property int modelCurrentIndex: root.model.currentIndex ?? 0 @@ -72,7 +64,7 @@ ListView { function addBinding() { ConnectionsEditorEditorBackend.bindingModel.add() if (root.currentItem) - dialog.popup(root.currentItem.delegateMouseArea) + dialog.show(root.currentItem.delegateMouseArea) } function resetIndex() { @@ -104,9 +96,11 @@ ListView { property alias delegateMouseArea: mouseArea + property bool hovered: mouseArea.containsMouse || toolTipArea.containsMouse + width: ListView.view.width height: root.style.squareControlSize.height - color: mouseArea.containsMouse ? + color: itemDelegate.hovered ? itemDelegate.ListView.isCurrentItem ? root.style.interactionHover : root.style.background.hover : "transparent" @@ -120,7 +114,7 @@ ListView { onClicked: { root.model.currentIndex = itemDelegate.index root.currentIndex = itemDelegate.index - dialog.popup(mouseArea) + dialog.show(mouseArea) } } diff --git a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml index c7f0034b211..85a847ba5e0 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml @@ -2,12 +2,13 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick -import StudioControls 1.0 as StudioControls -import StudioTheme 1.0 as StudioTheme -import HelperWidgets 2.0 as HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme +import HelperWidgets as HelperWidgets -PopupDialog { +StudioControls.PopupDialog { id: root + property alias backend: form.backend titleBar: Row { @@ -19,6 +20,7 @@ PopupDialog { text: qsTr("Target") font.pixelSize: StudioTheme.Values.myFontSize anchors.verticalCenter: parent.verticalCenter + HelperWidgets.ToolTipArea { anchors.fill: parent tooltip: qsTr("Sets the Component that is connected to a Signal.") @@ -46,6 +48,9 @@ PopupDialog { function onPopupShouldClose() { root.close() } + function onPopupShouldOpen() { + root.showGlobal() + } } } } diff --git a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml index 7f410f1c3ff..603903dbdd2 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml @@ -16,7 +16,6 @@ Column { property var backend - y: StudioTheme.Values.popupMargin width: parent.width spacing: root.verticalSpacing @@ -45,6 +44,7 @@ Column { StudioControls.TopLevelComboBox { id: signal + style: StudioTheme.Values.connectionPopupControlStyle width: root.columnWidth @@ -216,71 +216,100 @@ Column { && backend.hasCondition && backend.hasElse } - HelperWidgets.AbstractButton { - id: editorButton - buttonIcon: StudioTheme.Constants.codeEditor_medium - tooltip: qsTr("Write the conditions for the components and the signals manually.") - onClicked: expressionDialogLoader.show() - } - - // Editor - Rectangle { - id: editor + // code preview toolbar + Column { + id: miniToolbarEditor width: parent.width - height: 150 - color: StudioTheme.Values.themeConnectionCodeEditor + spacing: -2 - Text { - id: code - anchors.fill: parent - anchors.margins: 4 - text: backend.indentedSource - color: StudioTheme.Values.themeTextColor - font.pixelSize: StudioTheme.Values.myFontSize - wrapMode: Text.Wrap - horizontalAlignment: code.lineCount === 1 ? Text.AlignHCenter : Text.AlignLeft - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight + Rectangle { + id: miniToolbar + width: parent.width + height: editorButton.height + 2 + radius: 4 + z: -1 + color: StudioTheme.Values.themeConnectionEditorMicroToolbar - } - - Loader { - id: expressionDialogLoader - parent: editor - anchors.fill: parent - visible: false - active: visible - - function show() { - expressionDialogLoader.visible = true - } - - sourceComponent: Item { - id: bindingEditorParent - - Component.onCompleted: { - bindingEditor.showWidget() - bindingEditor.text = backend.source - bindingEditor.showControls(false) - bindingEditor.setMultilne(true) - bindingEditor.updateWindowName() + Row { + spacing: 2 + HelperWidgets.AbstractButton { + id: editorButton + style: StudioTheme.Values.microToolbarButtonStyle + buttonIcon: StudioTheme.Constants.codeEditor_medium + tooltip: qsTr("Write the conditions for the components and the signals manually.") + onClicked: expressionDialogLoader.show() } - - ActionEditor { - id: bindingEditor - - onRejected: { - hideWidget() - expressionDialogLoader.visible = false - } - - onAccepted: { - backend.setNewSource(bindingEditor.text) - hideWidget() - expressionDialogLoader.visible = false - } + HelperWidgets.AbstractButton { + id: jumpToCodeButton + style: StudioTheme.Values.microToolbarButtonStyle + buttonIcon: StudioTheme.Constants.jumpToCode_medium + tooltip: qsTr("Jump to the code.") + onClicked: backend.jumpToCode() } } } - } -} + + // Editor + Rectangle { + id: editor + width: parent.width + height: 150 + color: StudioTheme.Values.themeConnectionCodeEditor + + Text { + id: code + anchors.fill: parent + anchors.margins: 4 + text: backend.indentedSource + color: StudioTheme.Values.themeTextColor + font.pixelSize: StudioTheme.Values.myFontSize + wrapMode: Text.Wrap + horizontalAlignment: code.lineCount === 1 ? Text.AlignHCenter : Text.AlignLeft + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + + } + + + Loader { + id: expressionDialogLoader + parent: editor + anchors.fill: parent + visible: false + active: visible + + function show() { + expressionDialogLoader.visible = true + } + + sourceComponent: Item { + id: bindingEditorParent + + Component.onCompleted: { + bindingEditor.showWidget() + bindingEditor.text = backend.source + bindingEditor.showControls(false) + bindingEditor.setMultilne(true) + bindingEditor.updateWindowName() + } + + ActionEditor { + id: bindingEditor + + onRejected: { + hideWidget() + expressionDialogLoader.visible = false + } + + onAccepted: { + backend.setNewSource(bindingEditor.text) + hideWidget() + expressionDialogLoader.visible = false + } + } + } + } // loader + } // rect + } //col 2 +}//col1 + diff --git a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsListView.qml b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsListView.qml index b783627c827..53a51a89e97 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsListView.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsListView.qml @@ -15,12 +15,6 @@ ListView { property bool adsFocus: false - // Temporarily remove due to dockwidget focus issue - //onAdsFocusChanged: { - // if (!root.adsFocus) - // dialog.close() - //} - clip: true interactive: true highlightMoveDuration: 0 @@ -43,9 +37,7 @@ ListView { && verticalScrollBar.isNeeded } - onVisibleChanged: { - dialog.hide() - } + onVisibleChanged: dialog.close() property int modelCurrentIndex: root.model.currentIndex ?? 0 @@ -73,7 +65,7 @@ ListView { function addConnection() { ConnectionsEditorEditorBackend.connectionModel.add() if (root.currentItem) - dialog.popup(root.currentItem.delegateMouseArea) + dialog.show(root.currentItem.delegateMouseArea) } function resetIndex() { @@ -124,7 +116,7 @@ ListView { root.model.currentIndex = itemDelegate.index root.currentIndex = itemDelegate.index dialog.backend.currentRow = itemDelegate.index - dialog.popup(mouseArea) + dialog.show(mouseArea) } } diff --git a/share/qtcreator/qmldesigner/connectionseditor/Main.qml b/share/qtcreator/qmldesigner/connectionseditor/Main.qml index 0b3752ac812..5bbb9c8ce61 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/Main.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/Main.qml @@ -22,7 +22,7 @@ Rectangle { Column { id: column anchors.fill: parent - spacing: 8 // TODO + spacing: 5 // TODO Rectangle { id: toolbar diff --git a/share/qtcreator/qmldesigner/connectionseditor/PopupDialog.qml b/share/qtcreator/qmldesigner/connectionseditor/PopupDialog.qml deleted file mode 100644 index da911854ada..00000000000 --- a/share/qtcreator/qmldesigner/connectionseditor/PopupDialog.qml +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -import QtQuick -import QtQuick.Controls -import HelperWidgets 2.0 as HelperWidgets -import StudioTheme 1.0 as StudioTheme -import ConnectionsEditorEditorBackend - -Window { - id: window - - property alias titleBar: titleBarContent.children - default property alias content: mainContent.children - property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle - - width: 300 - height: column.implicitHeight - visible: true - flags: Qt.FramelessWindowHint | Qt.Dialog - - Rectangle { - anchors.fill: parent - color: StudioTheme.Values.themePopoutBackground - border.color: "#636363"//StudioTheme.Values.themeTextColor - } - - function ensureVerticalPosition() { - if ((window.y + window.height) > (Screen.height - window.style.dialogScreenMargin)) { - window.y = (Screen.height - window.height - window.style.dialogScreenMargin) - } - } - - onHeightChanged: window.ensureVerticalPosition() - - function popup(item) { - var padding = 12 - var p = item.mapToGlobal(0, 0) - window.x = p.x - window.width - padding - if (window.x < 0) - window.x = p.x + item.width + padding - window.y = p.y - - window.ensureVerticalPosition() - - window.show() - window.raise() - } - - Column { - id: column - anchors.fill: parent - - Item { - id: titleBarItem - width: parent.width - height: StudioTheme.Values.titleBarHeight - - DragHandler { - id: dragHandler - - target: null - grabPermissions: PointerHandler.CanTakeOverFromAnything - onActiveChanged: { - if (dragHandler.active) - window.startSystemMove() // QTBUG-102488 - } - } - - Row { - id: row - anchors.fill: parent - anchors.leftMargin: StudioTheme.Values.popupMargin - anchors.rightMargin: StudioTheme.Values.popupMargin - spacing: 0 - - Item { - id: titleBarContent - width: row.width - closeIndicator.width //- row.anchors.leftMargin - height: row.height - } - - HelperWidgets.IconIndicator { - id: closeIndicator - anchors.verticalCenter: parent.verticalCenter - icon: StudioTheme.Constants.colorPopupClose - pixelSize: StudioTheme.Values.myIconFontSize// * 1.4 - onClicked: window.close() - } - } - } - - Rectangle { - width: parent.width - 8 - height: 1 - anchors.horizontalCenter: parent.horizontalCenter - color: "#636363" - } - - Item { - id: mainContent - width: parent.width - 2 * StudioTheme.Values.popupMargin - height: mainContent.childrenRect.height + 2 * StudioTheme.Values.popupMargin - anchors.horizontalCenter: parent.horizontalCenter - } - } -} diff --git a/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialog.qml b/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialog.qml index 20e9568c14b..4595d940402 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialog.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialog.qml @@ -2,10 +2,11 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick -import StudioTheme 1.0 as StudioTheme +import StudioTheme as StudioTheme import HelperWidgets as HelperWidgets +import StudioControls as StudioControls -PopupDialog { +StudioControls.PopupDialog { property alias backend: form.backend titleBar: Row { @@ -17,6 +18,7 @@ PopupDialog { text: qsTr("Owner") font.pixelSize: StudioTheme.Values.myFontSize anchors.verticalCenter: parent.verticalCenter + HelperWidgets.ToolTipArea { anchors.fill: parent tooltip: qsTr("The owner of the property") @@ -29,12 +31,9 @@ PopupDialog { anchors.verticalCenter: parent.verticalCenter text: form.backend.targetNode } - } PropertiesDialogForm { id: form - y: 32 - height: 180 } } diff --git a/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialogForm.qml b/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialogForm.qml index 8d62daacf8d..eee45df8605 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialogForm.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialogForm.qml @@ -10,12 +10,11 @@ Column { id: root readonly property real horizontalSpacing: 10 - readonly property real verticalSpacing: 16 + readonly property real verticalSpacing: 12 readonly property real columnWidth: (root.width - root.horizontalSpacing) / 2 property var backend - y: StudioTheme.Values.popupMargin width: parent.width spacing: root.verticalSpacing @@ -23,11 +22,11 @@ Column { text: qsTr("Type") tooltip: qsTr("Sets the category of the Local Custom Property.") } + StudioControls.TopLevelComboBox { id: type style: StudioTheme.Values.connectionPopupControlStyle width: root.columnWidth - //width: root.width model: backend.type.model ?? [] onActivated: backend.type.activateIndex(type.currentIndex) @@ -53,6 +52,7 @@ Column { Row { spacing: root.horizontalSpacing + StudioControls.TextField { id: name @@ -61,10 +61,9 @@ Column { translationIndicatorVisible: false text: backend.name.text ?? "" - onEditingFinished: { - backend.name.activateText(name.text) - } + onEditingFinished: backend.name.activateText(name.text) } + StudioControls.TextField { id: value @@ -74,9 +73,7 @@ Column { text: backend.value.text ?? "" - onEditingFinished: { - backend.value.activateText(value.text) - } + onEditingFinished: backend.value.activateText(value.text) } } } diff --git a/share/qtcreator/qmldesigner/connectionseditor/PropertiesListView.qml b/share/qtcreator/qmldesigner/connectionseditor/PropertiesListView.qml index 53d1edea95a..0606febb0dd 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/PropertiesListView.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/PropertiesListView.qml @@ -15,12 +15,6 @@ ListView { property bool adsFocus: false - // Temporarily remove due to dockwidget focus issue - //onAdsFocusChanged: { - // if (!root.adsFocus) - // dialog.close() - //} - clip: true interactive: true highlightMoveDuration: 0 @@ -43,9 +37,7 @@ ListView { && verticalScrollBar.isNeeded } - onVisibleChanged: { - dialog.hide() - } + onVisibleChanged: dialog.close() property int modelCurrentIndex: root.model.currentIndex ?? 0 @@ -72,7 +64,7 @@ ListView { function addProperty() { ConnectionsEditorEditorBackend.dynamicPropertiesModel.add() if (root.currentItem) - dialog.popup(root.currentItem.delegateMouseArea) + dialog.show(root.currentItem.delegateMouseArea) } function resetIndex() { @@ -104,9 +96,11 @@ ListView { property alias delegateMouseArea: mouseArea + property bool hovered: mouseArea.containsMouse || toolTipArea.containsMouse + width: ListView.view.width height: root.style.squareControlSize.height - color: mouseArea.containsMouse ? + color: itemDelegate.hovered ? itemDelegate.ListView.isCurrentItem ? root.style.interactionHover : root.style.background.hover : "transparent" @@ -124,7 +118,7 @@ ListView { function onClicked() { root.model.currentIndex = itemDelegate.index root.currentIndex = itemDelegate.index - dialog.popup(mouseArea) + dialog.show(mouseArea) } } } diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml index 306f5405b30..63ac2517aed 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml @@ -19,8 +19,7 @@ Item { objectName: "__mainSrollView" // Called also from C++ to close context menu on focus out - function closeContextMenu() - { + function closeContextMenu() { materialsView.closeContextMenu() texturesView.closeContextMenu() environmentsView.closeContextMenu() @@ -29,9 +28,43 @@ Item { } // Called from C++ - function clearSearchFilter() - { - searchBox.clear(); + function clearSearchFilter() { + searchBox.clear() + } + + property int numColumns: 4 + property real thumbnailSize: 100 + + readonly property int minThumbSize: 100 + readonly property int maxThumbSize: 150 + + function responsiveResize(width: int, height: int) { + width -= 2 * StudioTheme.Values.sectionPadding + + let numColumns = Math.floor(width / root.minThumbSize) + let remainder = width % root.minThumbSize + let space = (numColumns - 1) * StudioTheme.Values.sectionGridSpacing + + if (remainder < space) + numColumns -= 1 + + if (numColumns < 1) + return + + let maxItems = Math.max(materialsView.count, + texturesView.count, + environmentsView.count, + effectsView.count) + + if (numColumns > maxItems) + numColumns = maxItems + + let rest = width - (numColumns * root.minThumbSize) + - ((numColumns - 1) * StudioTheme.Values.sectionGridSpacing) + + root.thumbnailSize = Math.min(root.minThumbSize + (rest / numColumns), + root.maxThumbSize) + root.numColumns = numColumns } Column { @@ -46,11 +79,11 @@ Item { Column { anchors.fill: parent - anchors.topMargin: 6 - anchors.bottomMargin: 6 - anchors.leftMargin: 10 - anchors.rightMargin: 10 - spacing: 12 + anchors.topMargin: StudioTheme.Values.toolbarVerticalMargin + anchors.bottomMargin: StudioTheme.Values.toolbarVerticalMargin + anchors.leftMargin: StudioTheme.Values.toolbarHorizontalMargin + anchors.rightMargin: StudioTheme.Values.toolbarHorizontalMargin + spacing: StudioTheme.Values.toolbarColumnSpacing StudioControls.SearchBox { id: searchBox @@ -94,16 +127,24 @@ Item { } StackLayout { + id: stackLayout width: root.width height: root.height - y currentIndex: tabBar.currIndex + onWidthChanged: root.responsiveResize(stackLayout.width, stackLayout.height) + ContentLibraryMaterialsView { id: materialsView adsFocus: root.adsFocus width: root.width + cellWidth: root.thumbnailSize + cellHeight: root.thumbnailSize + 20 + numColumns: root.numColumns + hideHorizontalScrollBar: true + searchBox: searchBox onUnimport: (bundleMat) => { @@ -111,6 +152,8 @@ Item { confirmUnimportDialog.targetBundleType = "material" confirmUnimportDialog.open() } + + onCountChanged: root.responsiveResize(stackLayout.width, stackLayout.height) } ContentLibraryTexturesView { @@ -118,10 +161,18 @@ Item { adsFocus: root.adsFocus width: root.width + + cellWidth: root.thumbnailSize + cellHeight: root.thumbnailSize + numColumns: root.numColumns + hideHorizontalScrollBar: true + model: ContentLibraryBackend.texturesModel sectionCategory: "ContentLib_Tex" searchBox: searchBox + + onCountChanged: root.responsiveResize(stackLayout.width, stackLayout.height) } ContentLibraryTexturesView { @@ -129,10 +180,18 @@ Item { adsFocus: root.adsFocus width: root.width + + cellWidth: root.thumbnailSize + cellHeight: root.thumbnailSize + numColumns: root.numColumns + hideHorizontalScrollBar: true + model: ContentLibraryBackend.environmentsModel sectionCategory: "ContentLib_Env" searchBox: searchBox + + onCountChanged: root.responsiveResize(stackLayout.width, stackLayout.height) } ContentLibraryEffectsView { @@ -141,6 +200,11 @@ Item { adsFocus: root.adsFocus width: root.width + cellWidth: root.thumbnailSize + cellHeight: root.thumbnailSize + 20 + numColumns: root.numColumns + hideHorizontalScrollBar: true + searchBox: searchBox onUnimport: (bundleItem) => { @@ -148,6 +212,8 @@ Item { confirmUnimportDialog.targetBundleType = "effect" confirmUnimportDialog.open() } + + onCountChanged: root.responsiveResize(stackLayout.width, stackLayout.height) } } } diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryEffect.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryEffect.qml index 6230d38974e..d418cab0dbe 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryEffect.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryEffect.qml @@ -36,12 +36,10 @@ Item { anchors.fill: parent spacing: 1 - Item { width: 1; height: 5 } // spacer - Image { id: img - width: root.width - 10 + width: root.width height: img.width anchors.horizontalCenter: parent.horizontalCenter source: modelData.bundleItemIcon @@ -74,16 +72,13 @@ Item { } Text { - text: modelData.bundleItemName - width: img.width - clip: true - anchors.horizontalCenter: parent.horizontalCenter horizontalAlignment: TextInput.AlignHCenter + text: modelData.bundleItemName + elide: Text.ElideRight font.pixelSize: StudioTheme.Values.myFontSize - color: StudioTheme.Values.themeTextColor } } // Column diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryEffectsView.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryEffectsView.qml index 7cb9a4721d8..f6df99156bf 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryEffectsView.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryEffectsView.qml @@ -14,20 +14,28 @@ HelperWidgets.ScrollView { interactive: !ctxMenu.opened && !ContentLibraryBackend.rootView.isDragging && !HelperWidgets.Controller.contextMenuOpened - readonly property int cellWidth: 100 - readonly property int cellHeight: 120 + property real cellWidth: 100 + property real cellHeight: 120 + property int numColumns: 4 + + property int count: 0 + function assignMaxCount() { + let c = 0 + for (let i = 0; i < categoryRepeater.count; ++i) + c = Math.max(c, categoryRepeater.itemAt(i)?.count ?? 0) + + root.count = c + } required property var searchBox signal unimport(var bundleItem); - function closeContextMenu() - { + function closeContextMenu() { ctxMenu.close() } - function expandVisibleSections() - { + function expandVisibleSections() { for (let i = 0; i < categoryRepeater.count; ++i) { let cat = categoryRepeater.itemAt(i) if (cat.visible && !cat.expanded) @@ -48,10 +56,15 @@ HelperWidgets.ScrollView { model: ContentLibraryBackend.effectsModel delegate: HelperWidgets.Section { + id: section + width: root.width + leftPadding: StudioTheme.Values.sectionPadding + rightPadding: StudioTheme.Values.sectionPadding + topPadding: StudioTheme.Values.sectionPadding + bottomPadding: StudioTheme.Values.sectionPadding + caption: bundleCategoryName - addTopPadding: false - sectionBackgroundColor: "transparent" visible: bundleCategoryVisible && !ContentLibraryBackend.effectsModel.isEmpty expanded: bundleCategoryExpanded expandOnClick: false @@ -65,14 +78,17 @@ HelperWidgets.ScrollView { bundleCategoryExpanded = true } + property alias count: repeater.count + + onCountChanged: root.assignMaxCount() + Grid { - width: root.width - leftPadding: 5 - rightPadding: 5 - bottomPadding: 5 - columns: root.width / root.cellWidth + width: section.width - section.leftPadding - section.rightPadding + spacing: StudioTheme.Values.sectionGridSpacing + columns: root.numColumns Repeater { + id: repeater model: bundleCategoryItems delegate: ContentLibraryEffect { @@ -81,6 +97,8 @@ HelperWidgets.ScrollView { onShowContextMenu: ctxMenu.popupMenu(modelData) } + + onCountChanged: root.assignMaxCount() } } } diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterial.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterial.qml index 9d723dc0ef8..9160c916064 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterial.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterial.qml @@ -1,16 +1,15 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -import QtQuick 2.15 -import QtQuick.Layouts 1.15 -import QtQuickDesignerTheme 1.0 -import HelperWidgets 2.0 +import QtQuick import QtQuick.Controls +import QtQuick.Layouts +import QtQuickDesignerTheme +import HelperWidgets +import StudioTheme as StudioTheme -import StudioTheme 1.0 as StudioTheme import ContentLibraryBackend - -import WebFetcher 1.0 +import WebFetcher Item { id: root @@ -45,8 +44,6 @@ Item { anchors.fill: parent spacing: 1 - Item { width: 1; height: 5 } // spacer - DownloadPane { id: downloadPane width: root.width - 10 @@ -59,7 +56,7 @@ Item { Image { id: img - width: root.width - 10 + width: root.width height: img.width anchors.horizontalCenter: parent.horizontalCenter source: modelData.bundleMaterialIcon @@ -108,7 +105,7 @@ Item { onClicked: { ContentLibraryBackend.materialsModel.addToProject(modelData) } - } // IconButton + } IconButton { id: downloadIcon @@ -154,29 +151,22 @@ Item { root.downloadState = "" downloader.start() } - } // IconButton - } // Image + } + } - TextInput { + Text { id: matName - text: modelData.bundleMaterialName - width: img.width - clip: true anchors.horizontalCenter: parent.horizontalCenter horizontalAlignment: TextInput.AlignHCenter + text: modelData.bundleMaterialName + elide: Text.ElideRight font.pixelSize: StudioTheme.Values.myFontSize - - readOnly: true - selectByMouse: !matName.readOnly - color: StudioTheme.Values.themeTextColor - selectionColor: StudioTheme.Values.themeTextSelectionColor - selectedTextColor: StudioTheme.Values.themeTextSelectedTextColor } - } // Column + } Timer { id: delayedFinish @@ -223,6 +213,6 @@ Item { probeUrl: false downloadEnabled: true targetFilePath: downloader.nextTargetPath - } // FileDownloader - } // MultiFileDownloader + } + } } diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml index f9678dcad80..c21baf4c580 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml @@ -14,8 +14,18 @@ HelperWidgets.ScrollView { interactive: !ctxMenu.opened && !ContentLibraryBackend.rootView.isDragging && !HelperWidgets.Controller.contextMenuOpened - readonly property int cellWidth: 100 - readonly property int cellHeight: 120 + property real cellWidth: 100 + property real cellHeight: 120 + property int numColumns: 4 + + property int count: 0 + function assignMaxCount() { + let c = 0 + for (let i = 0; i < categoryRepeater.count; ++i) + c = Math.max(c, categoryRepeater.itemAt(i)?.count ?? 0) + + root.count = c + } property var currMaterialItem: null property var rootItem: null @@ -23,15 +33,13 @@ HelperWidgets.ScrollView { required property var searchBox - signal unimport(var bundleMat); + signal unimport(var bundleMat) - function closeContextMenu() - { + function closeContextMenu() { ctxMenu.close() } - function expandVisibleSections() - { + function expandVisibleSections() { for (let i = 0; i < categoryRepeater.count; ++i) { let cat = categoryRepeater.itemAt(i) if (cat.visible && !cat.expanded) @@ -56,10 +64,15 @@ HelperWidgets.ScrollView { model: materialsModel delegate: HelperWidgets.Section { + id: section + width: root.width + leftPadding: StudioTheme.Values.sectionPadding + rightPadding: StudioTheme.Values.sectionPadding + topPadding: StudioTheme.Values.sectionPadding + bottomPadding: StudioTheme.Values.sectionPadding + caption: bundleCategoryName - addTopPadding: false - sectionBackgroundColor: "transparent" visible: bundleCategoryVisible && !materialsModel.isEmpty expanded: bundleCategoryExpanded expandOnClick: false @@ -73,14 +86,17 @@ HelperWidgets.ScrollView { bundleCategoryExpanded = true } + property alias count: repeater.count + + onCountChanged: root.assignMaxCount() + Grid { - width: root.width - leftPadding: 5 - rightPadding: 5 - bottomPadding: 5 - columns: root.width / root.cellWidth + width: section.width - section.leftPadding - section.rightPadding + spacing: StudioTheme.Values.sectionGridSpacing + columns: root.numColumns Repeater { + id: repeater model: bundleCategoryMaterials delegate: ContentLibraryMaterial { @@ -89,6 +105,8 @@ HelperWidgets.ScrollView { onShowContextMenu: ctxMenu.popupMenu(modelData) } + + onCountChanged: root.assignMaxCount() } } } diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexture.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexture.qml index 6a938ed14d6..74ece1c015b 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexture.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexture.qml @@ -1,16 +1,15 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -import QtQuick 2.15 -import QtQuick.Layouts 1.15 -import QtQuickDesignerTheme 1.0 -import HelperWidgets 2.0 +import QtQuick import QtQuick.Controls +import QtQuick.Layouts +import QtQuickDesignerTheme +import HelperWidgets +import StudioTheme as StudioTheme -import StudioTheme 1.0 as StudioTheme - -import WebFetcher 1.0 import ContentLibraryBackend +import WebFetcher Item { id: root @@ -30,8 +29,7 @@ Item { signal showContextMenu() - function statusText() - { + function statusText() { if (root.downloadState === "downloaded") return qsTr("Texture was already downloaded.") if (root.downloadState === "unavailable") @@ -42,16 +40,14 @@ Item { return qsTr("Click to download the texture.") } - function startDownload(message) - { + function startDownload(message) { if (root.downloadState !== "" && root.downloadState !== "failed") return root._startDownload(textureDownloader, message) } - function updateTexture() - { + function updateTexture() { if (root.downloadState !== "downloaded") return @@ -59,8 +55,7 @@ Item { root._startDownload(textureDownloader, qsTr("Updating...")) } - function _startDownload(downloader, message) - { + function _startDownload(downloader, message) { progressBar.visible = true tooltip.visible = false root.progressText = message @@ -144,8 +139,8 @@ Item { font.pixelSize: 12 } } - } // TextureProgressBar - } // Rectangle + } + } Image { id: image @@ -197,7 +192,7 @@ Item { onClicked: { root.startDownload(qsTr("Downloading...")) } - } // IconButton + } IconButton { id: updateButton @@ -233,7 +228,7 @@ Item { scale: updateButton.containsMouse ? 1.2 : 1 } - } // Update IconButton + } Rectangle { id: isNewFlag @@ -282,7 +277,7 @@ Item { visible: false } } - } // Image + } FileDownloader { id: textureDownloader diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml index 7471a22cfb7..1fac9f2234e 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml @@ -14,8 +14,18 @@ HelperWidgets.ScrollView { interactive: !ctxMenu.opened && !ContentLibraryBackend.rootView.isDragging && !HelperWidgets.Controller.contextMenuOpened - readonly property int cellWidth: 100 - readonly property int cellHeight: 100 + property int cellWidth: 100 + property int cellHeight: 100 + property int numColumns: 4 + + property int count: 0 + function assignMaxCount() { + let c = 0 + for (let i = 0; i < categoryRepeater.count; ++i) + c = Math.max(c, categoryRepeater.itemAt(i)?.count ?? 0) + + root.count = c + } property var currMaterialItem: null property var rootItem: null @@ -24,21 +34,20 @@ HelperWidgets.ScrollView { required property var model required property string sectionCategory - signal unimport(var bundleMat); + signal unimport(var bundleMat) - function closeContextMenu() - { + function closeContextMenu() { ctxMenu.close() } - function expandVisibleSections() - { + function expandVisibleSections() { for (let i = 0; i < categoryRepeater.count; ++i) { let cat = categoryRepeater.itemAt(i) if (cat.visible && !cat.expanded) cat.expandSection() } } + Column { ContentLibraryTextureContextMenu { id: ctxMenu @@ -52,10 +61,15 @@ HelperWidgets.ScrollView { model: root.model delegate: HelperWidgets.Section { + id: section + width: root.width + leftPadding: StudioTheme.Values.sectionPadding + rightPadding: StudioTheme.Values.sectionPadding + topPadding: StudioTheme.Values.sectionPadding + bottomPadding: StudioTheme.Values.sectionPadding + caption: bundleCategoryName - addTopPadding: false - sectionBackgroundColor: "transparent" visible: bundleCategoryVisible && !root.model.isEmpty expanded: bundleCategoryExpanded expandOnClick: false @@ -69,15 +83,17 @@ HelperWidgets.ScrollView { bundleCategoryExpanded = true } + property alias count: repeater.count + + onCountChanged: root.assignMaxCount() + Grid { - width: root.width - leftPadding: 5 - rightPadding: 5 - bottomPadding: 5 - spacing: 5 - columns: root.width / root.cellWidth + width: section.width - section.leftPadding - section.rightPadding + spacing: StudioTheme.Values.sectionGridSpacing + columns: root.numColumns Repeater { + id: repeater model: bundleCategoryTextures delegate: ContentLibraryTexture { @@ -86,6 +102,8 @@ HelperWidgets.ScrollView { onShowContextMenu: ctxMenu.popupMenu(modelData) } + + onCountChanged: root.assignMaxCount() } } } diff --git a/share/qtcreator/qmldesigner/designericons.json b/share/qtcreator/qmldesigner/designericons.json index 36a60ea3b31..85c76066bb7 100644 --- a/share/qtcreator/qmldesigner/designericons.json +++ b/share/qtcreator/qmldesigner/designericons.json @@ -69,6 +69,9 @@ "EnterComponentIcon": { "iconName": "editComponent_small" }, + "JumpToCodeIcon": { + "iconName": "jumpToCode_small" + }, "EventListIcon": { "iconName": "events_small" }, @@ -218,6 +221,9 @@ "EditColorIcon": { "iconName": "colorSelection_medium" }, + "BakeLightIcon": { + "iconName": "bakeLights_medium" + }, "EditLightIcon": { "Off": { "iconName": "editLightOff_medium" @@ -264,6 +270,9 @@ "SnappingConfIcon": { "iconName": "snapping_conf_medium" }, + "SplitViewIcon": { + "iconName": "splitScreen_medium" + }, "ToggleGroupIcon": { "Off": { "iconName": "selectOutline_medium" diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectCompositionNode.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectCompositionNode.qml index 9b0046f67c3..56a508377ec 100644 --- a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectCompositionNode.qml +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectCompositionNode.qml @@ -12,14 +12,7 @@ import EffectMakerBackend HelperWidgets.Section { id: root - // model properties - required property string nodeName - required property bool nodeEnabled - required property var nodeUniformsModel - - required property int index - - caption: root.nodeName + caption: nodeName category: "EffectMaker" draggable: true @@ -32,18 +25,18 @@ HelperWidgets.Section { } showEyeButton: true - eyeEnabled: root.nodeEnabled + eyeEnabled: nodeEnabled eyeButtonToolTip: qsTr("Enable/Disable Node") onEyeButtonClicked: { - root.nodeEnabled = root.eyeEnabled + nodeEnabled = root.eyeEnabled } Column { spacing: 10 Repeater { - model: root.nodeUniformsModel + model: nodeUniformsModel EffectCompositionNodeUniform { width: root.width diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectCompositionNodeUniform.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectCompositionNodeUniform.qml index 7c632730ccb..6aedc798f59 100644 --- a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectCompositionNodeUniform.qml +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectCompositionNodeUniform.qml @@ -28,7 +28,7 @@ Item { valueLoader.source = "ValueBool.qml" else if (uniformType === "color") valueLoader.source = "ValueColor.qml" - else if (uniformType === "image") + else if (uniformType === "sampler2D") valueLoader.source = "ValueImage.qml" else if (uniformType === "define") valueLoader.source = "ValueDefine.qml" @@ -50,6 +50,7 @@ Item { Layout.maximumWidth: 140 Layout.minimumWidth: 140 Layout.preferredWidth: 140 + elide: Text.ElideRight HelperWidgets.ToolTipArea { anchors.fill: parent diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMaker.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMaker.qml index 49731b37130..a60b41acf31 100644 --- a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMaker.qml +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMaker.qml @@ -14,6 +14,18 @@ Item { property var secsY: [] property int moveFromIdx: 0 property int moveToIdx: 0 + property bool previewAnimationRunning: false + + SaveDialog { + id: saveDialog + compositionName: EffectMakerBackend.effectMakerModel.currentComposition + anchors.centerIn: parent + onAccepted: { + let name = saveDialog.compositionName + EffectMakerBackend.effectMakerModel.exportComposition(name) + EffectMakerBackend.effectMakerModel.exportResources(name) + } + } Column { id: col @@ -21,11 +33,17 @@ Item { spacing: 1 EffectMakerTopBar { - + onSaveClicked: saveDialog.open() } EffectMakerPreview { mainRoot: root + + FrameAnimation { + id: previewFrameTimer + running: true + paused: !previewAnimationRunning + } } Rectangle { @@ -47,11 +65,14 @@ Item { style: StudioTheme.Values.viewBarButtonStyle buttonIcon: StudioTheme.Constants.code tooltip: qsTr("Open Shader in Code Editor") + visible: false // TODO: to be implemented onClicked: {} // TODO } } + Component.onCompleted: HelperWidgets.Controller.mainScrollView = scrollView + HelperWidgets.ScrollView { id: scrollView diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerPreview.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerPreview.qml index c23e95e5628..9b58cd56bd2 100644 --- a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerPreview.qml +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerPreview.qml @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick -import QtQuick.Layouts import QtQuickDesignerTheme import HelperWidgets as HelperWidgets import StudioControls as StudioControls @@ -12,24 +11,58 @@ import EffectMakerBackend Column { id: root + property real animatedTime: previewFrameTimer.elapsedTime + property int animatedFrame: previewFrameTimer.currentFrame + property bool timeRunning: previewAnimationRunning + width: parent.width required property Item mainRoot property var effectMakerModel: EffectMakerBackend.effectMakerModel property alias source: source // The delay in ms to wait until updating the effect - readonly property int updateDelay: 200 + readonly property int updateDelay: 100 + + // Create a dummy parent to host the effect qml object + function createNewComponent() { + // If we have a working effect, do not show preview image as it shows through + // transparent parts of the final image + source.visible = false; + + var oldComponent = componentParent.children[0]; + if (oldComponent) + oldComponent.destroy(); + try { + const newObject = Qt.createQmlObject( + effectMakerModel.qmlComponentString, + componentParent, + "" + ); + effectMakerModel.resetEffectError(0); + } catch (error) { + let errorString = "QML: ERROR: "; + let errorLine = -1; + if (error.qmlErrors.length > 0) { + // Show the first QML error + let e = error.qmlErrors[0]; + errorString += e.lineNumber + ": " + e.message; + errorLine = e.lineNumber; + } + effectMakerModel.setEffectError(errorString, 0, errorLine); + source.visible = true; + } + } Rectangle { // toolbar width: parent.width height: StudioTheme.Values.toolbarHeight color: StudioTheme.Values.themeToolbarBackground - RowLayout { - anchors.fill: parent + Row { spacing: 5 - anchors.rightMargin: 5 anchors.leftMargin: 5 + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter PreviewImagesComboBox { id: imagesComboBox @@ -37,9 +70,19 @@ Column { mainRoot: root.mainRoot } - Item { - Layout.fillWidth: true + StudioControls.ColorEditor { + id: colorEditor + + actionIndicatorVisible: false + showHexTextField: false + color: "#dddddd" } + } + + Row { + spacing: 5 + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter HelperWidgets.AbstractButton { enabled: sourceImage.scale > .4 @@ -73,20 +116,24 @@ Column { sourceImage.scale = 1 } } + } - Item { - Layout.fillWidth: true - } + Row { + spacing: 5 + anchors.rightMargin: 5 + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter Column { Text { - text: "0.000s" + text: animatedTime >= 100 + ? animatedTime.toFixed(1) + " s" : animatedTime.toFixed(3) + " s" color: StudioTheme.Values.themeTextColor font.pixelSize: 10 } Text { - text: "0000000" + text: (animatedFrame).toString().padStart(6, '0') color: StudioTheme.Values.themeTextColor font.pixelSize: 10 } @@ -97,15 +144,20 @@ Column { buttonIcon: StudioTheme.Constants.toStartFrame_medium tooltip: qsTr("Restart Animation") - onClicked: {} // TODO + onClicked: { + previewFrameTimer.reset() + } } HelperWidgets.AbstractButton { style: StudioTheme.Values.viewBarButtonStyle - buttonIcon: StudioTheme.Constants.topToolbar_runProject + buttonIcon: previewAnimationRunning ? StudioTheme.Constants.pause_medium + : StudioTheme.Constants.playOutline_medium tooltip: qsTr("Play Animation") - onClicked: {} // TODO + onClicked: { + previewAnimationRunning = !previewAnimationRunning + } } } } @@ -113,7 +165,7 @@ Column { Rectangle { // preview image id: preview - color: "#dddddd" + color: colorEditor.color width: parent.width height: 200 clip: true @@ -146,54 +198,27 @@ Column { id: componentParent width: source.width height: source.height - anchors.centerIn: parent - scale: 1 //TODO should come from toolbar + anchors.centerIn: parent // Cache the layer. This way heavy shaders rendering doesn't // slow down code editing & rest of the UI. layer.enabled: true layer.smooth: true } - // Create a dummy parent to host the effect qml object - function createNewComponent() { - var oldComponent = componentParent.children[0]; - if (oldComponent) - oldComponent.destroy(); - - try { - const newObject = Qt.createQmlObject( - effectMakerModel.qmlComponentString, - componentParent, - "" - ); - effectMakerModel.resetEffectError(0); - } catch (error) { - let errorString = "QML: ERROR: "; - let errorLine = -1; - if (error.qmlErrors.length > 0) { - // Show the first QML error - let e = error.qmlErrors[0]; - errorString += e.lineNumber + ": " + e.message; - errorLine = e.lineNumber; - } - effectMakerModel.setEffectError(errorString, 0, errorLine); - } - } - Connections { target: effectMakerModel function onShadersBaked() { console.log("Shaders Baked!") - //updateTimer.restart(); // Disable for now + updateTimer.restart() } } Timer { id: updateTimer - interval: updateDelay; + interval: updateDelay onTriggered: { - effectMakerModel.updateQmlComponent(); - createNewComponent(); + effectMakerModel.updateQmlComponent() + createNewComponent() } } } diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerTopBar.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerTopBar.qml index eb5463f3bdf..c8758ff2ebb 100644 --- a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerTopBar.qml +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerTopBar.qml @@ -15,15 +15,15 @@ Rectangle { height: StudioTheme.Values.toolbarHeight color: StudioTheme.Values.themeToolbarBackground + signal saveClicked + HelperWidgets.Button { anchors.verticalCenter: parent.verticalCenter x: 5 text: qsTr("Save in Library") - onClicked: { - // TODO - } + onClicked: root.saveClicked() } HelperWidgets.AbstractButton { diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectNodesComboBox.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectNodesComboBox.qml index b076e152cc0..0827b20c1e3 100644 --- a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectNodesComboBox.qml +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectNodesComboBox.qml @@ -47,7 +47,7 @@ StudioControls.ComboBox { width: row.width + 2 // 2: scrollView left and right 1px margins height: Math.min(800, Math.min(row.height + 2, Screen.height - y - 40)) // 40: some bottom margin to cover OS bottom toolbar - flags: Qt.Dialog | Qt.FramelessWindowHint + flags: Qt.Dialog | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint onActiveFocusItemChanged: { if (!window.activeFocusItem && !root.indicator.hover && root.popup.opened) diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/PreviewImagesComboBox.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/PreviewImagesComboBox.qml index a53a923ca6b..bcf4efbd4d9 100644 --- a/share/qtcreator/qmldesigner/effectMakerQmlSources/PreviewImagesComboBox.qml +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/PreviewImagesComboBox.qml @@ -67,7 +67,7 @@ StudioControls.ComboBox { width: col.width + 2 // 2: scrollView left and right 1px margins height: Math.min(800, Math.min(col.height + 2, Screen.height - y - 40)) // 40: some bottom margin to cover OS bottom toolbar - flags: Qt.Dialog | Qt.FramelessWindowHint + flags: Qt.Dialog | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint onActiveFocusItemChanged: { if (!window.activeFocusItem && !root.indicator.hover && root.popup.opened) diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/SaveDialog.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/SaveDialog.qml new file mode 100644 index 00000000000..8bd48e1d6c4 --- /dev/null +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/SaveDialog.qml @@ -0,0 +1,97 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import HelperWidgets as HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme +import AssetsLibraryBackend + +StudioControls.Dialog { + id: root + + title: qsTr("Save Effect") + + closePolicy: Popup.CloseOnEscape + modal: true + implicitWidth: 250 + implicitHeight: 160 + + property string compositionName: "" + + onOpened: { + nameText.text = compositionName //TODO: Generate unique name + emptyText.text = "" + nameText.forceActiveFocus() + } + + contentItem: Item { + Column { + spacing: 2 + + Row { + id: row + Text { + text: qsTr("Effect name: ") + anchors.verticalCenter: parent.verticalCenter + color: StudioTheme.Values.themeTextColor + } + + StudioControls.TextField { + id: nameText + + actionIndicator.visible: false + translationIndicator.visible: false + + onTextChanged: { + let errMsg = "" + if (/[^A-Za-z0-9_]+/.test(text)) + errMsg = qsTr("Name contains invalid characters.") + else if (!/^[A-Z]/.test(text)) + errMsg = qsTr("Name must start with a capital letter") + else if (text.length < 3) + errMsg = qsTr("Name must have at least 3 characters") + else if (/\s/.test(text)) + errMsg = qsTr("Name cannot contain white space") + + emptyText.text = errMsg + btnSave.enabled = errMsg.length === 0 + } + Keys.onEnterPressed: btnSave.onClicked() + Keys.onReturnPressed: btnSave.onClicked() + Keys.onEscapePressed: root.reject() + } + } + + Text { + id: emptyText + + color: StudioTheme.Values.themeError + anchors.right: row.right + } + } + + Row { + anchors.right: parent.right + anchors.bottom: parent.bottom + spacing: 2 + + HelperWidgets.Button { + id: btnSave + + text: qsTr("Save") + enabled: nameText.text !== "" + onClicked: { + root.compositionName = nameText.text + root.accept() //TODO: Check if name is unique + } + } + + HelperWidgets.Button { + text: qsTr("Cancel") + onClicked: root.reject() + } + } + } +} diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/ValueColor.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/ValueColor.qml index 8b381ccaa1b..1f601f90639 100644 --- a/share/qtcreator/qmldesigner/effectMakerQmlSources/ValueColor.qml +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/ValueColor.qml @@ -3,7 +3,7 @@ import QtQuick import QtQuickDesignerTheme -import HelperWidgets as HelperWidgets +import StudioControls as StudioControls import StudioTheme 1.0 as StudioTheme import EffectMakerBackend @@ -13,11 +13,11 @@ Row { width: parent.width spacing: 5 - HelperWidgets.ColorEditor { - backendValue: uniformBackendValue + StudioControls.ColorEditor { + actionIndicatorVisible: false - showExtendedFunctionButton: false + Component.onCompleted: color = uniformValue - onValueChanged: uniformValue = convertColorToString(color) + onColorChanged: uniformValue = color } } diff --git a/share/qtcreator/qmldesigner/insight/Main.qml b/share/qtcreator/qmldesigner/insight/Main.qml index 3d0cd777753..1abad0050f5 100644 --- a/share/qtcreator/qmldesigner/insight/Main.qml +++ b/share/qtcreator/qmldesigner/insight/Main.qml @@ -55,7 +55,7 @@ Rectangle { id: column width: root.width - Section { + HelperWidgets.Section { id: trackingSection caption: qsTr("Tracking") anchors.left: parent.left @@ -167,7 +167,7 @@ Rectangle { } } - Section { + HelperWidgets.Section { id: predefinedSection caption: qsTr("Predefined Categories") anchors.left: parent.left @@ -227,7 +227,7 @@ Rectangle { } } - Item { width: 1; height: 4} + Item { width: 1; height: 4 } Repeater { model: insightModel @@ -268,7 +268,7 @@ Rectangle { } } - Section { + HelperWidgets.Section { id: customSection caption: qsTr("Custom Categories") anchors.left: parent.left @@ -325,7 +325,7 @@ Rectangle { } } - Item { width: 1; height: 4} + Item { width: 1; height: 4 } Repeater { id: customRepeater @@ -387,7 +387,7 @@ Rectangle { } } - Item { width: 1; height: 4} + Item { width: 1; height: 4 } Row { spacing: StudioTheme.Values.checkBoxSpacing diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemsView.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemsView.qml index a90fbaaa15f..cf5049d6473 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemsView.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemsView.qml @@ -237,9 +237,7 @@ Item { Repeater { model: ItemLibraryBackend.itemLibraryModel // to be set in Qml context delegate: HelperWidgets.Section { - width: itemsView.width - - (verticalScrollView.verticalScrollBarVisible - ? verticalScrollView.verticalThickness : 0) + width: itemsView.width caption: importName visible: importVisible sectionHeight: 30 @@ -270,9 +268,7 @@ Item { Repeater { model: categoryModel delegate: HelperWidgets.Section { - width: itemsView.width - - (verticalScrollView.verticalScrollBarVisible - ? verticalScrollView.verticalThickness : 0) + width: itemsView.width sectionBackgroundColor: "transparent" showTopSeparator: index > 0 hideHeader: categoryModel.rowCount() <= 1 @@ -351,9 +347,7 @@ Item { Repeater { model: ItemLibraryBackend.itemLibraryModel // to be set in Qml context delegate: HelperWidgets.Section { - width: 265 - - (horizontalScrollView.verticalScrollBarVisible - ? horizontalScrollView.verticalThickness : 0) + width: 265 caption: importName visible: importVisible sectionHeight: 30 @@ -384,9 +378,7 @@ Item { Repeater { model: categoryModel delegate: Rectangle { - width: 265 - - (horizontalScrollView.verticalScrollBarVisible - ? horizontalScrollView.verticalThickness : 0) + width: 265 height: 25 visible: categoryVisible border.width: StudioTheme.Values.border diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml index 2d7e8fe8fef..e0dacef36be 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml @@ -12,8 +12,8 @@ Item { id: root focus: true - readonly property int cellWidth: 100 - readonly property int cellHeight: 120 + readonly property real cellWidth: root.thumbnailSize + readonly property real cellHeight: root.thumbnailSize + 20 readonly property bool enableUiElements: materialBrowserModel.hasMaterialLibrary && materialBrowserModel.hasQuick3DImport @@ -22,30 +22,60 @@ Item { property var materialBrowserModel: MaterialBrowserBackend.materialBrowserModel property var materialBrowserTexturesModel: MaterialBrowserBackend.materialBrowserTexturesModel + property int numColumns: 0 + property real thumbnailSize: 100 + + readonly property int minThumbSize: 100 + readonly property int maxThumbSize: 150 + + function responsiveResize(width: int, height: int) { + width -= 2 * StudioTheme.Values.sectionPadding + + let numColumns = Math.floor(width / root.minThumbSize) + let remainder = width % root.minThumbSize + let space = (numColumns - 1) * StudioTheme.Values.sectionGridSpacing + + if (remainder < space) + numColumns -= 1 + + if (numColumns < 1) + return + + let maxItems = Math.max(texturesRepeater.count, materialRepeater.count) + + if (numColumns > maxItems) + numColumns = maxItems + + let rest = width - (numColumns * root.minThumbSize) + - ((numColumns - 1) * StudioTheme.Values.sectionGridSpacing) + + root.thumbnailSize = Math.min(root.minThumbSize + (rest / numColumns), + root.maxThumbSize) + root.numColumns = numColumns + } + + onWidthChanged: root.responsiveResize(root.width, root.height) + // Called also from C++ to close context menu on focus out - function closeContextMenu() - { + function closeContextMenu() { ctxMenu.close() ctxMenuTextures.close() HelperWidgets.Controller.closeContextMenu() } // Called from C++ to refresh a preview material after it changes - function refreshPreview(idx) - { + function refreshPreview(idx) { var item = materialRepeater.itemAt(idx); if (item) - item.refreshPreview(); + item.refreshPreview() } // Called from C++ - function clearSearchFilter() - { - searchBox.clear(); + function clearSearchFilter() { + searchBox.clear() } - function nextVisibleItem(idx, count, itemModel) - { + function nextVisibleItem(idx, count, itemModel) { if (count === 0) return idx @@ -66,8 +96,7 @@ Item { return newIdx } - function visibleItemCount(itemModel) - { + function visibleItemCount(itemModel) { let curIdx = 0 let count = 0 @@ -79,8 +108,7 @@ Item { return count } - function rowIndexOfItem(idx, rowSize, itemModel) - { + function rowIndexOfItem(idx, rowSize, itemModel) { if (rowSize === 1) return 1 @@ -98,8 +126,7 @@ Item { return count % rowSize } - function selectNextVisibleItem(delta) - { + function selectNextVisibleItem(delta) { if (searchBox.activeFocus) return @@ -112,36 +139,36 @@ Item { if (delta < 0) { if (matSecFocused) { - targetIdx = nextVisibleItem(materialBrowserModel.selectedIndex, - delta, materialBrowserModel) + targetIdx = root.nextVisibleItem(materialBrowserModel.selectedIndex, + delta, materialBrowserModel) if (targetIdx >= 0) materialBrowserModel.selectMaterial(targetIdx) } else if (texSecFocused) { - targetIdx = nextVisibleItem(materialBrowserTexturesModel.selectedIndex, - delta, materialBrowserTexturesModel) + targetIdx = root.nextVisibleItem(materialBrowserTexturesModel.selectedIndex, + delta, materialBrowserTexturesModel) if (targetIdx >= 0) { materialBrowserTexturesModel.selectTexture(targetIdx) } else if (!materialBrowserModel.isEmpty && materialsSection.expanded) { - targetIdx = nextVisibleItem(materialBrowserModel.rowCount(), -1, materialBrowserModel) + targetIdx = root.nextVisibleItem(materialBrowserModel.rowCount(), -1, materialBrowserModel) if (targetIdx >= 0) { if (delta !== -1) { // Try to match column when switching between materials/textures - origRowIdx = rowIndexOfItem(materialBrowserTexturesModel.selectedIndex, - -delta, materialBrowserTexturesModel) - if (visibleItemCount(materialBrowserModel) > origRowIdx) { - rowIdx = rowIndexOfItem(targetIdx, -delta, materialBrowserModel) + origRowIdx = root.rowIndexOfItem(materialBrowserTexturesModel.selectedIndex, + -delta, materialBrowserTexturesModel) + if (root.visibleItemCount(materialBrowserModel) > origRowIdx) { + rowIdx = root.rowIndexOfItem(targetIdx, -delta, materialBrowserModel) if (rowIdx >= origRowIdx) { - newTargetIdx = nextVisibleItem(targetIdx, - -(rowIdx - origRowIdx), - materialBrowserModel) + newTargetIdx = root.nextVisibleItem(targetIdx, + -(rowIdx - origRowIdx), + materialBrowserModel) } else { - newTargetIdx = nextVisibleItem(targetIdx, - -(-delta - origRowIdx + rowIdx), - materialBrowserModel) + newTargetIdx = root.nextVisibleItem(targetIdx, + -(-delta - origRowIdx + rowIdx), + materialBrowserModel) } } else { - newTargetIdx = nextVisibleItem(materialBrowserModel.rowCount(), - -1, materialBrowserModel) + newTargetIdx = root.nextVisibleItem(materialBrowserModel.rowCount(), + -1, materialBrowserModel) } if (newTargetIdx >= 0) targetIdx = newTargetIdx @@ -153,25 +180,25 @@ Item { } } else if (delta > 0) { if (matSecFocused) { - targetIdx = nextVisibleItem(materialBrowserModel.selectedIndex, - delta, materialBrowserModel) + targetIdx = root.nextVisibleItem(materialBrowserModel.selectedIndex, + delta, materialBrowserModel) if (targetIdx >= 0) { materialBrowserModel.selectMaterial(targetIdx) } else if (!materialBrowserTexturesModel.isEmpty && texturesSection.expanded) { - targetIdx = nextVisibleItem(-1, 1, materialBrowserTexturesModel) + targetIdx = root.nextVisibleItem(-1, 1, materialBrowserTexturesModel) if (targetIdx >= 0) { if (delta !== 1) { // Try to match column when switching between materials/textures - origRowIdx = rowIndexOfItem(materialBrowserModel.selectedIndex, - delta, materialBrowserModel) - if (visibleItemCount(materialBrowserTexturesModel) > origRowIdx) { + origRowIdx = root.rowIndexOfItem(materialBrowserModel.selectedIndex, + delta, materialBrowserModel) + if (root.visibleItemCount(materialBrowserTexturesModel) > origRowIdx) { if (origRowIdx > 0) { - newTargetIdx = nextVisibleItem(targetIdx, origRowIdx, - materialBrowserTexturesModel) + newTargetIdx = root.nextVisibleItem(targetIdx, origRowIdx, + materialBrowserTexturesModel) } } else { - newTargetIdx = nextVisibleItem(materialBrowserTexturesModel.rowCount(), - -1, materialBrowserTexturesModel) + newTargetIdx = root.nextVisibleItem(materialBrowserTexturesModel.rowCount(), + -1, materialBrowserTexturesModel) } if (newTargetIdx >= 0) targetIdx = newTargetIdx @@ -181,8 +208,8 @@ Item { } } } else if (texSecFocused) { - targetIdx = nextVisibleItem(materialBrowserTexturesModel.selectedIndex, - delta, materialBrowserTexturesModel) + targetIdx = root.nextVisibleItem(materialBrowserTexturesModel.selectedIndex, + delta, materialBrowserTexturesModel) if (targetIdx >= 0) materialBrowserTexturesModel.selectTexture(targetIdx) } @@ -190,24 +217,12 @@ Item { } Keys.enabled: true - Keys.onDownPressed: { - selectNextVisibleItem(gridMaterials.columns) - } + Keys.onDownPressed: root.selectNextVisibleItem(gridMaterials.columns) + Keys.onUpPressed: root.selectNextVisibleItem(-gridMaterials.columns) + Keys.onLeftPressed: root.selectNextVisibleItem(-1) + Keys.onRightPressed: root.selectNextVisibleItem(1) - Keys.onUpPressed: { - selectNextVisibleItem(-gridMaterials.columns) - } - - Keys.onLeftPressed: { - selectNextVisibleItem(-1) - } - - Keys.onRightPressed: { - selectNextVisibleItem(1) - } - - function handleEnterPress() - { + function handleEnterPress() { if (searchBox.activeFocus) return @@ -217,13 +232,8 @@ Item { materialBrowserTexturesModel.openTextureEditor() } - Keys.onEnterPressed: { - handleEnterPress() - } - - Keys.onReturnPressed: { - handleEnterPress() - } + Keys.onEnterPressed: root.handleEnterPress() + Keys.onReturnPressed: root.handleEnterPress() MouseArea { id: focusGrabber @@ -249,10 +259,10 @@ Item { onClicked: (mouse) => { if (!root.enableUiElements) - return; + return var matsSecBottom = mapFromItem(materialsSection, 0, materialsSection.y).y - + materialsSection.height; + + materialsSection.height if (mouse.y < matsSecBottom) ctxMenu.popupMenu() @@ -261,8 +271,7 @@ Item { } } - function ensureVisible(yPos, itemHeight) - { + function ensureVisible(yPos, itemHeight) { let currentY = contentYBehavior.targetValue && scrollViewAnim.running ? contentYBehavior.targetValue : scrollView.contentY @@ -286,18 +295,18 @@ Item { return false } - function ensureSelectedVisible() - { + function ensureSelectedVisible() { if (rootView.materialSectionFocused && materialsSection.expanded && root.currMaterialItem && materialBrowserModel.isVisible(materialBrowserModel.selectedIndex)) { - return ensureVisible(root.currMaterialItem.mapToItem(scrollView.contentItem, 0, 0).y, - root.currMaterialItem.height) + return root.ensureVisible(root.currMaterialItem.mapToItem(scrollView.contentItem, 0, 0).y, + root.currMaterialItem.height) } else if (!rootView.materialSectionFocused && texturesSection.expanded) { let currItem = texturesRepeater.itemAt(materialBrowserTexturesModel.selectedIndex) if (currItem && materialBrowserTexturesModel.isVisible(materialBrowserTexturesModel.selectedIndex)) - return ensureVisible(currItem.mapToItem(scrollView.contentItem, 0, 0).y, currItem.height) + return root.ensureVisible(currItem.mapToItem(scrollView.contentItem, 0, 0).y, + currItem.height) } else { - return ensureVisible(0, 90) + return root.ensureVisible(0, 90) } } @@ -310,15 +319,14 @@ Item { onTriggered: { // Redo until ensuring didn't change things if (!root.ensureSelectedVisible()) { - stop() - interval = 20 - triggeredOnStart = true + ensureTimer.stop() + ensureTimer.interval = 20 + ensureTimer.triggeredOnStart = true } } } - function startDelayedEnsureTimer(delay) - { + function startDelayedEnsureTimer(delay) { // Ensuring visibility immediately in some cases like before new search results are rendered // causes mapToItem return incorrect values, leading to undesirable flicker, // so delay ensuring visibility a bit. @@ -330,8 +338,7 @@ Item { Connections { target: materialBrowserModel - function onSelectedIndexChanged() - { + function onSelectedIndexChanged() { // commit rename upon changing selection if (root.currMaterialItem) root.currMaterialItem.forceFinishEditing(); @@ -341,8 +348,7 @@ Item { ensureTimer.start() } - function onIsEmptyChanged() - { + function onIsEmptyChanged() { ensureTimer.start() } } @@ -350,13 +356,11 @@ Item { Connections { target: materialBrowserTexturesModel - function onSelectedIndexChanged() - { + function onSelectedIndexChanged() { ensureTimer.start() } - function onIsEmptyChanged() - { + function onIsEmptyChanged() { ensureTimer.start() } } @@ -364,8 +368,7 @@ Item { Connections { target: rootView - function onMaterialSectionFocusedChanged() - { + function onMaterialSectionFocusedChanged() { ensureTimer.start() } } @@ -614,9 +617,10 @@ Item { id: scrollView width: root.width - height: root.height - toolbar.height + height: root.height - toolbar.height - col.spacing clip: true visible: root.enableUiElements + hideHorizontalScrollBar: true interactive: !ctxMenu.opened && !ctxMenuTextures.opened && !rootView.isDragging && !HelperWidgets.Controller.contextMenuOpened @@ -637,6 +641,12 @@ Item { id: materialsSection width: root.width + + leftPadding: StudioTheme.Values.sectionPadding + rightPadding: StudioTheme.Values.sectionPadding + topPadding: StudioTheme.Values.sectionPadding + bottomPadding: StudioTheme.Values.sectionPadding + caption: qsTr("Materials") dropEnabled: true category: "MaterialBrowser" @@ -671,11 +681,10 @@ Item { Grid { id: gridMaterials - width: scrollView.width - leftPadding: 5 - rightPadding: 5 - bottomPadding: 5 - columns: root.width / root.cellWidth + width: scrollView.width - materialsSection.leftPadding + - materialsSection.rightPadding + spacing: StudioTheme.Values.sectionGridSpacing + columns: root.numColumns Repeater { id: materialRepeater @@ -691,10 +700,10 @@ Item { width: root.cellWidth height: root.cellHeight - onShowContextMenu: { - ctxMenu.popupMenu(this, model) - } + onShowContextMenu: ctxMenu.popupMenu(this, model) } + + onCountChanged: root.responsiveResize(root.width, root.height) } } @@ -727,6 +736,11 @@ Item { id: texturesSection width: root.width + leftPadding: StudioTheme.Values.sectionPadding + rightPadding: StudioTheme.Values.sectionPadding + topPadding: StudioTheme.Values.sectionPadding + bottomPadding: StudioTheme.Values.sectionPadding + caption: qsTr("Textures") category: "MaterialBrowser" @@ -768,11 +782,10 @@ Item { Grid { id: gridTextures - width: scrollView.width - leftPadding: 5 - rightPadding: 5 - bottomPadding: 5 - columns: root.width / root.cellWidth + width: scrollView.width - texturesSection.leftPadding + - texturesSection.rightPadding + spacing: StudioTheme.Values.sectionGridSpacing + columns: root.numColumns Repeater { id: texturesRepeater @@ -782,10 +795,10 @@ Item { width: root.cellWidth height: root.cellHeight - onShowContextMenu: { - ctxMenuTextures.popupMenu(model) - } + onShowContextMenu: ctxMenuTextures.popupMenu(model) } + + onCountChanged: root.responsiveResize(root.width, root.height) } } diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml index f055d1d665d..ce1b12aeaeb 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml @@ -8,32 +8,24 @@ import HelperWidgets 2.0 import StudioTheme 1.0 as StudioTheme import MaterialBrowserBackend -Rectangle { +Item { id: root signal showContextMenu() - function refreshPreview() - { + function refreshPreview() { img.source = "" img.source = "image://materialBrowser/" + materialInternalId } - function forceFinishEditing() - { + function forceFinishEditing() { matName.commitRename() } - function startRename() - { + function startRename() { matName.startRename() } - border.width: MaterialBrowserBackend.materialBrowserModel.selectedIndex === index ? MaterialBrowserBackend.rootView.materialSectionFocused ? 3 : 1 : 0 - border.color: MaterialBrowserBackend.materialBrowserModel.selectedIndex === index - ? StudioTheme.Values.themeControlOutlineInteraction - : "transparent" - color: "transparent" visible: materialVisible DropArea { @@ -81,12 +73,10 @@ Rectangle { anchors.fill: parent spacing: 1 - Item { width: 1; height: 5 } // spacer - Image { id: img - width: root.width - 10 + width: root.width height: img.width anchors.horizontalCenter: parent.horizontalCenter source: "image://materialBrowser/" + materialInternalId @@ -94,8 +84,8 @@ Rectangle { } // Eat keys so they are not passed to parent while editing name - Keys.onPressed: (e) => { - e.accepted = true; + Keys.onPressed: (event) => { + event.accepted = true } MaterialBrowserItemName { @@ -116,4 +106,14 @@ Rectangle { } } } + + Rectangle { + id: marker + anchors.fill: parent + border.width: MaterialBrowserBackend.materialBrowserModel.selectedIndex === index ? MaterialBrowserBackend.rootView.materialSectionFocused ? 3 : 1 : 0 + border.color: MaterialBrowserBackend.materialBrowserModel.selectedIndex === index + ? StudioTheme.Values.themeControlOutlineInteraction + : "transparent" + color: "transparent" + } } diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml index b0a5e138099..4eaa449c62a 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml @@ -9,22 +9,14 @@ import HelperWidgets import StudioTheme as StudioTheme import MaterialBrowserBackend -Rectangle { +Item { id: root visible: textureVisible - color: "transparent" - border.width: MaterialBrowserBackend.materialBrowserTexturesModel.selectedIndex === index - ? !MaterialBrowserBackend.rootView.materialSectionFocused ? 3 : 1 : 0 - border.color: MaterialBrowserBackend.materialBrowserTexturesModel.selectedIndex === index - ? StudioTheme.Values.themeControlOutlineInteraction - : "transparent" - signal showContextMenu() - function forceFinishEditing() - { + function forceFinishEditing() { txtId.commitRename() } @@ -68,12 +60,11 @@ Rectangle { anchors.fill: parent spacing: 1 - Item { width: 1; height: 5 } // spacer Image { id: img source: "image://materialBrowserTex/" + textureSource asynchronous: true - width: root.width - 10 + width: root.width height: img.width anchors.horizontalCenter: parent.horizontalCenter smooth: true @@ -81,8 +72,8 @@ Rectangle { } // Eat keys so they are not passed to parent while editing name - Keys.onPressed: (e) => { - e.accepted = true; + Keys.onPressed: (event) => { + event.accepted = true } MaterialBrowserItemName { @@ -103,4 +94,16 @@ Rectangle { } } } + + Rectangle { + id: marker + anchors.fill: parent + + color: "transparent" + border.width: MaterialBrowserBackend.materialBrowserTexturesModel.selectedIndex === index + ? !MaterialBrowserBackend.rootView.materialSectionFocused ? 3 : 1 : 0 + border.color: MaterialBrowserBackend.materialBrowserTexturesModel.selectedIndex === index + ? StudioTheme.Values.themeControlOutlineInteraction + : "transparent" + } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/Qt5HelperWindow.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/Qt5HelperWindow.qml deleted file mode 100644 index a2232b13da5..00000000000 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/Qt5HelperWindow.qml +++ /dev/null @@ -1,4 +0,0 @@ -import QtQuick.Window 2.15 - -Window { -} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/Qt6HelperWindow.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/Qt6HelperWindow.qml deleted file mode 100644 index 448a7575a47..00000000000 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/Qt6HelperWindow.qml +++ /dev/null @@ -1,4 +0,0 @@ -import QtQuick 2.15 - -Window { -} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtMultimedia/MediaPlayerSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtMultimedia/MediaPlayerSection.qml index 4b66032bcf4..89a489f1cff 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtMultimedia/MediaPlayerSection.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtMultimedia/MediaPlayerSection.qml @@ -20,6 +20,21 @@ Section { // TODO position property, what should be the range?! SectionLayout { + + PropertyLabel { + text: qsTr("Source") + tooltip: qsTr("Adds an image from the local file system.") + } + + SecondColumnLayout { + UrlChooser { + backendValue: backendValues.source + filter: "*.avi *.mp4 *.mpeg *.wav" + } + + ExpandingSpacer {} + } + PropertyLabel { text: qsTr("Playback rate") } SecondColumnLayout { diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/AnimatedSpriteSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/AnimatedSpriteSpecifics.qml new file mode 100644 index 00000000000..d5e93be11ed --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/AnimatedSpriteSpecifics.qml @@ -0,0 +1,364 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Layouts +import HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme + +Column { + anchors.left: parent.left + anchors.right: parent.right + + Section { + caption: qsTr("Animated Sprite") + + anchors.left: parent.left + anchors.right: parent.right + + SectionLayout { + PropertyLabel { + text: qsTr("Source") + tooltip: qsTr("Adds an image from the local file system.") + } + + SecondColumnLayout { + UrlChooser { + backendValue: backendValues.source + } + + ExpandingSpacer {} + } + + PropertyLabel { + text: qsTr("Frame size") + tooltip: qsTr("Sets the width and height of the frame.") + blockedByTemplate: !(backendValues.frameWidth.isAvailable || backendValues.frameHeight.isAvailable) + } + + SecondColumnLayout { + SpinBox { + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + backendValue: backendValues.frameWidth + minimumValue: 0 + maximumValue: 8192 + decimals: 0 + enabled: backendValue.isAvailable + } + + Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } + + ControlLabel { + //: The width of the animated sprite frame + text: qsTr("W", "width") + tooltip: qsTr("Width.") + enabled: backendValues.frameWidth.isAvailable + } + + Spacer { implicitWidth: StudioTheme.Values.controlGap } + + SpinBox { + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + backendValue: backendValues.frameHeight + minimumValue: 0 + maximumValue: 8192 + decimals: 0 + enabled: backendValue.isAvailable + } + + Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } + + ControlLabel { + //: The height of the animated sprite frame + text: qsTr("H", "height") + tooltip: qsTr("Height.") + enabled: backendValues.frameHeight.isAvailable + } + /* + TODO QDS-4836 + Spacer { implicitWidth: StudioTheme.Values.controlGap } + + LinkIndicator2D {} + */ + ExpandingSpacer {} + } + + PropertyLabel { + text: qsTr("Frame coordinates") + tooltip: qsTr("Sets the coordinates of the first frame of the animated sprite.") + blockedByTemplate: !(backendValues.frameX.isAvailable || backendValues.frameY.isAvailable) + } + + SecondColumnLayout { + SpinBox { + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + backendValue: backendValues.frameX + minimumValue: 0 + maximumValue: 8192 + decimals: 0 + enabled: backendValue.isAvailable + } + + Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } + + ControlLabel { + //: The width of the animated sprite frame + text: qsTr("X", "Frame X") + tooltip: qsTr("Frame X coordinate.") + enabled: backendValues.frameX.isAvailable + } + + Spacer { implicitWidth: StudioTheme.Values.controlGap } + + SpinBox { + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + backendValue: backendValues.frameY + minimumValue: 0 + maximumValue: 8192 + decimals: 0 + enabled: backendValue.isAvailable + } + + Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } + + ControlLabel { + //: The height of the animated sprite frame + text: qsTr("Y", "Frame Y") + tooltip: qsTr("Frame Y coordinate.") + enabled: backendValues.frameY.isAvailable + } + /* + TODO QDS-4836 + Spacer { implicitWidth: StudioTheme.Values.controlGap } + + LinkIndicator2D {} + */ + ExpandingSpacer {} + } + + PropertyLabel { + text: qsTr("Frame count") + tooltip: qsTr("Sets the number of frames in this animated sprite.") + blockedByTemplate: !backendValues.frameCount.isAvailable + } + + SecondColumnLayout { + SpinBox { + implicitWidth: StudioTheme.Values.singleControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + backendValue: backendValues.frameCount + decimals: 0 + minimumValue: 0 + maximumValue: 10000 + enabled: backendValue.isAvailable + } + + ExpandingSpacer {} + } + + //frame rate OR frame duration OR frame sync should be used + //frame rate has priority over frame duration + //frame sync has priority over rate and duration + PropertyLabel { + text: qsTr("Frame rate") + tooltip: qsTr("Sets the number of frames per second to show in the animation.") + blockedByTemplate: !backendValues.frameRate.isAvailable + } + + SecondColumnLayout { + SpinBox { + implicitWidth: StudioTheme.Values.singleControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + backendValue: backendValues.frameRate + decimals: 2 + minimumValue: 0 + maximumValue: 1000 + enabled: backendValue.isAvailable + } + + ExpandingSpacer {} + } + + //frame duration OR frame rate OR frame sync should be used + //frame rate has priority over frame duration + //frame sync has priority over rate and duration + PropertyLabel { + text: qsTr("Frame duration") + tooltip: qsTr("Sets the duration of each frame of the animation in milliseconds.") + blockedByTemplate: !backendValues.frameDuration.isAvailable + } + + SecondColumnLayout { + SpinBox { + implicitWidth: StudioTheme.Values.singleControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + backendValue: backendValues.frameDuration + decimals: 0 + minimumValue: 0 + maximumValue: 100000 + enabled: backendValue.isAvailable + } + + ExpandingSpacer {} + } + + //frame sync OR frame rate OR frame duration should be used + //frame rate has priority over frame duration + //frame sync has priority over rate and duration + PropertyLabel { + text: qsTr("Frame sync") + tooltip: qsTr("Sets frame advancements one frame each time a frame is rendered to the screen.") + blockedByTemplate: !backendValues.frameSync.isAvailable + } + + SecondColumnLayout { + CheckBox { + text: backendValues.frameSync.valueToString + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + backendValue: backendValues.frameSync + enabled: backendValue.isAvailable + } + + ExpandingSpacer {} + } + + PropertyLabel { + text: qsTr("Loops") + tooltip: qsTr("After playing the animation this many times, the animation will automatically stop.") + blockedByTemplate: !backendValues.loops.isAvailable + } + + SecondColumnLayout { + SpinBox { + implicitWidth: StudioTheme.Values.singleControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + backendValue: backendValues.loops + decimals: 0 + minimumValue: -1 //AnimatedSprite.Infinite = -1 + maximumValue: 100000 + enabled: backendValue.isAvailable + } + + ExpandingSpacer {} + } + + PropertyLabel { + text: qsTr("Interpolate") + tooltip: qsTr("If true, interpolation will occur between sprite frames to make the animation appear smoother.") + blockedByTemplate: !backendValues.interpolate.isAvailable + } + + SecondColumnLayout { + CheckBox { + text: backendValues.interpolate.valueToString + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + backendValue: backendValues.interpolate + enabled: backendValue.isAvailable + } + + ExpandingSpacer {} + } + + PropertyLabel { + text: qsTr("Finish behavior") + tooltip: qsTr("Sets the behavior when the animation finishes on its own.") + blockedByTemplate: !backendValues.finishBehavior.isAvailable + } + + SecondColumnLayout { + ComboBox { + implicitWidth: StudioTheme.Values.singleControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + width: implicitWidth + scope: "AnimatedSprite" + model: ["FinishAtInitialFrame", "FinishAtFinalFrame"] + backendValue: backendValues.finishBehavior + enabled: backendValue.isAvailable + } + + ExpandingSpacer {} + } + + PropertyLabel { + text: qsTr("Reverse") + tooltip: qsTr("If true, the animation will be played in reverse.") + blockedByTemplate: !backendValues.reverse.isAvailable + } + + SecondColumnLayout { + CheckBox { + text: backendValues.reverse.valueToString + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + backendValue: backendValues.reverse + enabled: backendValue.isAvailable + } + + ExpandingSpacer {} + } + + PropertyLabel { + text: qsTr("Running") + tooltip: qsTr("Whether the sprite is animating or not.") + blockedByTemplate: !backendValues.running.isAvailable + } + + SecondColumnLayout { + CheckBox { + text: backendValues.running.valueToString + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + backendValue: backendValues.running + enabled: backendValue.isAvailable + } + + ExpandingSpacer {} + } + + PropertyLabel { + text: qsTr("Paused") + tooltip: qsTr("When paused, the current frame can be advanced manually.") + blockedByTemplate: !backendValues.paused.isAvailable + } + + SecondColumnLayout { + CheckBox { + text: backendValues.paused.valueToString + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + backendValue: backendValues.paused + enabled: backendValue.isAvailable + } + + ExpandingSpacer {} + } + + PropertyLabel { + text: qsTr("Current frame") + tooltip: qsTr("When paused, the current frame can be advanced manually by setting this property.") + blockedByTemplate: !backendValues.currentFrame.isAvailable + } + + SecondColumnLayout { + SpinBox { + implicitWidth: StudioTheme.Values.singleControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + backendValue: backendValues.currentFrame + decimals: 0 + minimumValue: 0 + maximumValue: 100000 + enabled: backendValue.isAvailable + } + + ExpandingSpacer {} + } + } + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/QtObjectPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/QtObjectPane.qml index 36d39f829cf..92e157dfdc4 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/QtObjectPane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/QtObjectPane.qml @@ -12,6 +12,11 @@ PropertyEditorPane { ComponentSection {} + DynamicPropertiesSection { + propertiesModel: SelectionDynamicPropertiesModel {} + visible: !hasMultiSelection + } + Column { anchors.left: parent.left anchors.right: parent.right diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditor.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditor.qml index 7c5b9535473..4ff7c30c2bf 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditor.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditor.qml @@ -1,13 +1,14 @@ -// Copyright (C) 2021 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -import QtQuick 2.15 -import QtQuick.Layouts 1.15 -import QtQuick.Shapes 1.15 -import QtQuick.Templates 2.15 as T -import QtQuickDesignerTheme 1.0 -import StudioTheme 1.0 as StudioTheme -import StudioControls 1.0 as StudioControls -import QtQuickDesignerColorPalette 1.0 + +import QtQuick +import QtQuick.Layouts +import QtQuick.Shapes +import QtQuick.Templates as T +import StudioTheme as StudioTheme +import StudioControls as StudioControls +import QtQuickDesignerTheme +import QtQuickDesignerColorPalette SecondColumnLayout { id: colorEditor @@ -29,7 +30,7 @@ SecondColumnLayout { return colorEditor.backendValue.value } - property alias gradientPropertyName: popupLoader.gradientPropertyName + property alias gradientPropertyName: popupDialog.gradientPropertyName property alias gradientThumbnail: gradientThumbnail property alias shapeGradientThumbnail: shapeGradientThumbnail @@ -66,12 +67,12 @@ SecondColumnLayout { target: colorEditor function onValueChanged() { - if (popupLoader.isNotInGradientMode()) + if (popupDialog.isSolid()) colorEditor.syncColor() } function onBackendValueChanged() { - if (popupLoader.isNotInGradientMode()) + if (popupDialog.isSolid()) colorEditor.syncColor() } } @@ -101,13 +102,13 @@ SecondColumnLayout { if (colorEditor.__block) return - if (!popupLoader.isInValidState) + if (!popupDialog.isInValidState) return - popupLoader.commitToGradient() + popupDialog.commitToGradient() // Delay setting the color to keep ui responsive - if (popupLoader.isNotInGradientMode()) + if (popupDialog.isSolid()) colorEditorTimer.restart() } @@ -127,17 +128,16 @@ SecondColumnLayout { id: gradientThumbnail anchors.fill: parent anchors.margins: StudioTheme.Values.border - visible: !popupLoader.isNotInGradientMode() + visible: !popupDialog.isSolid() && !colorEditor.shapeGradients - && popupLoader.hasLinearGradient() + && popupDialog.isLinearGradient() } Shape { id: shape anchors.fill: parent anchors.margins: StudioTheme.Values.border - visible: !popupLoader.isNotInGradientMode() - && colorEditor.shapeGradients + visible: !popupDialog.isSolid() && colorEditor.shapeGradients ShapePath { id: shapeGradientThumbnail @@ -171,78 +171,77 @@ SecondColumnLayout { MouseArea { anchors.fill: parent onClicked: { - popupLoader.opened ? popupLoader.close() : popupLoader.open() + popupDialog.visibility ? popupDialog.close() : popupDialog.open() forceActiveFocus() } } - QtObject { - id: popupLoader - - property bool isInValidState: popupLoader.active ? popupLoader.dialog.isInValidState : true - - property QtObject dialog: popupLoader.loader.item - - property bool opened: popupLoader.active ? popupLoader.dialog.opened : false + StudioControls.PopupDialog { + id: popupDialog + property bool isInValidState: loader.active ? popupDialog.loaderItem.isInValidState : true + property QtObject loaderItem: loader.item property string gradientPropertyName + width: 260 + maximumHeight: Screen.desktopAvailableHeight * 0.7 + function commitToGradient() { - if (!popupLoader.active) + if (!loader.active) return - if (colorEditor.supportGradient && popupLoader.dialog.gradientModel.hasGradient) { + if (colorEditor.supportGradient && popupDialog.loaderItem.gradientModel.hasGradient) { var hexColor = convertColorToString(colorEditor.color) hexTextField.text = hexColor - popupLoader.dialog.commitGradientColor() + popupDialog.loaderItem.commitGradientColor() } } - function isNotInGradientMode() { - if (!popupLoader.active) + function isSolid() { + if (!loader.active) return true - return popupLoader.dialog.isNotInGradientMode() + + return popupDialog.loaderItem.isSolid() } - function hasLinearGradient(){ - if (!popupLoader.active) + function isLinearGradient(){ + if (!loader.active) return false - return popupLoader.dialog.hasLinearGradient() + + return popupDialog.loaderItem.isLinearGradient() } function ensureLoader() { - if (!popupLoader.active) - popupLoader.active = true + if (!loader.active) + loader.active = true } function open() { - popupLoader.ensureLoader() - popupLoader.dialog.open() - } - - function close() { - popupLoader.ensureLoader() - popupLoader.dialog.close() + popupDialog.ensureLoader() + popupDialog.show(preview) } function determineActiveColorMode() { - if (popupLoader.active && popupLoader.dialog) - popupLoader.dialog.determineActiveColorMode() + if (loader.active && popupDialog.loaderItem) + popupDialog.loaderItem.determineActiveColorMode() else colorEditor.syncColor() } - property alias active: popupLoader.loader.active - property Loader loader: Loader { - parent: preview + Loader { + id: loader active: colorEditor.supportGradient sourceComponent: ColorEditorPopup { - id: cePopup - x: cePopup.__defaultX - y: cePopup.__defaultY + shapeGradients: colorEditor.shapeGradients + supportGradient: colorEditor.supportGradient + width: popupDialog.contentWidth + } + + onLoaded: { + popupDialog.loaderItem.initEditor() + popupDialog.titleBar = loader.item.titleBarContent } - onLoaded: popupLoader.dialog.initEditor() } } } @@ -255,19 +254,26 @@ SecondColumnLayout { id: hexTextField implicitWidth: StudioTheme.Values.twoControlColumnWidth + StudioTheme.Values.actionIndicatorWidth - width: implicitWidth - enabled: popupLoader.isNotInGradientMode() + width: hexTextField.implicitWidth + enabled: popupDialog.isSolid() writeValueManually: true - validator: RegExpValidator { - regExp: /#[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?/g + validator: RegularExpressionValidator { + regularExpression: /#[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?/g } showTranslateCheckBox: false + indicatorVisible: true + indicator.icon.text: StudioTheme.Constants.copy_small + indicator.onClicked: { + hexTextField.selectAll() + hexTextField.copy() + hexTextField.deselect() + } backendValue: colorEditor.backendValue onAccepted: colorEditor.color = colorFromString(hexTextField.text) onCommitData: { colorEditor.color = colorFromString(hexTextField.text) - if (popupLoader.isNotInGradientMode()) { + if (popupDialog.isSolid()) { if (colorEditor.isVector3D) { backendValue.value = Qt.vector3d(colorEditor.color.r, colorEditor.color.g, @@ -283,9 +289,7 @@ SecondColumnLayout { id: spacer } - Component.onCompleted: popupLoader.determineActiveColorMode() + Component.onCompleted: popupDialog.determineActiveColorMode() - onBackendValueChanged: { - popupLoader.determineActiveColorMode() - } + onBackendValueChanged: popupDialog.determineActiveColorMode() } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditorPopup.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditorPopup.qml index 2a4b036effb..c5e0ef63ee0 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditorPopup.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditorPopup.qml @@ -1,65 +1,211 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -import QtQuick 2.15 -import QtQuick.Layouts 1.15 -import QtQuick.Shapes 1.15 -import QtQuick.Templates 2.15 as T -import QtQuickDesignerTheme 1.0 -import StudioTheme 1.0 as StudioTheme -import StudioControls 1.0 as StudioControls -import QtQuickDesignerColorPalette 1.0 +import QtQuick +import QtQuick.Layouts +import QtQuick.Shapes +import QtQuick.Templates as T +import QtQuickDesignerTheme +import StudioTheme as StudioTheme +import StudioControls as StudioControls +import QtQuickDesignerColorPalette -T.Popup { - id: cePopup +Column { + id: root + property bool supportGradient: false + property bool shapeGradients: false property alias gradientLine: gradientLine property alias popupHexTextField: popupHexTextField - property alias gradientPropertyName: cePopup.gradientModel.gradientPropertyName + property alias gradientPropertyName: root.gradientModel.gradientPropertyName property alias gradientOrientation: gradientOrientation property alias gradientModel: gradientModel property bool isInValidState: false + readonly property real twoColumnWidth: (colorColumn.width - StudioTheme.Values.controlGap) * 0.5 + readonly property real fourColumnWidth: (colorColumn.width - (3 * StudioTheme.Values.controlGap)) * 0.25 + + property Item titleBarContent: Row { + anchors.fill: parent + spacing: 10 + + StudioControls.ComboBox { + id: ceMode + + property ListModel items: ListModel {} + + anchors.verticalCenter: parent.verticalCenter + enabled: isBaseState + implicitWidth: StudioTheme.Values.colorEditorPopupComboBoxWidth + width: ceMode.implicitWidth + actionIndicatorVisible: false + textRole: "text" + valueRole: "value" + model: ceMode.items + onActivated: { + switch (ceMode.currentValue) { + case "Solid": + gradientLine.deleteGradient() + hexTextField.text = colorEditor.color + popupHexTextField.text = colorEditor.color + colorEditor.resetShapeColor() + break + case "LinearGradient": + colorEditor.resetShapeColor() + + if (root.shapeGradients) + gradientModel.gradientTypeName = "LinearGradient" + else + gradientModel.gradientTypeName = "Gradient" + + if (gradientModel.hasGradient) + gradientLine.updateGradient() + else { + gradientLine.deleteGradient() + gradientLine.addGradient() + } + break + case "RadialGradient": + colorEditor.resetShapeColor() + gradientModel.gradientTypeName = "RadialGradient" + + if (gradientLine.hasGradient) + gradientLine.updateGradient() + else { + gradientLine.deleteGradient() + gradientLine.addGradient() + } + break + case "ConicalGradient": + colorEditor.resetShapeColor() + gradientModel.gradientTypeName = "ConicalGradient" + + if (gradientModel.hasGradient) + gradientLine.updateGradient() + else { + gradientLine.deleteGradient() + gradientLine.addGradient() + } + break + default: + console.warn("Unknown item selected in color mode ComboBox.") + } + root.updateThumbnail() + } + + ToolTipArea { + enabled: !isBaseState + anchors.fill: parent + tooltip: qsTr("Fill type can only be changed in base state.") + z: 10 + } + } + + ExpandingSpacer {} + + IconIndicator { + id: transparentIndicator + anchors.verticalCenter: parent.verticalCenter + icon: StudioTheme.Constants.transparent + pixelSize: StudioTheme.Values.myIconFontSize * 1.4 + tooltip: qsTr("Transparent") + onClicked: { + colorPicker.alpha = 0 + colorPicker.invalidateColor() + colorPicker.updateColor() + } + } + + IconIndicator { + id: gradientPickerIndicator + anchors.verticalCenter: parent.verticalCenter + icon: StudioTheme.Constants.gradient + pixelSize: StudioTheme.Values.myIconFontSize * 1.4 + tooltip: qsTr("Gradient Picker") + enabled: root.supportGradient + onClicked: presetList.show() + + GradientPresetList { + id: presetList + visible: false + transientParent: root.parentWindow + + function applyPreset() { + if (!gradientModel.hasGradient) { + if (root.shapeGradients) + gradientModel.gradientTypeName = "LinearGradient" + else + gradientModel.gradientTypeName = "Gradient" + } + + if (presetList.gradientData.presetType == 0) { + gradientLine.setPresetByID(presetList.gradientData.presetID) + } else if (presetList.gradientData.presetType == 1) { + gradientLine.setPresetByStops( + presetList.gradientData.stops, + presetList.gradientData.colors, + presetList.gradientData.stopsCount) + } else { + console.warn("Invalid Gradient type:", presetList.gradientData.presetType) + } + } + + onApplied: { + if (presetList.gradientData.stopsCount > 0) + presetList.applyPreset() + } + + onSaved: { + gradientLine.savePreset() + presetList.updatePresets() + } + + onAccepted: { // return key + if (presetList.gradientData.stopsCount > 0) + presetList.applyPreset() + } + } + } + + IconIndicator { + id: eyeDropperIndicator + anchors.verticalCenter: parent.verticalCenter + icon: StudioTheme.Constants.eyeDropper + pixelSize: StudioTheme.Values.myIconFontSize * 1.4 + tooltip: qsTr("Eye Dropper") + onClicked: ColorPaletteBackend.eyeDropper() + } + } + function initEditor() { - if (colorEditor.supportGradient && gradientModel.hasGradient) { + if (root.supportGradient && gradientModel.hasGradient) { colorEditor.color = gradientLine.currentColor gradientLine.currentColor = colorEditor.color hexTextField.text = colorEditor.color popupHexTextField.text = colorEditor.color } - cePopup.isInValidState = true + root.isInValidState = true colorEditor.originalColor = colorEditor.color colorPalette.selectedColor = colorEditor.color colorPicker.color = colorEditor.color - cePopup.createModel() - cePopup.determineActiveColorMode() + root.createModel() + root.determineActiveColorMode() } function commitGradientColor() { var hexColor = convertColorToString(colorEditor.color) - cePopup.popupHexTextField.text = hexColor - cePopup.gradientLine.currentColor = colorEditor.color + root.popupHexTextField.text = hexColor + root.gradientLine.currentColor = colorEditor.color } - function isNotInGradientMode() { - return ceMode.currentValue === "Solid" - } - - function hasLinearGradient() { - return ceMode.currentValue === "LinearGradient" - } - - function hasConicalGradient() { - return ceMode.currentValue === "ConicalGradient" - } - - function hasRadialGradient() { - return ceMode.currentValue === "RadialGradient" - } + function isSolid() { return ceMode.currentValue === "Solid" } + function isLinearGradient() { return ceMode.currentValue === "LinearGradient" } + function isConicalGradient() { return ceMode.currentValue === "ConicalGradient" } + function isRadialGradient() { return ceMode.currentValue === "RadialGradient" } function createModel() { // Build the color editor combobox model @@ -72,23 +218,23 @@ T.Popup { ceMode.items.append({ value: "LinearGradient", text: qsTr("Linear"), - enabled: colorEditor.supportGradient + enabled: root.supportGradient }) ceMode.items.append({ value: "RadialGradient", text: qsTr("Radial"), - enabled: colorEditor.supportGradient && colorEditor.shapeGradients + enabled: root.supportGradient && root.shapeGradients }) ceMode.items.append({ value: "ConicalGradient", text: qsTr("Conical"), - enabled: colorEditor.supportGradient && colorEditor.shapeGradients + enabled: root.supportGradient && root.shapeGradients }) } function determineActiveColorMode() { - if (colorEditor.supportGradient && gradientModel.hasGradient) { - if (colorEditor.shapeGradients) { + if (root.supportGradient && gradientModel.hasGradient) { + if (root.shapeGradients) { switch (gradientModel.gradientTypeName) { case "LinearGradient": ceMode.currentIndex = ceMode.indexOfValue("LinearGradient") @@ -118,9 +264,9 @@ T.Popup { if (!gradientModel.hasGradient) return - if (!colorEditor.shapeGradients) { + if (!root.shapeGradients) { var gradientString = "import QtQuick 2.15; Gradient {" - var orientation = cePopup.gradientOrientation.currentValue + var orientation = root.gradientOrientation.currentValue === Gradient.Horizontal ? "Gradient.Horizontal" : "Gradient.Vertical" gradientString += "orientation: " + orientation + ";" @@ -193,986 +339,716 @@ T.Popup { gradientPropertyName: "gradient" } - WheelHandler { - onWheel: function(event) { - Controller.mainScrollView.flick(0, event.angleDelta.y * 5) + Column { + id: colorColumn + bottomPadding: StudioTheme.Values.popupMargin + width: root.width + spacing: StudioTheme.Values.colorEditorPopupSpacing + + GradientLine { + id: gradientLine + + width: parent.width + visible: !root.isSolid() + + model: gradientModel + + onCurrentColorChanged: { + if (root.supportGradient && gradientModel.hasGradient) { + colorEditor.color = gradientLine.currentColor + colorPicker.color = colorEditor.color + } + } + + onHasGradientChanged: { + if (!root.supportGradient) + return + + root.determineActiveColorMode() + } + + onSelectedNodeChanged: { + if (root.supportGradient && gradientModel.hasGradient) + colorEditor.originalColor = gradientLine.currentColor + } + + onInvalidated: root.updateThumbnail() + + Connections { + target: modelNodeBackend + function onSelectionToBeChanged() { + colorEditorTimer.stop() + root.isInValidState = false + + var hexOriginalColor = convertColorToString(colorEditor.originalColor) + var hexColor = convertColorToString(colorEditor.color) + + if (hexOriginalColor !== hexColor) { + if (colorEditor.color !== "#ffffff" + && colorEditor.color !== "#000000" + && colorEditor.color !== "#00000000") { + colorPalette.addColorToPalette(colorEditor.color) + } + } + } + } + + Connections { + target: modelNodeBackend + function onSelectionChanged() { + root.initEditor() + } + } } - } - // This connection is meant to update the popups y-position and the main scrollviews - // height as soon as the height of the color picker changes. Initially the height of the - // color picker is 0 until its completion is done. - Connections { - target: colorPicker - function onHeightChanged() { - cePopup.setPopupY() - cePopup.setMainScrollViewHeight() + StudioControls.ColorPicker { + id: colorPicker + + width: parent.width + sliderMargins: 4 + + onUpdateColor: { + colorEditor.color = colorPicker.color + + if (contextMenu.opened) + contextMenu.close() + } + onRightMouseButtonClicked: contextMenu.popup(colorPicker) + + onColorInvalidated: { + hslHueSpinBox.value = colorPicker.hue + hslSaturationSpinBox.value = colorPicker.saturationHSL + hslLightnessSpinBox.value = colorPicker.lightness + hslAlphaSpinBox.value = colorPicker.alpha + + redSpinBox.value = (colorPicker.red * 255) + greenSpinBox.value = (colorPicker.green * 255) + blueSpinBox.value = (colorPicker.blue * 255) + rgbAlphaSpinBox.value = (colorPicker.alpha * 255) + + hsvHueSpinBox.value = colorPicker.hue + hsvSaturationSpinBox.value = colorPicker.saturationHSV + hsvValueSpinBox.value = colorPicker.value + hsvAlphaSpinBox.value = colorPicker.alpha + } } - } - - onOpened: { - cePopup.setPopupY() - cePopup.setMainScrollViewHeight() - } - onYChanged: cePopup.setMainScrollViewHeight() - onHeightChanged: cePopup.setMainScrollViewHeight() - - function setMainScrollViewHeight() { - if (Controller.mainScrollView === null) - return - - var mapped = preview.mapToItem(Controller.mainScrollView.contentItem, cePopup.x, cePopup.y) - Controller.mainScrollView.temporaryHeight = mapped.y + cePopup.height - + StudioTheme.Values.colorEditorPopupMargin - } - - function setPopupY() { - if (Controller.mainScrollView === null) - return - - var tmp = preview.mapToItem(Controller.mainScrollView.contentItem, preview.x, preview.y) - cePopup.y = Math.max(-tmp.y + StudioTheme.Values.colorEditorPopupMargin, - cePopup.__defaultY) - } - - onClosed: Controller.mainScrollView.temporaryHeight = 0 - - property real __defaultX: - StudioTheme.Values.colorEditorPopupWidth * 0.5 - + preview.width * 0.5 - property real __defaultY: - StudioTheme.Values.colorEditorPopupPadding - - (StudioTheme.Values.colorEditorPopupSpacing * 2) - - StudioTheme.Values.defaultControlHeight - - StudioTheme.Values.colorEditorPopupLineHeight - - colorPicker.height * 0.5 - + preview.height * 0.5 - - width: StudioTheme.Values.colorEditorPopupWidth - height: colorColumn.height + sectionColumn.height - + StudioTheme.Values.colorEditorPopupPadding + 2 // TODO magic number - - padding: StudioTheme.Values.border - margins: -1 // If not defined margin will be -1 - - closePolicy: T.Popup.CloseOnPressOutside | T.Popup.CloseOnPressOutsideParent - - contentItem: Item { - id: todoItem - - property color color - property bool supportGradient: false Column { - id: colorColumn - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.margins: StudioTheme.Values.colorEditorPopupPadding - spacing: StudioTheme.Values.colorEditorPopupSpacing + id: colorCompare + width: parent.width RowLayout { width: parent.width Layout.alignment: Qt.AlignTop + spacing: StudioTheme.Values.controlGap - StudioControls.ComboBox { - id: ceMode + Label { + text: qsTr("Original") + width: root.twoColumnWidth + } - property ListModel items: ListModel {} + Label { + text: qsTr("New") + width: root.twoColumnWidth + } + } - enabled: isBaseState - implicitWidth: StudioTheme.Values.colorEditorPopupComboBoxWidth - width: implicitWidth - actionIndicatorVisible: false - textRole: "text" - valueRole: "value" - model: ceMode.items - onActivated: { - switch (ceMode.currentValue) { - case "Solid": - gradientLine.deleteGradient() - hexTextField.text = colorEditor.color - popupHexTextField.text = colorEditor.color - colorEditor.resetShapeColor() - break - case "LinearGradient": - colorEditor.resetShapeColor() + RowLayout { + width: parent.width + Layout.alignment: Qt.AlignTop + spacing: StudioTheme.Values.controlGap - if (colorEditor.shapeGradients) - gradientModel.gradientTypeName = "LinearGradient" - else - gradientModel.gradientTypeName = "Gradient" + Rectangle { + id: originalColorRectangle + color: colorEditor.originalColor + width: root.twoColumnWidth + height: StudioTheme.Values.height + border.width: StudioTheme.Values.border + border.color: StudioTheme.Values.themeControlOutline - if (gradientModel.hasGradient) - gradientLine.updateGradient() - else { - gradientLine.deleteGradient() - gradientLine.addGradient() - } - break - case "RadialGradient": - colorEditor.resetShapeColor() - gradientModel.gradientTypeName = "RadialGradient" - - if (gradientLine.hasGradient) - gradientLine.updateGradient() - else { - gradientLine.deleteGradient() - gradientLine.addGradient() - } - break - case "ConicalGradient": - colorEditor.resetShapeColor() - gradientModel.gradientTypeName = "ConicalGradient" - - if (gradientModel.hasGradient) - gradientLine.updateGradient() - else { - gradientLine.deleteGradient() - gradientLine.addGradient() - } - break - default: - console.log("Unknown item selected in color mode ComboBox.") - } - cePopup.updateThumbnail() + Image { + anchors.fill: parent + source: "images/checkers.png" + fillMode: Image.Tile + z: -1 } ToolTipArea { - enabled: !isBaseState anchors.fill: parent - tooltip: qsTr("Fill type can only be changed in base state.") - z: 10 - } - } + tooltip: originalColorRectangle.color + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: function(mouse) { + if (mouse.button === Qt.LeftButton) + colorEditor.color = colorEditor.originalColor - ExpandingSpacer {} - - IconIndicator { - id: transparentIndicator - icon: StudioTheme.Constants.transparent - pixelSize: StudioTheme.Values.myIconFontSize * 1.4 - tooltip: qsTr("Transparent") - onClicked: { - colorPicker.alpha = 0 - colorPicker.invalidateColor() - colorPicker.updateColor() - } - } - - IconIndicator { - id: gradientPickerIndicator - icon: StudioTheme.Constants.gradient - pixelSize: StudioTheme.Values.myIconFontSize * 1.4 - tooltip: qsTr("Gradient Picker") - enabled: colorEditor.supportGradient - onClicked: presetList.show() - - GradientPresetList { - id: presetList - visible: false - - function applyPreset() { - if (!gradientModel.hasGradient) { - if (colorEditor.shapeGradients) - gradientModel.gradientTypeName = "LinearGradient" - else - gradientModel.gradientTypeName = "Gradient" - } - - if (presetList.gradientData.presetType == 0) { - gradientLine.setPresetByID(presetList.gradientData.presetID) - } else if (presetList.gradientData.presetType == 1) { - gradientLine.setPresetByStops( - presetList.gradientData.stops, - presetList.gradientData.colors, - presetList.gradientData.stopsCount) - } else { - console.log("INVALID GRADIENT TYPE: " + - presetList.gradientData.presetType) - } - } - - onApplied: { - if (presetList.gradientData.stopsCount > 0) - applyPreset() - } - - onSaved: { - gradientLine.savePreset() - presetList.updatePresets() - } - - onAccepted: { // return key - if (presetList.gradientData.stopsCount > 0) - applyPreset() - } - } - } - - IconIndicator { - id: eyeDropperIndicator - icon: StudioTheme.Constants.eyeDropper - pixelSize: StudioTheme.Values.myIconFontSize * 1.4 - tooltip: qsTr("Eye Dropper") - onClicked: ColorPaletteBackend.eyeDropper() - } - - IconIndicator { - id: closeIndicator - icon: StudioTheme.Constants.colorPopupClose - pixelSize: StudioTheme.Values.myIconFontSize * 1.4 - onClicked: cePopup.close() - } - } - - ColorLine { - id: colorLine - width: parent.width - currentColor: colorEditor.color - visible: cePopup.isNotInGradientMode() - } - - GradientLine { - id: gradientLine - - width: parent.width - visible: !cePopup.isNotInGradientMode() - - model: gradientModel - - onCurrentColorChanged: { - if (colorEditor.supportGradient && gradientModel.hasGradient) { - colorEditor.color = gradientLine.currentColor - colorPicker.color = colorEditor.color - } - } - - onHasGradientChanged: { - if (!colorEditor.supportGradient) - return - - cePopup.determineActiveColorMode() - } - - onSelectedNodeChanged: { - if (colorEditor.supportGradient && gradientModel.hasGradient) - colorEditor.originalColor = gradientLine.currentColor - } - - onInvalidated: cePopup.updateThumbnail() - - Connections { - target: modelNodeBackend - function onSelectionToBeChanged() { - colorEditorTimer.stop() - cePopup.isInValidState = false - - var hexOriginalColor = convertColorToString(colorEditor.originalColor) - var hexColor = convertColorToString(colorEditor.color) - - if (hexOriginalColor !== hexColor) { - if (colorEditor.color !== "#ffffff" - && colorEditor.color !== "#000000" - && colorEditor.color !== "#00000000") { - colorPalette.addColorToPalette(colorEditor.color) + if (mouse.button === Qt.RightButton) { + contextMenuFavorite.currentColor = colorEditor.originalColor + contextMenuFavorite.popup() } } } } - Connections { - target: modelNodeBackend - function onSelectionChanged() { - cePopup.initEditor() + + Rectangle { + id: newColorRectangle + color: colorEditor.color + width: root.twoColumnWidth + height: StudioTheme.Values.height + border.width: StudioTheme.Values.border + border.color: StudioTheme.Values.themeControlOutline + + Image { + anchors.fill: parent + source: "images/checkers.png" + fillMode: Image.Tile + z: -1 + } + + ToolTipArea { + anchors.fill: parent + tooltip: newColorRectangle.color + acceptedButtons: Qt.RightButton + onClicked: function(mouse) { + contextMenuFavorite.currentColor = colorEditor.color + contextMenuFavorite.popup() + } } } } - ColorPicker { - id: colorPicker + StudioControls.Menu { + id: contextMenuFavorite - width: parent.width - sliderMargins: 4 + property color currentColor - onUpdateColor: { - colorEditor.color = colorPicker.color - - if (contextMenu.opened) - contextMenu.close() - } - onRightMouseButtonClicked: contextMenu.popup(colorPicker) - - onColorInvalidated: { - hslHueSpinBox.value = colorPicker.hue - hslSaturationSpinBox.value = colorPicker.saturationHSL - hslLightnessSpinBox.value = colorPicker.lightness - hslAlphaSpinBox.value = colorPicker.alpha - - redSpinBox.value = (colorPicker.red * 255) - greenSpinBox.value = (colorPicker.green * 255) - blueSpinBox.value = (colorPicker.blue * 255) - rgbAlphaSpinBox.value = (colorPicker.alpha * 255) - - hsvHueSpinBox.value = colorPicker.hue - hsvSaturationSpinBox.value = colorPicker.saturationHSV - hsvValueSpinBox.value = colorPicker.value - hsvAlphaSpinBox.value = colorPicker.alpha - } - } - - Column { - id: colorCompare - width: parent.width - - RowLayout { - width: parent.width - Layout.alignment: Qt.AlignTop - spacing: StudioTheme.Values.controlGap - - Label { - text: qsTr("Original") - width: 2 * StudioTheme.Values.colorEditorPopupSpinBoxWidth - + StudioTheme.Values.controlGap - } - - Label { - text: qsTr("New") - width: 2 * StudioTheme.Values.colorEditorPopupSpinBoxWidth - + StudioTheme.Values.controlGap - } - } - - RowLayout { - width: parent.width - Layout.alignment: Qt.AlignTop - spacing: StudioTheme.Values.controlGap - - Rectangle { - id: originalColorRectangle - color: colorEditor.originalColor - height: StudioTheme.Values.height - width: 2 * StudioTheme.Values.colorEditorPopupSpinBoxWidth - + StudioTheme.Values.controlGap - border.width: StudioTheme.Values.border - border.color: StudioTheme.Values.themeControlOutline - - Image { - anchors.fill: parent - source: "images/checkers.png" - fillMode: Image.Tile - z: -1 - } - - ToolTipArea { - anchors.fill: parent - tooltip: originalColorRectangle.color - acceptedButtons: Qt.LeftButton | Qt.RightButton - onClicked: function(mouse) { - if (mouse.button === Qt.LeftButton) - colorEditor.color = colorEditor.originalColor - - if (mouse.button === Qt.RightButton) { - contextMenuFavorite.currentColor = colorEditor.originalColor - contextMenuFavorite.popup() - } - } - } - } - - Rectangle { - id: newColorRectangle - color: colorEditor.color - height: StudioTheme.Values.height - width: 2 * StudioTheme.Values.colorEditorPopupSpinBoxWidth - + StudioTheme.Values.controlGap - border.width: StudioTheme.Values.border - border.color: StudioTheme.Values.themeControlOutline - - Image { - anchors.fill: parent - source: "images/checkers.png" - fillMode: Image.Tile - z: -1 - } - - ToolTipArea { - anchors.fill: parent - tooltip: newColorRectangle.color - acceptedButtons: Qt.RightButton - onClicked: function(mouse) { - if (mouse.button === Qt.RightButton) { - contextMenuFavorite.currentColor = colorEditor.color - contextMenuFavorite.popup() - } - } - } - } - } - - StudioControls.Menu { - id: contextMenuFavorite - - property color currentColor - - StudioControls.MenuItem { - text: qsTr("Add to Favorites") - onTriggered: ColorPaletteBackend.addFavoriteColor( - contextMenuFavorite.currentColor) - } + StudioControls.MenuItem { + text: qsTr("Add to Favorites") + onTriggered: ColorPaletteBackend.addFavoriteColor(contextMenuFavorite.currentColor) } } } + } - Column { - id: sectionColumn - anchors.topMargin: StudioTheme.Values.colorEditorPopupPadding - anchors.top: colorColumn.bottom + Column { + id: sectionColumn + width: root.width + + StudioControls.Section { + caption: qsTr("Color Details") anchors.left: parent.left anchors.right: parent.right - bottomPadding: 10 + Column { + spacing: StudioTheme.Values.colorEditorPopupSpacing - Section { - caption: qsTr("Color Details") - anchors.left: parent.left - anchors.right: parent.right + Row { + spacing: StudioTheme.Values.controlGap - leftPadding: 10 - rightPadding: 10 + StudioControls.ComboBox { + id: colorMode + implicitWidth: root.twoColumnWidth + width: colorMode.implicitWidth + actionIndicatorVisible: false + textRole: "text" + valueRole: "value" + model: [ + { value: ColorPicker.Mode.HSVA, text: "HSVA" }, + { value: ColorPicker.Mode.RGBA, text: "RGBA" }, + { value: ColorPicker.Mode.HSLA, text: "HSLA" } + ] - Column { - spacing: 10 - - RowLayout { - Layout.fillWidth: true - spacing: 0 - - LineEdit { - id: popupHexTextField - implicitWidth: 2 * StudioTheme.Values.colorEditorPopupSpinBoxWidth - + StudioTheme.Values.controlGap - width: implicitWidth - writeValueManually: true - validator: RegExpValidator { regExp: /#[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?/g } - showTranslateCheckBox: false - showExtendedFunctionButton: false - backendValue: colorEditor.backendValue - - onAccepted: colorEditor.color = colorFromString(popupHexTextField.text) - onCommitData: { - colorEditor.color = colorFromString(popupHexTextField.text) - if (cePopup.isNotInGradientMode()) { - if (colorEditor.isVector3D) { - backendValue.value = Qt.vector3d(colorEditor.color.r, - colorEditor.color.g, - colorEditor.color.b) - } else { - backendValue.value = colorEditor.color - } - } - } - } - - Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } - - ControlLabel { - text: "Hex" - width: 2 * StudioTheme.Values.colorEditorPopupSpinBoxWidth - + StudioTheme.Values.controlGap - horizontalAlignment: Text.AlignLeft - } + onActivated: colorPicker.mode = colorMode.currentValue } - RowLayout { - Layout.fillWidth: true - spacing: 0 + LineEdit { + id: popupHexTextField + implicitWidth: root.twoColumnWidth + width: popupHexTextField.implicitWidth + writeValueManually: true + validator: RegularExpressionValidator { + regularExpression: /#[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?/g + } + showTranslateCheckBox: false + showExtendedFunctionButton: false + indicatorVisible: true + indicator.icon.text: StudioTheme.Constants.copy_small + indicator.onClicked: { + popupHexTextField.selectAll() + popupHexTextField.copy() + popupHexTextField.deselect() + } + backendValue: colorEditor.backendValue - StudioControls.ComboBox { - id: colorMode - - implicitWidth: 3 * StudioTheme.Values.controlGap - + 4 * StudioTheme.Values.colorEditorPopupSpinBoxWidth - width: implicitWidth - actionIndicatorVisible: false - textRole: "text" - valueRole: "value" - model: [ - { value: ColorPicker.Mode.HSVA, text: "HSVA" }, - { value: ColorPicker.Mode.RGBA, text: "RGBA" }, - { value: ColorPicker.Mode.HSLA, text: "HSLA" } - ] - - onActivated: colorPicker.mode = colorMode.currentValue + onAccepted: colorEditor.color = colorFromString(popupHexTextField.text) + onCommitData: { + colorEditor.color = colorFromString(popupHexTextField.text) + if (root.isSolid()) { + if (colorEditor.isVector3D) { + backendValue.value = Qt.vector3d(colorEditor.color.r, + colorEditor.color.g, + colorEditor.color.b) + } else { + backendValue.value = colorEditor.color + } + } } } + } - RowLayout { - id: rgbaRow - visible: colorPicker.mode === ColorPicker.Mode.RGBA - Layout.fillWidth: true - spacing: StudioTheme.Values.controlGap + Row { + id: rgbaRow + visible: colorPicker.mode === ColorPicker.Mode.RGBA + spacing: StudioTheme.Values.controlGap - DoubleSpinBox { - id: redSpinBox - width: StudioTheme.Values.colorEditorPopupSpinBoxWidth - stepSize: 1 - minimumValue: 0 - maximumValue: 255 - decimals: 0 + DoubleSpinBox { + id: redSpinBox + width: root.fourColumnWidth + stepSize: 1 + minimumValue: 0 + maximumValue: 255 + decimals: 0 - onValueModified: { - var tmp = redSpinBox.value / 255.0 - if (colorPicker.red !== tmp && !colorPicker.block) { - colorPicker.red = tmp - colorPicker.invalidateColor() - colorPicker.updateColor() - } + onValueModified: { + var tmp = redSpinBox.value / 255.0 + if (colorPicker.red !== tmp && !colorPicker.block) { + colorPicker.red = tmp + colorPicker.invalidateColor() + colorPicker.updateColor() } - onDragStarted: colorEditorTimer.stop() - onIndicatorPressed: colorEditorTimer.stop() - } - - DoubleSpinBox { - id: greenSpinBox - width: StudioTheme.Values.colorEditorPopupSpinBoxWidth - stepSize: 1 - minimumValue: 0 - maximumValue: 255 - decimals: 0 - - onValueModified: { - var tmp = greenSpinBox.value / 255.0 - if (colorPicker.green !== tmp && !colorPicker.block) { - colorPicker.green = tmp - colorPicker.invalidateColor() - colorPicker.updateColor() - } - } - onDragStarted: colorEditorTimer.stop() - onIndicatorPressed: colorEditorTimer.stop() - } - - DoubleSpinBox { - id: blueSpinBox - width: StudioTheme.Values.colorEditorPopupSpinBoxWidth - stepSize: 1 - minimumValue: 0 - maximumValue: 255 - decimals: 0 - - onValueModified: { - var tmp = blueSpinBox.value / 255.0 - if (colorPicker.blue !== tmp && !colorPicker.block) { - colorPicker.blue = tmp - colorPicker.invalidateColor() - colorPicker.updateColor() - } - } - onDragStarted: colorEditorTimer.stop() - onIndicatorPressed: colorEditorTimer.stop() - } - - DoubleSpinBox { - id: rgbAlphaSpinBox - width: StudioTheme.Values.colorEditorPopupSpinBoxWidth - stepSize: 1 - minimumValue: 0 - maximumValue: 255 - decimals: 0 - - onValueModified: { - var tmp = rgbAlphaSpinBox.value / 255.0 - if (colorPicker.alpha !== tmp && !colorPicker.block) { - colorPicker.alpha = tmp - colorPicker.invalidateColor() - colorPicker.updateColor() - } - } - onDragStarted: colorEditorTimer.stop() - onIndicatorPressed: colorEditorTimer.stop() } + onDragStarted: colorEditorTimer.stop() + onIndicatorPressed: colorEditorTimer.stop() } - RowLayout { - id: hslaRow - visible: colorPicker.mode === ColorPicker.Mode.HSLA - Layout.fillWidth: true - spacing: StudioTheme.Values.controlGap + DoubleSpinBox { + id: greenSpinBox + width: root.fourColumnWidth + stepSize: 1 + minimumValue: 0 + maximumValue: 255 + decimals: 0 - DoubleSpinBox { - id: hslHueSpinBox - width: StudioTheme.Values.colorEditorPopupSpinBoxWidth - onValueModified: { - if (colorPicker.hue !== hslHueSpinBox.value - && !colorPicker.block) { - colorPicker.hue = hslHueSpinBox.value - colorPicker.invalidateColor() - colorPicker.updateColor() - } - } - onDragStarted: colorEditorTimer.stop() - onIndicatorPressed: colorEditorTimer.stop() - } - - DoubleSpinBox { - id: hslSaturationSpinBox - width: StudioTheme.Values.colorEditorPopupSpinBoxWidth - onValueModified: { - if (colorPicker.saturationHSL !== hslSaturationSpinBox.value - && !colorPicker.block) { - colorPicker.saturationHSL = hslSaturationSpinBox.value - colorPicker.invalidateColor() - colorPicker.updateColor() - } - } - onDragStarted: colorEditorTimer.stop() - onIndicatorPressed: colorEditorTimer.stop() - } - - DoubleSpinBox { - id: hslLightnessSpinBox - width: StudioTheme.Values.colorEditorPopupSpinBoxWidth - onValueModified: { - if (colorPicker.lightness !== hslLightnessSpinBox.value - && !colorPicker.block) { - colorPicker.lightness = hslLightnessSpinBox.value - colorPicker.invalidateColor() - colorPicker.updateColor() - } + onValueModified: { + var tmp = greenSpinBox.value / 255.0 + if (colorPicker.green !== tmp && !colorPicker.block) { + colorPicker.green = tmp + colorPicker.invalidateColor() + colorPicker.updateColor() } } - - DoubleSpinBox { - id: hslAlphaSpinBox - width: StudioTheme.Values.colorEditorPopupSpinBoxWidth - onValueModified: { - if (colorPicker.alpha !== hslAlphaSpinBox.value - && !colorPicker.block) { - colorPicker.alpha = hslAlphaSpinBox.value - colorPicker.invalidateColor() - colorPicker.updateColor() - } - } - onDragStarted: colorEditorTimer.stop() - onIndicatorPressed: colorEditorTimer.stop() - } + onDragStarted: colorEditorTimer.stop() + onIndicatorPressed: colorEditorTimer.stop() } - RowLayout { - id: hsvaRow - visible: colorPicker.mode === ColorPicker.Mode.HSVA - Layout.fillWidth: true - spacing: StudioTheme.Values.controlGap + DoubleSpinBox { + id: blueSpinBox + width: root.fourColumnWidth + stepSize: 1 + minimumValue: 0 + maximumValue: 255 + decimals: 0 - DoubleSpinBox { - id: hsvHueSpinBox - width: StudioTheme.Values.colorEditorPopupSpinBoxWidth - onValueModified: { - if (colorPicker.hue !== hsvHueSpinBox.value - && !colorPicker.block) { - colorPicker.hue = hsvHueSpinBox.value - colorPicker.invalidateColor() - colorPicker.updateColor() - } + onValueModified: { + var tmp = blueSpinBox.value / 255.0 + if (colorPicker.blue !== tmp && !colorPicker.block) { + colorPicker.blue = tmp + colorPicker.invalidateColor() + colorPicker.updateColor() } - onDragStarted: colorEditorTimer.stop() - onIndicatorPressed: colorEditorTimer.stop() } + onDragStarted: colorEditorTimer.stop() + onIndicatorPressed: colorEditorTimer.stop() + } - DoubleSpinBox { - id: hsvSaturationSpinBox - width: StudioTheme.Values.colorEditorPopupSpinBoxWidth - onValueModified: { - if (colorPicker.saturationHSV !== hsvSaturationSpinBox.value - && !colorPicker.block) { - colorPicker.saturationHSV = hsvSaturationSpinBox.value - colorPicker.invalidateColor() - colorPicker.updateColor() - } - } - onDragStarted: colorEditorTimer.stop() - onIndicatorPressed: colorEditorTimer.stop() - } + DoubleSpinBox { + id: rgbAlphaSpinBox + width: root.fourColumnWidth + stepSize: 1 + minimumValue: 0 + maximumValue: 255 + decimals: 0 - DoubleSpinBox { - id: hsvValueSpinBox - width: StudioTheme.Values.colorEditorPopupSpinBoxWidth - onValueModified: { - if (colorPicker.value !== hsvValueSpinBox.value - && !colorPicker.block) { - colorPicker.value = hsvValueSpinBox.value - colorPicker.invalidateColor() - colorPicker.updateColor() - } + onValueModified: { + var tmp = rgbAlphaSpinBox.value / 255.0 + if (colorPicker.alpha !== tmp && !colorPicker.block) { + colorPicker.alpha = tmp + colorPicker.invalidateColor() + colorPicker.updateColor() } - onDragStarted: colorEditorTimer.stop() - onIndicatorPressed: colorEditorTimer.stop() } + onDragStarted: colorEditorTimer.stop() + onIndicatorPressed: colorEditorTimer.stop() + } + } - DoubleSpinBox { - id: hsvAlphaSpinBox - width: StudioTheme.Values.colorEditorPopupSpinBoxWidth - onValueModified: { - if (colorPicker.alpha !== hsvAlphaSpinBox.value - && !colorPicker.block) { - colorPicker.alpha = hsvAlphaSpinBox.value - colorPicker.invalidateColor() - colorPicker.updateColor() - } + Row { + id: hslaRow + visible: colorPicker.mode === ColorPicker.Mode.HSLA + spacing: StudioTheme.Values.controlGap + + DoubleSpinBox { + id: hslHueSpinBox + width: root.fourColumnWidth + onValueModified: { + if (colorPicker.hue !== hslHueSpinBox.value + && !colorPicker.block) { + colorPicker.hue = hslHueSpinBox.value + colorPicker.invalidateColor() + colorPicker.updateColor() } - onDragStarted: colorEditorTimer.stop() - onIndicatorPressed: colorEditorTimer.stop() } + onDragStarted: colorEditorTimer.stop() + onIndicatorPressed: colorEditorTimer.stop() + } + + DoubleSpinBox { + id: hslSaturationSpinBox + width: root.fourColumnWidth + onValueModified: { + if (colorPicker.saturationHSL !== hslSaturationSpinBox.value + && !colorPicker.block) { + colorPicker.saturationHSL = hslSaturationSpinBox.value + colorPicker.invalidateColor() + colorPicker.updateColor() + } + } + onDragStarted: colorEditorTimer.stop() + onIndicatorPressed: colorEditorTimer.stop() + } + + DoubleSpinBox { + id: hslLightnessSpinBox + width: root.fourColumnWidth + onValueModified: { + if (colorPicker.lightness !== hslLightnessSpinBox.value + && !colorPicker.block) { + colorPicker.lightness = hslLightnessSpinBox.value + colorPicker.invalidateColor() + colorPicker.updateColor() + } + } + onDragStarted: colorEditorTimer.stop() + onIndicatorPressed: colorEditorTimer.stop() + } + + DoubleSpinBox { + id: hslAlphaSpinBox + width: root.fourColumnWidth + onValueModified: { + if (colorPicker.alpha !== hslAlphaSpinBox.value + && !colorPicker.block) { + colorPicker.alpha = hslAlphaSpinBox.value + colorPicker.invalidateColor() + colorPicker.updateColor() + } + } + onDragStarted: colorEditorTimer.stop() + onIndicatorPressed: colorEditorTimer.stop() + } + } + + Row { + id: hsvaRow + visible: colorPicker.mode === ColorPicker.Mode.HSVA + spacing: StudioTheme.Values.controlGap + + DoubleSpinBox { + id: hsvHueSpinBox + width: root.fourColumnWidth + onValueModified: { + if (colorPicker.hue !== hsvHueSpinBox.value + && !colorPicker.block) { + colorPicker.hue = hsvHueSpinBox.value + colorPicker.invalidateColor() + colorPicker.updateColor() + } + } + onDragStarted: colorEditorTimer.stop() + onIndicatorPressed: colorEditorTimer.stop() + } + + DoubleSpinBox { + id: hsvSaturationSpinBox + width: root.fourColumnWidth + onValueModified: { + if (colorPicker.saturationHSV !== hsvSaturationSpinBox.value + && !colorPicker.block) { + colorPicker.saturationHSV = hsvSaturationSpinBox.value + colorPicker.invalidateColor() + colorPicker.updateColor() + } + } + onDragStarted: colorEditorTimer.stop() + onIndicatorPressed: colorEditorTimer.stop() + } + + DoubleSpinBox { + id: hsvValueSpinBox + width: root.fourColumnWidth + onValueModified: { + if (colorPicker.value !== hsvValueSpinBox.value + && !colorPicker.block) { + colorPicker.value = hsvValueSpinBox.value + colorPicker.invalidateColor() + colorPicker.updateColor() + } + } + onDragStarted: colorEditorTimer.stop() + onIndicatorPressed: colorEditorTimer.stop() + } + + DoubleSpinBox { + id: hsvAlphaSpinBox + width: root.fourColumnWidth + onValueModified: { + if (colorPicker.alpha !== hsvAlphaSpinBox.value + && !colorPicker.block) { + colorPicker.alpha = hsvAlphaSpinBox.value + colorPicker.invalidateColor() + colorPicker.updateColor() + } + } + onDragStarted: colorEditorTimer.stop() + onIndicatorPressed: colorEditorTimer.stop() } } } - - Section { - caption: qsTr("Palette") - anchors.left: parent.left - anchors.right: parent.right - leftPadding: 10 - rightPadding: 10 - bottomPadding: 5 - - ColorPalette { - id: colorPalette - enableSingletonConnection: cePopup.opened - onSelectedColorChanged: { - colorPicker.color = colorPalette.selectedColor - colorEditor.color = colorPalette.selectedColor - } - onDialogColorChanged: { - colorPicker.color = colorPalette.selectedColor - colorEditor.color = colorPalette.selectedColor - } - } - } - - Section { - id: gradientControls - caption: qsTr("Gradient Controls") - anchors.left: parent.left - anchors.right: parent.right - visible: !cePopup.isNotInGradientMode() - leftPadding: 10 - rightPadding: 10 - - component ControlsRow: RowLayout { - property alias propertyName: spinBox.propertyName - property alias gradientTypeName: spinBox.gradientTypeName - property alias labelText: label.text - property alias labelTooltip: label.tooltip - property alias value: spinBox.value - - Layout.fillWidth: true - spacing: 0 - - Connections { - target: ceMode - function onActivated() { - spinBox.readValue() - } - } - - Connections { - target: modelNodeBackend - function onSelectionChanged() { - spinBox.readValue() - } - } - - ControlLabel { - id: label - horizontalAlignment: Text.AlignLeft - width: StudioTheme.Values.controlGap - + StudioTheme.Values.colorEditorPopupSpinBoxWidth - } - - Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } - - GradientPropertySpinBox { - id: spinBox - implicitWidth: StudioTheme.Values.controlGap - + 2 * StudioTheme.Values.colorEditorPopupSpinBoxWidth - width: implicitWidth - } - } - - // Default Gradient Controls - Column { - id: defaultGradientControls - spacing: 10 - visible: cePopup.hasLinearGradient() && !colorEditor.shapeGradients - - RowLayout { - id: defaultGradientOrientation - Layout.fillWidth: true - spacing: 0 - - StudioControls.ComboBox { - id: gradientOrientation - implicitWidth: StudioTheme.Values.controlGap - + 3 * StudioTheme.Values.colorEditorPopupSpinBoxWidth - width: implicitWidth - model: [{ value: Gradient.Vertical, text: qsTr("Vertical") }, - { value: Gradient.Horizontal, text: qsTr("Horizontal") }] - textRole: "text" - valueRole: "value" - - onActivated: { - gradientLine.model.setGradientOrientation(gradientOrientation.currentValue) - cePopup.updateThumbnail() - } - - Component.onCompleted: { - var orientation = gradientLine.model.readGradientOrientation() - - if (orientation === "Horizontal") - gradientOrientation.currentIndex = - gradientOrientation.indexOfValue(Gradient.Horizontal) - else - gradientOrientation.currentIndex = - gradientOrientation.indexOfValue(Gradient.Vertical) - } - } - - Spacer { implicitWidth: StudioTheme.Values.controlLabelGap + 6 } - - IconLabel { - id: iconLabel - icon: StudioTheme.Constants.orientation - pixelSize: StudioTheme.Values.myIconFontSize * 1.4 - tooltip: qsTr("Defines the direction of the gradient.") - } - } - } - - // Linear Gradient Controls - Column { - id: linearGradientControls - spacing: 10 - visible: cePopup.hasLinearGradient() && colorEditor.shapeGradients - readonly property string gradientTypeName: "LinearGradient" - - ControlsRow { - propertyName: "x1" - gradientTypeName: linearGradientControls.gradientTypeName - labelText: "X1" - labelTooltip: qsTr("Defines the start point for color interpolation.") - } - - ControlsRow { - propertyName: "x2" - gradientTypeName: linearGradientControls.gradientTypeName - labelText: "X2" - labelTooltip: qsTr("Defines the end point for color interpolation.") - } - - ControlsRow { - propertyName: "y1" - gradientTypeName: linearGradientControls.gradientTypeName - labelText: "Y1" - labelTooltip: qsTr("Defines the start point for color interpolation.") - } - - ControlsRow { - propertyName: "y2" - gradientTypeName: linearGradientControls.gradientTypeName - labelText: "Y2" - labelTooltip: qsTr("Defines the end point for color interpolation.") - } - } - - // Radial Gradient Controls - Column { - id: radialGradientControls - spacing: 10 - visible: cePopup.hasRadialGradient() - readonly property string gradientTypeName: "RadialGradient" - - ControlsRow { - propertyName: "centerX" - gradientTypeName: radialGradientControls.gradientTypeName - labelText: "CenterX" - labelTooltip: qsTr("Defines the center point.") - } - - ControlsRow { - propertyName: "centerY" - gradientTypeName: radialGradientControls.gradientTypeName - labelText: "CenterY" - labelTooltip: qsTr("Defines the center point.") - } - - ControlsRow { - propertyName: "focalX" - gradientTypeName: radialGradientControls.gradientTypeName - labelText: "FocalX" - labelTooltip: qsTr("Defines the focal point.") - } - - ControlsRow { - propertyName: "focalY" - gradientTypeName: radialGradientControls.gradientTypeName - labelText: "FocalY" - labelTooltip: qsTr("Defines the focal point.") - } - - ControlsRow { - propertyName: "centerRadius" - gradientTypeName: radialGradientControls.gradientTypeName - labelText: "Center Radius" - labelTooltip: qsTr("Defines the center radius.") - } - - ControlsRow { - propertyName: "focalRadius" - gradientTypeName: radialGradientControls.gradientTypeName - labelText: "Focal Radius" - labelTooltip: qsTr("Defines the focal radius. Set to 0 for simple radial gradients.") - } - } - - // Conical Gradient Controls - Column { - id: conicalGradientControls - spacing: 10 - visible: cePopup.hasConicalGradient() - readonly property string gradientTypeName: "ConicalGradient" - - ControlsRow { - propertyName: "centerX" - gradientTypeName: conicalGradientControls.gradientTypeName - labelText: "CenterX" - labelTooltip: qsTr("Defines the center point.") - } - - ControlsRow { - propertyName: "centerY" - gradientTypeName: conicalGradientControls.gradientTypeName - labelText: "CenterY" - labelTooltip: qsTr("Defines the center point.") - } - - ControlsRow { - propertyName: "angle" - gradientTypeName: conicalGradientControls.gradientTypeName - labelText: "Angle" - labelTooltip: qsTr("Defines the start angle for the conical gradient. The value is in degrees (0-360).") - } - } - } - - } - } //content - background: Rectangle { - color: StudioTheme.Values.themeControlBackground - border.color: StudioTheme.Values.themeInteraction - border.width: StudioTheme.Values.border + StudioControls.Section { + id: sectionPalette + caption: qsTr("Palette") + anchors.left: parent.left + anchors.right: parent.right + bottomPadding: root.isSolid() ? 0 : sectionPalette.style.sectionHeadSpacerHeight + collapsedBottomPadding: root.isSolid() ? 0 : StudioTheme.Values.border + + StudioControls.ColorPalette { + id: colorPalette + width: root.width + twoColumnWidth: root.twoColumnWidth + fourColumnWidth: root.fourColumnWidth + + enableSingletonConnection: root.opened + onSelectedColorChanged: { + colorPicker.color = colorPalette.selectedColor + colorEditor.color = colorPalette.selectedColor + } + onDialogColorChanged: { + colorPicker.color = colorPalette.selectedColor + colorEditor.color = colorPalette.selectedColor + } + } + } + + StudioControls.Section { + id: gradientControls + caption: qsTr("Gradient Controls") + visible: !root.isSolid() + anchors.left: parent.left + anchors.right: parent.right + bottomPadding: 0 + collapsedBottomPadding: 0 + + component ControlsRow: Row { + property alias propertyName: spinBox.propertyName + property alias gradientTypeName: spinBox.gradientTypeName + property alias labelText: label.text + property alias labelTooltip: label.tooltip + property alias value: spinBox.value + + spacing: StudioTheme.Values.controlGap + + Connections { + target: ceMode + function onActivated() { + spinBox.readValue() + } + } + + Connections { + target: modelNodeBackend + function onSelectionChanged() { + spinBox.readValue() + } + } + + ControlLabel { + id: label + anchors.verticalCenter: parent.verticalCenter + horizontalAlignment: Text.AlignRight + width: root.fourColumnWidth + } + + GradientPropertySpinBox { + id: spinBox + spinBoxWidth: root.twoColumnWidth + unitWidth: root.fourColumnWidth + } + } + + // Default Gradient Controls + Column { + id: defaultGradientControls + spacing: StudioTheme.Values.colorEditorPopupSpacing + visible: root.isLinearGradient() && !colorEditor.shapeGradients + + Row { + id: defaultGradientOrientation + spacing: StudioTheme.Values.controlGap + + StudioControls.ComboBox { + id: gradientOrientation + implicitWidth: root.twoColumnWidth + width: gradientOrientation.implicitWidth + model: [{ value: Gradient.Vertical, text: qsTr("Vertical") }, + { value: Gradient.Horizontal, text: qsTr("Horizontal") }] + textRole: "text" + valueRole: "value" + actionIndicatorVisible: false + + onActivated: { + gradientLine.model.setGradientOrientation(gradientOrientation.currentValue) + root.updateThumbnail() + } + + Component.onCompleted: { + var orientation = gradientLine.model.readGradientOrientation() + + if (orientation === "Horizontal") { + gradientOrientation.currentIndex = + gradientOrientation.indexOfValue(Gradient.Horizontal) + } else { + gradientOrientation.currentIndex = + gradientOrientation.indexOfValue(Gradient.Vertical) + } + } + } + + IconLabel { + id: iconLabel + anchors.verticalCenter: parent.verticalCenter + icon: StudioTheme.Constants.orientation + pixelSize: StudioTheme.Values.myIconFontSize * 1.4 + tooltip: qsTr("Defines the direction of the gradient.") + } + } + } + + // Linear Gradient Controls + Column { + id: linearGradientControls + spacing: StudioTheme.Values.colorEditorPopupSpacing + visible: root.isLinearGradient() && colorEditor.shapeGradients + readonly property string gradientTypeName: "LinearGradient" + + ControlsRow { + propertyName: "x1" + gradientTypeName: linearGradientControls.gradientTypeName + labelText: "X1" + labelTooltip: qsTr("Defines the start point for color interpolation.") + } + + ControlsRow { + propertyName: "x2" + gradientTypeName: linearGradientControls.gradientTypeName + labelText: "X2" + labelTooltip: qsTr("Defines the end point for color interpolation.") + } + + ControlsRow { + propertyName: "y1" + gradientTypeName: linearGradientControls.gradientTypeName + labelText: "Y1" + labelTooltip: qsTr("Defines the start point for color interpolation.") + } + + ControlsRow { + propertyName: "y2" + gradientTypeName: linearGradientControls.gradientTypeName + labelText: "Y2" + labelTooltip: qsTr("Defines the end point for color interpolation.") + } + } + + // Radial Gradient Controls + Column { + id: radialGradientControls + spacing: StudioTheme.Values.colorEditorPopupSpacing + visible: root.isRadialGradient() + readonly property string gradientTypeName: "RadialGradient" + + ControlsRow { + propertyName: "centerX" + gradientTypeName: radialGradientControls.gradientTypeName + labelText: "CenterX" + labelTooltip: qsTr("Defines the center point.") + } + + ControlsRow { + propertyName: "centerY" + gradientTypeName: radialGradientControls.gradientTypeName + labelText: "CenterY" + labelTooltip: qsTr("Defines the center point.") + } + + ControlsRow { + propertyName: "focalX" + gradientTypeName: radialGradientControls.gradientTypeName + labelText: "FocalX" + labelTooltip: qsTr("Defines the focal point.") + } + + ControlsRow { + propertyName: "focalY" + gradientTypeName: radialGradientControls.gradientTypeName + labelText: "FocalY" + labelTooltip: qsTr("Defines the focal point.") + } + + ControlsRow { + propertyName: "centerRadius" + gradientTypeName: radialGradientControls.gradientTypeName + labelText: "Center Radius" + labelTooltip: qsTr("Defines the center radius.") + } + + ControlsRow { + propertyName: "focalRadius" + gradientTypeName: radialGradientControls.gradientTypeName + labelText: "Focal Radius" + labelTooltip: qsTr("Defines the focal radius. Set to 0 for simple radial gradients.") + } + } + + // Conical Gradient Controls + Column { + id: conicalGradientControls + spacing: StudioTheme.Values.colorEditorPopupSpacing + visible: root.isConicalGradient() + readonly property string gradientTypeName: "ConicalGradient" + + ControlsRow { + propertyName: "centerX" + gradientTypeName: conicalGradientControls.gradientTypeName + labelText: "CenterX" + labelTooltip: qsTr("Defines the center point.") + } + + ControlsRow { + propertyName: "centerY" + gradientTypeName: conicalGradientControls.gradientTypeName + labelText: "CenterY" + labelTooltip: qsTr("Defines the center point.") + } + + ControlsRow { + propertyName: "angle" + gradientTypeName: conicalGradientControls.gradientTypeName + labelText: "Angle" + labelTooltip: qsTr("Defines the start angle for the conical gradient. The value is in degrees (0-360).") + } + } + } } - - enter: Transition {} - exit: Transition {} } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorLine.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorLine.qml deleted file mode 100644 index 245a655a62b..00000000000 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorLine.qml +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -import QtQuick 2.15 -import HelperWidgets 2.0 -import StudioTheme 1.0 as StudioTheme - -Item { - property alias currentColor: colorLine.color - - width: 300 - height: StudioTheme.Values.colorEditorPopupLineHeight - - Image { - id: checkerboard - anchors.fill: colorLine - source: "images/checkers.png" - fillMode: Image.Tile - } - - Rectangle { - id: colorLine - height: StudioTheme.Values.hueSliderHeight - width: parent.width - border.color: StudioTheme.Values.themeControlOutline - border.width: StudioTheme.Values.border - color: "white" - anchors.bottom: parent.bottom - } -} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/GradientPresetList.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/GradientPresetList.qml index c37787df500..6af54c11b98 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/GradientPresetList.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/GradientPresetList.qml @@ -1,27 +1,26 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -import QtQuick 2.15 -import QtQuick.Layouts 1.15 -import Qt.labs.platform 1.1 -import QtQuickDesignerTheme 1.0 -import HelperWidgets 2.0 -import StudioControls 1.0 as StudioControls -import StudioTheme 1.0 as StudioTheme +import QtQuick +import QtQuick.Layouts +import Qt.labs.platform +import QtQuickDesignerTheme +import HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme - -HelperWindow { - id: dialogWindow +Window { + id: root width: 1200 height: 650 title: qsTr("Gradient Picker") + flags: Qt.Dialog signal saved signal applied signal accepted property alias gradientData: gradientPickerData - QtObject { id: gradientPickerData property var stops @@ -104,9 +103,9 @@ HelperWindow { Layout.alignment: Qt.AlignBottom | Qt.AlignRight Layout.topMargin: 5 - Button { id: buttonClose; text: qsTr("Close"); onClicked: { dialogWindow.hide(); } } - Button { id: buttonSave; text: qsTr("Save"); onClicked: { dialogWindow.saved(); } } - Button { id: buttonApply; text: qsTr("Apply"); onClicked: { dialogWindow.applied(); dialogWindow.hide(); } } + Button { id: buttonClose; text: qsTr("Close"); onClicked: { root.hide(); } } + Button { id: buttonSave; text: qsTr("Save"); onClicked: { root.saved(); } } + Button { id: buttonApply; text: qsTr("Apply"); onClicked: { root.applied(); root.hide(); } } } } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/GradientPropertySpinBox.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/GradientPropertySpinBox.qml index 2656c251177..cdd080be09f 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/GradientPropertySpinBox.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/GradientPropertySpinBox.qml @@ -1,13 +1,12 @@ -// Copyright (C) 2021 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -import QtQuick 2.15 -import QtQuick.Layouts 1.15 -import StudioControls 1.0 as StudioControls -import StudioTheme 1.0 as StudioTheme +import QtQuick +import StudioControls as StudioControls +import StudioTheme as StudioTheme -SecondColumnLayout { - id: wrapper +Row { + id: root property string propertyName property string gradientTypeName @@ -20,24 +19,26 @@ SecondColumnLayout { property alias pixelsPerUnit: spinBox.pixelsPerUnit - width: 90 - implicitHeight: spinBox.height + property real spinBoxWidth: 100 + property real unitWidth: 50 + + spacing: StudioTheme.Values.controlGap onFocusChanged: restoreCursor() property bool __isPercentage: false - property bool __mightHavePercents: gradientLine.model.isPercentageSupportedByProperty(wrapper.propertyName, wrapper.gradientTypeName) + property bool __mightHavePercents: gradientLine.model.isPercentageSupportedByProperty(root.propertyName, root.gradientTypeName) function readValue() { - wrapper.__isPercentage = (gradientLine.model.readGradientPropertyUnits(wrapper.propertyName) === GradientModel.Percentage); + root.__isPercentage = (gradientLine.model.readGradientPropertyUnits(root.propertyName) === GradientModel.Percentage); - if (wrapper.__isPercentage) { + if (root.__isPercentage) { unitType.currentIndex = 1; - spinBox.realValue = gradientLine.model.readGradientPropertyPercentage(wrapper.propertyName) + spinBox.realValue = gradientLine.model.readGradientPropertyPercentage(root.propertyName) } else { unitType.currentIndex = 0; - spinBox.realValue = gradientLine.model.readGradientProperty(wrapper.propertyName) + spinBox.realValue = gradientLine.model.readGradientProperty(root.propertyName) } } @@ -46,21 +47,21 @@ SecondColumnLayout { __devicePixelRatio: devicePixelRatio() - implicitWidth: StudioTheme.Values.colorEditorPopupSpinBoxWidth * 1.5 - width: implicitWidth + implicitWidth: root.spinBoxWidth + width: spinBox.implicitWidth actionIndicatorVisible: false realFrom: -9999 realTo: 9999 - realStepSize: wrapper.__isPercentage ? 0.1 : 1 - decimals: wrapper.__isPercentage ? 4 : 0 + realStepSize: root.__isPercentage ? 0.1 : 1 + decimals: root.__isPercentage ? 4 : 0 - Component.onCompleted: wrapper.readValue() + Component.onCompleted: root.readValue() onCompressedRealValueModified: { - if (wrapper.__isPercentage) - gradientLine.model.setGradientPropertyPercentage(wrapper.propertyName, spinBox.realValue) + if (root.__isPercentage) + gradientLine.model.setGradientPropertyPercentage(root.propertyName, spinBox.realValue) else - gradientLine.model.setGradientProperty(wrapper.propertyName, spinBox.realValue) + gradientLine.model.setGradientProperty(root.propertyName, spinBox.realValue) } onDragStarted: hideCursor() @@ -68,30 +69,24 @@ SecondColumnLayout { onDragging: holdCursorInPlace() } - Spacer { - implicitWidth: StudioTheme.Values.twoControlColumnGap - } - StudioControls.ComboBox { id: unitType - implicitWidth: StudioTheme.Values.colorEditorPopupSpinBoxWidth - width: implicitWidth + implicitWidth: root.unitWidth + width: unitType.implicitWidth model: ["px", "%"] //px = 0, % = 1 actionIndicatorVisible: false - visible: wrapper.__mightHavePercents + visible: root.__mightHavePercents onActivated: { - if (!wrapper.__mightHavePercents) + if (!root.__mightHavePercents) return if (unitType.currentIndex === 0) - gradientLine.model.setGradientPropertyUnits(wrapper.propertyName, GradientModel.Pixels) + gradientLine.model.setGradientPropertyUnits(root.propertyName, GradientModel.Pixels) else - gradientLine.model.setGradientPropertyUnits(wrapper.propertyName, GradientModel.Percentage) + gradientLine.model.setGradientPropertyUnits(root.propertyName, GradientModel.Percentage) - wrapper.readValue() + root.readValue() } } - - ExpandingSpacer {} } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/IconIndicator.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/IconIndicator.qml index a1dd39c39b9..700d4465415 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/IconIndicator.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/IconIndicator.qml @@ -1,74 +1,11 @@ -// Copyright (C) 2021 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -import QtQuick 2.15 -import QtQuick.Templates 2.15 as T -import HelperWidgets 2.0 -import StudioTheme 1.0 as StudioTheme +import QtQuick +import StudioControls as StudioControls -Rectangle { +StudioControls.IconIndicator { id: root - property Item myControl - property alias icon: indicatorIcon.text - property alias iconColor: indicatorIcon.color - property alias pixelSize: indicatorIcon.font.pixelSize - property alias tooltip: toolTipArea.tooltip - - property bool hovered: toolTipArea.containsMouse && root.enabled - - signal clicked() - - color: "transparent" - border.color: "transparent" - - implicitWidth: StudioTheme.Values.linkControlWidth - implicitHeight: StudioTheme.Values.linkControlHeight - - z: 10 - - T.Label { - id: indicatorIcon - anchors.fill: parent - text: "?" - visible: true - color: StudioTheme.Values.themeTextColor - font.family: StudioTheme.Constants.iconFont.family - font.pixelSize: StudioTheme.Values.myIconFontSize - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - } - - ToolTipArea { - id: toolTipArea - anchors.fill: parent - onClicked: root.clicked() - } - - states: [ - State { - name: "default" - when: !toolTipArea.containsMouse && root.enabled - PropertyChanges { - target: indicatorIcon - color: StudioTheme.Values.themeLinkIndicatorColor - } - }, - State { - name: "hover" - when: toolTipArea.containsMouse && root.enabled - PropertyChanges { - target: indicatorIcon - color: StudioTheme.Values.themeLinkIndicatorColorHover - } - }, - State { - name: "disable" - when: !root.enabled - PropertyChanges { - target: indicatorIcon - color: StudioTheme.Values.themeLinkIndicatorColorDisabled - } - } - ] + property alias tooltip: root.toolTip } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ScrollView.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ScrollView.qml index f17d2025aa2..abe62052e86 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ScrollView.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ScrollView.qml @@ -16,6 +16,9 @@ Flickable { readonly property bool bothVisible: flickable.verticalScrollBarVisible && flickable.horizontalScrollBarVisible + property bool hideVerticalScrollBar: false + property bool hideHorizontalScrollBar: false + property real temporaryHeight: 0 default property alias content: areaItem.children @@ -36,6 +39,8 @@ Flickable { width: flickable.availableWidth - (verticalScrollBar.isNeeded ? verticalScrollBar.thickness : 0) orientation: Qt.Horizontal + visible: !flickable.hideHorizontalScrollBar + show: (hoverHandler.hovered || flickable.focus || flickable.adsFocus || horizontalScrollBar.inUse || horizontalScrollBar.otherInUse) && horizontalScrollBar.isNeeded @@ -51,6 +56,8 @@ Flickable { height: flickable.availableHeight - (horizontalScrollBar.isNeeded ? horizontalScrollBar.thickness : 0) orientation: Qt.Vertical + visible: !flickable.hideVerticalScrollBar + show: (hoverHandler.hovered || flickable.focus || flickable.adsFocus || horizontalScrollBar.inUse || horizontalScrollBar.otherInUse) && verticalScrollBar.isNeeded diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ToolTipArea.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ToolTipArea.qml index d54d64007a6..e093c09e0ca 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ToolTipArea.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ToolTipArea.qml @@ -1,26 +1,10 @@ -// Copyright (C) 2021 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick -import QtQuick.Layouts -import HelperWidgets +import StudioControls as StudioControls -MouseArea { - id: mouseArea - - Tooltip { id: myTooltip } - - onExited: myTooltip.hideText() - onCanceled: myTooltip.hideText() - onClicked: forceActiveFocus() - - hoverEnabled: true - - property string tooltip - - Timer { - interval: 1000 - running: mouseArea.containsMouse && tooltip.length - onTriggered: myTooltip.showText(mouseArea, Qt.point(mouseArea.mouseX, mouseArea.mouseY), tooltip) - } +StudioControls.ToolTipArea { + id: root + property alias tooltip: root.text } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir index 9c19a45e2ef..6fc885ac57b 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir @@ -10,10 +10,7 @@ ButtonRowButton 2.0 ButtonRowButton.qml CharacterSection 2.0 CharacterSection.qml CheckBox 2.0 CheckBox.qml ColorEditor 2.0 ColorEditor.qml -ColorLine 2.0 ColorLine.qml ColorLogic 2.0 ColorLogic.qml -ColorPalette 2.0 ColorPalette.qml -ColorPicker 2.0 ColorPicker.qml ComboBox 2.0 ComboBox.qml ComponentButton 2.0 ComponentButton.qml ComponentSection 2.0 ComponentSection.qml @@ -36,7 +33,6 @@ GradientPresetList 2.0 GradientPresetList.qml GradientPresetTabContent 2.0 GradientPresetTabContent.qml GradientPropertySpinBox 2.0 GradientPropertySpinBox.qml HorizontalScrollBar 2.0 HorizontalScrollBar.qml -HueSlider 2.0 HueSlider.qml IconIndicator 2.0 IconIndicator.qml IconButton 2.0 IconButton.qml IconLabel 2.0 IconLabel.qml @@ -48,10 +44,8 @@ Label 2.0 Label.qml LineEdit 2.0 LineEdit.qml LinkIndicator2D 2.0 LinkIndicator2D.qml ListViewComboBox 2.0 ListViewComboBox.qml -LuminanceSlider 2.0 LuminanceSlider.qml MarginSection 2.0 MarginSection.qml MultiIconLabel 2.0 MultiIconLabel.qml -OpacitySlider 2.0 OpacitySlider.qml OriginControl 2.0 OriginControl.qml OriginIndicator 2.0 OriginIndicator.qml OriginSelector 2.0 OriginSelector.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/AbstractButton.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/AbstractButton.qml index 0acf8418f79..79f27ddaf27 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/AbstractButton.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/AbstractButton.qml @@ -12,6 +12,7 @@ T.AbstractButton { property bool globalHover: false property bool hover: control.hovered + property bool press: control.pressed property alias buttonIcon: buttonIcon.text property alias iconColor: buttonIcon.color @@ -67,7 +68,7 @@ T.AbstractButton { states: [ State { name: "default" - when: control.enabled && !control.pressed && !control.checked && !control.hover + when: control.enabled && !control.press && !control.checked && !control.hover PropertyChanges { target: buttonIcon color: control.style.icon.idle @@ -75,7 +76,7 @@ T.AbstractButton { }, State { name: "hover" - when: control.enabled && !control.pressed && !control.checked && control.hover + when: control.enabled && !control.press && !control.checked && control.hover PropertyChanges { target: buttonIcon color: control.style.icon.hover @@ -83,7 +84,7 @@ T.AbstractButton { }, State { name: "press" - when: control.enabled && control.pressed + when: control.enabled && control.press PropertyChanges { target: buttonIcon color: control.style.icon.interaction @@ -91,7 +92,7 @@ T.AbstractButton { }, State { name: "check" - when: control.enabled && !control.pressed && control.checked + when: control.enabled && !control.press && control.checked PropertyChanges { target: buttonIcon color: control.checkedInverted ? control.style.text.selectedText // TODO rather something in icon @@ -114,7 +115,7 @@ T.AbstractButton { State { name: "default" when: control.enabled && !control.globalHover && !control.hover - && !control.pressed && !control.checked + && !control.press && !control.checked PropertyChanges { target: buttonBackground color: control.style.background.idle @@ -127,7 +128,7 @@ T.AbstractButton { }, State { name: "globalHover" - when: control.globalHover && !control.hover && !control.pressed && control.enabled + when: control.globalHover && !control.hover && !control.press && control.enabled PropertyChanges { target: buttonBackground color: control.style.background.idle @@ -136,7 +137,7 @@ T.AbstractButton { }, State { name: "hover" - when: !control.checked && control.hover && !control.pressed && control.enabled + when: !control.checked && control.hover && !control.press && control.enabled PropertyChanges { target: buttonBackground color: control.style.background.hover @@ -149,7 +150,7 @@ T.AbstractButton { }, State { name: "hoverCheck" - when: control.checked && control.hover && !control.pressed && control.enabled + when: control.checked && control.hover && !control.press && control.enabled PropertyChanges { target: buttonBackground color: control.checkedInverted ? control.style.interactionHover @@ -164,7 +165,7 @@ T.AbstractButton { }, State { name: "press" - when: control.hover && control.pressed && control.enabled + when: control.hover && control.press && control.enabled PropertyChanges { target: buttonBackground color: control.style.interaction @@ -177,7 +178,7 @@ T.AbstractButton { }, State { name: "check" - when: control.enabled && !control.pressed && control.checked + when: control.enabled && !control.press && control.checked PropertyChanges { target: buttonBackground color: control.checkedInverted ? control.style.interaction diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ActionIndicator.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ActionIndicator.qml index 1d52ac9244a..96a28d6d5c8 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ActionIndicator.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ActionIndicator.qml @@ -30,11 +30,11 @@ Rectangle { id: icon anchors.fill: parent text: StudioTheme.Constants.actionIcon - visible: text !== StudioTheme.Constants.actionIcon || control.forceVisible - || (control.__parentControl !== undefined && - ((control.__parentControl.edit !== undefined && control.__parentControl.edit) - || (control.__parentControl.hover !== undefined && control.__parentControl.hover) - || (control.__parentControl.drag !== undefined && control.__parentControl.drag))) + visible: icon.text !== StudioTheme.Constants.actionIcon || control.forceVisible + || ((control.__parentControl ?? false) && + ((control.__parentControl.edit ?? false) + || (control.__parentControl.hover ?? false) + || (control.__parentControl.drag ?? false))) color: control.style.icon.idle font.family: StudioTheme.Constants.iconFont.family font.pixelSize: control.style.baseIconFontSize diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ColorEditor.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ColorEditor.qml new file mode 100644 index 00000000000..458cceb31e1 --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ColorEditor.qml @@ -0,0 +1,138 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import StudioTheme as StudioTheme +import QtQuickDesignerTheme +import QtQuickDesignerColorPalette +import StudioHelpers + +Item { + id: root + + property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle + + property alias color: colorBackend.color + + property alias actionIndicatorVisible: actionIndicator.visible + property real __actionIndicatorWidth: root.style.actionIndicatorSize.width + property real __actionIndicatorHeight: root.style.actionIndicatorSize.height + + property alias showHexTextField: hexTextField.visible + + width: root.style.controlSize.width + height: root.style.controlSize.height + + ColorBackend { + id: colorBackend + } + + ActionIndicator { + id: actionIndicator + style: root.style + __parentControl: root + x: 0 + y: 0 + width: actionIndicator.visible ? root.__actionIndicatorWidth : 0 + height: actionIndicator.visible ? root.__actionIndicatorHeight : 0 + } + + Rectangle { + id: preview + x: actionIndicator.width + y: 0 + z: previewMouseArea.containsMouse ? 10 : 0 + implicitWidth: root.style.controlSize.height + implicitHeight: root.style.controlSize.height + color: root.color + border.color: previewMouseArea.containsMouse ? root.style.border.hover : root.style.border.idle + border.width: root.style.borderWidth + + Image { + anchors.fill: parent + source: "qrc:/navigator/icon/checkers.png" + fillMode: Image.Tile + z: -1 + } + + MouseArea { + id: previewMouseArea + anchors.fill: parent + hoverEnabled: true + onClicked: { + popupDialog.visibility ? popupDialog.close() : popupDialog.open() + previewMouseArea.forceActiveFocus() + } + } + + PopupDialog { + id: popupDialog + + property QtObject loaderItem: loader.item + + width: 260 + + function ensureLoader() { + if (!loader.active) + loader.active = true + } + + function open() { + popupDialog.ensureLoader() + popupDialog.show(preview) + + if (loader.status === Loader.Ready) + loader.item.originalColor = root.color + } + + Loader { + id: loader + + sourceComponent: ColorEditorPopup { + id: popup + width: popupDialog.contentWidth + + onActivateColor: function(color) { + colorBackend.activateColor(color) + } + } + + Binding { + target: loader.item + property: "color" + value: root.color + when: loader.status === Loader.Ready + } + + onLoaded: { + loader.item.originalColor = root.color + popupDialog.titleBar = loader.item.titleBarContent + } + } + } + } + + TextField { + id: hexTextField + style: root.style + x: actionIndicator.width + preview.width - preview.border.width + y: 0 + width: root.width - hexTextField.x + text: root.color + actionIndicatorVisible: false + translationIndicatorVisible: false + indicatorVisible: true + indicator.icon.text: StudioTheme.Constants.copy_small + indicator.onClicked: { + hexTextField.selectAll() + hexTextField.copy() + hexTextField.deselect() + } + + validator: RegularExpressionValidator { + regularExpression: /#[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?/g + } + + onAccepted: colorBackend.activateColor(colorFromString(hexTextField.text)) + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/FilterComboBox.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/FilterComboBox.qml index 8531d791348..f17ef05a8f6 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/FilterComboBox.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/FilterComboBox.qml @@ -694,7 +694,7 @@ Item { ScrollBar.vertical: TransientScrollBar { id: popupScrollBar parent: listView - x: listView.width - verticalScrollBar.width + x: listView.width - popupScrollBar.width y: 0 height: listView.availableHeight orientation: Qt.Vertical diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/IconIndicator.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/IconIndicator.qml new file mode 100644 index 00000000000..cc8bf7645e1 --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/IconIndicator.qml @@ -0,0 +1,70 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Templates as T +import StudioTheme as StudioTheme + +Item { + id: root + + property Item myControl + property alias icon: indicatorIcon.text + property alias iconColor: indicatorIcon.color + property alias pixelSize: indicatorIcon.font.pixelSize + property alias toolTip: toolTipArea.text + + property bool hovered: toolTipArea.containsMouse && root.enabled + + signal clicked() + + implicitWidth: StudioTheme.Values.linkControlWidth + implicitHeight: StudioTheme.Values.linkControlHeight + + z: 10 + + T.Label { + id: indicatorIcon + anchors.fill: parent + text: "?" + visible: true + color: StudioTheme.Values.themeTextColor + font.family: StudioTheme.Constants.iconFont.family + font.pixelSize: StudioTheme.Values.myIconFontSize + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + } + + ToolTipArea { + id: toolTipArea + anchors.fill: parent + onClicked: root.clicked() + } + + states: [ + State { + name: "default" + when: !toolTipArea.containsMouse && root.enabled + PropertyChanges { + target: indicatorIcon + color: StudioTheme.Values.themeLinkIndicatorColor + } + }, + State { + name: "hover" + when: toolTipArea.containsMouse && root.enabled + PropertyChanges { + target: indicatorIcon + color: StudioTheme.Values.themeLinkIndicatorColorHover + } + }, + State { + name: "disable" + when: !root.enabled + PropertyChanges { + target: indicatorIcon + color: StudioTheme.Values.themeLinkIndicatorColorDisabled + } + } + ] +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/PopupDialog.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/PopupDialog.qml new file mode 100644 index 00000000000..ae821804a5d --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/PopupDialog.qml @@ -0,0 +1,470 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls.Basic as Basic +import QtQuick.Window +import QtQuick.Shapes +import StudioTheme as StudioTheme +import StudioWindowManager + +QtObject { + id: root + + property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle + + property alias titleBar: titleBarContent.children + default property alias content: mainContent.children + property alias contentWidth: mainContent.width + + property int width: 320 + property int height: column.implicitHeight + property int maximumWidth: -1 + property int maximumHeight: -1 + + property alias flags: window.flags + property alias visible: window.visible + + property int edge: Qt.LeftEdge + property int actualEdge: root.edge + property alias chevronVisible: chevron.visible + + property rect __itemGlobal: Qt.rect(0, 0, 100, 100) + + signal closing(close: var) + + function showGlobal() + { + var pos = WindowManager.globalCursorPosition(); + root.__itemGlobal = Qt.rect(pos.x, pos.y, 300, 20) + root.chevronVisible = false + root.layout() + window.show() + window.raise() + } + + function show(target: Item) { + var originGlobal = target.mapToGlobal(0, 0) + root.__itemGlobal = Qt.rect(originGlobal.x, originGlobal.y, target.width, target.height) + root.chevronVisible = true + root.layout() + window.show() + window.raise() + } + + function close() { + window.close() + } + + function layout() { + // Setup + var screen = Qt.rect(0, //Screen.virtualX, // TODO + 0, //Screen.virtualY, // TODO + Screen.desktopAvailableWidth, + Screen.desktopAvailableHeight) + + // Collect region information + let edges = window.getRegions(screen, root.__itemGlobal) + + if (Object.keys(edges).length === 0) { + console.log("Warning: Couldn't find regions.") + return + } + + // Determine edge + let edge = window.findPlacement(edges) + root.actualEdge = edge + + let anchor = edges[edge].anchor + let popoverRect = window.popoverGeometry(edge, anchor, edges[edge].region) + + if (chevron.visible) + chevron.layout(edge, popoverRect, anchor) + + window.x = popoverRect.x + window.y = popoverRect.y + } + + property Window window: Window { + id: window + + property int margin: 20 + + width: root.width + (2 * window.margin) + height: root.height + (2 * window.margin) + maximumWidth: { + if (root.maximumWidth < 0) + return root.width + (2 * window.margin) + else + return root.maximumWidth + (2 * window.margin) + } + maximumHeight:{ + if (root.maximumHeight < 0) + return root.height + (2 * window.margin) + else + return root.maximumHeight + (2 * window.margin) + } + visible: false + flags: Qt.FramelessWindowHint | Qt.Dialog + color: "transparent" + + onClosing: function (close) { + root.closing(close) + } + + function findPlacement(edges: var): int { + if (edges[root.edge].fit) // Original edge does fit + return root.edge + + return window.findAlternativePlacement(edges) + } + + function findAlternativePlacement(edges: var): int { + let horizontal = (Qt.LeftEdge | Qt.RightEdge) + if (root.edge & horizontal) + if (edges[root.edge ^ horizontal].fit) + return root.edge ^ horizontal + + let vertical = (Qt.TopEdge | Qt.BottomEdge) + if (root.edge & vertical) + if (edges[root.edge ^ vertical].fit) + return root.edge ^ vertical + + // Take the first that fits + for (var key in edges) { + if (edges[key].fit) + return Number(key) + } + + return Qt.LeftEdge // Default + } + + function contains(a: rect, b: rect): boolean { + let halfSizeA = Qt.size(a.width * 0.5, a.height * 0.5) + let halfSizeB = Qt.size(b.width * 0.5, b.height * 0.5) + + let centerA = Qt.point(a.x + halfSizeA.width, a.y + halfSizeA.height) + let centerB = Qt.point(b.x + halfSizeB.width, b.y + halfSizeB.height) + + if (Math.abs(centerA.x - centerB.x) > (halfSizeA.width + halfSizeB.width)) + return false + + if (Math.abs(centerA.y - centerB.y) > (halfSizeA.height + halfSizeB.height)) + return false + + return true + } + + function getRegions(source: rect, target: rect) { + var edges = {} + + // Overlaps or Inside + if (!window.contains(source, target)) + return edges + + var targetCenter = Qt.point(target.x + (target.width * 0.5), + target.y + (target.height * 0.5)) + + // TOP + let topAnchor = Qt.point(targetCenter.x, target.y) + let topRegion = Qt.rect(source.x, source.y, source.width, Math.max(0, topAnchor.y)) + + edges[Qt.TopEdge] = { + anchor: topAnchor, + region: topRegion, + fit: topRegion.width >= window.maximumWidth + && topRegion.height >= window.maximumHeight + } + + // RIGHT + let rightAnchor = Qt.point(target.x + target.width, targetCenter.y) + let rightRegion = Qt.rect(rightAnchor.x, + source.y, + Math.max(0, source.width - rightAnchor.x), + source.height) + + edges[Qt.RightEdge] = { + anchor: rightAnchor, + region: rightRegion, + fit: rightRegion.width >= window.maximumWidth + && rightRegion.height >= window.maximumHeight + } + + // BOTTOM + let bottomAnchor = Qt.point(targetCenter.x, target.y + target.height) + let bottomRegion = Qt.rect(source.x, + bottomAnchor.y, + source.width, + Math.max(0, source.height - bottomAnchor.y)) + + edges[Qt.BottomEdge] = { + anchor: bottomAnchor, + region: bottomRegion, + fit: bottomRegion.width >= window.maximumWidth + && bottomRegion.height >= window.maximumHeight + } + + // LEFT + let leftAnchor = Qt.point(target.x, targetCenter.y) + let leftRegion = Qt.rect(source.x, source.y, Math.max(0, leftAnchor.x), source.height) + + edges[Qt.LeftEdge] = { + anchor: leftAnchor, + region: leftRegion, + fit: leftRegion.width >= window.maximumWidth + && leftRegion.height >= window.maximumHeight + } + + return edges + } + + function popoverGeometry(edge: int, anchor: point, region: rect) { + if (edge === Qt.TopEdge) { + let height = Math.min(window.height, region.height) + return Qt.rect(Math.max(0, Math.min(anchor.x - (window.width * 0.5), region.width - window.width)), + anchor.y - height, + Math.min(window.width, region.width), + height) + } + + if (edge === Qt.RightEdge) { + let width = Math.min(window.width, region.width) + return Qt.rect(anchor.x, + Math.max(0, Math.min(anchor.y - (window.height * 0.5), region.height - window.height)), + width, + Math.min(window.height, region.height)) + } + + if (edge === Qt.BottomEdge) { + let height = Math.min(window.height, region.height) + return Qt.rect(Math.max(0, Math.min(anchor.x - (window.width * 0.5), region.width - window.width)), + anchor.y, + Math.min(window.width, region.width), + height) + } + + if (edge === Qt.LeftEdge) { + let width = Math.min(window.width, region.width) + return Qt.rect(anchor.x - width, + Math.max(0, Math.min(anchor.y - (window.height * 0.5), region.height - window.height)), + width, + Math.min(window.height, region.height)) + } + } + + onHeightChanged: { + if (window.visible) + root.layout() + } + + Component.onCompleted: { + if (window.visible) + root.layout() + } + + Connections { + target: WindowManager + enabled: root.visible + + function onFocusWindowChanged(focusWindow) { + if (!focusWindow) + return + + if (focusWindow !== window && focusWindow.transientParent !== window) + root.close() + } + + function onAboutToQuit() { + root.close() + } + + function onMainWindowVisibleChanged(value) { + if (!value) + root.close() + } + } + + Rectangle { + id: background + anchors.fill: parent + anchors.margins: window.margin + color: StudioTheme.Values.themePopoutBackground + border.color: "#636363" + border.width: StudioTheme.Values.border + + TapHandler { + id: tapHandler + onTapped: root.close() + } + + containmentMask: QtObject { + function contains(point: point): bool { + return point.x < 0 || point.x > background.width + || point.y < 0 || point.y > background.height + } + } + } + + Shape { + id: chevron + + property int chevronWidth: window.margin + + anchors.fill: parent + + function layout(edge: int, rect: rect, anchor: point) { + let center = Qt.point(rect.x + (rect.width * 0.5), + rect.y + (rect.height * 0.5)) + + // Horizontal + if (edge === Qt.LeftEdge || edge === Qt.RightEdge) { + let topLimit = window.margin + let bottomLimit = window.height - window.margin - background.border.width + let point = Math.round((window.height * 0.5) + (anchor.y - center.y)) + + peak.y = Math.max(topLimit, Math.min(bottomLimit, point)) + + let topLimitChevron = topLimit + chevron.chevronWidth + let bottomLimitChevron = bottomLimit - chevron.chevronWidth + + path.startY = Math.max(topLimit, Math.min(bottomLimitChevron, point - chevron.chevronWidth)) + end.y = Math.max(topLimitChevron, Math.min(bottomLimit, point + chevron.chevronWidth)) + } + + if (edge === Qt.LeftEdge) { + peak.x = window.width - background.border.width + path.startX = end.x = window.width - window.margin - background.border.width + } + + if (edge === Qt.RightEdge) { + peak.x = background.border.width + path.startX = end.x = window.margin + background.border.width + } + + // Vertical + if (edge === Qt.TopEdge || edge === Qt.BottomEdge) { + let leftLimit = window.margin + background.border.width + let rightLimit = window.width - window.margin + let point = Math.round((window.width * 0.5) + (anchor.x - center.x)) + + peak.x = Math.max(leftLimit, Math.min(rightLimit, point)) + + let leftLimitChevron = leftLimit + chevron.chevronWidth + let rightLimitChevron = rightLimit - chevron.chevronWidth + + path.startX = Math.max(leftLimit, Math.min(rightLimitChevron, point - chevron.chevronWidth)) + end.x = Math.max(leftLimitChevron, Math.min(rightLimit, point + chevron.chevronWidth)) + } + + if (edge === Qt.TopEdge) { + peak.y = window.height - background.border.width + path.startY = end.y = window.height - window.margin - background.border.width + } + + if (edge === Qt.BottomEdge) { + peak.y = background.border.width + path.startY = end.y = window.margin + background.border.width + } + } + + ShapePath { + id: path + strokeStyle: ShapePath.SolidLine + strokeWidth: background.border.width + strokeColor: background.border.color + fillColor: background.color + startX: 0 + startY: 0 + + PathLine { id: peak; x: 0; y: 0 } + + PathLine { id: end; x: 0; y: 0 } + } + } + + Column { + id: column + anchors.fill: parent + anchors.margins: window.margin + StudioTheme.Values.border + + Item { + id: titleBarItem + width: parent.width + height: StudioTheme.Values.titleBarHeight + + Row { + id: row + anchors.fill: parent + anchors.leftMargin: StudioTheme.Values.popupMargin + anchors.rightMargin: StudioTheme.Values.popupMargin + spacing: 0 + + Item { + id: titleBarContent + width: row.width - closeIndicator.width + height: row.height + } + + IconIndicator { + id: closeIndicator + anchors.verticalCenter: parent.verticalCenter + icon: StudioTheme.Constants.colorPopupClose + pixelSize: StudioTheme.Values.myIconFontSize + onClicked: root.close() + } + } + } + + Rectangle { + width: parent.width - 8 + height: StudioTheme.Values.border + anchors.horizontalCenter: parent.horizontalCenter + color: "#636363" + } + + Basic.ScrollView { + id: scrollView + width: parent.width + height: { + let actualHeight = mainContent.childrenRect.height + 2 * StudioTheme.Values.popupMargin + + if (root.maximumHeight < 0) + return actualHeight + + return Math.min(actualHeight, + root.maximumHeight - titleBarItem.height - 3 * StudioTheme.Values.border) + } + padding: StudioTheme.Values.popupMargin + clip: true + + ScrollBar.vertical: TransientScrollBar { + id: verticalBar + style: StudioTheme.Values.controlStyle + parent: scrollView + x: scrollView.width - verticalBar.width + y: scrollView.topPadding + height: scrollView.availableHeight + orientation: Qt.Vertical + + show: (scrollView.hovered || scrollView.focus) && verticalBar.isNeeded + //otherInUse: horizontalBar.inUse + } + + Flickable { + id: frame + boundsMovement: Flickable.StopAtBounds + boundsBehavior: Flickable.StopAtBounds + contentWidth: mainContent.width + contentHeight: mainContent.height + + Item { + id: mainContent + width: scrollView.width - 2 * scrollView.padding + height: mainContent.childrenRect.height + anchors.horizontalCenter: parent.horizontalCenter + } + } + } + } + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/Section.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/Section.qml index 5b2fc328455..d937055dff2 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/Section.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/Section.qml @@ -3,7 +3,7 @@ import QtQuick import QtQuick.Layouts -import StudioTheme 1.0 as StudioTheme +import StudioTheme as StudioTheme Item { id: control @@ -14,11 +14,13 @@ Item { property alias captionPixelSize: label.font.pixelSize property alias captionColor: header.color property alias captionTextColor: label.color - property int leftPadding: 8 - property int topPadding: 4 + property int leftPadding: 0 property int rightPadding: 0 + property int topPadding: control.style.sectionHeadSpacerHeight + property int bottomPadding: control.style.sectionHeadSpacerHeight property int animationDuration: 120 property bool expanded: true + property int collapsedBottomPadding: StudioTheme.Values.border clip: true @@ -28,16 +30,7 @@ Item { anchors.right: parent.right height: control.style.sectionHeadHeight color: control.style.section.head - - SectionLabel { - id: label - style: control.style - anchors.verticalCenter: parent.verticalCenter - color: control.style.text.idle - x: 22 - font.pixelSize: control.style.baseFontSize - font.capitalization: Font.AllUppercase - } + radius: StudioTheme.Values.smallRadius SectionLabel { id: arrow @@ -52,6 +45,7 @@ Item { anchors.verticalCenter: parent.verticalCenter font.pixelSize: control.style.smallIconFontSize font.family: StudioTheme.Constants.iconFont.family + Behavior on rotation { NumberAnimation { easing.type: Easing.OutCubic @@ -60,6 +54,17 @@ Item { } } + SectionLabel { + id: label + style: control.style + anchors.verticalCenter: parent.verticalCenter + color: control.style.text.idle + x: 22 + width: header.width - label.x + font.pixelSize: control.style.baseFontSize + font.capitalization: Font.AllUppercase + } + MouseArea { anchors.fill: parent onClicked: { @@ -78,22 +83,22 @@ Item { Row { id: topRow - height: control.style.sectionHeadSpacerHeight + height: control.topPadding anchors.top: header.bottom } Column { id: column anchors.left: parent.left - anchors.leftMargin: leftPadding + anchors.leftMargin: control.leftPadding anchors.right: parent.right - anchors.rightMargin: rightPadding + anchors.rightMargin: control.rightPadding anchors.top: topRow.bottom } Row { id: bottomRow - height: control.style.sectionHeadSpacerHeight + height: control.bottomPadding anchors.top: column.bottom } @@ -118,7 +123,7 @@ Item { when: !control.expanded PropertyChanges { target: control - implicitHeight: header.height + implicitHeight: header.height + control.collapsedBottomPadding } PropertyChanges { target: arrow diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ToolTipArea.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ToolTipArea.qml new file mode 100644 index 00000000000..6a7aed8068c --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ToolTipArea.qml @@ -0,0 +1,25 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Layouts + +MouseArea { + id: root + + ToolTipExt { id: toolTip } + + onExited: toolTip.hideText() + onCanceled: toolTip.hideText() + onClicked: root.forceActiveFocus() + + hoverEnabled: true + + property string text + + Timer { + interval: 1000 + running: root.containsMouse && root.text.length + onTriggered: toolTip.showText(root, Qt.point(root.mouseX, root.mouseY), root.text) + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/TopLevelComboBox.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/TopLevelComboBox.qml index ea80abe7ea3..3aa5d709402 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/TopLevelComboBox.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/TopLevelComboBox.qml @@ -3,8 +3,9 @@ import QtQuick import QtQuick.Templates as T -import StudioTheme 1.0 as StudioTheme -import QtQuickDesignerTheme 1.0 +import StudioTheme as StudioTheme +import QtQuickDesignerTheme +import StudioWindowManager T.ComboBox { id: control @@ -110,9 +111,9 @@ T.ComboBox { width: control.listView.width height: control.listView.height + 2 * control.style.borderWidth visible: false - flags: Qt.FramelessWindowHint | Qt.Dialog | Qt.NoDropShadowWindowHint + flags: Qt.FramelessWindowHint | Qt.Dialog | Qt.NoDropShadowWindowHint | Qt.WindowStaysOnTopHint modality: Qt.NonModal - transientParent: null + transientParent: control.Window.window color: "transparent" onActiveFocusItemChanged: { @@ -157,6 +158,10 @@ T.ComboBox { id: itemDelegate onClicked: { + // Necessary to keep the transient parent open otherwise it will change the focus + // to the main window "Utils::AppMainWindowClassWindow" and closes the transient + // parent. + window.transientParent.requestActivate() comboBoxPopup.close() control.currentIndex = index control.activated(index) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/impl/ColorEditorPopup.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/impl/ColorEditorPopup.qml new file mode 100644 index 00000000000..ba37645239f --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/impl/ColorEditorPopup.qml @@ -0,0 +1,560 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Templates as T +import QtQuickDesignerTheme +import StudioControls as StudioControls +import StudioTheme as StudioTheme +import QtQuickDesignerColorPalette + +Column { + id: root + + property color color + property color originalColor + + readonly property real twoColumnWidth: (colorColumn.width - StudioTheme.Values.controlGap) * 0.5 + readonly property real fourColumnWidth: (colorColumn.width - (3 * StudioTheme.Values.controlGap)) * 0.25 + + property Item titleBarContent: Row { + anchors.fill: parent + spacing: 10 + + StudioControls.IconIndicator { + id: transparentIndicator + anchors.verticalCenter: parent.verticalCenter + icon: StudioTheme.Constants.transparent + pixelSize: StudioTheme.Values.myIconFontSize * 1.4 + toolTip: qsTr("Transparent") + + onClicked: { + colorPicker.alpha = 0 + colorPicker.invalidateColor() + colorPicker.updateColor() + } + } + + StudioControls.IconIndicator { + id: eyeDropperIndicator + anchors.verticalCenter: parent.verticalCenter + icon: StudioTheme.Constants.eyeDropper + pixelSize: StudioTheme.Values.myIconFontSize * 1.4 + toolTip: qsTr("Eye Dropper") + onClicked: ColorPaletteBackend.eyeDropper() + } + } + + onColorChanged: { + colorPicker.color = root.color + hexTextField.text = root.color + } + + signal activateColor(var color) + + StudioControls.Menu { + id: contextMenu + + StudioControls.MenuItem { + text: qsTr("Open Color Dialog") + onTriggered: colorPalette.showColorDialog(colorEditor.color) + } + } + + Column { + id: colorColumn + bottomPadding: StudioTheme.Values.popupMargin + width: root.width + spacing: StudioTheme.Values.colorEditorPopupSpacing + + ColorPicker { + id: colorPicker + + width: parent.width + sliderMargins: 4 + + onUpdateColor: { + root.activateColor(colorPicker.color) + + if (contextMenu.opened) + contextMenu.close() + } + onRightMouseButtonClicked: contextMenu.popup(colorPicker) + + onColorInvalidated: { + hslHueSpinBox.realValue = colorPicker.hue + hslSaturationSpinBox.realValue = colorPicker.saturationHSL + hslLightnessSpinBox.realValue = colorPicker.lightness + hslAlphaSpinBox.realValue = colorPicker.alpha + + redSpinBox.realValue = (colorPicker.red * 255) + greenSpinBox.realValue = (colorPicker.green * 255) + blueSpinBox.realValue = (colorPicker.blue * 255) + rgbAlphaSpinBox.realValue = (colorPicker.alpha * 255) + + hsvHueSpinBox.realValue = colorPicker.hue + hsvSaturationSpinBox.realValue = colorPicker.saturationHSV + hsvValueSpinBox.realValue = colorPicker.value + hsvAlphaSpinBox.realValue = colorPicker.alpha + } + } + + Column { + id: colorCompare + width: parent.width + + RowLayout { + width: parent.width + Layout.alignment: Qt.AlignTop + spacing: StudioTheme.Values.controlGap + + Label { + text: qsTr("Original") + width: root.twoColumnWidth + color: StudioTheme.Values.themeTextColor + elide: Text.ElideRight + font.pixelSize: StudioTheme.Values.myFontSize + Layout.preferredWidth: width + Layout.minimumWidth: width + Layout.maximumWidth: width + } + + Label { + text: qsTr("New") + width: root.twoColumnWidth + color: StudioTheme.Values.themeTextColor + elide: Text.ElideRight + font.pixelSize: StudioTheme.Values.myFontSize + Layout.preferredWidth: width + Layout.minimumWidth: width + Layout.maximumWidth: width + } + } + + RowLayout { + width: parent.width + Layout.alignment: Qt.AlignTop + spacing: StudioTheme.Values.controlGap + + Rectangle { + id: originalColorRectangle + color: root.originalColor + width: root.twoColumnWidth + height: StudioTheme.Values.height + border.width: StudioTheme.Values.border + border.color: StudioTheme.Values.themeControlOutline + + Image { + anchors.fill: parent + source: "qrc:/navigator/icon/checkers.png" + fillMode: Image.Tile + z: -1 + } + + StudioControls.ToolTipArea { + anchors.fill: parent + text: originalColorRectangle.color + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: function(mouse) { + if (mouse.button === Qt.LeftButton) + root.activateColor(root.originalColor) + + if (mouse.button === Qt.RightButton) { + contextMenuFavorite.currentColor = root.originalColor + contextMenuFavorite.popup() + } + } + } + } + + Rectangle { + id: newColorRectangle + color: root.color + width: root.twoColumnWidth + height: StudioTheme.Values.height + border.width: StudioTheme.Values.border + border.color: StudioTheme.Values.themeControlOutline + + Image { + anchors.fill: parent + source: "qrc:/navigator/icon/checkers.png" + fillMode: Image.Tile + z: -1 + } + + StudioControls.ToolTipArea { + anchors.fill: parent + text: newColorRectangle.color + acceptedButtons: Qt.RightButton + onClicked: function(mouse) { + if (mouse.button === Qt.RightButton) { + contextMenuFavorite.currentColor = colorEditor.color + contextMenuFavorite.popup() + } + } + } + } + } + + StudioControls.Menu { + id: contextMenuFavorite + + property color currentColor + + StudioControls.MenuItem { + text: qsTr("Add to Favorites") + onTriggered: ColorPaletteBackend.addFavoriteColor(contextMenuFavorite.currentColor) + } + } + } + } + + Column { + id: sectionColumn + width: root.width + + StudioControls.Section { + caption: qsTr("Color Details") + anchors.left: parent.left + anchors.right: parent.right + + Column { + spacing: StudioTheme.Values.colorEditorPopupSpacing + + Row { + spacing: StudioTheme.Values.controlGap + + StudioControls.ComboBox { + id: colorMode + implicitWidth: root.twoColumnWidth + width: colorMode.implicitWidth + actionIndicatorVisible: false + textRole: "text" + valueRole: "value" + model: [ + { value: ColorPicker.Mode.HSVA, text: "HSVA" }, + { value: ColorPicker.Mode.RGBA, text: "RGBA" }, + { value: ColorPicker.Mode.HSLA, text: "HSLA" } + ] + + onActivated: colorPicker.mode = colorMode.currentValue + } + + StudioControls.TextField { + id: hexTextField + implicitWidth: root.twoColumnWidth + width: hexTextField.implicitWidth + actionIndicatorVisible: false + translationIndicatorVisible: false + indicatorVisible: true + indicator.icon.text: StudioTheme.Constants.copy_small + indicator.onClicked: { + hexTextField.selectAll() + hexTextField.copy() + hexTextField.deselect() + } + + validator: RegularExpressionValidator { + regularExpression: /#[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?/g + } + + onEditingFinished: root.activateColor(colorFromString(hexTextField.text)) + } + } + + Row { + id: rgbaRow + visible: colorPicker.mode === ColorPicker.Mode.RGBA + spacing: StudioTheme.Values.controlGap + + StudioControls.RealSpinBox { + id: redSpinBox + width: root.fourColumnWidth + realStepSize: 1 + realFrom: 0 + realTo: 255 + decimals: 0 + actionIndicatorVisible: false + + onRealValueModified: { + var tmp = redSpinBox.realValue / 255.0 + if (colorPicker.red !== tmp && !colorPicker.block) { + colorPicker.red = tmp + colorPicker.invalidateColor() + colorPicker.updateColor() + } + } + //onDragStarted: colorEditorTimer.stop() + //onIndicatorPressed: colorEditorTimer.stop() + } + + StudioControls.RealSpinBox { + id: greenSpinBox + width: root.fourColumnWidth + realStepSize: 1 + realFrom: 0 + realTo: 255 + decimals: 0 + actionIndicatorVisible: false + + onRealValueModified: { + var tmp = greenSpinBox.realValue / 255.0 + if (colorPicker.green !== tmp && !colorPicker.block) { + colorPicker.green = tmp + colorPicker.invalidateColor() + colorPicker.updateColor() + } + } + //onDragStarted: colorEditorTimer.stop() + //onIndicatorPressed: colorEditorTimer.stop() + } + + StudioControls.RealSpinBox { + id: blueSpinBox + width: root.fourColumnWidth + realStepSize: 1 + realFrom: 0 + realTo: 255 + decimals: 0 + actionIndicatorVisible: false + + onRealValueModified: { + var tmp = blueSpinBox.realValue / 255.0 + if (colorPicker.blue !== tmp && !colorPicker.block) { + colorPicker.blue = tmp + colorPicker.invalidateColor() + colorPicker.updateColor() + } + } + //onDragStarted: colorEditorTimer.stop() + //onIndicatorPressed: colorEditorTimer.stop() + } + + StudioControls.RealSpinBox { + id: rgbAlphaSpinBox + width: root.fourColumnWidth + realStepSize: 1 + realFrom: 0 + realTo: 255 + decimals: 0 + actionIndicatorVisible: false + + onRealValueModified: { + var tmp = rgbAlphaSpinBox.realValue / 255.0 + if (colorPicker.alpha !== tmp && !colorPicker.block) { + colorPicker.alpha = tmp + colorPicker.invalidateColor() + colorPicker.updateColor() + } + } + //onDragStarted: colorEditorTimer.stop() + //onIndicatorPressed: colorEditorTimer.stop() + } + } + + Row { + id: hslaRow + visible: colorPicker.mode === ColorPicker.Mode.HSLA + spacing: StudioTheme.Values.controlGap + + StudioControls.RealSpinBox { + id: hslHueSpinBox + width: root.fourColumnWidth + realFrom: 0.0 + realTo: 1.0 + realStepSize: 0.1 + decimals: 2 + actionIndicatorVisible: false + + onRealValueModified: { + if (colorPicker.hue !== hslHueSpinBox.realValue + && !colorPicker.block) { + colorPicker.hue = hslHueSpinBox.realValue + colorPicker.invalidateColor() + colorPicker.updateColor() + } + } + //onDragStarted: colorEditorTimer.stop() + //onIndicatorPressed: colorEditorTimer.stop() + } + + StudioControls.RealSpinBox { + id: hslSaturationSpinBox + width: root.fourColumnWidth + realFrom: 0.0 + realTo: 1.0 + realStepSize: 0.1 + decimals: 2 + actionIndicatorVisible: false + + onRealValueModified: { + if (colorPicker.saturationHSL !== hslSaturationSpinBox.realValue + && !colorPicker.block) { + colorPicker.saturationHSL = hslSaturationSpinBox.realValue + colorPicker.invalidateColor() + colorPicker.updateColor() + } + } + //onDragStarted: colorEditorTimer.stop() + //onIndicatorPressed: colorEditorTimer.stop() + } + + StudioControls.RealSpinBox { + id: hslLightnessSpinBox + width: root.fourColumnWidth + realFrom: 0.0 + realTo: 1.0 + realStepSize: 0.1 + decimals: 2 + actionIndicatorVisible: false + + onValueModified: { + if (colorPicker.lightness !== hslLightnessSpinBox.realValue + && !colorPicker.block) { + colorPicker.lightness = hslLightnessSpinBox.realValue + colorPicker.invalidateColor() + colorPicker.updateColor() + } + } + //onDragStarted: colorEditorTimer.stop() + //onIndicatorPressed: colorEditorTimer.stop() + } + + StudioControls.RealSpinBox { + id: hslAlphaSpinBox + width: root.fourColumnWidth + realFrom: 0.0 + realTo: 1.0 + realStepSize: 0.1 + decimals: 2 + actionIndicatorVisible: false + + onRealValueModified: { + if (colorPicker.alpha !== hslAlphaSpinBox.realValue + && !colorPicker.block) { + colorPicker.alpha = hslAlphaSpinBox.realValue + colorPicker.invalidateColor() + colorPicker.updateColor() + } + } + //onDragStarted: colorEditorTimer.stop() + //onIndicatorPressed: colorEditorTimer.stop() + } + } + + Row { + id: hsvaRow + visible: colorPicker.mode === ColorPicker.Mode.HSVA + spacing: StudioTheme.Values.controlGap + + StudioControls.RealSpinBox { + id: hsvHueSpinBox + width: root.fourColumnWidth + realFrom: 0.0 + realTo: 1.0 + realStepSize: 0.1 + decimals: 2 + actionIndicatorVisible: false + + onRealValueModified: { + if (colorPicker.hue !== hsvHueSpinBox.realValue + && !colorPicker.block) { + colorPicker.hue = hsvHueSpinBox.realValue + colorPicker.invalidateColor() + colorPicker.updateColor() + } + } + //onDragStarted: colorEditorTimer.stop() + //onIndicatorPressed: colorEditorTimer.stop() + } + + StudioControls.RealSpinBox { + id: hsvSaturationSpinBox + width: root.fourColumnWidth + realFrom: 0.0 + realTo: 1.0 + realStepSize: 0.1 + decimals: 2 + actionIndicatorVisible: false + + onRealValueModified: { + if (colorPicker.saturationHSV !== hsvSaturationSpinBox.realValue + && !colorPicker.block) { + colorPicker.saturationHSV = hsvSaturationSpinBox.realValue + colorPicker.invalidateColor() + colorPicker.updateColor() + } + } + //onDragStarted: colorEditorTimer.stop() + //onIndicatorPressed: colorEditorTimer.stop() + } + + StudioControls.RealSpinBox { + id: hsvValueSpinBox + width: root.fourColumnWidth + realFrom: 0.0 + realTo: 1.0 + realStepSize: 0.1 + decimals: 2 + actionIndicatorVisible: false + + onRealValueModified: { + if (colorPicker.value !== hsvValueSpinBox.realValue + && !colorPicker.block) { + colorPicker.value = hsvValueSpinBox.realValue + colorPicker.invalidateColor() + colorPicker.updateColor() + } + } + //onDragStarted: colorEditorTimer.stop() + //onIndicatorPressed: colorEditorTimer.stop() + } + + StudioControls.RealSpinBox { + id: hsvAlphaSpinBox + width: root.fourColumnWidth + realFrom: 0.0 + realTo: 1.0 + realStepSize: 0.1 + decimals: 2 + actionIndicatorVisible: false + + onRealValueModified: { + if (colorPicker.alpha !== hsvAlphaSpinBox.realValue + && !colorPicker.block) { + colorPicker.alpha = hsvAlphaSpinBox.realValue + colorPicker.invalidateColor() + colorPicker.updateColor() + } + } + //onDragStarted: colorEditorTimer.stop() + //onIndicatorPressed: colorEditorTimer.stop() + } + } + } + } + + StudioControls.Section { + caption: qsTr("Palette") + anchors.left: parent.left + anchors.right: parent.right + bottomPadding: 0 + collapsedBottomPadding: 0 + + StudioControls.ColorPalette { + id: colorPalette + + width: root.width + + twoColumnWidth: root.twoColumnWidth + fourColumnWidth: root.fourColumnWidth + + enableSingletonConnection: root.visible + onSelectedColorChanged: root.activateColor(colorPalette.selectedColor) + onDialogColorChanged: root.activateColor(colorPalette.selectedColor) + } + } + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorPalette.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/impl/ColorPalette.qml similarity index 61% rename from share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorPalette.qml rename to share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/impl/ColorPalette.qml index c79a399691f..976573ba5be 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorPalette.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/impl/ColorPalette.qml @@ -1,12 +1,12 @@ -// Copyright (C) 2021 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -import QtQuick 2.15 -import QtQuick.Layouts 1.15 -import HelperWidgets 2.0 -import StudioControls 1.0 as StudioControls -import StudioTheme 1.0 as StudioTheme -import QtQuickDesignerColorPalette 1.0 +import QtQuick +import QtQuick.Layouts +import HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme +import QtQuickDesignerColorPalette Column { id: root @@ -16,7 +16,10 @@ Column { property alias enableSingletonConnection: singletonConnection.enabled - spacing: 10 + property real twoColumnWidth: 50 + property real fourColumnWidth: 25 + + spacing: StudioTheme.Values.colorEditorPopupSpacing function addColorToPalette(colorStr) { ColorPaletteBackend.addRecentColor(colorStr) @@ -35,7 +38,7 @@ Column { Rectangle { id: colorRectangle - width: StudioTheme.Values.colorEditorPopupSpinBoxWidth + width: root.fourColumnWidth height: StudioTheme.Values.defaultControlHeight color: (modelData !== "") ? modelData : "transparent" border.color: (modelData !== "") ? StudioTheme.Values.themeControlOutline @@ -45,7 +48,7 @@ Column { Image { visible: modelData !== "" anchors.fill: parent - source: "images/checkers.png" + source: "qrc:/navigator/icon/checkers.png" fillMode: Image.Tile z: -1 } @@ -90,48 +93,36 @@ Column { function onCurrentColorChanged(color) { root.selectedColor = color - dialogColorChanged() + root.dialogColorChanged() } function onColorDialogRejected() { root.selectedColor = root.oldColor - dialogColorChanged() + root.dialogColorChanged() } } - RowLayout { - Layout.fillWidth: true - spacing: 0 + StudioControls.ComboBox { + id: colorMode + implicitWidth: root.width + width: colorMode.implicitWidth + actionIndicatorVisible: false + model: ColorPaletteBackend.palettes + currentIndex: colorMode.find(ColorPaletteBackend.currentPalette) - StudioControls.ComboBox { - id: colorMode + onActivated: ColorPaletteBackend.currentPalette = colorMode.currentText - implicitWidth: 3 * StudioTheme.Values.controlGap - + 4 * StudioTheme.Values.colorEditorPopupSpinBoxWidth - width: implicitWidth - actionIndicatorVisible: false - model: ColorPaletteBackend.palettes - currentIndex: colorMode.find(ColorPaletteBackend.currentPalette) - - onActivated: ColorPaletteBackend.currentPalette = colorMode.currentText - - Component.onCompleted: colorMode.currentIndex = colorMode.find(ColorPaletteBackend.currentPalette) - } + Component.onCompleted: colorMode.currentIndex = colorMode.find(ColorPaletteBackend.currentPalette) } - GridView { + Grid { id: colorPaletteView - model: ColorPaletteBackend.currentPaletteColors - delegate: colorItemDelegate - cellWidth: StudioTheme.Values.colorEditorPopupSpinBoxWidth - + StudioTheme.Values.controlGap - cellHeight: StudioTheme.Values.defaultControlHeight - + StudioTheme.Values.controlGap - width: 4 * (StudioTheme.Values.colorEditorPopupSpinBoxWidth - + StudioTheme.Values.controlGap) - height: 2 * (StudioTheme.Values.defaultControlHeight - + StudioTheme.Values.controlGap) - clip: true - interactive: false + columns: 4 + spacing: StudioTheme.Values.controlGap + + Repeater { + model: ColorPaletteBackend.currentPaletteColors + delegate: colorItemDelegate + } } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorPicker.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/impl/ColorPicker.qml similarity index 99% rename from share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorPicker.qml rename to share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/impl/ColorPicker.qml index 4e0101a2e4f..cd525114268 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorPicker.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/impl/ColorPicker.qml @@ -192,7 +192,7 @@ Column { width: parent.width - 2 * StudioTheme.Values.border height: width - source: "images/checkers.png" + source: "qrc:/navigator/icon/checkers.png" fillMode: Image.Tile // Note: We smoothscale the shader from a smaller version to improve performance @@ -234,7 +234,7 @@ Column { id: pickerCross property color strokeStyle: "lightGray" - property string loadImageUrl: "images/checkers.png" + property string loadImageUrl: "qrc:/navigator/icon/checkers.png" property int radius: 10 Component.onCompleted: pickerCross.loadImage(pickerCross.loadImageUrl) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/HueSlider.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/impl/HueSlider.qml similarity index 100% rename from share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/HueSlider.qml rename to share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/impl/HueSlider.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/LuminanceSlider.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/impl/LuminanceSlider.qml similarity index 100% rename from share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/LuminanceSlider.qml rename to share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/impl/LuminanceSlider.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/OpacitySlider.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/impl/OpacitySlider.qml similarity index 96% rename from share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/OpacitySlider.qml rename to share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/impl/OpacitySlider.qml index b3b8ff2daab..aaed4ef98ce 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/OpacitySlider.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/impl/OpacitySlider.qml @@ -35,7 +35,7 @@ Item { Image { anchors.fill: parent - source: "images/checkers.png" + source: "qrc:/navigator/icon/checkers.png" fillMode: Image.Tile } @@ -66,7 +66,7 @@ Item { Image { anchors.fill: handleInside - source: "images/checkers.png" + source: "qrc:/navigator/icon/checkers.png" fillMode: Image.Tile } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/qmldir b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/qmldir index 5ce433812c2..5376aeb0df2 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/qmldir +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/qmldir @@ -5,6 +5,10 @@ ButtonGroup 1.0 ButtonGroup.qml ButtonRow 1.0 ButtonRow.qml CheckBox 1.0 CheckBox.qml CheckIndicator 1.0 CheckIndicator.qml +ColorEditor 1.0 ColorEditor.qml +ColorEditorPopup 1.0 impl/ColorEditorPopup.qml +ColorPalette 1.0 impl/ColorPalette.qml +ColorPicker 1.0 impl/ColorPicker.qml ComboBox 1.0 ComboBox.qml ComboBoxInput 1.0 ComboBoxInput.qml ContextMenu 1.0 ContextMenu.qml @@ -12,16 +16,21 @@ Dialog 1.0 Dialog.qml DialogButton 1.0 DialogButton.qml DialogButtonBox 1.0 DialogButtonBox.qml FilterComboBox 1.0 FilterComboBox.qml +HueSlider 1.0 impl/HueSlider.qml +IconIndicator 1.0 IconIndicator.qml Indicator 1.0 Indicator.qml InfinityLoopIndicator 1.0 InfinityLoopIndicator.qml ItemDelegate 1.0 ItemDelegate.qml LinkIndicator2D 1.0 LinkIndicator2D.qml LinkIndicator3D 1.0 LinkIndicator3D.qml LinkIndicator3DComponent 1.0 LinkIndicator3DComponent.qml +LuminanceSlider 1.0 impl/LuminanceSlider.qml Menu 1.0 Menu.qml MenuItem 1.0 MenuItem.qml MenuItemWithIcon 1.0 MenuItemWithIcon.qml MenuSeparator 1.0 MenuSeparator.qml +OpacitySlider 1.0 impl/OpacitySlider.qml +PopupDialog 1.0 PopupDialog.qml ProgressBar 1.0 ProgressBar.qml RadioButton 1.0 RadioButton.qml RealSliderPopup 1.0 RealSliderPopup.qml @@ -47,6 +56,7 @@ TabButton 1.0 TabButton.qml TextArea 1.0 TextArea.qml TextField 1.0 TextField.qml ToolTip 1.0 ToolTip.qml +ToolTipArea 1.0 ToolTipArea.qml TransientScrollBar 1.0 TransientScrollBar.qml TranslationIndicator 1.0 TranslationIndicator.qml TopLevelComboBox 1.0 TopLevelComboBox.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml index 168f3c37908..1f7351a8a00 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml @@ -18,353 +18,361 @@ QtObject { readonly property string addColumnAfter: "\u0023" readonly property string addColumnBefore: "\u0024" readonly property string addFile: "\u0025" - readonly property string addRowAfter: "\u0026" - readonly property string addRowBefore: "\u0027" - readonly property string addTable: "\u0028" - readonly property string add_medium: "\u0029" - readonly property string add_small: "\u002A" - readonly property string addcolumnleft_medium: "\u002B" - readonly property string addcolumnright_medium: "\u002C" - readonly property string addrowabove_medium: "\u002D" - readonly property string addrowbelow_medium: "\u002E" - readonly property string adsClose: "\u002F" - readonly property string adsDetach: "\u0030" - readonly property string adsDropDown: "\u0031" - readonly property string alias: "\u0032" - readonly property string aliasAnimated: "\u0033" - readonly property string alignBottom: "\u0034" - readonly property string alignCenterHorizontal: "\u0035" - readonly property string alignCenterVertical: "\u0036" - readonly property string alignLeft: "\u0037" - readonly property string alignRight: "\u0038" - readonly property string alignTo: "\u0039" - readonly property string alignToCam_medium: "\u003A" - readonly property string alignToCamera_small: "\u003B" - readonly property string alignToObject_small: "\u003C" - readonly property string alignToView_medium: "\u003D" - readonly property string alignTop: "\u003E" - readonly property string anchorBaseline: "\u003F" - readonly property string anchorBottom: "\u0040" - readonly property string anchorFill: "\u0041" - readonly property string anchorLeft: "\u0042" - readonly property string anchorRight: "\u0043" - readonly property string anchorTop: "\u0044" - readonly property string anchors_small: "\u0045" - readonly property string animatedProperty: "\u0046" - readonly property string annotationBubble: "\u0047" - readonly property string annotationDecal: "\u0048" - readonly property string annotations_large: "\u0049" - readonly property string annotations_small: "\u004A" - readonly property string applyMaterialToSelected: "\u004B" - readonly property string apply_medium: "\u004C" - readonly property string apply_small: "\u004D" - readonly property string arrange_small: "\u004E" - readonly property string arrow_small: "\u004F" - readonly property string assign: "\u0050" - readonly property string attach_medium: "\u0051" - readonly property string back_medium: "\u0052" - readonly property string backspace_small: "\u0053" - readonly property string bevelAll: "\u0054" - readonly property string bevelCorner: "\u0055" - readonly property string bezier_medium: "\u0056" - readonly property string binding_medium: "\u0057" - readonly property string bounds_small: "\u0058" - readonly property string branch_medium: "\u0059" - readonly property string camera_small: "\u005A" - readonly property string centerHorizontal: "\u005B" - readonly property string centerVertical: "\u005C" - readonly property string cleanLogs_medium: "\u005D" - readonly property string closeCross: "\u005E" - readonly property string closeFile_large: "\u005F" - readonly property string closeLink: "\u0060" - readonly property string close_small: "\u0061" - readonly property string code: "\u0062" - readonly property string codeEditor_medium: "\u0063" - readonly property string codeview_medium: "\u0064" - readonly property string colorPopupClose: "\u0065" - readonly property string colorSelection_medium: "\u0066" - readonly property string columnsAndRows: "\u0067" - readonly property string cone_medium: "\u0068" - readonly property string cone_small: "\u0069" - readonly property string connection_small: "\u006A" - readonly property string connections_medium: "\u006B" - readonly property string copyLink: "\u006C" - readonly property string copyStyle: "\u006D" - readonly property string copy_small: "\u006E" - readonly property string cornerA: "\u006F" - readonly property string cornerB: "\u0070" - readonly property string cornersAll: "\u0071" - readonly property string createComponent_large: "\u0072" - readonly property string createComponent_small: "\u0073" - readonly property string create_medium: "\u0074" - readonly property string create_small: "\u0075" - readonly property string cube_medium: "\u0076" - readonly property string cube_small: "\u0077" - readonly property string curveDesigner: "\u0078" - readonly property string curveDesigner_medium: "\u0079" - readonly property string curveEditor: "\u007A" - readonly property string customMaterialEditor: "\u007B" - readonly property string cylinder_medium: "\u007C" - readonly property string cylinder_small: "\u007D" - readonly property string decisionNode: "\u007E" - readonly property string deleteColumn: "\u007F" - readonly property string deleteMaterial: "\u0080" - readonly property string deleteRow: "\u0081" - readonly property string deleteTable: "\u0082" - readonly property string delete_medium: "\u0083" - readonly property string delete_small: "\u0084" - readonly property string deletecolumn_medium: "\u0085" - readonly property string deleterow_medium: "\u0086" - readonly property string designMode_large: "\u0087" - readonly property string detach: "\u0088" - readonly property string directionalLight_small: "\u0089" - readonly property string distributeBottom: "\u008A" - readonly property string distributeCenterHorizontal: "\u008B" - readonly property string distributeCenterVertical: "\u008C" - readonly property string distributeLeft: "\u008D" - readonly property string distributeOriginBottomRight: "\u008E" - readonly property string distributeOriginCenter: "\u008F" - readonly property string distributeOriginNone: "\u0090" - readonly property string distributeOriginTopLeft: "\u0091" - readonly property string distributeRight: "\u0092" - readonly property string distributeSpacingHorizontal: "\u0093" - readonly property string distributeSpacingVertical: "\u0094" - readonly property string distributeTop: "\u0095" - readonly property string download: "\u0096" - readonly property string downloadUnavailable: "\u0097" - readonly property string downloadUpdate: "\u0098" - readonly property string downloadcsv_large: "\u0099" - readonly property string downloadcsv_medium: "\u009A" - readonly property string downloaded: "\u009B" - readonly property string downloadjson_large: "\u009D" - readonly property string downloadjson_medium: "\u009E" - readonly property string dragmarks: "\u009F" - readonly property string duplicate_small: "\u00A0" - readonly property string edit: "\u00A1" - readonly property string editComponent_large: "\u00A2" - readonly property string editComponent_small: "\u00A3" - readonly property string editLightOff_medium: "\u00A4" - readonly property string editLightOn_medium: "\u00A5" - readonly property string edit_medium: "\u00A6" - readonly property string edit_small: "\u00A7" - readonly property string effects: "\u00A8" - readonly property string events_small: "\u00A9" - readonly property string export_medium: "\u00AA" - readonly property string eyeDropper: "\u00AB" - readonly property string favorite: "\u00AC" - readonly property string fitAll_medium: "\u00AE" - readonly property string fitSelected_small: "\u00AF" - readonly property string fitSelection_medium: "\u00B0" - readonly property string fitToView_medium: "\u00B1" - readonly property string flowAction: "\u00B2" - readonly property string flowTransition: "\u00B3" - readonly property string fontStyleBold: "\u00B4" - readonly property string fontStyleItalic: "\u00B5" - readonly property string fontStyleStrikethrough: "\u00B6" - readonly property string fontStyleUnderline: "\u00B7" - readonly property string forward_medium: "\u00B8" - readonly property string globalOrient_medium: "\u00B9" - readonly property string gradient: "\u00BA" - readonly property string gridView: "\u00BB" - readonly property string grid_medium: "\u00BC" - readonly property string group_small: "\u00BD" - readonly property string help: "\u00BE" - readonly property string home_large: "\u00BF" - readonly property string idAliasOff: "\u00C0" - readonly property string idAliasOn: "\u00C1" - readonly property string import_medium: "\u00C2" - readonly property string imported: "\u00C3" - readonly property string importedModels_small: "\u00C4" - readonly property string infinity: "\u00C5" - readonly property string invisible_medium: "\u00C6" - readonly property string invisible_small: "\u00C7" - readonly property string keyframe: "\u00C8" - readonly property string languageList_medium: "\u00C9" - readonly property string layouts_small: "\u00CA" - readonly property string lights_small: "\u00CB" - readonly property string linear_medium: "\u00CC" - readonly property string linkTriangle: "\u00CD" - readonly property string linked: "\u00CE" - readonly property string listView: "\u00CF" - readonly property string list_medium: "\u00D0" - readonly property string localOrient_medium: "\u00D1" - readonly property string lockOff: "\u00D2" - readonly property string lockOn: "\u00D3" - readonly property string loopPlayback_medium: "\u00D4" - readonly property string materialBrowser_medium: "\u00D5" - readonly property string materialPreviewEnvironment: "\u00D6" - readonly property string materialPreviewModel: "\u00D7" - readonly property string material_medium: "\u00D8" - readonly property string maxBar_small: "\u00D9" - readonly property string mergeCells: "\u00DA" - readonly property string merge_small: "\u00DB" - readonly property string minus: "\u00DC" - readonly property string mirror: "\u00DD" - readonly property string more_medium: "\u00DE" - readonly property string mouseArea_small: "\u00DF" - readonly property string moveDown_medium: "\u00E0" - readonly property string moveInwards_medium: "\u00E1" - readonly property string moveUp_medium: "\u00E2" - readonly property string moveUpwards_medium: "\u00E3" - readonly property string move_medium: "\u00E4" - readonly property string newMaterial: "\u00E5" - readonly property string nextFile_large: "\u00E6" - readonly property string normalBar_small: "\u00E7" - readonly property string openLink: "\u00E8" - readonly property string openMaterialBrowser: "\u00E9" - readonly property string orientation: "\u00EA" - readonly property string orthCam_medium: "\u00EB" - readonly property string orthCam_small: "\u00EC" - readonly property string paddingEdge: "\u00ED" - readonly property string paddingFrame: "\u00EE" - readonly property string particleAnimation_medium: "\u00EF" - readonly property string pasteStyle: "\u00F0" - readonly property string paste_small: "\u00F1" - readonly property string pause: "\u00F2" - readonly property string perspectiveCam_medium: "\u00F3" - readonly property string perspectiveCam_small: "\u00F4" - readonly property string pin: "\u00F5" - readonly property string plane_medium: "\u00F6" - readonly property string plane_small: "\u00F7" - readonly property string play: "\u00F8" - readonly property string playFill_medium: "\u00F9" - readonly property string playOutline_medium: "\u00FA" - readonly property string plus: "\u00FB" - readonly property string pointLight_small: "\u00FC" - readonly property string positioners_small: "\u00FD" - readonly property string previewEnv_medium: "\u00FE" - readonly property string previousFile_large: "\u00FF" - readonly property string promote: "\u0100" - readonly property string properties_medium: "\u0101" - readonly property string readOnly: "\u0102" - readonly property string recordFill_medium: "\u0103" - readonly property string recordOutline_medium: "\u0104" - readonly property string redo: "\u0105" - readonly property string reload_medium: "\u0106" - readonly property string remove_medium: "\u0107" - readonly property string remove_small: "\u0108" - readonly property string rename_small: "\u0109" - readonly property string replace_small: "\u010A" - readonly property string resetView_small: "\u010B" - readonly property string restartParticles_medium: "\u010C" - readonly property string reverseOrder_medium: "\u010D" - readonly property string roatate_medium: "\u010E" - readonly property string rotationFill: "\u010F" - readonly property string rotationOutline: "\u0110" - readonly property string runProjFill_large: "\u0111" - readonly property string runProjOutline_large: "\u0112" - readonly property string s_anchors: "\u0113" - readonly property string s_annotations: "\u0114" - readonly property string s_arrange: "\u0115" - readonly property string s_boundingBox: "\u0116" - readonly property string s_component: "\u0117" - readonly property string s_connections: "\u0118" - readonly property string s_edit: "\u0119" - readonly property string s_enterComponent: "\u011A" - readonly property string s_eventList: "\u011B" - readonly property string s_group: "\u011C" - readonly property string s_layouts: "\u011D" - readonly property string s_merging: "\u011E" - readonly property string s_mouseArea: "\u011F" - readonly property string s_positioners: "\u0120" - readonly property string s_selection: "\u0121" - readonly property string s_snapping: "\u0122" - readonly property string s_timeline: "\u0123" - readonly property string s_visibility: "\u0124" - readonly property string saveLogs_medium: "\u0125" - readonly property string scale_medium: "\u0126" - readonly property string search: "\u0127" - readonly property string search_small: "\u0128" - readonly property string sectionToggle: "\u0129" - readonly property string selectFill_medium: "\u012A" - readonly property string selectOutline_medium: "\u012B" - readonly property string selectParent_small: "\u012C" - readonly property string selection_small: "\u012D" - readonly property string settings_medium: "\u012E" - readonly property string signal_small: "\u012F" - readonly property string snapping_conf_medium: "\u0130" - readonly property string snapping_medium: "\u0131" - readonly property string snapping_small: "\u0132" - readonly property string sortascending_medium: "\u0133" - readonly property string sortdescending_medium: "\u0134" - readonly property string sphere_medium: "\u0135" - readonly property string sphere_small: "\u0136" - readonly property string splitColumns: "\u0137" - readonly property string splitRows: "\u0138" - readonly property string spotLight_small: "\u0139" - readonly property string stackedContainer_small: "\u013A" - readonly property string startNode: "\u013B" - readonly property string step_medium: "\u013C" - readonly property string stop_medium: "\u013D" - readonly property string testIcon: "\u013E" - readonly property string textAlignBottom: "\u013F" - readonly property string textAlignCenter: "\u0140" - readonly property string textAlignJustified: "\u0141" - readonly property string textAlignLeft: "\u0142" - readonly property string textAlignMiddle: "\u0143" - readonly property string textAlignRight: "\u0144" - readonly property string textAlignTop: "\u0145" - readonly property string textBulletList: "\u0146" - readonly property string textFullJustification: "\u0147" - readonly property string textNumberedList: "\u0148" - readonly property string textures_medium: "\u0149" - readonly property string tickIcon: "\u014A" - readonly property string tickMark_small: "\u014B" - readonly property string timeline_small: "\u014C" - readonly property string toEndFrame_medium: "\u014D" - readonly property string toNextFrame_medium: "\u014E" - readonly property string toPrevFrame_medium: "\u014F" - readonly property string toStartFrame_medium: "\u0150" - readonly property string topToolbar_annotations: "\u0151" - readonly property string topToolbar_closeFile: "\u0152" - readonly property string topToolbar_designMode: "\u0153" - readonly property string topToolbar_enterComponent: "\u0154" - readonly property string topToolbar_home: "\u0155" - readonly property string topToolbar_makeComponent: "\u0156" - readonly property string topToolbar_navFile: "\u0157" - readonly property string topToolbar_runProject: "\u0158" - readonly property string translationCreateFiles: "\u0159" - readonly property string translationCreateReport: "\u015A" - readonly property string translationExport: "\u015B" - readonly property string translationImport: "\u015C" - readonly property string translationSelectLanguages: "\u015D" - readonly property string translationTest: "\u015E" - readonly property string transparent: "\u015F" - readonly property string triState: "\u0160" - readonly property string triangleArcA: "\u0161" - readonly property string triangleArcB: "\u0162" - readonly property string triangleCornerA: "\u0163" - readonly property string triangleCornerB: "\u0164" - readonly property string unLinked: "\u0165" - readonly property string undo: "\u0166" - readonly property string unify_medium: "\u0167" - readonly property string unpin: "\u0168" - readonly property string upDownIcon: "\u0169" - readonly property string upDownSquare2: "\u016A" - readonly property string updateAvailable_medium: "\u016B" - readonly property string updateContent_medium: "\u016C" - readonly property string uploadcsv_large: "\u016D" - readonly property string uploadcsv_medium: "\u016E" - readonly property string uploadjson_large: "\u016F" - readonly property string uploadjson_medium: "\u0170" - readonly property string visibilityOff: "\u0171" - readonly property string visibilityOn: "\u0172" - readonly property string visible_medium: "\u0173" - readonly property string visible_small: "\u0174" - readonly property string wildcard: "\u0175" - readonly property string wizardsAutomotive: "\u0176" - readonly property string wizardsDesktop: "\u0177" - readonly property string wizardsGeneric: "\u0178" - readonly property string wizardsMcuEmpty: "\u0179" - readonly property string wizardsMcuGraph: "\u017A" - readonly property string wizardsMobile: "\u017B" - readonly property string wizardsUnknown: "\u017C" - readonly property string zoomAll: "\u017D" - readonly property string zoomIn: "\u017E" - readonly property string zoomIn_medium: "\u017F" - readonly property string zoomOut: "\u0180" - readonly property string zoomOut_medium: "\u0181" - readonly property string zoomSelection: "\u0182" + readonly property string addGroup_medium: "\u0026" + readonly property string addRowAfter: "\u0027" + readonly property string addRowBefore: "\u0028" + readonly property string addTable: "\u0029" + readonly property string add_medium: "\u002A" + readonly property string add_small: "\u002B" + readonly property string addcolumnleft_medium: "\u002C" + readonly property string addcolumnright_medium: "\u002D" + readonly property string addrowabove_medium: "\u002E" + readonly property string addrowbelow_medium: "\u002F" + readonly property string adsClose: "\u0030" + readonly property string adsDetach: "\u0031" + readonly property string adsDropDown: "\u0032" + readonly property string alias: "\u0033" + readonly property string aliasAnimated: "\u0034" + readonly property string alignBottom: "\u0035" + readonly property string alignCenterHorizontal: "\u0036" + readonly property string alignCenterVertical: "\u0037" + readonly property string alignLeft: "\u0038" + readonly property string alignRight: "\u0039" + readonly property string alignTo: "\u003A" + readonly property string alignToCam_medium: "\u003B" + readonly property string alignToCamera_small: "\u003C" + readonly property string alignToObject_small: "\u003D" + readonly property string alignToView_medium: "\u003E" + readonly property string alignTop: "\u003F" + readonly property string alphabetical_medium: "\u0040" + readonly property string anchorBaseline: "\u0041" + readonly property string anchorBottom: "\u0042" + readonly property string anchorFill: "\u0043" + readonly property string anchorLeft: "\u0044" + readonly property string anchorRight: "\u0045" + readonly property string anchorTop: "\u0046" + readonly property string anchors_small: "\u0047" + readonly property string animatedProperty: "\u0048" + readonly property string annotationBubble: "\u0049" + readonly property string annotationDecal: "\u004A" + readonly property string annotations_large: "\u004B" + readonly property string annotations_small: "\u004C" + readonly property string applyMaterialToSelected: "\u004D" + readonly property string apply_medium: "\u004E" + readonly property string apply_small: "\u004F" + readonly property string arrange_small: "\u0050" + readonly property string arrow_small: "\u0051" + readonly property string assign: "\u0052" + readonly property string assignTo: "\u0053" + readonly property string assignTo_medium: "\u0054" + readonly property string attach_medium: "\u0055" + readonly property string back_medium: "\u0056" + readonly property string backspace_small: "\u0057" + readonly property string bakeLights_medium: "\u0058" + readonly property string bevelAll: "\u0059" + readonly property string bevelCorner: "\u005A" + readonly property string bezier_medium: "\u005B" + readonly property string binding_medium: "\u005C" + readonly property string bounds_small: "\u005D" + readonly property string branch_medium: "\u005E" + readonly property string camera_small: "\u005F" + readonly property string centerHorizontal: "\u0060" + readonly property string centerVertical: "\u0061" + readonly property string cleanLogs_medium: "\u0062" + readonly property string closeCross: "\u0063" + readonly property string closeFile_large: "\u0064" + readonly property string closeLink: "\u0065" + readonly property string close_small: "\u0066" + readonly property string code: "\u0067" + readonly property string codeEditor_medium: "\u0068" + readonly property string codeview_medium: "\u0069" + readonly property string colorPopupClose: "\u006A" + readonly property string colorSelection_medium: "\u006B" + readonly property string columnsAndRows: "\u006C" + readonly property string comboBox_medium: "\u006D" + readonly property string cone_medium: "\u006E" + readonly property string cone_small: "\u006F" + readonly property string connection_small: "\u0070" + readonly property string connections_medium: "\u0071" + readonly property string copyLink: "\u0072" + readonly property string copyStyle: "\u0073" + readonly property string copy_small: "\u0074" + readonly property string cornerA: "\u0075" + readonly property string cornerB: "\u0076" + readonly property string cornersAll: "\u0077" + readonly property string createComponent_large: "\u0078" + readonly property string createComponent_small: "\u0079" + readonly property string createObject_medium: "\u007A" + readonly property string create_medium: "\u007B" + readonly property string create_small: "\u007C" + readonly property string cube_medium: "\u007D" + readonly property string cube_small: "\u007E" + readonly property string curveDesigner: "\u007F" + readonly property string curveDesigner_medium: "\u0080" + readonly property string curveEditor: "\u0081" + readonly property string customMaterialEditor: "\u0082" + readonly property string cylinder_medium: "\u0083" + readonly property string cylinder_small: "\u0084" + readonly property string decisionNode: "\u0085" + readonly property string deleteColumn: "\u0086" + readonly property string deleteMaterial: "\u0087" + readonly property string deleteRow: "\u0088" + readonly property string deleteTable: "\u0089" + readonly property string delete_medium: "\u008A" + readonly property string delete_small: "\u008B" + readonly property string deletecolumn_medium: "\u008C" + readonly property string deletepermanently_medium: "\u008D" + readonly property string deleterow_medium: "\u008E" + readonly property string designMode_large: "\u008F" + readonly property string detach: "\u0090" + readonly property string directionalLight_small: "\u0091" + readonly property string distributeBottom: "\u0092" + readonly property string distributeCenterHorizontal: "\u0093" + readonly property string distributeCenterVertical: "\u0094" + readonly property string distributeLeft: "\u0095" + readonly property string distributeOriginBottomRight: "\u0096" + readonly property string distributeOriginCenter: "\u0097" + readonly property string distributeOriginNone: "\u0098" + readonly property string distributeOriginTopLeft: "\u0099" + readonly property string distributeRight: "\u009A" + readonly property string distributeSpacingHorizontal: "\u009B" + readonly property string distributeSpacingVertical: "\u009D" + readonly property string distributeTop: "\u009E" + readonly property string download: "\u009F" + readonly property string downloadUnavailable: "\u00A0" + readonly property string downloadUpdate: "\u00A1" + readonly property string downloaded: "\u00A2" + readonly property string dragmarks: "\u00A3" + readonly property string duplicate_small: "\u00A4" + readonly property string edit: "\u00A5" + readonly property string editComponent_large: "\u00A6" + readonly property string editComponent_small: "\u00A7" + readonly property string editLightOff_medium: "\u00A8" + readonly property string editLightOn_medium: "\u00A9" + readonly property string edit_medium: "\u00AA" + readonly property string edit_small: "\u00AB" + readonly property string effects: "\u00AC" + readonly property string events_small: "\u00AE" + readonly property string export_medium: "\u00AF" + readonly property string eyeDropper: "\u00B0" + readonly property string favorite: "\u00B1" + readonly property string fitAll_medium: "\u00B2" + readonly property string fitSelected_small: "\u00B3" + readonly property string fitSelection_medium: "\u00B4" + readonly property string fitToView_medium: "\u00B5" + readonly property string flowAction: "\u00B6" + readonly property string flowTransition: "\u00B7" + readonly property string fontStyleBold: "\u00B8" + readonly property string fontStyleItalic: "\u00B9" + readonly property string fontStyleStrikethrough: "\u00BA" + readonly property string fontStyleUnderline: "\u00BB" + readonly property string forward_medium: "\u00BC" + readonly property string globalOrient_medium: "\u00BD" + readonly property string gradient: "\u00BE" + readonly property string gridView: "\u00BF" + readonly property string grid_medium: "\u00C0" + readonly property string group_small: "\u00C1" + readonly property string help: "\u00C2" + readonly property string home_large: "\u00C3" + readonly property string idAliasOff: "\u00C4" + readonly property string idAliasOn: "\u00C5" + readonly property string import_medium: "\u00C6" + readonly property string imported: "\u00C7" + readonly property string importedModels_small: "\u00C8" + readonly property string infinity: "\u00C9" + readonly property string invisible_medium: "\u00CA" + readonly property string invisible_small: "\u00CB" + readonly property string jumpToCode_medium: "\u00CC" + readonly property string jumpToCode_small: "\u00CD" + readonly property string keyframe: "\u00CE" + readonly property string languageList_medium: "\u00CF" + readonly property string layouts_small: "\u00D0" + readonly property string lights_small: "\u00D1" + readonly property string linear_medium: "\u00D2" + readonly property string linkTriangle: "\u00D3" + readonly property string linked: "\u00D4" + readonly property string listView: "\u00D5" + readonly property string listView_medium: "\u00D6" + readonly property string list_medium: "\u00D7" + readonly property string localOrient_medium: "\u00D8" + readonly property string lockOff: "\u00D9" + readonly property string lockOn: "\u00DA" + readonly property string loopPlayback_medium: "\u00DB" + readonly property string materialBrowser_medium: "\u00DC" + readonly property string materialPreviewEnvironment: "\u00DD" + readonly property string materialPreviewModel: "\u00DE" + readonly property string material_medium: "\u00DF" + readonly property string maxBar_small: "\u00E0" + readonly property string mergeCells: "\u00E1" + readonly property string merge_small: "\u00E2" + readonly property string minus: "\u00E3" + readonly property string mirror: "\u00E4" + readonly property string more_medium: "\u00E5" + readonly property string mouseArea_small: "\u00E6" + readonly property string moveDown_medium: "\u00E7" + readonly property string moveInwards_medium: "\u00E8" + readonly property string moveUp_medium: "\u00E9" + readonly property string moveUpwards_medium: "\u00EA" + readonly property string move_medium: "\u00EB" + readonly property string newMaterial: "\u00EC" + readonly property string nextFile_large: "\u00ED" + readonly property string normalBar_small: "\u00EE" + readonly property string openLink: "\u00EF" + readonly property string openMaterialBrowser: "\u00F0" + readonly property string orientation: "\u00F1" + readonly property string orthCam_medium: "\u00F2" + readonly property string orthCam_small: "\u00F3" + readonly property string paddingEdge: "\u00F4" + readonly property string paddingFrame: "\u00F5" + readonly property string particleAnimation_medium: "\u00F6" + readonly property string pasteStyle: "\u00F7" + readonly property string paste_small: "\u00F8" + readonly property string pause: "\u00F9" + readonly property string pause_medium: "\u00FA" + readonly property string perspectiveCam_medium: "\u00FB" + readonly property string perspectiveCam_small: "\u00FC" + readonly property string pin: "\u00FD" + readonly property string plane_medium: "\u00FE" + readonly property string plane_small: "\u00FF" + readonly property string play: "\u0100" + readonly property string playFill_medium: "\u0101" + readonly property string playOutline_medium: "\u0102" + readonly property string plus: "\u0103" + readonly property string pointLight_small: "\u0104" + readonly property string positioners_small: "\u0105" + readonly property string previewEnv_medium: "\u0106" + readonly property string previousFile_large: "\u0107" + readonly property string promote: "\u0108" + readonly property string properties_medium: "\u0109" + readonly property string readOnly: "\u010A" + readonly property string recent_medium: "\u010B" + readonly property string recordFill_medium: "\u010C" + readonly property string recordOutline_medium: "\u010D" + readonly property string redo: "\u010E" + readonly property string reload_medium: "\u010F" + readonly property string remove_medium: "\u0110" + readonly property string remove_small: "\u0111" + readonly property string rename_small: "\u0112" + readonly property string replace_small: "\u0113" + readonly property string resetView_small: "\u0114" + readonly property string restartParticles_medium: "\u0115" + readonly property string reverseOrder_medium: "\u0116" + readonly property string roatate_medium: "\u0117" + readonly property string rotationFill: "\u0118" + readonly property string rotationOutline: "\u0119" + readonly property string runProjFill_large: "\u011A" + readonly property string runProjOutline_large: "\u011B" + readonly property string s_anchors: "\u011C" + readonly property string s_annotations: "\u011D" + readonly property string s_arrange: "\u011E" + readonly property string s_boundingBox: "\u011F" + readonly property string s_component: "\u0120" + readonly property string s_connections: "\u0121" + readonly property string s_edit: "\u0122" + readonly property string s_enterComponent: "\u0123" + readonly property string s_eventList: "\u0124" + readonly property string s_group: "\u0125" + readonly property string s_layouts: "\u0126" + readonly property string s_merging: "\u0127" + readonly property string s_mouseArea: "\u0128" + readonly property string s_positioners: "\u0129" + readonly property string s_selection: "\u012A" + readonly property string s_snapping: "\u012B" + readonly property string s_timeline: "\u012C" + readonly property string s_visibility: "\u012D" + readonly property string saveLogs_medium: "\u012E" + readonly property string scale_medium: "\u012F" + readonly property string search: "\u0130" + readonly property string search_small: "\u0131" + readonly property string sectionToggle: "\u0132" + readonly property string selectFill_medium: "\u0133" + readonly property string selectOutline_medium: "\u0134" + readonly property string selectParent_small: "\u0135" + readonly property string selection_small: "\u0136" + readonly property string settings_medium: "\u0137" + readonly property string signal_small: "\u0138" + readonly property string snapping_conf_medium: "\u0139" + readonly property string snapping_medium: "\u013A" + readonly property string snapping_small: "\u013B" + readonly property string sortascending_medium: "\u013C" + readonly property string sortdescending_medium: "\u013D" + readonly property string sphere_medium: "\u013E" + readonly property string sphere_small: "\u013F" + readonly property string splitColumns: "\u0140" + readonly property string splitRows: "\u0141" + readonly property string splitScreen_medium: "\u0142" + readonly property string spotLight_small: "\u0143" + readonly property string stackedContainer_small: "\u0144" + readonly property string startNode: "\u0145" + readonly property string step_medium: "\u0146" + readonly property string stop_medium: "\u0147" + readonly property string tableView_medium: "\u0148" + readonly property string testIcon: "\u0149" + readonly property string textAlignBottom: "\u014A" + readonly property string textAlignCenter: "\u014B" + readonly property string textAlignJustified: "\u014C" + readonly property string textAlignLeft: "\u014D" + readonly property string textAlignMiddle: "\u014E" + readonly property string textAlignRight: "\u014F" + readonly property string textAlignTop: "\u0150" + readonly property string textBulletList: "\u0151" + readonly property string textFullJustification: "\u0152" + readonly property string textNumberedList: "\u0153" + readonly property string textures_medium: "\u0154" + readonly property string tickIcon: "\u0155" + readonly property string tickMark_small: "\u0156" + readonly property string timeline_small: "\u0157" + readonly property string toEndFrame_medium: "\u0158" + readonly property string toNextFrame_medium: "\u0159" + readonly property string toPrevFrame_medium: "\u015A" + readonly property string toStartFrame_medium: "\u015B" + readonly property string topToolbar_annotations: "\u015C" + readonly property string topToolbar_closeFile: "\u015D" + readonly property string topToolbar_designMode: "\u015E" + readonly property string topToolbar_enterComponent: "\u015F" + readonly property string topToolbar_home: "\u0160" + readonly property string topToolbar_makeComponent: "\u0161" + readonly property string topToolbar_navFile: "\u0162" + readonly property string topToolbar_runProject: "\u0163" + readonly property string translationCreateFiles: "\u0164" + readonly property string translationCreateReport: "\u0165" + readonly property string translationExport: "\u0166" + readonly property string translationImport: "\u0167" + readonly property string translationSelectLanguages: "\u0168" + readonly property string translationTest: "\u0169" + readonly property string transparent: "\u016A" + readonly property string triState: "\u016B" + readonly property string triangleArcA: "\u016C" + readonly property string triangleArcB: "\u016D" + readonly property string triangleCornerA: "\u016E" + readonly property string triangleCornerB: "\u016F" + readonly property string unLinked: "\u0170" + readonly property string undo: "\u0171" + readonly property string unify_medium: "\u0172" + readonly property string unpin: "\u0173" + readonly property string upDownIcon: "\u0174" + readonly property string upDownSquare2: "\u0175" + readonly property string updateAvailable_medium: "\u0176" + readonly property string updateContent_medium: "\u0177" + readonly property string visibilityOff: "\u0178" + readonly property string visibilityOn: "\u0179" + readonly property string visible_medium: "\u017A" + readonly property string visible_small: "\u017B" + readonly property string warning_medium: "\u017C" + readonly property string wildcard: "\u017D" + readonly property string wizardsAutomotive: "\u017E" + readonly property string wizardsDesktop: "\u017F" + readonly property string wizardsGeneric: "\u0180" + readonly property string wizardsMcuEmpty: "\u0181" + readonly property string wizardsMcuGraph: "\u0182" + readonly property string wizardsMobile: "\u0183" + readonly property string wizardsUnknown: "\u0184" + readonly property string zoomAll: "\u0185" + readonly property string zoomIn: "\u0186" + readonly property string zoomIn_medium: "\u0187" + readonly property string zoomOut: "\u0188" + readonly property string zoomOut_medium: "\u0189" + readonly property string zoomSelection: "\u018A" readonly property font iconFont: Qt.font({ "family": controlIcons.name, diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/MicroToolbarButtonStyle.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/MicroToolbarButtonStyle.qml new file mode 100644 index 00000000000..24d5f1517ee --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/MicroToolbarButtonStyle.qml @@ -0,0 +1,34 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +import QtQuick + +ControlStyle { + + baseIconFontSize: Values.bigFont + controlSize: Qt.size(Values.viewBarComboWidth, Values.viewBarComboHeight) + smallIconFontSize: Values.viewBarComboIcon + + radius: Values.smallRadius + + icon: ControlStyle.IconColors { + idle: Values.themeTextColor + hover: Values.themeTextColor + interaction: Values.themeIconColor + disabled: "#636363" + } + + background: ControlStyle.BackgroundColors { + idle: Values.themeConnectionEditorMicroToolbar + hover: Values.themeConnectionEditorButtonBackground_hover //"#373737" + interaction: Values.themeInteraction + disabled: Values.themecontrolBackground_statusbarIdle + } + + border: ControlStyle.BorderColors { + idle: Values.themeConnectionEditorMicroToolbar + hover: Values.themeConnectionEditorButtonBorder_hover //"#5E5E5E" + interaction: Values.themeInteraction + disabled: Values.themecontrolBackground_statusbarIdle + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml index 0aa9a1b7217..82dbf132b19 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml @@ -118,6 +118,9 @@ QtObject { property real sectionHeadHeight: 21 // tab and section property real sectionHeadSpacerHeight: 10 + property real sectionPadding: 10 + property real sectionGridSpacing: 5 + property real controlLabelWidth: 15 property real controlLabelGap: 5 @@ -259,10 +262,12 @@ QtObject { property color themeControlBackground_toolbarHover: Theme.color(Theme.DScontrolBackground_toolbarHover) property color themeControlBackground_topToolbarHover: Theme.color(Theme.DScontrolBackground_topToolbarHover) property color themeToolbarBackground: Theme.color(Theme.DStoolbarBackground) + property color themeConnectionEditorButtonBackground_hover: Theme.color(Theme.DSconnectionEditorButtonBackground_hover) //outlines property color controlOutline_toolbarIdle: Theme.color(Theme.DScontrolOutline_topToolbarIdle) property color controlOutline_toolbarHover: Theme.color(Theme.DScontrolOutline_topToolbarHover) + property color themeConnectionEditorButtonBorder_hover: Theme.color(Theme.DSconnectionEditorButtonBorder_hover) //icons property color themeToolbarIcon_blocked: Theme.color(Theme.DStoolbarIcon_blocked) @@ -341,6 +346,7 @@ QtObject { property color themeBackgroundColorNormal: Theme.color(Theme.DSBackgroundColorNormal) property color themeBackgroundColorAlternate: Theme.color(Theme.DSBackgroundColorAlternate) property color themeConnectionCodeEditor: Theme.color(Theme.DSconnectionCodeEditor) + property color themeConnectionEditorMicroToolbar: Theme.color(Theme.DSconnectionEditorMicroToolbar) // Text colors property color themeTextColor: Theme.color(Theme.DStextColor) @@ -466,6 +472,7 @@ QtObject { property ControlStyle toolbarStyle: ToolbarStyle {} property ControlStyle primaryToolbarStyle: PrimaryButtonStyle {} property ControlStyle toolbarButtonStyle: TopToolbarButtonStyle {} + property ControlStyle microToolbarButtonStyle: MicroToolbarButtonStyle {} property ControlStyle viewBarButtonStyle: ViewBarButtonStyle {} property ControlStyle viewBarControlStyle: ViewBarControlStyle {} property ControlStyle statusbarButtonStyle: StatusBarButtonStyle {} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf index 4d18734ddac..c622c1950af 100644 Binary files a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf and b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf differ diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/qmldir b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/qmldir index 45e55c4edc6..e22b7d9a583 100755 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/qmldir +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/qmldir @@ -12,6 +12,7 @@ SearchControlStyle 1.0 SearchControlStyle.qml StatusBarButtonStyle 1.0 StatusBarButtonStyle.qml StatusBarControlStyle 1.0 StatusBarControlStyle.qml TopToolbarButtonStyle 1.0 TopToolbarButtonStyle.qml +MicroToolbarButtonStyle 1.0 MicroToolbarButtonStyle.qml ViewBarButtonStyle 1.0 ViewBarButtonStyle.qml ViewBarControlStyle 1.0 ViewBarControlStyle.qml ViewStyle 1.0 ViewStyle.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/quick.metainfo b/share/qtcreator/qmldesigner/propertyEditorQmlSources/quick.metainfo new file mode 100644 index 00000000000..f390f72260d --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/quick.metainfo @@ -0,0 +1,837 @@ +MetaInfo { + + Type { + name: "QtQuick.Item" + icon: ":/qtquickplugin/images/item-icon16.png" + + Hints { + visibleNonDefaultProperties: "layer.effect" + } + + ItemLibraryEntry { + name: "Item" + category: "a.Qt Quick - Basic" + libraryIcon: ":/qtquickplugin/images/item-icon.png" + version: "2.0" + + Property { name: "width"; type: "int"; value: 200; } + Property { name: "height"; type: "int"; value: 200; } + toolTip: qsTr("Groups several visual items.") + } + } + + Type { + name: "QtQuick.Rectangle" + icon: ":/qtquickplugin/images/rect-icon16.png" + + ItemLibraryEntry { + name: "Rectangle" + category: "a.Qt Quick - Basic" + libraryIcon: ":/qtquickplugin/images/rect-icon.png" + version: "2.0" + + 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.") + } + } + + Type { + name: "QtQuick.Text" + icon: ":/qtquickplugin/images/text-icon16.png" + + ItemLibraryEntry { + name: "Text" + category: "a.Qt Quick - Basic" + libraryIcon: ":/qtquickplugin/images/text-icon.png" + version: "2.0" + + Property { name: "font.pixelSize"; type: "int"; value: 12; } + Property { name: "text"; type: "binding"; value: "qsTr(\"Text\")"; } + toolTip: qsTr("A read-only text label.") + } + } + + Type { + name: "QtQuick.TextEdit" + icon: ":/qtquickplugin/images/text-edit-icon16.png" + + ItemLibraryEntry { + name: "Text Edit" + category: "a.Qt Quick - Basic" + libraryIcon: ":/qtquickplugin/images/text-edit-icon.png" + version: "2.0" + + Property { name: "width"; type: "int"; value: 80; } + 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.") + } + } + + Type { + name: "QtQuick.TextInput" + icon: ":/qtquickplugin/images/text-input-icon16.png" + + ItemLibraryEntry { + name: "Text Input" + category: "a.Qt Quick - Basic" + libraryIcon: ":/qtquickplugin/images/text-input-icon.png" + version: "2.0" + + Property { name: "width"; type: "int"; value: 80; } + 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.") + } + } + + Type { + name: "QtQuick.MouseArea" + icon: ":/qtquickplugin/images/mouse-area-icon16.png" + + ItemLibraryEntry { + name: "Mouse Area" + category: "a.Qt Quick - Basic" + libraryIcon: ":/qtquickplugin/images/mouse-area-icon.png" + version: "2.0" + + Property { name: "width"; type: "int"; value: 100; } + Property { name: "height"; type: "int"; value: 100; } + toolTip: qsTr("An area with mouse functionality.") + } + } + + Type { + name: "QtQuick.Image" + icon: ":/qtquickplugin/images/image-icon16.png" + + ItemLibraryEntry { + name: "Image" + category: "a.Qt Quick - Basic" + libraryIcon: ":/qtquickplugin/images/image-icon.png" + version: "2.0" + + 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"; } + Property { name: "fillMode"; type: "enum"; value: "Image.PreserveAspectFit"; } + toolTip: qsTr("Displays an image.") + } + } + + Type { + name: "QtQuick.AnimatedImage" + icon: ":/qtquickplugin/images/animated-image-icon16.png" + + ItemLibraryEntry { + name: "Animated Image" + category: "a.Qt Quick - Basic" + libraryIcon: ":/qtquickplugin/images/animated-image-icon.png" + version: "2.0" + + 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.") + } + } + + Type { + name: "QtQuick.AnimatedSprite" + icon: ":/qtquickplugin/images/animated-image-icon16.png" + + ItemLibraryEntry { + name: "Animated Sprite" + category: "a.Qt Quick - Basic" + libraryIcon: ":/qtquickplugin/images/animated-image-icon.png" + version: "2.0" + + Property { name: "frameWidth"; type: "int"; value: 64; } + Property { name: "frameHeight"; type: "int"; value: 64; } + Property { name: "frameCount"; type: "int"; value: 4; } + Property { name: "frameDuration"; type: "int"; value: 500; } + Property { name: "source"; type: "QUrl"; value:"animatedsprite-loading.png"; } + ExtraFile { source: ":/qtquickplugin/images/animatedsprite-loading.png" } + toolTip: qsTr("Draws a sprite animation.") + } + } + + Type { + name: "QtQuick.BorderImage" + icon: ":/qtquickplugin/images/border-image-icon16.png" + + ItemLibraryEntry { + name: "Border Image" + category: "a.Qt Quick - Basic" + libraryIcon: ":/qtquickplugin/images/border-image-icon.png" + version: "2.0" + + 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.") + } + } + + Type { + name: "QtQuick.Flickable" + icon: ":/qtquickplugin/images/flickable-icon16.png" + + ItemLibraryEntry { + name: "Flickable" + category: "a.Qt Quick - Basic" + libraryIcon: ":/qtquickplugin/images/flickable-icon.png" + version: "2.0" + + Property { name: "width"; type: "int"; value: 300; } + Property { name: "height"; type: "int"; value: 300; } + toolTip: qsTr("An area for keeping dragable objects.") + } + } + + Type { + name: "QtQuick.GridView" + icon: ":/qtquickplugin/images/gridview-icon16.png" + + ItemLibraryEntry { + name: "Grid View" + category: "b.Qt Quick - Views" + libraryIcon: ":/qtquickplugin/images/gridview-icon.png" + version: "2.0" + + QmlSource { source: ":/qtquickplugin/source/gridviewv2.qml" } + toolTip: qsTr("Organizes dynamic data sets in a grid.") + } + } + + Type { + name: "QtQuick.ListView" + icon: ":/qtquickplugin/images/listview-icon16.png" + + ItemLibraryEntry { + name: "List View" + category: "b.Qt Quick - Views" + libraryIcon: ":/qtquickplugin/images/listview-icon.png" + version: "2.0" + + QmlSource { source: ":/qtquickplugin/source/listviewv2.qml" } + toolTip: qsTr("Organizes dynamic data sets in a list.") + } + } + + Type { + name: "QtQuick.PathView" + icon: ":/qtquickplugin/images/pathview-icon16.png" + + ItemLibraryEntry { + name: "Path View" + category: "b.Qt Quick - Views" + libraryIcon: ":/qtquickplugin/images/pathview-icon.png" + version: "2.0" + + QmlSource { source: ":/qtquickplugin/source/pathviewv2.qml" } + toolTip: qsTr("Organizes dynamic data sets along a path.") + } + } + + Type { + name: "QtQuick.FocusScope" + icon: ":/qtquickplugin/images/focusscope-icon16.png" + + ItemLibraryEntry { + name: "Focus Scope" + category: "a.Qt Quick - Basic" + libraryIcon: ":/qtquickplugin/images/focusscope-icon.png" + version: "2.0" + + 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.") + } + } + + Type { + name: "QtQuick.Column" + icon: ":/qtquickplugin/images/column-positioner-icon-16px.png" + + ItemLibraryEntry { + name: "Column" + category: "c.Qt Quick - Positioner" + libraryIcon: ":/qtquickplugin/images/column-positioner-icon.png" + version: "2.0" + + Property { name: "width"; type: "int"; value: 200; } + Property { name: "height"; type: "int"; value: 400; } + + toolTip: qsTr("Organizes items in a column.") + } + } + + Type { + name: "QtQuick.Row" + icon: ":/qtquickplugin/images/row-positioner-icon-16px.png" + + ItemLibraryEntry { + name: "Row" + 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; } + } + } + + Type { + name: "QtQuick.Grid" + icon: ":/qtquickplugin/images/grid-positioner-icon-16px.png" + + ItemLibraryEntry { + name: "Grid" + category: "c.Qt Quick - Positioner" + libraryIcon: ":/qtquickplugin/images/grid-positioner-icon.png" + version: "2.0" + + Property { name: "width"; type: "int"; value: 400; } + Property { name: "height"; type: "int"; value: 400; } + toolTip: qsTr("Organizes items in a fixed grid.") + } + } + + Type { + name: "QtQuick.Flow" + icon: ":/qtquickplugin/images/flow-positioner-icon-16px.png" + + ItemLibraryEntry { + name: "Flow" + category: "c.Qt Quick - Positioner" + libraryIcon: ":/qtquickplugin/images/flow-positioner-icon.png" + version: "2.0" + + Property { name: "width"; type: "int"; value: 400; } + Property { name: "height"; type: "int"; value: 400; } + toolTip: qsTr("Organizes items in free-flowing rows.") + } + } + + Type { + name: "QtQuick.Timeline.Timeline" + icon: ":/qtquickplugin/images/timeline-16px.png" + + Hints { + visibleNonDefaultProperties: "animations" + visibleInLibrary: false + visibleInNavigator: true + } + ItemLibraryEntry { + name: "Timeline" + category: "none" + version: "1.0" + } + } + + Type { + name: "QtQuick.Timeline.TimelineAnimation" + icon: ":/qtquickplugin/images/timeline-animation-16px.png" + + Hints { + visibleInLibrary: false + visibleInNavigator: true + } + ItemLibraryEntry { + name: "Animation" + category: "none" + version: "1.0" + } + } + + Type { + name: "QtQuick.Timeline.Keyframe" + icon: ":/qtquickplugin/images/keyframe-16px.png" + + ItemLibraryEntry { + name: "Keyframe" + category: "none" + version: "1.0" + requiredImport: "none" + } + } + + Type { + name: "QtQuick.Timeline.KeyframeGroup" + icon: ":/qtquickplugin/images/keyframe-16px.png" + + ItemLibraryEntry { + name: "KeyframeGroup" + category: "none" + version: "1.0" + requiredImport: "none" + } + } + + Type { + name: "QtQuick.PropertyAnimation" + icon: ":/qtquickplugin/images/item-icon16.png" + + Hints { + visibleInNavigator: true + canBeDroppedInNavigator: true + canBeDroppedInFormEditor: false + canBeContainer: false + } + + ItemLibraryEntry { + name: "Property Animation" + category: "d.Qt Quick - Animation" + libraryIcon: ":/qtquickplugin/images/item-icon.png" + version: "2.0" + toolTip: qsTr("Animates changes in property values.") + } + } + + Type { + name: "QtQuick.PauseAnimation" + icon: ":/qtquickplugin/images/item-icon16.png" + + Hints { + visibleInNavigator: true + canBeDroppedInNavigator: true + canBeDroppedInFormEditor: false + canBeContainer: false + } + + ItemLibraryEntry { + name: "Pause Animation" + category: "d.Qt Quick - Animation" + libraryIcon: ":/qtquickplugin/images/item-icon.png" + version: "2.0" + toolTip: qsTr("Provides a pause between animations.") + } + } + + Type { + name: "QtQuick.SequentialAnimation" + icon: ":/qtquickplugin/images/item-icon16.png" + + Hints { + visibleInNavigator: true + canBeDroppedInNavigator: true + canBeDroppedInFormEditor: false + } + + ItemLibraryEntry { + name: "Sequential Animation" + category: "d.Qt Quick - Animation" + libraryIcon: ":/qtquickplugin/images/item-icon.png" + version: "2.0" + toolTip: qsTr("Runs animations one after the other.") + } + } + + Type { + name: "QtQuick.ParallelAnimation" + icon: ":/qtquickplugin/images/item-icon16.png" + + Hints { + visibleInNavigator: true + canBeDroppedInNavigator: true + canBeDroppedInFormEditor: false + } + + ItemLibraryEntry { + name: "Parallel Animation" + category: "d.Qt Quick - Animation" + libraryIcon: ":/qtquickplugin/images/item-icon.png" + version: "2.0" + toolTip: qsTr("Runs animations together at the same time.") + } + } + + Type { + name: "QtQuick.PropertyAction" + icon: ":/qtquickplugin/images/item-icon16.png" + + Hints { + visibleInNavigator: true + canBeDroppedInNavigator: true + canBeDroppedInFormEditor: false + canBeContainer: false + } + + ItemLibraryEntry { + name: "Property Action" + category: "d.Qt Quick - Animation" + libraryIcon: ":/qtquickplugin/images/item-icon.png" + version: "2.0" + toolTip: qsTr("Provides an immediate property change during animations.") + } + } + + Type { + name: "QtQuick.ScriptAction" + icon: ":/qtquickplugin/images/item-icon16.png" + + Hints { + visibleInNavigator: true + canBeDroppedInNavigator: true + canBeDroppedInFormEditor: false + canBeContainer: false + } + + ItemLibraryEntry { + name: "Script Action" + category: "d.Qt Quick - Animation" + libraryIcon: ":/qtquickplugin/images/item-icon.png" + version: "2.0" + toolTip: qsTr("Runs a script during animation.") + } + } + + Type { + name: "QtQuick.ColorAnimation" + icon: ":/qtquickplugin/images/item-icon16.png" + + Hints { + visibleInNavigator: true + canBeDroppedInNavigator: true + canBeDroppedInFormEditor: false + canBeContainer: false + } + + ItemLibraryEntry { + name: "Color Animation" + category: "d.Qt Quick - Animation" + libraryIcon: ":/qtquickplugin/images/item-icon.png" + version: "2.0" + toolTip: qsTr("Animates the color of an item.") + } + } + + Type { + name: "QtQuick.NumberAnimation" + icon: ":/qtquickplugin/images/item-icon16.png" + + Hints { + visibleInNavigator: true + canBeDroppedInNavigator: true + canBeDroppedInFormEditor: false + canBeContainer: false + } + + ItemLibraryEntry { + name: "Number Animation" + category: "d.Qt Quick - Animation" + libraryIcon: ":/qtquickplugin/images/item-icon.png" + 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.") + } + } + + Type { + name: "QtQml.Timer" + icon: ":/qtquickplugin/images/timer-16px.png" + + Hints { + visibleInNavigator: true + canBeDroppedInNavigator: true + canBeDroppedInFormEditor: false + canBeContainer: false + } + + ItemLibraryEntry { + name: "Timer" + category: "d.Qt Quick - Animation" + libraryIcon: ":/qtquickplugin/images/timer-24px.png" + version: "2.0" + toolTip: qsTr(" Triggers an action at a given time.") + } + } + + Type { + name: "QML.Component" + icon: ":/qtquickplugin/images/component-icon16.png" + + Hints { + canBeDroppedInNavigator: true + canBeDroppedInFormEditor: false + } + + ItemLibraryEntry { + name: "Component" + category: "e.Qt Quick - Instancers" + libraryIcon: ":/qtquickplugin/images/component-icon.png" + version: "1.0" + + QmlSource { source: ":/qtquickplugin/source/component.qml" } + toolTip: qsTr("Allows you to define components inline, within a QML document.") + } + } + + Type { + name: "QML.Component" + icon: ":/qtquickplugin/images/component-icon16.png" + + Hints { + canBeDroppedInNavigator: true + canBeDroppedInFormEditor: false + } + + ItemLibraryEntry { + name: "Component 3D" + category: "Instancers" + libraryIcon: ":/qtquickplugin/images/component-icon.png" + version: "1.0" + requiredImport: "QtQuick3D" + + QmlSource { source: ":/qtquickplugin/source/component3d.qml" } + toolTip: qsTr("Allows you to define 3D components inline, within a QML document.") + } + } + + Type { + name: "QtQuick.Loader" + icon: ":/qtquickplugin/images/loader-icon16.png" + + ItemLibraryEntry { + name: "Loader" + category: "e.Qt Quick - Instancers" + libraryIcon: ":/qtquickplugin/images/loader-icon.png" + 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.") + } + } + + Type { + name: "QtQuick.Repeater" + icon: ":/qtquickplugin/images/repeater-icon16.png" + + Hints { + canBeDroppedInFormEditor: false + hasFormEditorItem: false + } + + ItemLibraryEntry { + name: "Repeater" + category: "e.Qt Quick - Instancers" + libraryIcon: ":/qtquickplugin/images/repeater-icon.png" + version: "2.0" + toolTip: qsTr("Creates a number of copies of the same item.") + } + } + + Type { + name: "QtMultimedia.MediaPlayer" + icon: ":/qtquickplugin/images/media-player-16px.png" + + Hints { + visibleInNavigator: true + canBeDroppedInNavigator: true + canBeDroppedInFormEditor: false + canBeContainer: false + } + + ItemLibraryEntry { + name: "Media Player" + category: "f.Qt Quick - Multimedia" + libraryIcon: ":/qtquickplugin/images/media-player-24px.png" + version: "6.0" + requiredImport: "QtMultimedia" + } + } + + Type { + name: "QtMultimedia.AudioOutput" + icon: ":/qtquickplugin/images/audio-output-16px.png" + + Hints { + visibleInNavigator: true + canBeDroppedInNavigator: true + canBeDroppedInFormEditor: false + canBeContainer: false + } + + ItemLibraryEntry { + name: "Audio Output" + category: "f.Qt Quick - Multimedia" + libraryIcon: ":/qtquickplugin/images/audio-output-24px.png" + version: "6.0" + requiredImport: "QtMultimedia" + } + } + + Type { + name: "QtMultimedia.VideoOutput" + icon: ":/qtquickplugin/images/video-output-16px.png" + + Hints { + visibleInNavigator: true + canBeDroppedInNavigator: true + canBeDroppedInFormEditor: false + canBeContainer: false + } + + ItemLibraryEntry { + name: "Video Output" + category: "f.Qt Quick - Multimedia" + libraryIcon: ":/qtquickplugin/images/video-output-24px.png" + version: "6.0" + requiredImport: "QtMultimedia" + } + } + + Type { + name: "QtMultimedia.Video" + icon: ":/qtquickplugin/images/video-16px.png" + + Hints { + visibleInNavigator: true + canBeDroppedInNavigator: true + canBeDroppedInFormEditor: true + canBeContainer: false + } + + ItemLibraryEntry { + name: "Video" + category: "f.Qt Quick - Multimedia" + libraryIcon: ":/qtquickplugin/images/video-24px.png" + version: "6.0" + requiredImport: "QtMultimedia" + + Property { name: "width"; type: "int"; value: 200; } + Property { name: "height"; type: "int"; value: 200; } + } + } + + Type { + name: "QtQuick3D.SpatialAudio.AmbientSound" + icon: ":/qtquickplugin/images/ambient-sound-16.png" + + Hints { + canBeDroppedInNavigator: true + canBeDroppedInFormEditor: false + canBeDroppedInView3D: false + canBeContainer: false + } + + ItemLibraryEntry { + name: "Ambient Sound" + category: "Spatial Audio" + libraryIcon: ":/qtquickplugin/images/ambient-sound-24.png" + version: "6.0" + requiredImport: "QtQuick3D.SpatialAudio" + toolTip: qsTr("An ambient background sound.") + } + } + + Type { + name: "QtQuick3D.SpatialAudio.AudioEngine" + icon: ":/qtquickplugin/images/audio-engine-16.png" + + Hints { + canBeDroppedInNavigator: true + canBeDroppedInFormEditor: false + canBeDroppedInView3D: false + canBeContainer: false + } + + ItemLibraryEntry { + name: "Audio Engine" + category: "Spatial Audio" + libraryIcon: ":/qtquickplugin/images/audio-engine-24.png" + version: "6.0" + requiredImport: "QtQuick3D.SpatialAudio" + toolTip: qsTr("Manages sound objects inside a 3D scene.") + } + } + + Type { + name: "QtQuick3D.SpatialAudio.AudioListener" + icon: ":/qtquickplugin/images/audio-listener-16.png" + + Hints { + canBeDroppedInNavigator: true + canBeDroppedInFormEditor: false + canBeDroppedInView3D: true + } + + ItemLibraryEntry { + name: "Audio Listener" + category: "Spatial Audio" + libraryIcon: ":/qtquickplugin/images/audio-listener-24.png" + version: "6.0" + requiredImport: "QtQuick3D.SpatialAudio" + toolTip: qsTr("Sets the position and orientation of listening.") + } + } + + Type { + name: "QtQuick3D.SpatialAudio.AudioRoom" + icon: ":/qtquickplugin/images/audio-room-16.png" + + Hints { + canBeDroppedInNavigator: true + canBeDroppedInFormEditor: false + canBeDroppedInView3D: true + } + + ItemLibraryEntry { + name: "Audio Room" + category: "Spatial Audio" + libraryIcon: ":/qtquickplugin/images/audio-room-24.png" + version: "6.0" + requiredImport: "QtQuick3D.SpatialAudio" + toolTip: qsTr("Sets up a room for the spatial audio engine.") + } + } + + Type { + name: "QtQuick3D.SpatialAudio.SpatialSound" + icon: ":/qtquickplugin/images/spatial-audio-16.png" + + Hints { + canBeDroppedInNavigator: true + canBeDroppedInFormEditor: false + canBeDroppedInView3D: true + } + + ItemLibraryEntry { + name: "Spatial Sound" + category: "Spatial Audio" + libraryIcon: ":/qtquickplugin/images/spatial-audio-24.png" + version: "6.0" + requiredImport: "QtQuick3D.SpatialAudio" + toolTip: qsTr("A sound object in 3D space.") + } + } + + Type { + name: "QtQuick3D.BakedLightmap" + icon: ":/ItemLibrary/images/item-default-icon.png" + + Hints { + canBeDroppedInNavigator: true + canBeDroppedInFormEditor: false + canBeDroppedInView3D: false + } + + ItemLibraryEntry { + name: "Baked Lightmap" + category: "Components" + libraryIcon: ":/ItemLibrary/images/item-default-icon.png" + version: "6.5" + requiredImport: "QtQuick3D" + toolTip: qsTr("An object to specify details about baked lightmap of a model.") + + Property { name: "loadPrefix"; type: "string"; value: "lightmaps"; } + } + } +} diff --git a/share/qtcreator/qmldesigner/qt4mcu/metadata.qml b/share/qtcreator/qmldesigner/qt4mcu/metadata.qml index d208ee096ac..b4ae840bddb 100644 --- a/share/qtcreator/qmldesigner/qt4mcu/metadata.qml +++ b/share/qtcreator/qmldesigner/qt4mcu/metadata.qml @@ -5,7 +5,7 @@ Metadata { id: metadataFile - defaultVersion: v25 + defaultVersion: v26 VersionData { id: v14 diff --git a/share/qtcreator/qmldesigner/qt4mcu/qul-24.qml b/share/qtcreator/qmldesigner/qt4mcu/qul-24.qml index dbeeabf971d..202033b65cb 100644 --- a/share/qtcreator/qmldesigner/qt4mcu/qul-24.qml +++ b/share/qtcreator/qmldesigner/qt4mcu/qul-24.qml @@ -149,6 +149,12 @@ VersionData { bannedProperties: ["paused"] } + QtQuick.AnimatedSprite { + allowedProperties: ["currentFrame", "frameCount"] + bannedProperties: ["finishBehavior", "frameRate", "frameSync", + "frameX", "frameY", "interpolate", "reverse", "paused"] + } + //Quick Controls2 Items and properties: QtQuick.Controls.Control { diff --git a/share/qtcreator/qmldesigner/qt4mcu/qul-25.qml b/share/qtcreator/qmldesigner/qt4mcu/qul-25.qml index 3fb7e1b9259..eada7c998b6 100644 --- a/share/qtcreator/qmldesigner/qt4mcu/qul-25.qml +++ b/share/qtcreator/qmldesigner/qt4mcu/qul-25.qml @@ -146,6 +146,12 @@ VersionData { bannedProperties: ["paused"] } + QtQuick.AnimatedSprite { + allowedProperties: ["currentFrame", "frameCount", "paused"] + bannedProperties: ["finishBehavior", "frameRate", "frameSync", + "frameX", "frameY", "interpolate", "reverse"] + } + //Quick Controls2 Items and properties: QtQuick.Controls.Control { diff --git a/share/qtcreator/qmldesigner/qt4mcu/qul-26.qml b/share/qtcreator/qmldesigner/qt4mcu/qul-26.qml index 26c68a6e7d4..55d04d18bed 100644 --- a/share/qtcreator/qmldesigner/qt4mcu/qul-26.qml +++ b/share/qtcreator/qmldesigner/qt4mcu/qul-26.qml @@ -57,7 +57,8 @@ VersionData { "QtQuick.Shapes", "QtQuick.Timeline", "QtQuickUltralite.Extras", - "QtQuickUltralite.Layers" + "QtQuickUltralite.Layers", + "QtQuickUltralite.Profiling" ] bannedImports: [ @@ -148,6 +149,12 @@ VersionData { bannedProperties: ["paused"] } + QtQuick.AnimatedSprite { + allowedProperties: ["currentFrame", "frameCount", "paused"] + bannedProperties: ["finishBehavior", "frameRate", "frameSync", + "frameX", "frameY", "interpolate", "reverse"] + } + //Quick Controls2 Items and properties: QtQuick.Controls.Control { diff --git a/share/qtcreator/qmldesigner/stateseditor/Main.qml b/share/qtcreator/qmldesigner/stateseditor/Main.qml index b160bd5e86f..8774f3a02b2 100644 --- a/share/qtcreator/qmldesigner/stateseditor/Main.qml +++ b/share/qtcreator/qmldesigner/stateseditor/Main.qml @@ -412,6 +412,8 @@ Rectangle { anchors.verticalCenter: parent.verticalCenter width: stateGroupLabel.visible ? StudioTheme.Values.defaultControlWidth : root.width - 2 * root.padding + enabled: !(StatesEditorBackend.statesEditorModel.isMCUs + && stateGroupComboBox.count <= 1) HelperWidgets.Tooltip { id: comboBoxTooltip } @@ -420,7 +422,9 @@ Rectangle { running: stateGroupComboBox.hovered onTriggered: comboBoxTooltip.showText(stateGroupComboBox, hoverHandler.point.position, - qsTr("Switch State Group")) + StatesEditorBackend.statesEditorModel.isMCUs + ? qsTr("State Groups are not supported with Qt for MCUs") + : qsTr("Switch State Group")) } onHoverChanged: { @@ -463,8 +467,11 @@ Rectangle { style: StudioTheme.Values.viewBarButtonStyle buttonIcon: StudioTheme.Constants.add_medium anchors.verticalCenter: parent.verticalCenter - tooltip: qsTr("Create State Group") + tooltip: StatesEditorBackend.statesEditorModel.isMCUs + ? qsTr("State Groups are not supported with Qt for MCUs") + : qsTr("Create State Group") onClicked: StatesEditorBackend.statesEditorModel.addStateGroup("stateGroup") + enabled: !StatesEditorBackend.statesEditorModel.isMCUs } HelperWidgets.AbstractButton { @@ -557,7 +564,7 @@ Rectangle { isTiny: root.tinyMode onFocusSignal: root.currentStateInternalId = 0 - onDefaultClicked: StatesEditorBackend.statesEditorModel.resetDefaultState + onDefaultClicked: StatesEditorBackend.statesEditorModel.resetDefaultState() } } diff --git a/share/qtcreator/qmldesigner/stateseditor/StateMenu.qml b/share/qtcreator/qmldesigner/stateseditor/StateMenu.qml index d26572ebde6..e7139162d77 100644 --- a/share/qtcreator/qmldesigner/stateseditor/StateMenu.qml +++ b/share/qtcreator/qmldesigner/stateseditor/StateMenu.qml @@ -44,6 +44,7 @@ StudioControls.Menu { signal remove() signal toggle() signal resetWhenCondition() + signal jumpToCode() signal editAnnotation() signal removeAnnotation() @@ -84,6 +85,14 @@ StudioControls.Menu { StudioControls.MenuSeparator {} + StudioControls.MenuItem { + enabled: !root.isBaseState + text: qsTr("Jump to the code") + onTriggered: root.jumpToCode() + } + + StudioControls.MenuSeparator {} + StudioControls.MenuItem { enabled: !root.isBaseState && root.hasWhenCondition text: qsTr("Reset when Condition") diff --git a/share/qtcreator/qmldesigner/stateseditor/StateThumbnail.qml b/share/qtcreator/qmldesigner/stateseditor/StateThumbnail.qml index 1d8147d0425..781ddc4601f 100644 --- a/share/qtcreator/qmldesigner/stateseditor/StateThumbnail.qml +++ b/share/qtcreator/qmldesigner/stateseditor/StateThumbnail.qml @@ -736,6 +736,9 @@ Item { onRemove: root.remove() onToggle: root.setPropertyChangesVisible(!root.propertyChangesVisible) onResetWhenCondition: statesEditorModel.resetWhenCondition(root.internalNodeId) + + onJumpToCode: StatesEditorBackend.statesEditorModel.jumpToCode() + onEditAnnotation: { StatesEditorBackend.statesEditorModel.setAnnotation(root.internalNodeId) stateMenu.hasAnnotation = root.checkAnnotation() diff --git a/share/qtcreator/qmldesigner/statusbar/Main.qml b/share/qtcreator/qmldesigner/statusbar/Main.qml index 16b687abc00..1eb33b82360 100644 --- a/share/qtcreator/qmldesigner/statusbar/Main.qml +++ b/share/qtcreator/qmldesigner/statusbar/Main.qml @@ -34,8 +34,11 @@ Item { id: settingButton style: StudioTheme.Values.statusbarButtonStyle buttonIcon: StudioTheme.Constants.settings_medium - onClicked: backend.triggerProjectSettings() - enabled: backend.isInDesignMode || (backend.isInEditMode && backend.projectOpened) + checkable: true + checkedInverted: true + checked: backend.isInSessionMode + onClicked: settingButton.checked ? backend.triggerProjectSettings() : backend.triggerModeChange() + enabled: backend.projectOpened tooltip: qsTr("Set runtime configuration for the project.") } @@ -86,9 +89,9 @@ Item { model: backend.styles onActivated: backend.setCurrentStyle(styles.currentIndex) openUpwards: true - enabled: backend.isInDesignMode + enabled: backend.isInDesignMode && !backend.isMCUs property int currentStyleIndex: backend.currentStyle - onCurrentStyleIndexChanged: currentIndex = backend.currentStyle + onCurrentStyleIndexChanged: styles.currentIndex = backend.currentStyle } } } diff --git a/share/qtcreator/qmldesigner/studio_templates/files/swipeview/file.qml.tpl b/share/qtcreator/qmldesigner/studio_templates/files/swipeview/file.qml.tpl index 8da593bd703..b0554bd0760 100644 --- a/share/qtcreator/qmldesigner/studio_templates/files/swipeview/file.qml.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/files/swipeview/file.qml.tpl @@ -8,7 +8,7 @@ Item { height: Constants.height SwipeView { - id: stackLayout + id: stackView width: 100 anchors.top: tabBar.bottom anchors.right: parent.right @@ -16,7 +16,7 @@ Item { anchors.bottom: parent.bottom currentIndex: tabBar.currentIndex - onCurrentIndexChanged: tabBar.currentIndex = stackLayout.currentIndex + onCurrentIndexChanged: tabBar.currentIndex = stackView.currentIndex Item { Label { @@ -47,8 +47,8 @@ Item { id: tabBar currentIndex: 0 anchors.top: parent.top - anchors.right: stackLayout.right - anchors.left: stackLayout.left + anchors.right: stackView.right + anchors.left: stackView.left TabButton { text: qsTr("Tab 1") diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/Screen01.ui.qml.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/Screen01.ui.qml.tpl index 843185a029f..1c57554f38b 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/Screen01.ui.qml.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/Screen01.ui.qml.tpl @@ -8,6 +8,7 @@ Check out https://doc.qt.io/qtcreator/creator-quick-ui-forms.html for details on import QtQuick %{QtQuickVersion} import QtQuick.Controls %{QtQuickVersion} import QtQuick3D %{QtQuick3DVersion} +import QtQuick3D.Effects %{QtQuick3DVersion} import %{ImportModuleName} %{ImportModuleVersion} Rectangle { 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 4809b7d410b..d5b1fef1e45 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/wizard.json @@ -23,9 +23,7 @@ { "key": "UIClassFileName", "value": "%{JS: Util.fileName('%{UIClassName}', 'ui.qml')}" }, { "key": "QtQuickVersion", "value": "%{JS: %{TargetQtVersion}.TargetQuickVersion}" }, { "key": "QtQuickFeature", "value": "QtSupport.Wizards.FeatureQtQuick.%{QtQuickVersion}" }, - { "key": "QtQuickControlsStyleInternalQt5", "value": "%{JS: %{ControlsStyle}.QtQuickControlsStyle}" }, - { "key": "QtQuickControlsStyleInternalQt6", "value": "%{JS: value('QtQuickControlsStyleInternalQt5') === 'Default' ? 'Basic' : value('QtQuickControlsStyleInternalQt5')}" }, - { "key": "QtQuickControlsStyle", "value": "%{JS: value('IsQt6Project') === 'true' ? value('QtQuickControlsStyleInternalQt6') : value('QtQuickControlsStyleInternalQt5')}" }, + { "key": "QtQuickControlsStyle", "value": "Basic" }, { "key": "QtQuickControlsStyleTheme", "value": "%{JS: %{ControlsStyle}.QtQuickControlsStyleTheme}" }, { "key": "ApplicationImport", "value": "%{JS: value('IsQt6Project') === 'true' ? '%{ImportModuleName}' : '%{ImportModuleName} 1.0'}" }, { "key": "UseStandardResolution", "value": "%{JS: value('CustomScreenWidth') === '' || value('CustomScreenHeight') === ''}" }, @@ -134,10 +132,10 @@ "items": [ { - "trKey": "Default", + "trKey": "Basic", "value": "({ - 'QtQuickControlsStyle': 'Default', + 'QtQuickControlsStyle': 'Basic', 'QtQuickControlsStyleTheme': '' })" }, diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/application/wizard.json b/share/qtcreator/qmldesigner/studio_templates/projects/application/wizard.json index e1a25e23fb5..0708838a5ae 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/application/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/application/wizard.json @@ -23,16 +23,14 @@ { "key": "IsQt6Project", "value": "%{JS: value('QtQuickVersion') !== '2.15' }" }, { "key": "QtQuickVersion", "value": "%{JS: %{TargetQtVersion}.TargetQuickVersion}" }, { "key": "QtQuickFeature", "value": "QtSupport.Wizards.FeatureQtQuick.%{QtQuickVersion}" }, - { "key": "QtQuickControlsStyleInternalQt5", "value": "%{JS: %{ControlsStyle}.QtQuickControlsStyle}" }, - { "key": "QtQuickControlsStyleInternalQt6", "value": "%{JS: value('QtQuickControlsStyleInternalQt5') === 'Default' ? 'Basic' : value('QtQuickControlsStyleInternalQt5')}" }, - { "key": "QtQuickControlsStyle", "value": "%{JS: value('IsQt6Project') === 'true' ? value('QtQuickControlsStyleInternalQt6') : value('QtQuickControlsStyleInternalQt5')}" }, + { "key": "QtQuickControlsStyle", "value": "Basic" }, { "key": "QtQuickControlsStyleTheme", "value": "%{JS: %{ControlsStyle}.QtQuickControlsStyleTheme}" }, { "key": "ApplicationImport", "value": "%{JS: value('IsQt6Project') === 'true' ? '%{ImportModuleName}' : '%{ImportModuleName} 1.0'}" }, { "key": "UseStandardResolution", "value": "%{JS: value('CustomScreenWidth') === '' || value('CustomScreenHeight') === ''}" }, { "key": "ScreenWidth", "value": "%{JS: value('UseStandardResolution') === 'true' ? %{ScreenFactor}.ScreenWidth : value('CustomScreenWidth')}" }, { "key": "ScreenHeight", "value": "%{JS: value('UseStandardResolution') === 'true' ? %{ScreenFactor}.ScreenHeight : value('CustomScreenHeight')}" }, { "key": "UseVirtualKeyboardDefault", "value": "%{JS: false}" }, - { "key": "DefaultStyle", "value": "%{JS: value('IsQt6Project') === 'true' ? 'Basic' : 'Default'}" }, + { "key": "DefaultStyle", "value": "Basic" }, { "key": "ImportModuleVersion", "value": "%{JS: value('IsQt6Project') === 'true' ? '' : '1.0'}" } ], @@ -134,10 +132,10 @@ "items": [ { - "trKey": "Default", + "trKey": "Basic", "value": "({ - 'QtQuickControlsStyle': 'Default', + 'QtQuickControlsStyle': 'Basic', 'QtQuickControlsStyleTheme': '' })" }, 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 0adf6e1c98e..79f9385dad6 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 @@ -28,6 +28,12 @@ target_link_libraries(%{ProjectName}App PRIVATE Qt6::Quick ) +set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/qml) +set(QML_IMPORT_PATH ${QT_QML_OUTPUT_DIRECTORY} + CACHE STRING "Import paths for Qt Creator's code model" + FORCE +) + if (BUILD_QDS_COMPONENTS) include(${CMAKE_CURRENT_SOURCE_DIR}/qmlcomponents) endif() 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 50840bae070..6169688f16b 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/common/app.qmlproject.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/common/app.qmlproject.tpl @@ -68,6 +68,11 @@ Project { directory: "asset_imports" } + Files { + filter: "*.qad" + directory: "asset_imports" + } + Files { filter: "*.qml" directory: "asset_imports" @@ -105,7 +110,7 @@ Project { /* Required for deployment */ targetDirectory: "/opt/%{ProjectName}" - qdsVersion: "4.3" + qdsVersion: "4.4" quickVersion: "%{QtQuickVersion}" diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/common/qmlcomponents.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/common/qmlcomponents.tpl index 81f2ceba948..2e940be0329 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/common/qmlcomponents.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/common/qmlcomponents.tpl @@ -8,7 +8,7 @@ set(QT_QML_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/qml") include(FetchContent) FetchContent_Declare( ds - GIT_TAG qds-4.3 + GIT_TAG qds-4.4 GIT_REPOSITORY https://code.qt.io/qt-labs/qtquickdesigner-components.git ) @@ -24,6 +24,7 @@ target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE QuickStudioMultiTextplugin QuickStudioEventSimulatorplugin QuickStudioEventSystemplugin + QuickStudioUtilsplugin ) add_subdirectory(${ds_SOURCE_DIR} ${ds_BINARY_DIR}) 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 58fb3f30ef7..913eb0bf28b 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/desktop-launcher/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/desktop-launcher/wizard.json @@ -23,9 +23,7 @@ { "key": "IsQt6Project", "value": "%{JS: value('QtQuickVersion') !== '2.15' }" }, { "key": "QtQuickVersion", "value": "%{JS: %{TargetQtVersion}.TargetQuickVersion}" }, { "key": "QtQuickFeature", "value": "QtSupport.Wizards.FeatureQtQuick.%{QtQuickVersion}" }, - { "key": "QtQuickControlsStyleInternalQt5", "value": "%{JS: %{ControlsStyle}.QtQuickControlsStyle}" }, - { "key": "QtQuickControlsStyleInternalQt6", "value": "%{JS: value('QtQuickControlsStyleInternalQt5') === 'Default' ? 'Basic' : value('QtQuickControlsStyleInternalQt5')}" }, - { "key": "QtQuickControlsStyle", "value": "%{JS: value('IsQt6Project') === 'true' ? value('QtQuickControlsStyleInternalQt6') : value('QtQuickControlsStyleInternalQt5')}" }, + { "key": "QtQuickControlsStyle", "value": "Basic" }, { "key": "QtQuickControlsStyleTheme", "value": "%{JS: %{ControlsStyle}.QtQuickControlsStyleTheme}" }, { "key": "ApplicationImport", "value": "%{JS: value('IsQt6Project') === 'true' ? '%{ImportModuleName}' : '%{ImportModuleName} 1.0'}" }, { "key": "UseStandardResolution", "value": "%{JS: value('CustomScreenWidth') === '' || value('CustomScreenHeight') === ''}" }, @@ -132,10 +130,10 @@ "items": [ { - "trKey": "Default", + "trKey": "Basic", "value": "({ - 'QtQuickControlsStyle': 'Default', + 'QtQuickControlsStyle': 'Basic', 'QtQuickControlsStyleTheme': '' })" }, 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 d79059a6a5a..944b6b6289c 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-scroll/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-scroll/wizard.json @@ -23,9 +23,7 @@ { "key": "IsQt6Project", "value": "%{JS: value('QtQuickVersion') !== '2.15' }" }, { "key": "QtQuickVersion", "value": "%{JS: %{TargetQtVersion}.TargetQuickVersion}" }, { "key": "QtQuickFeature", "value": "QtSupport.Wizards.FeatureQtQuick.%{QtQuickVersion}" }, - { "key": "QtQuickControlsStyleInternalQt5", "value": "%{JS: %{ControlsStyle}.QtQuickControlsStyle}" }, - { "key": "QtQuickControlsStyleInternalQt6", "value": "%{JS: value('QtQuickControlsStyleInternalQt5') === 'Default' ? 'Basic' : value('QtQuickControlsStyleInternalQt5')}" }, - { "key": "QtQuickControlsStyle", "value": "%{JS: value('IsQt6Project') === 'true' ? value('QtQuickControlsStyleInternalQt6') : value('QtQuickControlsStyleInternalQt5')}" }, + { "key": "QtQuickControlsStyle", "value": "Basic" }, { "key": "QtQuickControlsStyleTheme", "value": "%{JS: %{ControlsStyle}.QtQuickControlsStyleTheme}" }, { "key": "ApplicationImport", "value": "%{JS: value('IsQt6Project') === 'true' ? '%{ImportModuleName}' : '%{ImportModuleName} 1.0'}" }, { "key": "UseStandardResolution", "value": "%{JS: value('CustomScreenWidth') === '' || value('CustomScreenHeight') === ''}" }, @@ -100,10 +98,10 @@ "items": [ { - "trKey": "Default", + "trKey": "Basic", "value": "({ - 'QtQuickControlsStyle': 'Default', + 'QtQuickControlsStyle': 'Basic', 'QtQuickControlsStyleTheme': '' })" }, 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 cd837cf5688..c8733770e07 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-stack/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-stack/wizard.json @@ -21,9 +21,7 @@ { "key": "IsQt6Project", "value": "%{JS: value('QtQuickVersion') !== '2.15' }" }, { "key": "QtQuickVersion", "value": "%{JS: %{TargetQtVersion}.TargetQuickVersion}" }, { "key": "QtQuickFeature", "value": "QtSupport.Wizards.FeatureQtQuick.%{QtQuickVersion}" }, - { "key": "QtQuickControlsStyleInternalQt5", "value": "%{JS: %{ControlsStyle}.QtQuickControlsStyle}" }, - { "key": "QtQuickControlsStyleInternalQt6", "value": "%{JS: value('QtQuickControlsStyleInternalQt5') === 'Default' ? 'Basic' : value('QtQuickControlsStyleInternalQt5')}" }, - { "key": "QtQuickControlsStyle", "value": "%{JS: value('IsQt6Project') === 'true' ? value('QtQuickControlsStyleInternalQt6') : value('QtQuickControlsStyleInternalQt5')}" }, + { "key": "QtQuickControlsStyle", "value": "Basic" }, { "key": "QtQuickControlsStyleTheme", "value": "%{JS: %{ControlsStyle}.QtQuickControlsStyleTheme}" }, { "key": "ApplicationImport", "value": "%{JS: value('IsQt6Project') === 'true' ? '%{ImportModuleName}' : '%{ImportModuleName} 1.0'}" }, { "key": "UseStandardResolution", "value": "%{JS: value('CustomScreenWidth') === '' || value('CustomScreenHeight') === ''}" }, @@ -98,10 +96,10 @@ "items": [ { - "trKey": "Default", + "trKey": "Basic", "value": "({ - 'QtQuickControlsStyle': 'Default', + 'QtQuickControlsStyle': 'Basic', 'QtQuickControlsStyleTheme': '' })" }, 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 abef03752e2..295c85aa63a 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-swipe/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-swipe/wizard.json @@ -21,9 +21,7 @@ { "key": "IsQt6Project", "value": "%{JS: value('QtQuickVersion') !== '2.15' }" }, { "key": "QtQuickVersion", "value": "%{JS: %{TargetQtVersion}.TargetQuickVersion}" }, { "key": "QtQuickFeature", "value": "QtSupport.Wizards.FeatureQtQuick.%{QtQuickVersion}" }, - { "key": "QtQuickControlsStyleInternalQt5", "value": "%{JS: %{ControlsStyle}.QtQuickControlsStyle}" }, - { "key": "QtQuickControlsStyleInternalQt6", "value": "%{JS: value('QtQuickControlsStyleInternalQt5') === 'Default' ? 'Basic' : value('QtQuickControlsStyleInternalQt5')}" }, - { "key": "QtQuickControlsStyle", "value": "%{JS: value('IsQt6Project') === 'true' ? value('QtQuickControlsStyleInternalQt6') : value('QtQuickControlsStyleInternalQt5')}" }, + { "key": "QtQuickControlsStyle", "value": "Basic" }, { "key": "QtQuickControlsStyleTheme", "value": "%{JS: %{ControlsStyle}.QtQuickControlsStyleTheme}" }, { "key": "ApplicationImport", "value": "%{JS: value('IsQt6Project') === 'true' ? '%{ImportModuleName}' : '%{ImportModuleName} 1.0'}" }, { "key": "UseStandardResolution", "value": "%{JS: value('CustomScreenWidth') === '' || value('CustomScreenHeight') === ''}" }, @@ -98,10 +96,10 @@ "items": [ { - "trKey": "Default", + "trKey": "Basic", "value": "({ - 'QtQuickControlsStyle': 'Default', + 'QtQuickControlsStyle': 'Basic', 'QtQuickControlsStyleTheme': '' })" }, diff --git a/share/qtcreator/qmldesigner/toolbar/Main.qml b/share/qtcreator/qmldesigner/toolbar/Main.qml index 1ddedd08b48..63b3aef5653 100644 --- a/share/qtcreator/qmldesigner/toolbar/Main.qml +++ b/share/qtcreator/qmldesigner/toolbar/Main.qml @@ -26,26 +26,75 @@ Rectangle { anchors.fill: parent visible: !backend.isInDesignMode - ToolbarButton { - id: homeOther - anchors.verticalCenter: parent.verticalCenter + Rectangle { + id: returnExtended + height: homeOther.height + width: backTo.visible ? (homeOther.width + backTo.width + contentRow.spacing + 6) : homeOther.width + anchors.verticalCenter: topToolbarOtherMode.verticalCenter anchors.left: parent.left anchors.leftMargin: 10 - tooltip: backend.isDesignModeEnabled ? qsTr("Switch to Design Mode.") - : qsTr("Switch to Welcome Mode.") - buttonIcon: backend.isDesignModeEnabled ? StudioTheme.Constants.designMode_large - : StudioTheme.Constants.home_large - onClicked: backend.triggerModeChange() - } + color: StudioTheme.Values.themeToolbarBackground + radius: StudioTheme.Values.smallRadius + state: "default" - Text { - id: backTo - visible: backend.isDesignModeEnabled - anchors.verticalCenter: parent.verticalCenter - text: qsTr("Return to Design") - anchors.left: homeOther.right - anchors.leftMargin: 10 - color: StudioTheme.Values.themeTextColor + Row { + id: contentRow + spacing: 6 + anchors.fill: parent + + ToolbarButton { + id: homeOther + tooltip: backend.isDesignModeEnabled ? qsTr("Switch to Design Mode.") + : qsTr("Switch to Welcome Mode.") + buttonIcon: backend.isDesignModeEnabled ? StudioTheme.Constants.designMode_large + : StudioTheme.Constants.home_large + hover: mouseArea.containsMouse + press: mouseArea.pressed + + onClicked: backend.triggerModeChange() + + } + + Text { + id: backTo + height: homeOther.height + visible: backend.isDesignModeEnabled + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + text: qsTr("Return to Design") + color: StudioTheme.Values.themeTextColor + } + } + + MouseArea{ + id: mouseArea + anchors.fill: parent + hoverEnabled: true + onClicked: homeOther.onClicked() + } + + states: [ + State { + name: "default" + when: !mouseArea.containsMouse && !mouseArea.pressed + }, + State { + name: "hover" + when: mouseArea.containsMouse && !mouseArea.pressed + PropertyChanges { + target: returnExtended + color: StudioTheme.Values.themeControlBackground_topToolbarHover + } + }, + State { + name: "pressed" + when: mouseArea.pressed + PropertyChanges { + target: returnExtended + color: StudioTheme.Values.themeInteraction + } + } + ] } } @@ -215,7 +264,7 @@ Rectangle { ToolbarButton { id: enterComponent anchors.verticalCenter: parent.verticalCenter - anchors.right: workspaces.left + anchors.right: lockWorkspace.left anchors.rightMargin: 10 enabled: goIntoComponentBackend.available tooltip: goIntoComponentBackend.tooltip @@ -230,6 +279,22 @@ Rectangle { } } + ToolbarButton { + id: lockWorkspace + anchors.verticalCenter: parent.verticalCenter + anchors.right: workspaces.left + anchors.rightMargin: 10 + tooltip: qsTr("Sets the visible Views to immovable across the Workspaces.") + buttonIcon: backend.lockWorkspace ? StudioTheme.Constants.lockOn + : StudioTheme.Constants.lockOff + visible: !root.flyoutEnabled + checkable: true + checked: backend.lockWorkspace + checkedInverted: true + + onClicked: backend.setLockWorkspace(lockWorkspace.checked) + } + StudioControls.TopLevelComboBox { id: workspaces style: StudioTheme.Values.toolbarStyle @@ -376,6 +441,19 @@ Rectangle { onClicked: backend.editGlobalAnnoation() } + ToolbarButton { + id: lockWorkspaceFlyout + style: StudioTheme.Values.statusbarButtonStyle + anchors.verticalCenter: parent.verticalCenter + tooltip: lockWorkspace.tooltip + buttonIcon: backend.lockWorkspace ? StudioTheme.Constants.lockOn + : StudioTheme.Constants.lockOff + checkable: true + checked: backend.lockWorkspace + + onClicked: backend.setLockWorkspace(lockWorkspaceFlyout.checked) + } + ToolbarButton { anchors.verticalCenter: parent.verticalCenter style: StudioTheme.Values.primaryToolbarStyle diff --git a/share/qtcreator/themes/dark.creatortheme b/share/qtcreator/themes/dark.creatortheme index 7cd5888e15e..09bdd9931d2 100644 --- a/share/qtcreator/themes/dark.creatortheme +++ b/share/qtcreator/themes/dark.creatortheme @@ -35,6 +35,7 @@ graniteGrey=ff343434 ashGrey=ff434343 midnightGrey=ff333333 dawnGrey=ff2a2a2a +coalGrey=ff282828 offBlack=ff202020 nearBlack=ff1b1b1b fullBlack=ff000000 @@ -102,6 +103,9 @@ DSpopoutButtonBorder_disabled=offBlack ;4.4 DSconnectionCodeEditor=midnightGrey +DSconnectionEditorMicroToolbar=coalGrey +DSconnectionEditorButtonBackground_hover=midnightGrey +DSconnectionEditorButtonBorder_hover=duskGrey DSpillText=fullWhite DSpillTextSelected=fullBlack DspillTextEdit=fullWhite diff --git a/share/qtcreator/themes/default.creatortheme b/share/qtcreator/themes/default.creatortheme index d47e0df1a68..5e03ed155f0 100644 --- a/share/qtcreator/themes/default.creatortheme +++ b/share/qtcreator/themes/default.creatortheme @@ -27,6 +27,7 @@ graniteGrey=ff343434 ashGrey=ff434343 midnightGrey=ff333333 dawnGrey=ff2a2a2a +coalGrey=ff282828 offBlack=ff202020 nearBlack=ff1b1b1b fullBlack=ff000000 @@ -96,6 +97,9 @@ DSpopoutButtonBorder_disabled=offWhite ;4.4 DSconnectionCodeEditor=lightWhite +DSconnectionEditorMicroToolbar=concreteGrey +DSconnectionEditorButtonBackground_hover=lightWhite +DSconnectionEditorButtonBorder_hover=duskGrey DSpillText=fullWhite DSpillTextSelected=fullBlack DspillTextEdit=fullBlack diff --git a/share/qtcreator/themes/design-light.creatortheme b/share/qtcreator/themes/design-light.creatortheme index 9e1e86d3188..46ccc654a62 100644 --- a/share/qtcreator/themes/design-light.creatortheme +++ b/share/qtcreator/themes/design-light.creatortheme @@ -40,6 +40,7 @@ graniteGrey=ff343434 ashGrey=ff434343 midnightGrey=ff333333 dawnGrey=ff2a2a2a +coalGrey=ff282828 offBlack=ff202020 nearBlack=ff1b1b1b fullBlack=ff000000 @@ -109,6 +110,9 @@ DSpopoutButtonBorder_disabled=offWhite ;4.4 DSconnectionCodeEditor=lightWhite +DSconnectionEditorMicroToolbar=concreteGrey +DSconnectionEditorButtonBackground_hover=lightWhite +DSconnectionEditorButtonBorder_hover=duskGrey DSpillText=fullWhite DSpillTextSelected=fullBlack DspillTextEdit=fullBlack @@ -211,9 +215,8 @@ DStableHeaderText=ff00ff00 DSdockContainerBackground=ff323232 DSdockContainerSplitter=ff323232 -DSdockAreaBackground=ff262728 - -DSdockWidgetBackground=ff00ff00 +DSdockAreaBackground=ffeaeaea +DSdockWidgetBackground=ffeaeaea DSdockWidgetSplitter=ff595959 DSdockWidgetTitleBar=ffeaeaea diff --git a/share/qtcreator/themes/design.creatortheme b/share/qtcreator/themes/design.creatortheme index cc2499c6e72..57403cec598 100644 --- a/share/qtcreator/themes/design.creatortheme +++ b/share/qtcreator/themes/design.creatortheme @@ -38,6 +38,7 @@ graniteGrey=ff343434 ashGrey=ff434343 midnightGrey=ff333333 dawnGrey=ff2a2a2a +coalGrey=ff282828 offBlack=ff202020 nearBlack=ff1b1b1b fullBlack=ff000000 @@ -107,6 +108,9 @@ DSpopoutButtonBorder_disabled=offBlack ;4.4 DSconnectionCodeEditor=midnightGrey +DSconnectionEditorMicroToolbar=coalGrey +DSconnectionEditorButtonBackground_hover=midnightGrey +DSconnectionEditorButtonBorder_hover=duskGrey DSpillText=fullWhite DSpillTextSelected=fullBlack DspillTextEdit=fullWhite @@ -228,8 +232,8 @@ DStableHeaderText=ff00ff00 DSdockContainerBackground=ff242424 DSdockContainerSplitter=ff323232 -DSdockAreaBackground=ff262728 -DSdockWidgetBackground=ff00ff00 +DSdockAreaBackground=dawnGrey +DSdockWidgetBackground=dawnGrey DStitleBarText=ffdadada DStitleBarIcon=ffffffff DStitleBarButtonHover=40ffffff diff --git a/share/qtcreator/themes/flat-dark.creatortheme b/share/qtcreator/themes/flat-dark.creatortheme index b384c8f7baf..b6998e73df7 100644 --- a/share/qtcreator/themes/flat-dark.creatortheme +++ b/share/qtcreator/themes/flat-dark.creatortheme @@ -39,6 +39,7 @@ graniteGrey=ff343434 ashGrey=ff434343 midnightGrey=ff333333 dawnGrey=ff2a2a2a +coalGrey=ff282828 offBlack=ff202020 nearBlack=ff1b1b1b fullBlack=ff000000 @@ -107,6 +108,9 @@ DSpopoutButtonBorder_disabled=offBlack ;4.4 DSconnectionCodeEditor=midnightGrey +DSconnectionEditorMicroToolbar=coalGrey +DSconnectionEditorButtonBackground_hover=midnightGrey +DSconnectionEditorButtonBorder_hover=duskGrey DSpillText=fullWhite DSpillTextSelected=fullBlack DspillTextEdit=fullWhite diff --git a/share/qtcreator/themes/flat-light.creatortheme b/share/qtcreator/themes/flat-light.creatortheme index 1afd27f2888..a97cd158e99 100644 --- a/share/qtcreator/themes/flat-light.creatortheme +++ b/share/qtcreator/themes/flat-light.creatortheme @@ -36,6 +36,7 @@ graniteGrey=ff343434 ashGrey=ff434343 midnightGrey=ff333333 dawnGrey=ff2a2a2a +coalGrey=ff282828 offBlack=ff202020 nearBlack=ff1b1b1b fullBlack=ff000000 @@ -105,6 +106,9 @@ DSpopoutButtonBorder_disabled=offWhite ;4.4 DSconnectionCodeEditor=lightWhite +DSconnectionEditorMicroToolbar=concreteGrey +DSconnectionEditorButtonBackground_hover=lightWhite +DSconnectionEditorButtonBorder_hover=duskGrey DSpillText=fullWhite DSpillTextSelected=fullBlack DspillTextEdit=fullBlack diff --git a/share/qtcreator/themes/flat.creatortheme b/share/qtcreator/themes/flat.creatortheme index e1bda7b18a3..1f5bdb40dbc 100644 --- a/share/qtcreator/themes/flat.creatortheme +++ b/share/qtcreator/themes/flat.creatortheme @@ -33,6 +33,7 @@ graniteGrey=ff343434 ashGrey=ff434343 midnightGrey=ff333333 dawnGrey=ff2a2a2a +coalGrey=ff282828 offBlack=ff202020 nearBlack=ff1b1b1b fullBlack=ff000000 @@ -101,6 +102,9 @@ DSpopoutButtonBorder_disabled=offBlack ;4.4 DSconnectionCodeEditor=midnightGrey +DSconnectionEditorMicroToolbar=coalGrey +DSconnectionEditorButtonBackground_hover=midnightGrey +DSconnectionEditorButtonBorder_hover=duskGrey DSpillText=fullWhite DSpillTextSelected=fullBlack DspillTextEdit=fullWhite diff --git a/src/app/main.cpp b/src/app/main.cpp index b555596f875..ef5f5a1eb4b 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -524,6 +525,15 @@ int main(int argc, char **argv) } } + if (Utils::HostOsInfo::isMacHost()) { + QSurfaceFormat surfaceFormat; + surfaceFormat.setStencilBufferSize(8); + surfaceFormat.setDepthBufferSize(24); + surfaceFormat.setVersion(4, 1); + surfaceFormat.setProfile(QSurfaceFormat::CoreProfile); + QSurfaceFormat::setDefaultFormat(surfaceFormat); + } + qputenv("QSG_RHI_BACKEND", "opengl"); if (qEnvironmentVariableIsSet("QTCREATOR_DISABLE_NATIVE_MENUBAR") diff --git a/src/libs/3rdparty/CMakeLists.txt b/src/libs/3rdparty/CMakeLists.txt index 165c5f5ca02..8445a0d01b1 100644 --- a/src/libs/3rdparty/CMakeLists.txt +++ b/src/libs/3rdparty/CMakeLists.txt @@ -2,6 +2,7 @@ add_subdirectory(cplusplus) add_subdirectory(syntax-highlighting) add_subdirectory(libvterm) add_subdirectory(libptyqt) +add_subdirectory(qrcodegen) add_subdirectory(qtkeychain) if(WIN32) diff --git a/src/libs/3rdparty/qrcodegen/CMakeLists.txt b/src/libs/3rdparty/qrcodegen/CMakeLists.txt new file mode 100644 index 00000000000..15a03147ee4 --- /dev/null +++ b/src/libs/3rdparty/qrcodegen/CMakeLists.txt @@ -0,0 +1,11 @@ +add_qtc_library(QrCodeGenerator STATIC + CONDITION TARGET Qt6::Quick AND TARGET Qt6::Svg + PUBLIC_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/src + DEPENDS + Qt6::Qml Qt6::Quick Qt6::Svg + SOURCES + src/qrcodegen.cpp + src/qrcodegen.h + src/qrcodeimageprovider.cpp + src/qrcodeimageprovider.h +) diff --git a/src/libs/3rdparty/qrcodegen/LICENSE b/src/libs/3rdparty/qrcodegen/LICENSE new file mode 100644 index 00000000000..da7a8e0aae4 --- /dev/null +++ b/src/libs/3rdparty/qrcodegen/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Alex Spataru + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/libs/3rdparty/qrcodegen/README.md b/src/libs/3rdparty/qrcodegen/README.md new file mode 100644 index 00000000000..dc811384775 --- /dev/null +++ b/src/libs/3rdparty/qrcodegen/README.md @@ -0,0 +1,25 @@ +# Qt QR Code Generator Library + +Qt QR Code Generator is a simple C++ class that uses the [qrcodegen](https://github.com/nayuki/QR-Code-generator) library to generate QR codes from QStrings in Qt applications. + +[![Screenshot](example/screenshot.png)](example/screenshot.png) + +## Usage + +1. Copy the *Qt-QrCodeGenerator* folder in your `lib` folder. +2. Include the *Qt-QrCodeGenerator* project include (pri) file using the `include()` qmake function. +3. Use the `QrCodeGenerator` class in your code: + +```cpp +#include + +QrCodeGenerator generator; +QString data = "https://www.example.com"; +QImage qrCodeImage = generator.generateQr(data); +``` + +4. That's all! Check the [example](example) project as a reference for your project if needed. + +## License + +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. diff --git a/src/libs/3rdparty/qrcodegen/src/qrcodegen.cpp b/src/libs/3rdparty/qrcodegen/src/qrcodegen.cpp new file mode 100644 index 00000000000..e594b8b6b75 --- /dev/null +++ b/src/libs/3rdparty/qrcodegen/src/qrcodegen.cpp @@ -0,0 +1,863 @@ +/* + * QR Code generator library (C++) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +#include "qrcodegen.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using std::int8_t; +using std::size_t; +using std::uint8_t; +using std::vector; + +namespace qrcodegen { + +/*---- Class QrSegment ----*/ + +QrSegment::Mode::Mode(int mode, int cc0, int cc1, int cc2) + : modeBits(mode) +{ + numBitsCharCount[0] = cc0; + numBitsCharCount[1] = cc1; + numBitsCharCount[2] = cc2; +} + +int QrSegment::Mode::getModeBits() const +{ + return modeBits; +} + +int QrSegment::Mode::numCharCountBits(int ver) const +{ + return numBitsCharCount[(ver + 7) / 17]; +} + +const QrSegment::Mode QrSegment::Mode::NUMERIC(0x1, 10, 12, 14); +const QrSegment::Mode QrSegment::Mode::ALPHANUMERIC(0x2, 9, 11, 13); +const QrSegment::Mode QrSegment::Mode::BYTE(0x4, 8, 16, 16); +const QrSegment::Mode QrSegment::Mode::KANJI(0x8, 8, 10, 12); +const QrSegment::Mode QrSegment::Mode::ECI(0x7, 0, 0, 0); + +QrSegment QrSegment::makeBytes(const vector &data) +{ + if (data.size() > static_cast(INT_MAX)) + throw std::length_error("Data too long"); + BitBuffer bb; + for (uint8_t b : data) + bb.appendBits(b, 8); + return QrSegment(Mode::BYTE, static_cast(data.size()), std::move(bb)); +} + +QrSegment QrSegment::makeNumeric(const char *digits) +{ + BitBuffer bb; + int accumData = 0; + int accumCount = 0; + int charCount = 0; + for (; *digits != '\0'; digits++, charCount++) { + char c = *digits; + if (c < '0' || c > '9') + throw std::domain_error("String contains non-numeric characters"); + accumData = accumData * 10 + (c - '0'); + accumCount++; + if (accumCount == 3) { + bb.appendBits(static_cast(accumData), 10); + accumData = 0; + accumCount = 0; + } + } + if (accumCount > 0) // 1 or 2 digits remaining + bb.appendBits(static_cast(accumData), accumCount * 3 + 1); + return QrSegment(Mode::NUMERIC, charCount, std::move(bb)); +} + +QrSegment QrSegment::makeAlphanumeric(const char *text) +{ + BitBuffer bb; + int accumData = 0; + int accumCount = 0; + int charCount = 0; + for (; *text != '\0'; text++, charCount++) { + const char *temp = std::strchr(ALPHANUMERIC_CHARSET, *text); + if (temp == nullptr) + throw std::domain_error("String contains unencodable characters in alphanumeric mode"); + accumData = accumData * 45 + static_cast(temp - ALPHANUMERIC_CHARSET); + accumCount++; + if (accumCount == 2) { + bb.appendBits(static_cast(accumData), 11); + accumData = 0; + accumCount = 0; + } + } + if (accumCount > 0) // 1 character remaining + bb.appendBits(static_cast(accumData), 6); + return QrSegment(Mode::ALPHANUMERIC, charCount, std::move(bb)); +} + +vector QrSegment::makeSegments(const char *text) +{ + // Select the most efficient segment encoding automatically + vector result; + if (*text == '\0') + ; // Leave result empty + else if (isNumeric(text)) + result.push_back(makeNumeric(text)); + else if (isAlphanumeric(text)) + result.push_back(makeAlphanumeric(text)); + else { + vector bytes; + for (; *text != '\0'; text++) + bytes.push_back(static_cast(*text)); + result.push_back(makeBytes(bytes)); + } + return result; +} + +QrSegment QrSegment::makeEci(long assignVal) +{ + BitBuffer bb; + if (assignVal < 0) + throw std::domain_error("ECI assignment value out of range"); + else if (assignVal < (1 << 7)) + bb.appendBits(static_cast(assignVal), 8); + else if (assignVal < (1 << 14)) { + bb.appendBits(2, 2); + bb.appendBits(static_cast(assignVal), 14); + } else if (assignVal < 1000000L) { + bb.appendBits(6, 3); + bb.appendBits(static_cast(assignVal), 21); + } else + throw std::domain_error("ECI assignment value out of range"); + return QrSegment(Mode::ECI, 0, std::move(bb)); +} + +QrSegment::QrSegment(const Mode &md, int numCh, const std::vector &dt) + : mode(&md) + , numChars(numCh) + , data(dt) +{ + if (numCh < 0) + throw std::domain_error("Invalid value"); +} + +QrSegment::QrSegment(const Mode &md, int numCh, std::vector &&dt) + : mode(&md) + , numChars(numCh) + , data(std::move(dt)) +{ + if (numCh < 0) + throw std::domain_error("Invalid value"); +} + +int QrSegment::getTotalBits(const vector &segs, int version) +{ + int result = 0; + for (const QrSegment &seg : segs) { + int ccbits = seg.mode->numCharCountBits(version); + if (seg.numChars >= (1L << ccbits)) + return -1; // The segment's length doesn't fit the field's bit width + if (4 + ccbits > INT_MAX - result) + return -1; // The sum will overflow an int type + result += 4 + ccbits; + if (seg.data.size() > static_cast(INT_MAX - result)) + return -1; // The sum will overflow an int type + result += static_cast(seg.data.size()); + } + return result; +} + +bool QrSegment::isNumeric(const char *text) +{ + for (; *text != '\0'; text++) { + char c = *text; + if (c < '0' || c > '9') + return false; + } + return true; +} + +bool QrSegment::isAlphanumeric(const char *text) +{ + for (; *text != '\0'; text++) { + if (std::strchr(ALPHANUMERIC_CHARSET, *text) == nullptr) + return false; + } + return true; +} + +const QrSegment::Mode &QrSegment::getMode() const +{ + return *mode; +} + +int QrSegment::getNumChars() const +{ + return numChars; +} + +const std::vector &QrSegment::getData() const +{ + return data; +} + +const char *QrSegment::ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"; + +/*---- Class QrCode ----*/ + +int QrCode::getFormatBits(Ecc ecl) +{ + switch (ecl) { + case Ecc::LOW: + return 1; + case Ecc::MEDIUM: + return 0; + case Ecc::QUARTILE: + return 3; + case Ecc::HIGH: + return 2; + default: + throw std::logic_error("Unreachable"); + } +} + +QrCode QrCode::encodeText(const char *text, Ecc ecl) +{ + vector segs = QrSegment::makeSegments(text); + return encodeSegments(segs, ecl); +} + +QrCode QrCode::encodeBinary(const vector &data, Ecc ecl) +{ + vector segs{QrSegment::makeBytes(data)}; + return encodeSegments(segs, ecl); +} + +QrCode QrCode::encodeSegments( + const vector &segs, Ecc ecl, int minVersion, int maxVersion, int mask, bool boostEcl) +{ + if (!(MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= MAX_VERSION) + || mask < -1 || mask > 7) + throw std::invalid_argument("Invalid value"); + + // Find the minimal version number to use + int version, dataUsedBits; + for (version = minVersion;; version++) { + int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available + dataUsedBits = QrSegment::getTotalBits(segs, version); + if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits) + break; // This version number is found to be suitable + if (version >= maxVersion) { // All versions in the range could not fit the given data + std::ostringstream sb; + if (dataUsedBits == -1) + sb << "Segment too long"; + else { + sb << "Data length = " << dataUsedBits << " bits, "; + sb << "Max capacity = " << dataCapacityBits << " bits"; + } + throw data_too_long(sb.str()); + } + } + assert(dataUsedBits != -1); + + // Increase the error correction level while the data still fits in the current version number + for (Ecc newEcl : {Ecc::MEDIUM, Ecc::QUARTILE, Ecc::HIGH}) { // From low to high + if (boostEcl && dataUsedBits <= getNumDataCodewords(version, newEcl) * 8) + ecl = newEcl; + } + + // Concatenate all segments to create the data bit string + BitBuffer bb; + for (const QrSegment &seg : segs) { + bb.appendBits(static_cast(seg.getMode().getModeBits()), 4); + bb.appendBits(static_cast(seg.getNumChars()), + seg.getMode().numCharCountBits(version)); + bb.insert(bb.end(), seg.getData().begin(), seg.getData().end()); + } + assert(bb.size() == static_cast(dataUsedBits)); + + // Add terminator and pad up to a byte if applicable + size_t dataCapacityBits = static_cast(getNumDataCodewords(version, ecl)) * 8; + assert(bb.size() <= dataCapacityBits); + bb.appendBits(0, std::min(4, static_cast(dataCapacityBits - bb.size()))); + bb.appendBits(0, (8 - static_cast(bb.size() % 8)) % 8); + assert(bb.size() % 8 == 0); + + // Pad with alternating bytes until data capacity is reached + for (uint8_t padByte = 0xEC; bb.size() < dataCapacityBits; padByte ^= 0xEC ^ 0x11) + bb.appendBits(padByte, 8); + + // Pack bits into bytes in big endian + vector dataCodewords(bb.size() / 8); + for (size_t i = 0; i < bb.size(); i++) + dataCodewords.at(i >> 3) |= (bb.at(i) ? 1 : 0) << (7 - (i & 7)); + + // Create the QR Code object + return QrCode(version, ecl, dataCodewords, mask); +} + +QrCode::QrCode(int ver, Ecc ecl, const vector &dataCodewords, int msk) + : // Initialize fields and check arguments + version(ver) + , errorCorrectionLevel(ecl) +{ + if (ver < MIN_VERSION || ver > MAX_VERSION) + throw std::domain_error("Version value out of range"); + if (msk < -1 || msk > 7) + throw std::domain_error("Mask value out of range"); + size = ver * 4 + 17; + size_t sz = static_cast(size); + modules = vector>(sz, vector(sz)); // Initially all light + isFunction = vector>(sz, vector(sz)); + + // Compute ECC, draw modules + drawFunctionPatterns(); + const vector allCodewords = addEccAndInterleave(dataCodewords); + drawCodewords(allCodewords); + + // Do masking + if (msk == -1) { // Automatically choose best mask + long minPenalty = LONG_MAX; + for (int i = 0; i < 8; i++) { + applyMask(i); + drawFormatBits(i); + long penalty = getPenaltyScore(); + if (penalty < minPenalty) { + msk = i; + minPenalty = penalty; + } + applyMask(i); // Undoes the mask due to XOR + } + } + assert(0 <= msk && msk <= 7); + mask = msk; + applyMask(msk); // Apply the final choice of mask + drawFormatBits(msk); // Overwrite old format bits + + isFunction.clear(); + isFunction.shrink_to_fit(); +} + +int QrCode::getVersion() const +{ + return version; +} + +int QrCode::getSize() const +{ + return size; +} + +QrCode::Ecc QrCode::getErrorCorrectionLevel() const +{ + return errorCorrectionLevel; +} + +int QrCode::getMask() const +{ + return mask; +} + +bool QrCode::getModule(int x, int y) const +{ + return 0 <= x && x < size && 0 <= y && y < size && module(x, y); +} + +void QrCode::drawFunctionPatterns() +{ + // Draw horizontal and vertical timing patterns + for (int i = 0; i < size; i++) { + setFunctionModule(6, i, i % 2 == 0); + setFunctionModule(i, 6, i % 2 == 0); + } + + // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) + drawFinderPattern(3, 3); + drawFinderPattern(size - 4, 3); + drawFinderPattern(3, size - 4); + + // Draw numerous alignment patterns + const vector alignPatPos = getAlignmentPatternPositions(); + size_t numAlign = alignPatPos.size(); + for (size_t i = 0; i < numAlign; i++) { + for (size_t j = 0; j < numAlign; j++) { + // Don't draw on the three finder corners + if (!((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0))) + drawAlignmentPattern(alignPatPos.at(i), alignPatPos.at(j)); + } + } + + // Draw configuration data + drawFormatBits(0); // Dummy mask value; overwritten later in the constructor + drawVersion(); +} + +void QrCode::drawFormatBits(int msk) +{ + // Calculate error correction code and pack bits + int data = getFormatBits(errorCorrectionLevel) << 3 | msk; // errCorrLvl is uint2, msk is uint3 + int rem = data; + for (int i = 0; i < 10; i++) + rem = (rem << 1) ^ ((rem >> 9) * 0x537); + int bits = (data << 10 | rem) ^ 0x5412; // uint15 + assert(bits >> 15 == 0); + + // Draw first copy + for (int i = 0; i <= 5; i++) + setFunctionModule(8, i, getBit(bits, i)); + setFunctionModule(8, 7, getBit(bits, 6)); + setFunctionModule(8, 8, getBit(bits, 7)); + setFunctionModule(7, 8, getBit(bits, 8)); + for (int i = 9; i < 15; i++) + setFunctionModule(14 - i, 8, getBit(bits, i)); + + // Draw second copy + for (int i = 0; i < 8; i++) + setFunctionModule(size - 1 - i, 8, getBit(bits, i)); + for (int i = 8; i < 15; i++) + setFunctionModule(8, size - 15 + i, getBit(bits, i)); + setFunctionModule(8, size - 8, true); // Always dark +} + +void QrCode::drawVersion() +{ + if (version < 7) + return; + + // Calculate error correction code and pack bits + int rem = version; // version is uint6, in the range [7, 40] + for (int i = 0; i < 12; i++) + rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); + long bits = static_cast(version) << 12 | rem; // uint18 + assert(bits >> 18 == 0); + + // Draw two copies + for (int i = 0; i < 18; i++) { + bool bit = getBit(bits, i); + int a = size - 11 + i % 3; + int b = i / 3; + setFunctionModule(a, b, bit); + setFunctionModule(b, a, bit); + } +} + +void QrCode::drawFinderPattern(int x, int y) +{ + for (int dy = -4; dy <= 4; dy++) { + for (int dx = -4; dx <= 4; dx++) { + int dist = std::max(std::abs(dx), std::abs(dy)); // Chebyshev/infinity norm + int xx = x + dx, yy = y + dy; + if (0 <= xx && xx < size && 0 <= yy && yy < size) + setFunctionModule(xx, yy, dist != 2 && dist != 4); + } + } +} + +void QrCode::drawAlignmentPattern(int x, int y) +{ + for (int dy = -2; dy <= 2; dy++) { + for (int dx = -2; dx <= 2; dx++) + setFunctionModule(x + dx, y + dy, std::max(std::abs(dx), std::abs(dy)) != 1); + } +} + +void QrCode::setFunctionModule(int x, int y, bool isDark) +{ + size_t ux = static_cast(x); + size_t uy = static_cast(y); + modules.at(uy).at(ux) = isDark; + isFunction.at(uy).at(ux) = true; +} + +bool QrCode::module(int x, int y) const +{ + return modules.at(static_cast(y)).at(static_cast(x)); +} + +vector QrCode::addEccAndInterleave(const vector &data) const +{ + if (data.size() != static_cast(getNumDataCodewords(version, errorCorrectionLevel))) + throw std::invalid_argument("Invalid argument"); + + // Calculate parameter numbers + int numBlocks = NUM_ERROR_CORRECTION_BLOCKS[static_cast(errorCorrectionLevel)][version]; + int blockEccLen = ECC_CODEWORDS_PER_BLOCK[static_cast(errorCorrectionLevel)][version]; + int rawCodewords = getNumRawDataModules(version) / 8; + int numShortBlocks = numBlocks - rawCodewords % numBlocks; + int shortBlockLen = rawCodewords / numBlocks; + + // Split data into blocks and append ECC to each block + vector> blocks; + const vector rsDiv = reedSolomonComputeDivisor(blockEccLen); + for (int i = 0, k = 0; i < numBlocks; i++) { + vector dat(data.cbegin() + k, + data.cbegin() + + (k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1))); + k += static_cast(dat.size()); + const vector ecc = reedSolomonComputeRemainder(dat, rsDiv); + if (i < numShortBlocks) + dat.push_back(0); + dat.insert(dat.end(), ecc.cbegin(), ecc.cend()); + blocks.push_back(std::move(dat)); + } + + // Interleave (not concatenate) the bytes from every block into a single sequence + vector result; + for (size_t i = 0; i < blocks.at(0).size(); i++) { + for (size_t j = 0; j < blocks.size(); j++) { + // Skip the padding byte in short blocks + if (i != static_cast(shortBlockLen - blockEccLen) + || j >= static_cast(numShortBlocks)) + result.push_back(blocks.at(j).at(i)); + } + } + assert(result.size() == static_cast(rawCodewords)); + return result; +} + +void QrCode::drawCodewords(const vector &data) +{ + if (data.size() != static_cast(getNumRawDataModules(version) / 8)) + throw std::invalid_argument("Invalid argument"); + + size_t i = 0; // Bit index into the data + // Do the funny zigzag scan + for (int right = size - 1; right >= 1; right -= 2) { // Index of right column in each column pair + if (right == 6) + right = 5; + for (int vert = 0; vert < size; vert++) { // Vertical counter + for (int j = 0; j < 2; j++) { + size_t x = static_cast(right - j); // Actual x coordinate + bool upward = ((right + 1) & 2) == 0; + size_t y = static_cast(upward ? size - 1 - vert : vert); // Actual y coordinate + if (!isFunction.at(y).at(x) && i < data.size() * 8) { + modules.at(y).at(x) = getBit(data.at(i >> 3), 7 - static_cast(i & 7)); + i++; + } + // If this QR Code has any remainder bits (0 to 7), they were assigned as + // 0/false/light by the constructor and are left unchanged by this method + } + } + } + assert(i == data.size() * 8); +} + +void QrCode::applyMask(int msk) +{ + if (msk < 0 || msk > 7) + throw std::domain_error("Mask value out of range"); + size_t sz = static_cast(size); + for (size_t y = 0; y < sz; y++) { + for (size_t x = 0; x < sz; x++) { + bool invert; + switch (msk) { + case 0: + invert = (x + y) % 2 == 0; + break; + case 1: + invert = y % 2 == 0; + break; + case 2: + invert = x % 3 == 0; + break; + case 3: + invert = (x + y) % 3 == 0; + break; + case 4: + invert = (x / 3 + y / 2) % 2 == 0; + break; + case 5: + invert = x * y % 2 + x * y % 3 == 0; + break; + case 6: + invert = (x * y % 2 + x * y % 3) % 2 == 0; + break; + case 7: + invert = ((x + y) % 2 + x * y % 3) % 2 == 0; + break; + default: + throw std::logic_error("Unreachable"); + } + modules.at(y).at(x) = modules.at(y).at(x) ^ (invert & !isFunction.at(y).at(x)); + } + } +} + +long QrCode::getPenaltyScore() const +{ + long result = 0; + + // Adjacent modules in row having same color, and finder-like patterns + for (int y = 0; y < size; y++) { + bool runColor = false; + int runX = 0; + std::array runHistory = {}; + for (int x = 0; x < size; x++) { + if (module(x, y) == runColor) { + runX++; + if (runX == 5) + result += PENALTY_N1; + else if (runX > 5) + result++; + } else { + finderPenaltyAddHistory(runX, runHistory); + if (!runColor) + result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3; + runColor = module(x, y); + runX = 1; + } + } + result += finderPenaltyTerminateAndCount(runColor, runX, runHistory) * PENALTY_N3; + } + // Adjacent modules in column having same color, and finder-like patterns + for (int x = 0; x < size; x++) { + bool runColor = false; + int runY = 0; + std::array runHistory = {}; + for (int y = 0; y < size; y++) { + if (module(x, y) == runColor) { + runY++; + if (runY == 5) + result += PENALTY_N1; + else if (runY > 5) + result++; + } else { + finderPenaltyAddHistory(runY, runHistory); + if (!runColor) + result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3; + runColor = module(x, y); + runY = 1; + } + } + result += finderPenaltyTerminateAndCount(runColor, runY, runHistory) * PENALTY_N3; + } + + // 2*2 blocks of modules having same color + for (int y = 0; y < size - 1; y++) { + for (int x = 0; x < size - 1; x++) { + bool color = module(x, y); + if (color == module(x + 1, y) && color == module(x, y + 1) + && color == module(x + 1, y + 1)) + result += PENALTY_N2; + } + } + + // Balance of dark and light modules + int dark = 0; + for (const vector &row : modules) { + for (bool color : row) { + if (color) + dark++; + } + } + int total = size * size; // Note that size is odd, so dark/total != 1/2 + // Compute the smallest integer k >= 0 such that (45-5k)% <= dark/total <= (55+5k)% + int k = static_cast((std::abs(dark * 20L - total * 10L) + total - 1) / total) - 1; + assert(0 <= k && k <= 9); + result += k * PENALTY_N4; + assert(0 <= result + && result <= 2568888L); // Non-tight upper bound based on default values of PENALTY_N1, ..., N4 + return result; +} + +vector QrCode::getAlignmentPatternPositions() const +{ + if (version == 1) + return vector(); + else { + int numAlign = version / 7 + 2; + int step = (version == 32) ? 26 : (version * 4 + numAlign * 2 + 1) / (numAlign * 2 - 2) * 2; + vector result; + for (int i = 0, pos = size - 7; i < numAlign - 1; i++, pos -= step) + result.insert(result.begin(), pos); + result.insert(result.begin(), 6); + return result; + } +} + +int QrCode::getNumRawDataModules(int ver) +{ + if (ver < MIN_VERSION || ver > MAX_VERSION) + throw std::domain_error("Version number out of range"); + int result = (16 * ver + 128) * ver + 64; + if (ver >= 2) { + int numAlign = ver / 7 + 2; + result -= (25 * numAlign - 10) * numAlign - 55; + if (ver >= 7) + result -= 36; + } + assert(208 <= result && result <= 29648); + return result; +} + +int QrCode::getNumDataCodewords(int ver, Ecc ecl) +{ + return getNumRawDataModules(ver) / 8 + - ECC_CODEWORDS_PER_BLOCK[static_cast(ecl)][ver] + * NUM_ERROR_CORRECTION_BLOCKS[static_cast(ecl)][ver]; +} + +vector QrCode::reedSolomonComputeDivisor(int degree) +{ + if (degree < 1 || degree > 255) + throw std::domain_error("Degree out of range"); + // Polynomial coefficients are stored from highest to lowest power, excluding the leading term which is always 1. + // For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array {255, 8, 93}. + vector result(static_cast(degree)); + result.at(result.size() - 1) = 1; // Start off with the monomial x^0 + + // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), + // and drop the highest monomial term which is always 1x^degree. + // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). + uint8_t root = 1; + for (int i = 0; i < degree; i++) { + // Multiply the current product by (x - r^i) + for (size_t j = 0; j < result.size(); j++) { + result.at(j) = reedSolomonMultiply(result.at(j), root); + if (j + 1 < result.size()) + result.at(j) ^= result.at(j + 1); + } + root = reedSolomonMultiply(root, 0x02); + } + return result; +} + +vector QrCode::reedSolomonComputeRemainder(const vector &data, + const vector &divisor) +{ + vector result(divisor.size()); + for (uint8_t b : data) { // Polynomial division + uint8_t factor = b ^ result.at(0); + result.erase(result.begin()); + result.push_back(0); + for (size_t i = 0; i < result.size(); i++) + result.at(i) ^= reedSolomonMultiply(divisor.at(i), factor); + } + return result; +} + +uint8_t QrCode::reedSolomonMultiply(uint8_t x, uint8_t y) +{ + // Russian peasant multiplication + int z = 0; + for (int i = 7; i >= 0; i--) { + z = (z << 1) ^ ((z >> 7) * 0x11D); + z ^= ((y >> i) & 1) * x; + } + assert(z >> 8 == 0); + return static_cast(z); +} + +int QrCode::finderPenaltyCountPatterns(const std::array &runHistory) const +{ + int n = runHistory.at(1); + assert(n <= size * 3); + bool core = n > 0 && runHistory.at(2) == n && runHistory.at(3) == n * 3 && runHistory.at(4) == n + && runHistory.at(5) == n; + return (core && runHistory.at(0) >= n * 4 && runHistory.at(6) >= n ? 1 : 0) + + (core && runHistory.at(6) >= n * 4 && runHistory.at(0) >= n ? 1 : 0); +} + +int QrCode::finderPenaltyTerminateAndCount(bool currentRunColor, + int currentRunLength, + std::array &runHistory) const +{ + if (currentRunColor) { // Terminate dark run + finderPenaltyAddHistory(currentRunLength, runHistory); + currentRunLength = 0; + } + currentRunLength += size; // Add light border to final run + finderPenaltyAddHistory(currentRunLength, runHistory); + return finderPenaltyCountPatterns(runHistory); +} + +void QrCode::finderPenaltyAddHistory(int currentRunLength, std::array &runHistory) const +{ + if (runHistory.at(0) == 0) + currentRunLength += size; // Add light border to initial run + std::copy_backward(runHistory.cbegin(), runHistory.cend() - 1, runHistory.end()); + runHistory.at(0) = currentRunLength; +} + +bool QrCode::getBit(long x, int i) +{ + return ((x >> i) & 1) != 0; +} + +/*---- Tables of constants ----*/ + +const int QrCode::PENALTY_N1 = 3; +const int QrCode::PENALTY_N2 = 3; +const int QrCode::PENALTY_N3 = 40; +const int QrCode::PENALTY_N4 = 10; + +const int8_t QrCode::ECC_CODEWORDS_PER_BLOCK[4][41] = { + // Version: (note that index 0 is for padding, and is set to an illegal value) + //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + {-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, + 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Low + {-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, + 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28}, // Medium + {-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, + 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Quartile + {-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, + 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // High +}; + +const int8_t QrCode::NUM_ERROR_CORRECTION_BLOCKS[4][41] = { + // Version: (note that index 0 is for padding, and is set to an illegal value) + //0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + {-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, + 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low + {-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, + 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium + {-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, + 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile + {-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, + 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High +}; + +data_too_long::data_too_long(const std::string &msg) + : std::length_error(msg) +{} + +/*---- Class BitBuffer ----*/ + +BitBuffer::BitBuffer() + : std::vector() +{} + +void BitBuffer::appendBits(std::uint32_t val, int len) +{ + if (len < 0 || len > 31 || val >> len != 0) + throw std::domain_error("Value out of range"); + for (int i = len - 1; i >= 0; i--) // Append bit by bit + this->push_back(((val >> i) & 1) != 0); +} + +} // namespace qrcodegen diff --git a/src/libs/3rdparty/qrcodegen/src/qrcodegen.h b/src/libs/3rdparty/qrcodegen/src/qrcodegen.h new file mode 100644 index 00000000000..e2d285cf3e8 --- /dev/null +++ b/src/libs/3rdparty/qrcodegen/src/qrcodegen.h @@ -0,0 +1,566 @@ +/* + * QR Code generator library (C++) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace qrcodegen { + +/* + * A segment of character/binary/control data in a QR Code symbol. + * Instances of this class are immutable. + * The mid-level way to create a segment is to take the payload data + * and call a static factory function such as QrSegment::makeNumeric(). + * The low-level way to create a segment is to custom-make the bit buffer + * and call the QrSegment() constructor with appropriate values. + * This segment class imposes no length restrictions, but QR Codes have restrictions. + * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data. + * Any segment longer than this is meaningless for the purpose of generating QR Codes. + */ +class QrSegment final +{ + /*---- Public helper enumeration ----*/ + + /* + * Describes how a segment's data bits are interpreted. Immutable. + */ +public: + class Mode final + { + /*-- Constants --*/ + + public: + static const Mode NUMERIC; + + public: + static const Mode ALPHANUMERIC; + + public: + static const Mode BYTE; + + public: + static const Mode KANJI; + + public: + static const Mode ECI; + + /*-- Fields --*/ + + // The mode indicator bits, which is a uint4 value (range 0 to 15). + private: + int modeBits; + + // Number of character count bits for three different version ranges. + private: + int numBitsCharCount[3]; + + /*-- Constructor --*/ + + private: + Mode(int mode, int cc0, int cc1, int cc2); + + /*-- Methods --*/ + + /* + * (Package-private) Returns the mode indicator bits, which is an unsigned 4-bit value (range 0 to 15). + */ + public: + int getModeBits() const; + + /* + * (Package-private) Returns the bit width of the character count field for a segment in + * this mode in a QR Code at the given version number. The result is in the range [0, 16]. + */ + public: + int numCharCountBits(int ver) const; + }; + + /*---- Static factory functions (mid level) ----*/ + + /* + * Returns a segment representing the given binary data encoded in + * byte mode. All input byte vectors are acceptable. Any text string + * can be converted to UTF-8 bytes and encoded as a byte mode segment. + */ +public: + static QrSegment makeBytes(const std::vector &data); + + /* + * Returns a segment representing the given string of decimal digits encoded in numeric mode. + */ +public: + static QrSegment makeNumeric(const char *digits); + + /* + * Returns a segment representing the given text string encoded in alphanumeric mode. + * The characters allowed are: 0 to 9, A to Z (uppercase only), space, + * dollar, percent, asterisk, plus, hyphen, period, slash, colon. + */ +public: + static QrSegment makeAlphanumeric(const char *text); + + /* + * Returns a list of zero or more segments to represent the given text string. The result + * may use various segment modes and switch modes to optimize the length of the bit stream. + */ +public: + static std::vector makeSegments(const char *text); + + /* + * Returns a segment representing an Extended Channel Interpretation + * (ECI) designator with the given assignment value. + */ +public: + static QrSegment makeEci(long assignVal); + + /*---- Public static helper functions ----*/ + + /* + * Tests whether the given string can be encoded as a segment in numeric mode. + * A string is encodable iff each character is in the range 0 to 9. + */ +public: + static bool isNumeric(const char *text); + + /* + * Tests whether the given string can be encoded as a segment in alphanumeric mode. + * A string is encodable iff each character is in the following set: 0 to 9, A to Z + * (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon. + */ +public: + static bool isAlphanumeric(const char *text); + + /*---- Instance fields ----*/ + + /* The mode indicator of this segment. Accessed through getMode(). */ +private: + const Mode *mode; + + /* The length of this segment's unencoded data. Measured in characters for + * numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode. + * Always zero or positive. Not the same as the data's bit length. + * Accessed through getNumChars(). */ +private: + int numChars; + + /* The data bits of this segment. Accessed through getData(). */ +private: + std::vector data; + + /*---- Constructors (low level) ----*/ + + /* + * Creates a new QR Code segment with the given attributes and data. + * The character count (numCh) must agree with the mode and the bit buffer length, + * but the constraint isn't checked. The given bit buffer is copied and stored. + */ +public: + QrSegment(const Mode &md, int numCh, const std::vector &dt); + + /* + * Creates a new QR Code segment with the given parameters and data. + * The character count (numCh) must agree with the mode and the bit buffer length, + * but the constraint isn't checked. The given bit buffer is moved and stored. + */ +public: + QrSegment(const Mode &md, int numCh, std::vector &&dt); + + /*---- Methods ----*/ + + /* + * Returns the mode field of this segment. + */ +public: + const Mode &getMode() const; + + /* + * Returns the character count field of this segment. + */ +public: + int getNumChars() const; + + /* + * Returns the data bits of this segment. + */ +public: + const std::vector &getData() const; + + // (Package-private) Calculates the number of bits needed to encode the given segments at + // the given version. Returns a non-negative number if successful. Otherwise returns -1 if a + // segment has too many characters to fit its length field, or the total bits exceeds INT_MAX. +public: + static int getTotalBits(const std::vector &segs, int version); + + /*---- Private constant ----*/ + + /* The set of all legal characters in alphanumeric mode, where + * each character value maps to the index in the string. */ +private: + static const char *ALPHANUMERIC_CHARSET; +}; + +/* + * A QR Code symbol, which is a type of two-dimension barcode. + * Invented by Denso Wave and described in the ISO/IEC 18004 standard. + * Instances of this class represent an immutable square grid of dark and light cells. + * The class provides static factory functions to create a QR Code from text or binary data. + * The class covers the QR Code Model 2 specification, supporting all versions (sizes) + * from 1 to 40, all 4 error correction levels, and 4 character encoding modes. + * + * Ways to create a QR Code object: + * - High level: Take the payload data and call QrCode::encodeText() or QrCode::encodeBinary(). + * - Mid level: Custom-make the list of segments and call QrCode::encodeSegments(). + * - Low level: Custom-make the array of data codeword bytes (including + * segment headers and final padding, excluding error correction codewords), + * supply the appropriate version number, and call the QrCode() constructor. + * (Note that all ways require supplying the desired error correction level.) + */ +class QrCode final +{ + /*---- Public helper enumeration ----*/ + + /* + * The error correction level in a QR Code symbol. + */ +public: + enum class Ecc { + LOW = 0, // The QR Code can tolerate about 7% erroneous codewords + MEDIUM, // The QR Code can tolerate about 15% erroneous codewords + QUARTILE, // The QR Code can tolerate about 25% erroneous codewords + HIGH, // The QR Code can tolerate about 30% erroneous codewords + }; + + // Returns a value in the range 0 to 3 (unsigned 2-bit integer). +private: + static int getFormatBits(Ecc ecl); + + /*---- Static factory functions (high level) ----*/ + + /* + * Returns a QR Code representing the given Unicode text string at the given error correction level. + * As a conservative upper bound, this function is guaranteed to succeed for strings that have 2953 or fewer + * UTF-8 code units (not Unicode code points) if the low error correction level is used. The smallest possible + * QR Code version is automatically chosen for the output. The ECC level of the result may be higher than + * the ecl argument if it can be done without increasing the version. + */ +public: + static QrCode encodeText(const char *text, Ecc ecl); + + /* + * Returns a QR Code representing the given binary data at the given error correction level. + * This function always encodes using the binary segment mode, not any text mode. The maximum number of + * bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output. + * The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version. + */ +public: + static QrCode encodeBinary(const std::vector &data, Ecc ecl); + + /*---- Static factory functions (mid level) ----*/ + + /* + * Returns a QR Code representing the given segments with the given encoding parameters. + * The smallest possible QR Code version within the given range is automatically + * chosen for the output. Iff boostEcl is true, then the ECC level of the result + * may be higher than the ecl argument if it can be done without increasing the + * version. The mask number is either between 0 to 7 (inclusive) to force that + * mask, or -1 to automatically choose an appropriate mask (which may be slow). + * This function allows the user to create a custom sequence of segments that switches + * between modes (such as alphanumeric and byte) to encode text in less space. + * This is a mid-level API; the high-level API is encodeText() and encodeBinary(). + */ +public: + static QrCode encodeSegments(const std::vector &segs, + Ecc ecl, + int minVersion = 1, + int maxVersion = 40, + int mask = -1, + bool boostEcl = true); // All optional parameters + + /*---- Instance fields ----*/ + + // Immutable scalar parameters: + + /* The version number of this QR Code, which is between 1 and 40 (inclusive). + * This determines the size of this barcode. */ +private: + int version; + + /* The width and height of this QR Code, measured in modules, between + * 21 and 177 (inclusive). This is equal to version * 4 + 17. */ +private: + int size; + + /* The error correction level used in this QR Code. */ +private: + Ecc errorCorrectionLevel; + + /* The index of the mask pattern used in this QR Code, which is between 0 and 7 (inclusive). + * Even if a QR Code is created with automatic masking requested (mask = -1), + * the resulting object still has a mask value between 0 and 7. */ +private: + int mask; + + // Private grids of modules/pixels, with dimensions of size*size: + + // The modules of this QR Code (false = light, true = dark). + // Immutable after constructor finishes. Accessed through getModule(). +private: + std::vector> modules; + + // Indicates function modules that are not subjected to masking. Discarded when constructor finishes. +private: + std::vector> isFunction; + + /*---- Constructor (low level) ----*/ + + /* + * Creates a new QR Code with the given version number, + * error correction level, data codeword bytes, and mask number. + * This is a low-level API that most users should not use directly. + * A mid-level API is the encodeSegments() function. + */ +public: + QrCode(int ver, Ecc ecl, const std::vector &dataCodewords, int msk); + + /*---- Public instance methods ----*/ + + /* + * Returns this QR Code's version, in the range [1, 40]. + */ +public: + int getVersion() const; + + /* + * Returns this QR Code's size, in the range [21, 177]. + */ +public: + int getSize() const; + + /* + * Returns this QR Code's error correction level. + */ +public: + Ecc getErrorCorrectionLevel() const; + + /* + * Returns this QR Code's mask, in the range [0, 7]. + */ +public: + int getMask() const; + + /* + * Returns the color of the module (pixel) at the given coordinates, which is false + * for light or true for dark. The top left corner has the coordinates (x=0, y=0). + * If the given coordinates are out of bounds, then false (light) is returned. + */ +public: + bool getModule(int x, int y) const; + + /*---- Private helper methods for constructor: Drawing function modules ----*/ + + // Reads this object's version field, and draws and marks all function modules. +private: + void drawFunctionPatterns(); + + // Draws two copies of the format bits (with its own error correction code) + // based on the given mask and this object's error correction level field. +private: + void drawFormatBits(int msk); + + // Draws two copies of the version bits (with its own error correction code), + // based on this object's version field, iff 7 <= version <= 40. +private: + void drawVersion(); + + // Draws a 9*9 finder pattern including the border separator, + // with the center module at (x, y). Modules can be out of bounds. +private: + void drawFinderPattern(int x, int y); + + // Draws a 5*5 alignment pattern, with the center module + // at (x, y). All modules must be in bounds. +private: + void drawAlignmentPattern(int x, int y); + + // Sets the color of a module and marks it as a function module. + // Only used by the constructor. Coordinates must be in bounds. +private: + void setFunctionModule(int x, int y, bool isDark); + + // Returns the color of the module at the given coordinates, which must be in range. +private: + bool module(int x, int y) const; + + /*---- Private helper methods for constructor: Codewords and masking ----*/ + + // Returns a new byte string representing the given data with the appropriate error correction + // codewords appended to it, based on this object's version and error correction level. +private: + std::vector addEccAndInterleave(const std::vector &data) const; + + // Draws the given sequence of 8-bit codewords (data and error correction) onto the entire + // data area of this QR Code. Function modules need to be marked off before this is called. +private: + void drawCodewords(const std::vector &data); + + // XORs the codeword modules in this QR Code with the given mask pattern. + // The function modules must be marked and the codeword bits must be drawn + // before masking. Due to the arithmetic of XOR, calling applyMask() with + // the same mask value a second time will undo the mask. A final well-formed + // QR Code needs exactly one (not zero, two, etc.) mask applied. +private: + void applyMask(int msk); + + // Calculates and returns the penalty score based on state of this QR Code's current modules. + // This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. +private: + long getPenaltyScore() const; + + /*---- Private helper functions ----*/ + + // Returns an ascending list of positions of alignment patterns for this version number. + // Each position is in the range [0,177), and are used on both the x and y axes. + // This could be implemented as lookup table of 40 variable-length lists of unsigned bytes. +private: + std::vector getAlignmentPatternPositions() const; + + // Returns the number of data bits that can be stored in a QR Code of the given version number, after + // all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8. + // The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table. +private: + static int getNumRawDataModules(int ver); + + // Returns the number of 8-bit data (i.e. not error correction) codewords contained in any + // QR Code of the given version number and error correction level, with remainder bits discarded. + // This stateless pure function could be implemented as a (40*4)-cell lookup table. +private: + static int getNumDataCodewords(int ver, Ecc ecl); + + // Returns a Reed-Solomon ECC generator polynomial for the given degree. This could be + // implemented as a lookup table over all possible parameter values, instead of as an algorithm. +private: + static std::vector reedSolomonComputeDivisor(int degree); + + // Returns the Reed-Solomon error correction codeword for the given data and divisor polynomials. +private: + static std::vector reedSolomonComputeRemainder( + const std::vector &data, const std::vector &divisor); + + // Returns the product of the two given field elements modulo GF(2^8/0x11D). + // All inputs are valid. This could be implemented as a 256*256 lookup table. +private: + static std::uint8_t reedSolomonMultiply(std::uint8_t x, std::uint8_t y); + + // Can only be called immediately after a light run is added, and + // returns either 0, 1, or 2. A helper function for getPenaltyScore(). +private: + int finderPenaltyCountPatterns(const std::array &runHistory) const; + + // Must be called at the end of a line (row or column) of modules. A helper function for getPenaltyScore(). +private: + int finderPenaltyTerminateAndCount(bool currentRunColor, + int currentRunLength, + std::array &runHistory) const; + + // Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore(). +private: + void finderPenaltyAddHistory(int currentRunLength, std::array &runHistory) const; + + // Returns true iff the i'th bit of x is set to 1. +private: + static bool getBit(long x, int i); + + /*---- Constants and tables ----*/ + + // The minimum version number supported in the QR Code Model 2 standard. +public: + static constexpr int MIN_VERSION = 1; + + // The maximum version number supported in the QR Code Model 2 standard. +public: + static constexpr int MAX_VERSION = 40; + + // For use in getPenaltyScore(), when evaluating which mask is best. +private: + static const int PENALTY_N1; + +private: + static const int PENALTY_N2; + +private: + static const int PENALTY_N3; + +private: + static const int PENALTY_N4; + +private: + static const std::int8_t ECC_CODEWORDS_PER_BLOCK[4][41]; + +private: + static const std::int8_t NUM_ERROR_CORRECTION_BLOCKS[4][41]; +}; + +/*---- Public exception class ----*/ + +/* + * Thrown when the supplied data does not fit any QR Code version. Ways to handle this exception include: + * - Decrease the error correction level if it was greater than Ecc::LOW. + * - If the encodeSegments() function was called with a maxVersion argument, then increase + * it if it was less than QrCode::MAX_VERSION. (This advice does not apply to the other + * factory functions because they search all versions up to QrCode::MAX_VERSION.) + * - Split the text data into better or optimal segments in order to reduce the number of bits required. + * - Change the text or binary data to be shorter. + * - Change the text to fit the character set of a particular segment mode (e.g. alphanumeric). + * - Propagate the error upward to the caller/user. + */ +class data_too_long : public std::length_error +{ +public: + explicit data_too_long(const std::string &msg); +}; + +/* + * An appendable sequence of bits (0s and 1s). Mainly used by QrSegment. + */ +class BitBuffer final : public std::vector +{ + /*---- Constructor ----*/ + + // Creates an empty bit buffer (length 0). +public: + BitBuffer(); + + /*---- Method ----*/ + + // Appends the given number of low-order bits of the given value + // to this buffer. Requires 0 <= len <= 31 and val < 2^len. +public: + void appendBits(std::uint32_t val, int len); +}; + +} // namespace qrcodegen diff --git a/src/libs/3rdparty/qrcodegen/src/qrcodeimageprovider.cpp b/src/libs/3rdparty/qrcodegen/src/qrcodeimageprovider.cpp new file mode 100644 index 00000000000..b889b677600 --- /dev/null +++ b/src/libs/3rdparty/qrcodegen/src/qrcodeimageprovider.cpp @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd +** All rights reserved. +** For any questions to The Qt Company, please use contact form at http://www.qt.io/contact-us +** +** This file is part of the Custom Merge QtDesignStudio plugin. +** +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** If you have questions regarding the use of this file, please use +** contact form at http://www.qt.io/contact-us +** +******************************************************************************/ + +#include "qrcodeimageprovider.h" +#include "qrcodegen.h" +#include +#include + +QrCodeImageProvider::QrCodeImageProvider() + : QQuickImageProvider(QQuickImageProvider::Pixmap) +{ +} + + +static QString qrToSvgString(const qrcodegen::QrCode &qr, int border) { + if (border < 0) + throw std::domain_error("Border must be non-negative"); + if (border > INT_MAX / 2 || border * 2 > INT_MAX - qr.getSize()) + throw std::overflow_error("Border too large"); + + QString svgString; + QTextStream stream(&svgString); + + stream << "\n"; + stream << "\n"; + stream << "\n"; + stream << "\t\n"; + stream << "\t\n"; + stream << "\n"; + + return svgString; + +} + +QPixmap QrCodeImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) +{ + int width = 1000; + int height = 1000; + + const qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(id.toLatin1(), qrcodegen::QrCode::Ecc::LOW); + + QString svgString = qrToSvgString(qr, 3); + QSvgRenderer svgRenderer(svgString.toLatin1()); + + + if (size) + *size = QSize(width, height); + + QPixmap pixmap(requestedSize.width() > 0 ? requestedSize.width() : width, + requestedSize.height() > 0 ? requestedSize.height() : height); + + QPainter painter(&pixmap); + + svgRenderer.render(&painter); + + return pixmap; + +} + + diff --git a/src/libs/3rdparty/qrcodegen/src/qrcodeimageprovider.h b/src/libs/3rdparty/qrcodegen/src/qrcodeimageprovider.h new file mode 100644 index 00000000000..1516d214bd8 --- /dev/null +++ b/src/libs/3rdparty/qrcodegen/src/qrcodeimageprovider.h @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd +** All rights reserved. +** For any questions to The Qt Company, please use contact form at http://www.qt.io/contact-us +** +** This file is part of the Custom Merge QtDesignStudio plugin. +** +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** If you have questions regarding the use of this file, please use +** contact form at http://www.qt.io/contact-us +** +******************************************************************************/ + +#ifndef QRCODEIMAGEPROVIDER_H +#define QRCODEIMAGEPROVIDER_H + +#include +#include + +class QrCodeImageProvider : public QQuickImageProvider +{ +public: + QrCodeImageProvider(); + + QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize); +}; + +#endif // QRCODEIMAGEPROVIDER_H diff --git a/src/libs/advanceddockingsystem/dockareatabbar.h b/src/libs/advanceddockingsystem/dockareatabbar.h index c21b577947f..33f5b117e4a 100644 --- a/src/libs/advanceddockingsystem/dockareatabbar.h +++ b/src/libs/advanceddockingsystem/dockareatabbar.h @@ -17,13 +17,11 @@ class FloatingDockContainer; class AbstractFloatingWidget; /** - * Custom tabbar implementation for tab area that is shown on top of a - * dock area widget. - * The tabbar displays the tab widgets of the contained dock widgets. - * We cannot use QTabBar here because it does a lot of fancy animations - * that will crash the application if a tab is removed while the animation - * has not finished. And we need to remove a tab, if the user drags a - * a dock widget out of a group of tabbed widgets + * Custom tabbar implementation for tab area that is shown on top of a dock area widget. The tabbar + * displays the tab widgets of the contained dock widgets. We cannot use QTabBar here because it + * does a lot of fancy animations that will crash the application if a tab is removed while the + * animation has not finished. And we need to remove a tab, if the user drags a dock widget out + * of a group of tabbed widgets. */ class ADS_EXPORT DockAreaTabBar : public QScrollArea { @@ -55,24 +53,24 @@ public: ~DockAreaTabBar() override; /** - * Inserts the given dock widget tab at the given position. - * Inserting a new tab at an index less than or equal to the current index - * will increment the current index, but keep the current tab. + * Inserts the given dock widget tab at the given position. Inserting a new tab at an index + * less than or equal to the current index will increment the current index, but keep the + * current tab. */ void insertTab(int Index, DockWidgetTab *tab); /** - * Removes the given DockWidgetTab from the tabbar + * Removes the given DockWidgetTab from the tabbar. */ void removeTab(DockWidgetTab *tab); /** - * Returns the number of tabs in this tabbar + * Returns the number of tabs in this tabbar. */ int count() const; /** - * Returns the current index or -1 if no tab is selected + * Returns the current index or -1 if no tab is selected. */ int currentIndex() const; @@ -82,7 +80,7 @@ public: DockWidgetTab *currentTab() const; /** - * Returns the tab with the given index + * Returns the tab with the given index. */ DockWidgetTab *tab(int index) const; @@ -99,14 +97,13 @@ public: int tabInsertIndexAt(const QPoint &pos) const; /** - * Filters the tab widget events + * Filters the tab widget events. */ bool eventFilter(QObject *watched, QEvent *event) override; /** - * This function returns true if the tab is open, that means if it is - * visible to the user. If the function returns false, the tab is - * closed + * This function returns true if the tab is open, that means if it is visible to the user. + * If the function returns false, the tab is closed. */ bool isTabOpen(int index) const; @@ -120,19 +117,18 @@ public: QSize minimumSizeHint() const override; /** - * The function provides a sizeHint that matches the height of the - * internal viewport. + * The function provides a sizeHint that matches the height of the internal viewport. */ QSize sizeHint() const override; /** - * This property sets the index of the tab bar's visible tab + * This property sets the index of the tab bar's visible tab. */ void setCurrentIndex(int index); /** * This function will close the tab given in Index param. - * Closing a tab means, the tab will be hidden, it will not be removed + * Closing a tab means, the tab will be hidden, it will not be removed. */ void closeTab(int index); @@ -145,12 +141,12 @@ signals: /** * This signal is emitted when the tab bar's current tab changes. The new - * current has the given index, or -1 if there isn't a new one + * current has the given index, or -1 if there isn't a new one. */ void currentChanged(int index); /** - * This signal is emitted when user clicks on a tab + * This signal is emitted when user clicks on a tab. */ void tabBarClicked(int index); @@ -161,13 +157,12 @@ signals: void tabCloseRequested(int index); /** - * This signal is emitted if a tab has been closed + * This signal is emitted if a tab has been closed. */ void tabClosed(int index); /** - * This signal is emitted if a tab has been opened. - * A tab is opened if it has been made visible + * This signal is emitted if a tab has been opened. A tab is opened if it has been made visible. */ void tabOpened(int index); @@ -178,18 +173,17 @@ signals: void tabMoved(int from, int to); /** - * This signal is emitted, just before the tab with the given index is - * removed + * This signal is emitted, just before the tab with the given index is removed. */ void removingTab(int index); /** - * This signal is emitted if a tab has been inserted + * This signal is emitted if a tab has been inserted. */ void tabInserted(int index); /** - * This signal is emitted when a tab title elide state has been changed + * This signal is emitted when a tab title elide state has been changed. */ void elidedChanged(bool elided); }; // class DockAreaTabBar diff --git a/src/libs/advanceddockingsystem/dockareawidget.cpp b/src/libs/advanceddockingsystem/dockareawidget.cpp index c4c74a38c1d..cc263895af6 100644 --- a/src/libs/advanceddockingsystem/dockareawidget.cpp +++ b/src/libs/advanceddockingsystem/dockareawidget.cpp @@ -46,11 +46,11 @@ static bool isAutoHideFeatureEnabled() } /** - * Internal dock area layout mimics stack layout but only inserts the current - * widget into the internal QLayout object. - * \warning Only the current widget has a parent. All other widgets - * do not have a parent. That means, a widget that is in this layout may - * return nullptr for its parent() function if it is not the current widget. + * Internal dock area layout mimics stack layout but only inserts the current widget into the + * internal QLayout object. + * \warning Only the current widget has a parent. All other widgets do not have a parent. That + * means, a widget that is in this layout may return nullptr for its parent() function if it is + * not the current widget. */ class DockAreaLayout { @@ -74,8 +74,7 @@ public: int count() const { return m_widgets.count(); } /** - * Inserts the widget at the given index position into the internal widget - * list + * Inserts the widget at the given index position into the internal widget list */ void insertWidget(int index, QWidget *widget) { @@ -195,8 +194,7 @@ struct DockAreaWidgetPrivate DockManager *m_dockManager = nullptr; AutoHideDockContainer *m_autoHideDockContainer = nullptr; bool m_updateTitleBarButtons = false; - DockWidgetAreas m_allowedAreas = AllDockAreas; - QSize m_minSizeHint; + DockWidgetAreas m_allowedAreas = DefaultAllowedAreas; DockAreaWidget::DockAreaFlags m_flags{DockAreaWidget::DefaultFlags}; /** @@ -252,20 +250,6 @@ struct DockAreaWidgetPrivate * Updates the visibility of the close and detach button */ void updateTitleBarButtonVisibility(bool isTopLevel); - - /** - * Scans all contained dock widgets for the max. minimum size hint - */ - void updateMinimumSizeHint() - { - m_minSizeHint = QSize(); - for (int i = 0; i < m_contentsLayout->count(); ++i) { - auto widget = m_contentsLayout->widget(i); - m_minSizeHint.setHeight( - qMax(m_minSizeHint.height(), widget->minimumSizeHint().height())); - m_minSizeHint.setWidth(qMax(m_minSizeHint.width(), widget->minimumSizeHint().width())); - } - } }; // struct DockAreaWidgetPrivate @@ -391,26 +375,24 @@ void DockAreaWidget::insertDockWidget(int index, DockWidget *dockWidget, bool ac dockWidget->setDockArea(this); dockWidget->tabWidget()->setDockAreaWidget(this); auto tabWidget = dockWidget->tabWidget(); - // Inserting the tab will change the current index which in turn will - // make the tab widget visible in the slot + // Inserting the tab will change the current index which in turn will make the tab widget + // visible in the slot d->tabBar()->blockSignals(true); d->tabBar()->insertTab(index, tabWidget); d->tabBar()->blockSignals(false); tabWidget->setVisible(!dockWidget->isClosed()); d->m_titleBar->autoHideTitleLabel()->setText(dockWidget->windowTitle()); dockWidget->setProperty(g_indexProperty, index); - d->m_minSizeHint.setHeight( - qMax(d->m_minSizeHint.height(), dockWidget->minimumSizeHint().height())); - d->m_minSizeHint.setWidth(qMax(d->m_minSizeHint.width(), dockWidget->minimumSizeHint().width())); if (activate) { setCurrentIndex(index); - dockWidget->setClosedState( - false); // Set current index can show the widget without changing the close state, added to keep the close state consistent + // Set current index can show the widget without changing the close state, added to keep + // the close state consistent + dockWidget->setClosedState(false); } - // If this dock area is hidden, then we need to make it visible again - // by calling dockWidget->toggleViewInternal(true); - if (!this->isVisible() && d->m_contentsLayout->count() > 1 && !dockManager()->isRestoringState()) + // If this dock area is hidden, then we need to make it visible again by calling + // dockWidget->toggleViewInternal(true); + if (!isVisible() && d->m_contentsLayout->count() > 1 && !dockManager()->isRestoringState()) dockWidget->toggleViewInternal(true); d->updateTitleBarButtonStates(); @@ -423,8 +405,7 @@ void DockAreaWidget::removeDockWidget(DockWidget *dockWidget) if (!dockWidget) return; - // If this dock area is in a auto hide container, then we can delete - // the auto hide container now + // If this dock area is in a auto hide container, then we can delete the auto hide container now if (isAutoHide()) { autoHideDockContainer()->cleanupAndDelete(); return; @@ -445,7 +426,7 @@ void DockAreaWidget::removeDockWidget(DockWidget *dockWidget) } else if (d->m_contentsLayout->isEmpty() && dockContainerWidget->dockAreaCount() >= 1) { qCInfo(adsLog) << "Dock Area empty"; dockContainerWidget->removeDockArea(this); - this->deleteLater(); + deleteLater(); if (dockContainerWidget->dockAreaCount() == 0) { if (FloatingDockContainer *floatingDockContainer = dockContainerWidget->floatingWidget()) { @@ -454,15 +435,13 @@ void DockAreaWidget::removeDockWidget(DockWidget *dockWidget) } } } else if (dockWidget == current) { - // if contents layout is not empty but there are no more open dock - // widgets, then we need to hide the dock area because it does not - // contain any visible content + // If contents layout is not empty but there are no more open dock widgets, then we need to + // hide the dock area because it does not contain any visible content hideAreaWithNoVisibleContent(); } d->updateTitleBarButtonStates(); updateTitleBarVisibility(); - d->updateMinimumSizeHint(); auto topLevelDockWidget = dockContainerWidget->topLevelDockWidget(); if (topLevelDockWidget) topLevelDockWidget->emitTopLevelChanged(true); @@ -474,16 +453,16 @@ void DockAreaWidget::removeDockWidget(DockWidget *dockWidget) void DockAreaWidget::hideAreaWithNoVisibleContent() { - this->toggleView(false); + toggleView(false); // Hide empty parent splitters auto splitter = internal::findParent(this); internal::hideEmptyParentSplitters(splitter); - //Hide empty floating widget - DockContainerWidget *container = this->dockContainer(); + // Hide empty floating widget + DockContainerWidget *container = dockContainer(); if (!container->isFloating() - && DockManager::testConfigFlag(DockManager::HideSingleCentralWidgetTitleBar)) + && !DockManager::testConfigFlag(DockManager::HideSingleCentralWidgetTitleBar)) return; updateTitleBarVisibility(); @@ -532,8 +511,9 @@ void DockAreaWidget::internalSetCurrentDockWidget(DockWidget *dockWidget) return; setCurrentIndex(index); - dockWidget->setClosedState( - false); // Set current index can show the widget without changing the close state, added to keep the close state consistent + // Set current index can show the widget without changing the close state, added to keep + // the close state consistent + dockWidget->setClosedState(false); } void DockAreaWidget::setCurrentIndex(int index) @@ -601,7 +581,7 @@ QList DockAreaWidget::openedDockWidgets() const for (int i = 0; i < d->m_contentsLayout->count(); ++i) { DockWidget *currentDockWidget = dockWidget(i); if (!currentDockWidget->isClosed()) - dockWidgetList.append(dockWidget(i)); + dockWidgetList.append(currentDockWidget); } return dockWidgetList; } @@ -672,8 +652,8 @@ void DockAreaWidget::updateTitleBarVisibility() if (isAutoHideFeatureEnabled()) { auto tabBar = d->m_titleBar->tabBar(); tabBar->setVisible(!autoHide); // Never show tab bar when auto hidden - d->m_titleBar->autoHideTitleLabel()->setVisible( - autoHide); // Always show when auto hidden, never otherwise + // Always show when auto hidden, never otherwise + d->m_titleBar->autoHideTitleLabel()->setVisible(autoHide); updateTitleBarButtonVisibility(container->topLevelDockArea() == this); } } @@ -786,8 +766,8 @@ bool DockAreaWidget::restoreState(DockingStateReader &stateReader, if (dockWidget->autoHideDockContainer()) dockWidget->autoHideDockContainer()->cleanupAndDelete(); - // We hide the DockArea here to prevent the short display (the flashing) - // of the dock areas during application startup + // We hide the DockArea here to prevent the short display (the flashing) of the dock + // areas during application startup dockArea->hide(); dockArea->addDockWidget(dockWidget); dockWidget->setToggleViewActionChecked(!closed); @@ -919,10 +899,10 @@ QAbstractButton *DockAreaWidget::titleBarButton(eTitleBarButton which) const void DockAreaWidget::closeArea() { - // If there is only one single dock widget and this widget has the - // DeleteOnClose feature or CustomCloseHandling, then we delete the dock widget now; - // in the case of CustomCloseHandling, the CDockWidget class will emit its - // closeRequested signal and not actually delete unless the signal is handled in a way that deletes it + // If there is only one single dock widget and this widget has the DeleteOnClose feature or + // CustomCloseHandling, then we delete the dock widget now; in the case of CustomCloseHandling, + // the DockWidget class will emit its closeRequested signal and not actually delete unless + // the signal is handled in a way that deletes it auto openDockWidgets = openedDockWidgets(); if (openDockWidgets.count() == 1 && (openDockWidgets[0]->features().testFlag(DockWidget::DockWidgetDeleteOnClose) @@ -983,27 +963,23 @@ SideBarLocation DockAreaWidget::calculateSideTabBarArea() const int distance = qAbs(contentRect.topLeft().y() - dockAreaRect.topLeft().y()); borderDistance[SideBarLocation::SideBarTop] = (distance < minBorderDistance) ? 0 : distance; - if (!borderDistance[SideBarLocation::SideBarTop]) { + if (!borderDistance[SideBarLocation::SideBarTop]) borders |= BorderTop; - } distance = qAbs(contentRect.bottomRight().y() - dockAreaRect.bottomRight().y()); borderDistance[SideBarLocation::SideBarBottom] = (distance < minBorderDistance) ? 0 : distance; - if (!borderDistance[SideBarLocation::SideBarBottom]) { + if (!borderDistance[SideBarLocation::SideBarBottom]) borders |= BorderBottom; - } distance = qAbs(contentRect.topLeft().x() - dockAreaRect.topLeft().x()); borderDistance[SideBarLocation::SideBarLeft] = (distance < minBorderDistance) ? 0 : distance; - if (!borderDistance[SideBarLocation::SideBarLeft]) { + if (!borderDistance[SideBarLocation::SideBarLeft]) borders |= BorderLeft; - } distance = qAbs(contentRect.bottomRight().x() - dockAreaRect.bottomRight().x()); borderDistance[SideBarLocation::SideBarRight] = (distance < minBorderDistance) ? 0 : distance; - if (!borderDistance[SideBarLocation::SideBarRight]) { + if (!borderDistance[SideBarLocation::SideBarRight]) borders |= BorderRight; - } auto sideTab = SideBarLocation::SideBarRight; switch (borders) { @@ -1145,13 +1121,19 @@ bool DockAreaWidget::containsCentralWidget() const QSize DockAreaWidget::minimumSizeHint() const { - if (!d->m_minSizeHint.isValid()) - return Super::minimumSizeHint(); + QSize s(0, 0); + + if (d->m_contentsLayout) { + for (int i = 0; i < d->m_contentsLayout->count(); ++i) { + auto widget = d->m_contentsLayout->widget(i); + s = s.expandedTo(widget->minimumSizeHint()); + } + } if (d->m_titleBar->isVisible()) - return d->m_minSizeHint + QSize(0, d->m_titleBar->minimumSizeHint().height()); - else - return d->m_minSizeHint; + s += QSize(0, d->m_titleBar->minimumSizeHint().height()); + + return s; } void DockAreaWidget::onDockWidgetFeaturesChanged() diff --git a/src/libs/advanceddockingsystem/dockcontainerwidget.cpp b/src/libs/advanceddockingsystem/dockcontainerwidget.cpp index 0b2a03d1348..c57e277eddb 100644 --- a/src/libs/advanceddockingsystem/dockcontainerwidget.cpp +++ b/src/libs/advanceddockingsystem/dockcontainerwidget.cpp @@ -227,8 +227,7 @@ public: eDropMode getDropMode(const QPoint &targetPosition); /** - * Initializes the visible dock area count variable if it is not initialized - * yet + * Initializes the visible dock area count variable if it is not initialized yet */ void initVisibleDockAreaCount() { @@ -245,15 +244,14 @@ public: */ int visibleDockAreaCount() { - // Lazy initialization - we initialize the m_visibleDockAreaCount variable - // on first use + // Lazy initialization - we initialize the m_visibleDockAreaCount variable on first use initVisibleDockAreaCount(); return m_visibleDockAreaCount; } /** - * The visible dock area count changes, if dock areas are remove, added or - * when its view is toggled + * The visible dock area count changes, if dock areas are removed, added or when its view + * is toggled */ void onVisibleDockAreaCountChanged(); @@ -274,15 +272,14 @@ public: */ DockSplitter *createSplitter(Qt::Orientation orientation, QWidget *parent = nullptr) { - auto *splitter = new DockSplitter(orientation, parent); + auto splitter = new DockSplitter(orientation, parent); splitter->setOpaqueResize(DockManager::testConfigFlag(DockManager::OpaqueSplitterResize)); splitter->setChildrenCollapsible(false); return splitter; } /** - * Ensures equal distribution of the sizes of a splitter if an dock widget - * is inserted from code + * Ensures equal distribution of the sizes of a splitter if a dock widget is inserted from code */ void adjustSplitterSizesOnInsertion(QSplitter *splitter, qreal lastRatio = 1.0) { @@ -299,15 +296,14 @@ public: } /** - * This function forces the dock container widget to update handles of splitters - * based if a central widget exists. + * This function forces the dock container widget to update handles of splitters based if + * a central widget exists. */ void updateSplitterHandles(QSplitter *splitter); /** - * If no central widget exists, the widgets resize with the container. - * If a central widget exists, the widgets surrounding the central widget - * do not resize its height or width. + * If no central widget exists, the widgets resize with the container. If a central widget + * exists, the widgets surrounding the central widget do not resize its height or width. */ bool widgetResizesWithContainer(QWidget *widget); @@ -388,12 +384,12 @@ void DockContainerWidgetPrivate::dropIntoContainer(FloatingDockContainer *floati auto newDockAreas = floatingDockContainer->findChildren(QString(), Qt::FindChildrenRecursively); - auto *splitter = m_rootSplitter; + auto splitter = m_rootSplitter; if (m_dockAreas.count() <= 1) { splitter->setOrientation(insertParam.orientation()); } else if (splitter->orientation() != insertParam.orientation()) { - auto *newSplitter = createSplitter(insertParam.orientation()); + auto newSplitter = createSplitter(insertParam.orientation()); QLayoutItem *layoutItem = m_layout->replaceWidget(splitter, newSplitter); newSplitter->addWidget(splitter); updateSplitterHandles(newSplitter); @@ -419,9 +415,8 @@ void DockContainerWidgetPrivate::dropIntoContainer(FloatingDockContainer *floati m_rootSplitter = splitter; addDockAreasToList(newDockAreas); - // If we dropped the floating widget into the main dock container that does - // not contain any dock widgets, then splitter is invisible and we need to - // show it to display the docked widgets + // If we dropped the floating widget into the main dock container that does not contain any dock + // widgets, then splitter is invisible and we need to show it to display the docked widgets if (!splitter->isVisible()) splitter->show(); @@ -467,7 +462,6 @@ void DockContainerWidgetPrivate::dropIntoCenterOfSection(FloatingDockContainer * } targetArea->setCurrentIndex(newCurrentIndex + tabIndex); targetArea->updateTitleBarVisibility(); - return; } void DockContainerWidgetPrivate::dropIntoSection(FloatingDockContainer *floatingWidget, @@ -489,7 +483,7 @@ void DockContainerWidgetPrivate::dropIntoSection(FloatingDockContainer *floating QSplitter *targetAreaSplitter = internal::findParent(targetArea); if (!targetAreaSplitter) { - QSplitter *splitter = createSplitter(insertParam.orientation()); + auto splitter = createSplitter(insertParam.orientation()); m_layout->replaceWidget(targetArea, splitter); splitter->addWidget(targetArea); targetAreaSplitter = splitter; @@ -523,7 +517,7 @@ void DockContainerWidgetPrivate::dropIntoSection(FloatingDockContainer *floating targetAreaSplitter->setSizes(sizes); } } else { - QSplitter *newSplitter = createSplitter(insertParam.orientation()); + auto newSplitter = createSplitter(insertParam.orientation()); int targetAreaSize = (insertParam.orientation() == Qt::Horizontal) ? targetArea->width() : targetArea->height(); bool adjustSplitterSizes = true; @@ -539,8 +533,7 @@ void DockContainerWidgetPrivate::dropIntoSection(FloatingDockContainer *floating } } - // Save the sizes before insertion and restore it later to prevent - // shrinking of existing area + // Save the sizes before insertion and restore it later to prevent shrinking of existing area auto sizes = targetAreaSplitter->sizes(); insertWidgetIntoSplitter(newSplitter, targetArea, !insertParam.append()); updateSplitterHandles(newSplitter); @@ -585,7 +578,7 @@ void DockContainerWidgetPrivate::moveToNewSection(QWidget *widget, } auto insertParam = internal::dockAreaInsertParameters(area); - QSplitter *targetAreaSplitter = internal::findParent(targetArea); + auto targetAreaSplitter = internal::findParent(targetArea); const int areaIndex = targetAreaSplitter->indexOf(targetArea); auto sizes = targetAreaSplitter->sizes(); if (targetAreaSplitter->orientation() == insertParam.orientation()) { @@ -601,7 +594,7 @@ void DockContainerWidgetPrivate::moveToNewSection(QWidget *widget, const int targetAreaSize = (insertParam.orientation() == Qt::Horizontal) ? targetArea->width() : targetArea->height(); - QSplitter *newSplitter = createSplitter(insertParam.orientation()); + auto newSplitter = createSplitter(insertParam.orientation()); newSplitter->addWidget(targetArea); insertWidgetIntoSplitter(newSplitter, newDockArea, insertParam.append()); updateSplitterHandles(newSplitter); @@ -629,10 +622,9 @@ void DockContainerWidgetPrivate::moveToContainer(QWidget *widget, DockWidgetArea newDockArea->addDockWidget(droppedDockWidget); } else { - // We check, if we insert the dropped widget into the same place that - // it already has and do nothing, if it is the same place. It would - // also work without this check, but it looks nicer with the check - // because there will be no layout updates + // We check, if we insert the dropped widget into the same place that it already has and do + // nothing, if it is the same place. It would also work without this check, but it looks + // nicer with the check because there will be no layout updates. auto splitter = internal::findParent(droppedDockArea); auto insertParam = internal::dockAreaInsertParameters(area); if (splitter == m_rootSplitter && insertParam.orientation() == splitter->orientation()) { @@ -679,7 +671,6 @@ void DockContainerWidgetPrivate::moveIntoCenterOfSection(QWidget *widget, } targetArea->updateTitleBarVisibility(); - return; } void DockContainerWidgetPrivate::moveToAutoHideSideBar(QWidget *widget, @@ -740,16 +731,16 @@ void DockContainerWidgetPrivate::addDockAreasToList(const QListtitleBarButton(TitleBarButtonUndock)->setVisible(true); dockArea->titleBarButton(TitleBarButtonClose)->setVisible(true); } - // We need to ensure, that the dock area title bar is visible. The title bar - // is invisible, if the dock are is a single dock area in a floating widget. + // We need to ensure, that the dock area title bar is visible. The title bar is invisible, + // if the dock area is a single dock area in a floating widget. if (1 == countBefore) m_dockAreas.at(0)->updateTitleBarVisibility(); @@ -892,6 +883,9 @@ bool DockContainerWidgetPrivate::restoreSplitter(DockingStateReader &stateReader visible |= childNode->isVisibleTo(splitter); } + if (!testing) + updateSplitterHandles(splitter); + if (sizes.count() != widgetCount) return false; @@ -995,7 +989,7 @@ void DockContainerWidgetPrivate::addDockArea(DockAreaWidget *newDockArea, DockWi if (m_dockAreas.count() <= 1) m_rootSplitter->setOrientation(insertParam.orientation()); - auto *splitter = m_rootSplitter; + auto splitter = m_rootSplitter; if (splitter->orientation() == insertParam.orientation()) { insertWidgetIntoSplitter(splitter, newDockArea, insertParam.append()); updateSplitterHandles(splitter); @@ -1003,7 +997,7 @@ void DockContainerWidgetPrivate::addDockArea(DockAreaWidget *newDockArea, DockWi splitter->show(); } else { - auto *newSplitter = createSplitter(insertParam.orientation()); + auto newSplitter = createSplitter(insertParam.orientation()); if (insertParam.append()) { QLayoutItem *layoutItem = m_layout->replaceWidget(splitter, newSplitter); newSplitter->addWidget(splitter); @@ -1083,18 +1077,19 @@ DockAreaWidget *DockContainerWidgetPrivate::addDockWidgetToDockArea(DockWidgetAr newDockArea->addDockWidget(dockWidget); auto insertParam = internal::dockAreaInsertParameters(area); - QSplitter *targetAreaSplitter = internal::findParent(targetDockArea); + auto targetAreaSplitter = internal::findParent(targetDockArea); int targetIndex = targetAreaSplitter->indexOf(targetDockArea); if (targetAreaSplitter->orientation() == insertParam.orientation()) { qCInfo(adsLog) << "TargetAreaSplitter->orientation() == InsertParam.orientation()"; targetAreaSplitter->insertWidget(targetIndex + insertParam.insertOffset(), newDockArea); + updateSplitterHandles(targetAreaSplitter); // do nothing, if flag is not enabled if (DockManager::testConfigFlag(DockManager::EqualSplitOnInsertion)) adjustSplitterSizesOnInsertion(targetAreaSplitter); } else { qCInfo(adsLog) << "TargetAreaSplitter->orientation() != InsertParam.orientation()"; auto targetAreaSizes = targetAreaSplitter->sizes(); - QSplitter *newSplitter = createSplitter(insertParam.orientation()); + auto newSplitter = createSplitter(insertParam.orientation()); newSplitter->addWidget(targetDockArea); insertWidgetIntoSplitter(newSplitter, newDockArea, insertParam.append()); updateSplitterHandles(newSplitter); @@ -1143,12 +1138,17 @@ DockContainerWidget::~DockContainerWidget() delete d; } +QSize DockContainerWidget::minimumSizeHint() const +{ + return d->m_layout->minimumSize(); +} + DockAreaWidget *DockContainerWidget::addDockWidget(DockWidgetArea area, DockWidget *dockWidget, DockAreaWidget *dockAreaWidget, int index) { - auto currentTopLevelDockWIdget = topLevelDockWidget(); + auto currentTopLevelDockWidget = topLevelDockWidget(); DockAreaWidget *oldDockArea = dockWidget->dockAreaWidget(); if (oldDockArea) oldDockArea->removeDockWidget(dockWidget); @@ -1160,12 +1160,12 @@ DockAreaWidget *DockContainerWidget::addDockWidget(DockWidgetArea area, else dockArea = d->addDockWidgetToContainer(area, dockWidget); - if (currentTopLevelDockWIdget) { + if (currentTopLevelDockWidget) { auto newTopLevelDockWidget = topLevelDockWidget(); // If the container contained only one visible dock widget, we need to emit a top level // event for this widget because it is not the one and only visible docked widget anymore. if (!newTopLevelDockWidget) - DockWidget::emitTopLevelEventForWidget(currentTopLevelDockWIdget, false); + DockWidget::emitTopLevelEventForWidget(currentTopLevelDockWidget, false); } return dockArea; @@ -1177,7 +1177,7 @@ AutoHideDockContainer *DockContainerWidget::createAndSetupAutoHideContainer(Side { if (!DockManager::testAutoHideConfigFlag(DockManager::AutoHideFeatureEnabled)) { Q_ASSERT_X(false, - "CDockContainerWidget::createAndInitializeDockWidgetOverlayContainer", + "DockContainerWidget::createAndInitializeDockWidgetOverlayContainer", "Requested area does not exist in config"); return nullptr; } @@ -1203,7 +1203,7 @@ unsigned int DockContainerWidget::zOrderIndex() const bool DockContainerWidget::isInFrontOf(DockContainerWidget *other) const { - return this->zOrderIndex() > other->zOrderIndex(); + return zOrderIndex() > other->zOrderIndex(); } bool DockContainerWidget::event(QEvent *event) @@ -1243,10 +1243,10 @@ void DockContainerWidget::removeDockArea(DockAreaWidget *area) area->disconnect(this); d->m_dockAreas.removeAll(area); - DockSplitter *splitter = internal::findParent(area); + auto splitter = internal::findParent(area); - // Remove area from parent splitter and recursively hide tree of parent - // splitters if it has no visible content + // Remove area from parent splitter and recursively hide tree of parent splitters if it has + // no visible content area->setParent(nullptr); internal::hideEmptyParentSplitters(splitter); @@ -1262,8 +1262,7 @@ void DockContainerWidget::removeDockArea(DockAreaWidget *area) return; } - // If this is the RootSplitter we need to remove empty splitters to - // avoid too many empty splitters + // If this is the RootSplitter we need to remove empty splitters to avoid too many empty splitters if (splitter == d->m_rootSplitter) { qCInfo(adsLog) << "Removed from RootSplitter"; // If splitter is empty, we are finished @@ -1275,9 +1274,8 @@ void DockContainerWidget::removeDockArea(DockAreaWidget *area) } QWidget *widget = splitter->widget(0); - auto *childSplitter = qobject_cast(widget); - // If the one and only content widget of the splitter is not a splitter - // then we are finished + auto childSplitter = qobject_cast(widget); + // If the one and only content widget of the splitter is not a splitter then we are finished if (!childSplitter) { updateSplitterHandles(splitter); emitAndExit(); @@ -1292,7 +1290,7 @@ void DockContainerWidget::removeDockArea(DockAreaWidget *area) qCInfo(adsLog) << "RootSplitter replaced by child splitter"; } else if (splitter->count() == 1) { qCInfo(adsLog) << "Replacing splitter with content"; - auto *parentSplitter = internal::findParent(splitter); + auto parentSplitter = internal::findParent(splitter); auto sizes = parentSplitter->sizes(); QWidget *widget = splitter->widget(0); widget->setParent(this); @@ -1312,8 +1310,8 @@ void DockContainerWidget::emitAndExit() const { DockWidget *topLevelWidget = topLevelDockWidget(); - // Updated the title bar visibility of the dock widget if there is only - // one single visible dock widget + // Updated the title bar visibility of the dock widget if there is only one single visible + // dock widget DockWidget::emitTopLevelEventForWidget(topLevelWidget, true); dumpLayout(); d->emitDockAreasRemoved(); @@ -1407,12 +1405,11 @@ void DockContainerWidget::dropFloatingWidget(FloatingDockContainer *floatingWidg // Fix https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/351 floatingWidget->hideAndDeleteLater(); - // If we dropped a floating widget with only one single dock widget, then we - // drop a top level widget that changes from floating to docked now + // If we dropped a floating widget with only one single dock widget, then we drop a top + // level widget that changes from floating to docked now DockWidget::emitTopLevelEventForWidget(singleDroppedDockWidget, false); - // If there was a top level widget before the drop, then it is not top - // level widget anymore + // If there was a top level widget before the drop, then it is not top level widget anymore DockWidget::emitTopLevelEventForWidget(singleDockWidget, false); } @@ -1436,8 +1433,7 @@ void DockContainerWidget::dropWidget(QWidget *widget, else d->moveToContainer(widget, dropArea); - // If there was a top level widget before the drop, then it is not top - // level widget anymore + // If there was a top level widget before the drop, then it is not top level widget anymore DockWidget::emitTopLevelEventForWidget(singleDockWidget, false); window()->activateWindow(); @@ -1458,7 +1454,7 @@ QList DockContainerWidget::openedDockAreas() const QList DockContainerWidget::openedDockWidgets() const { QList dockWidgetList; - for (auto dockArea : d->m_dockAreas) { + for (auto dockArea : std::as_const(d->m_dockAreas)) { if (!dockArea->isHidden()) dockWidgetList.append(dockArea->openedDockWidgets()); } @@ -1467,7 +1463,7 @@ QList DockContainerWidget::openedDockWidgets() const bool DockContainerWidget::hasOpenDockAreas() const { - for (auto dockArea : d->m_dockAreas) { + for (auto dockArea : std::as_const(d->m_dockAreas)) { if (!dockArea->isHidden()) return true; } @@ -1531,13 +1527,13 @@ bool DockContainerWidget::restoreState(DockingStateReader &stateReader, bool tes if (testing) return true; - // If the root splitter is empty, restoreChildNodes returns a nullptr - // and we need to create a new empty root splitter + // If the root splitter is empty, restoreChildNodes returns a nullptr and we need to create + // a new empty root splitter if (!newRootSplitter) newRootSplitter = d->createSplitter(Qt::Horizontal); d->m_layout->replaceWidget(d->m_rootSplitter, newRootSplitter); - QSplitter *oldRoot = d->m_rootSplitter; + auto oldRoot = d->m_rootSplitter; d->m_rootSplitter = qobject_cast(newRootSplitter); oldRoot->deleteLater(); @@ -1653,7 +1649,7 @@ void DockContainerWidget::updateSplitterHandles(QSplitter *splitter) void DockContainerWidget::registerAutoHideWidget(AutoHideDockContainer *autohideWidget) { d->m_autoHideWidgets.append(autohideWidget); - Q_EMIT autoHideWidgetCreated(autohideWidget); + emit autoHideWidgetCreated(autohideWidget); qCInfo(adsLog) << "d->AutoHideWidgets.count() " << d->m_autoHideWidgets.count(); } @@ -1699,14 +1695,6 @@ AutoHideSideBar *DockContainerWidget::autoHideSideBar(SideBarLocation area) cons } QRect DockContainerWidget::contentRect() const -{ - if (!d->m_rootSplitter) - return QRect(); - - return d->m_rootSplitter->geometry(); -} - -QRect DockContainerWidget::contentRectGlobal() const { if (!d->m_rootSplitter) return QRect(); @@ -1724,6 +1712,14 @@ QRect DockContainerWidget::contentRectGlobal() const } } +QRect DockContainerWidget::contentRectGlobal() const +{ + if (!d->m_rootSplitter) + return QRect(); + + return internal::globalGeometry(d->m_rootSplitter); +} + DockManager *DockContainerWidget::dockManager() const { return d->m_dockManager; diff --git a/src/libs/advanceddockingsystem/dockcontainerwidget.h b/src/libs/advanceddockingsystem/dockcontainerwidget.h index f2c6085a1d3..77205707bf6 100644 --- a/src/libs/advanceddockingsystem/dockcontainerwidget.h +++ b/src/libs/advanceddockingsystem/dockcontainerwidget.h @@ -193,6 +193,8 @@ public: */ ~DockContainerWidget() override; + QSize minimumSizeHint() const override; + /** * Adds dockwidget into the given area. * If DockAreaWidget is not null, then the area parameter indicates the area diff --git a/src/libs/advanceddockingsystem/dockmanager.cpp b/src/libs/advanceddockingsystem/dockmanager.cpp index 49ce3945748..15e7a9dbb15 100644 --- a/src/libs/advanceddockingsystem/dockmanager.cpp +++ b/src/libs/advanceddockingsystem/dockmanager.cpp @@ -90,6 +90,7 @@ public: QString m_workspacePresetsPath; QList m_workspaces; Workspace m_workspace; + bool m_workspaceLocked = false; QtcSettings *m_settings = nullptr; bool m_modeChangeState = false; @@ -366,6 +367,7 @@ DockManager::~DockManager() emit aboutToUnloadWorkspace(d->m_workspace.fileName()); save(); saveStartupWorkspace(); + saveLockWorkspace(); // Fix memory leaks, see https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/307 std::vector areas; @@ -468,19 +470,17 @@ void DockManager::initialize() if (!workspaceExists(lastWorkspace)) { // This is a fallback mechanism for pre 4.1 settings which stored the workspace name // instead of the file name. - QString minusVariant = lastWorkspace; - minusVariant.replace(" ", "-"); - minusVariant.append("." + workspaceFileExtension); - if (workspaceExists(minusVariant)) - workspace = minusVariant; + const std::vector separators = {"-", "_"}; - QString underscoreVariant = lastWorkspace; - underscoreVariant.replace(" ", "_"); - underscoreVariant.append("." + workspaceFileExtension); + for (const QString &separator : separators) { + QString workspaceVariant = lastWorkspace; + workspaceVariant.replace(" ", separator); + workspaceVariant.append("." + workspaceFileExtension); - if (workspaceExists(underscoreVariant)) - workspace = underscoreVariant; + if (workspaceExists(workspaceVariant)) + workspace = workspaceVariant; + } } else { workspace = lastWorkspace; } @@ -489,6 +489,8 @@ void DockManager::initialize() } openWorkspace(workspace); + + lockWorkspace(d->m_settings->value(Constants::LOCK_WORKSPACE_SETTINGS_KEY, false).toBool()); } DockAreaWidget *DockManager::addDockWidget(DockWidgetArea area, @@ -1103,6 +1105,32 @@ void DockManager::showWorkspaceMananger() workspaceDialog.autoLoadWorkspace()); } +void DockManager::lockWorkspace(bool value) +{ + if (value == d->m_workspaceLocked) + return; + + d->m_workspaceLocked = value; + + DockWidget::DockWidgetFeatures features = DockWidget::DefaultDockWidgetFeatures; + + if (value) { + internal::setFlag(features, DockWidget::DockWidgetMovable, false); + internal::setFlag(features, DockWidget::DockWidgetFloatable, false); + } + + const auto &dockWidgets = dockWidgetsMap(); + for (auto dockWidget : dockWidgets) + dockWidget->setFeatures(features); + + emit lockWorkspaceChanged(); +} + +bool DockManager::isWorkspaceLocked() const +{ + return d->m_workspaceLocked; +} + expected_str DockManager::createWorkspace(const QString &workspaceName) { qCInfo(adsLog) << "Create workspace" << workspaceName; @@ -1111,7 +1139,7 @@ expected_str DockManager::createWorkspace(const QString &workspaceName) uniqueWorkspaceFileName(fileName); const FilePath filePath = userDirectory().pathAppended(fileName); - expected_str result = write(filePath, saveState(workspaceName)); + expected_str result = write(filePath, saveState(workspaceName)); // TODO utils if (!result) return make_unexpected(result.error()); @@ -1458,9 +1486,6 @@ QByteArray DockManager::loadFile(const FilePath &filePath) QString DockManager::readDisplayName(const FilePath &filePath) { - if (!filePath.exists()) - return {}; - auto data = loadFile(filePath); if (data.isEmpty()) @@ -1564,8 +1589,11 @@ void DockManager::syncWorkspacePresets() // If *.wrk file and displayName attribute is empty set the displayName. This // should fix old workspace files which don't have the displayName attribute. - if (userFile.suffix() == workspaceFileExtension && readDisplayName(userFile).isEmpty()) - writeDisplayName(userFile, readDisplayName(filePath)); + if (userFile.suffix() == workspaceFileExtension) { + const QString name = readDisplayName(userFile); + if (name.isEmpty()) + writeDisplayName(userFile, name); + } continue; } @@ -1615,4 +1643,10 @@ void DockManager::saveStartupWorkspace() activeWorkspace()->fileName()); } +void DockManager::saveLockWorkspace() +{ + QTC_ASSERT(d->m_settings, return); + d->m_settings->setValue(Constants::LOCK_WORKSPACE_SETTINGS_KEY, d->m_workspaceLocked); +} + } // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockmanager.h b/src/libs/advanceddockingsystem/dockmanager.h index 242b773fa77..c11bd6ebb16 100644 --- a/src/libs/advanceddockingsystem/dockmanager.h +++ b/src/libs/advanceddockingsystem/dockmanager.h @@ -33,6 +33,7 @@ namespace Constants { const char DEFAULT_WORKSPACE[] = "Basic.wrk"; // Needs to align with a shipped preset const char STARTUP_WORKSPACE_SETTINGS_KEY[] = "QML/Designer/StartupWorkspace"; const char AUTO_RESTORE_WORKSPACE_SETTINGS_KEY[] = "QML/Designer/AutoRestoreLastWorkspace"; +const char LOCK_WORKSPACE_SETTINGS_KEY[] = "QML/Designer/LockWorkspace"; } // namespace Constants class DockManagerPrivate; @@ -663,6 +664,9 @@ public: // Workspace management functionality void showWorkspaceMananger(); + void lockWorkspace(bool value); + bool isWorkspaceLocked() const; + /** * \brief Create a workspace. * @@ -735,6 +739,7 @@ signals: void workspaceLoaded(QString fileName); void workspaceReloaded(QString fileName); void aboutToSaveWorkspace(); + void lockWorkspaceChanged(); private: static Utils::expected_str write(const Utils::FilePath &filePath, const QByteArray &data); @@ -748,6 +753,7 @@ private: void prepareWorkspaces(); void saveStartupWorkspace(); + void saveLockWorkspace(); }; // class DockManager } // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockwidget.cpp b/src/libs/advanceddockingsystem/dockwidget.cpp index f2e0e72249f..6c89c92c66f 100644 --- a/src/libs/advanceddockingsystem/dockwidget.cpp +++ b/src/libs/advanceddockingsystem/dockwidget.cpp @@ -557,9 +557,8 @@ void DockWidget::toggleViewInternal(bool open) if (open && topLevelDockWidgetBefore) DockWidget::emitTopLevelEventForWidget(topLevelDockWidgetBefore, false); - // Here we need to call the dockContainer() function again, because if - // this dock widget was unassigned before the call to showDockWidget() then - // it has a dock container now + // Here we need to call the dockContainer() function again, because if this dock widget was + // unassigned before the call to showDockWidget() then it has a dock container now const DockContainerWidget *const dockContainerWidget = dockContainer(); DockWidget *topLevelDockWidgetAfter = dockContainerWidget ? dockContainerWidget->topLevelDockWidget() @@ -614,18 +613,17 @@ bool DockWidget::event(QEvent *event) case QEvent::WindowTitleChange: { const auto title = windowTitle(); - if (d->m_tabWidget) { + if (d->m_tabWidget) d->m_tabWidget->setText(title); - } + if (d->m_sideTabWidget) d->m_sideTabWidget->setText(title); - if (d->m_toggleViewAction) { + if (d->m_toggleViewAction) d->m_toggleViewAction->setText(title); - } - if (d->m_dockArea) { + + if (d->m_dockArea) d->m_dockArea->markTitleBarMenuOutdated(); // update tabs menu - } auto floatingWidget = floatingDockContainer(); if (floatingWidget) @@ -771,14 +769,6 @@ QSize DockWidget::minimumSizeHint() const if (!d->m_widget) return QSize(60, 40); - // TODO - DockContainerWidget *container = this->dockContainer(); - if (!container || container->isFloating()) { - const QSize sh = d->m_widget->minimumSizeHint(); - const QSize s = d->m_widget->minimumSize(); - return {std::max(s.width(), sh.width()), std::max(s.height(), sh.height())}; - } - switch (d->m_minimumSizeHintMode) { case MinimumSizeHintFromDockWidget: return QSize(60, 40); @@ -837,8 +827,7 @@ bool DockWidget::closeDockWidgetInternal(bool forceClose) return false; if (features().testFlag(DockWidget::DockWidgetDeleteOnClose)) { - // If the dock widget is floating, then we check if we also need to - // delete the floating widget + // If the dock widget is floating, then check if we also need to delete the floating widget if (isFloating()) { FloatingDockContainer *floatingWidget = internal::findParent( this); diff --git a/src/libs/advanceddockingsystem/dockwidgettab.cpp b/src/libs/advanceddockingsystem/dockwidgettab.cpp index 44a7c33d89e..56bdd10123e 100644 --- a/src/libs/advanceddockingsystem/dockwidgettab.cpp +++ b/src/libs/advanceddockingsystem/dockwidgettab.cpp @@ -420,22 +420,21 @@ void DockWidgetTab::mouseMoveEvent(QMouseEvent *event) int dragDistanceY = qAbs(d->m_globalDragStartMousePosition.y() - event->globalPosition().toPoint().y()); if (dragDistanceY >= DockManager::startDragDistance() || mouseOutsideBar) { - // If this is the last dock area in a dock container with only - // one single dock widget it does not make sense to move it to a new - // floating widget and leave this one empty + // If this is the last dock area in a dock container with only one single dock widget it + // does not make sense to move it to a new floating widget and leave this one empty if (d->m_dockArea->dockContainer()->isFloating() && d->m_dockArea->openDockWidgetsCount() == 1 && d->m_dockArea->dockContainer()->visibleDockAreaCount() == 1) { return; } - // Floating is only allowed for widgets that are floatable - // We can create the drag preview if the widget is movable. + // Floating is only allowed for widgets that are floatable. We can create the drag preview + // if the widget is movable. auto features = d->m_dockWidget->features(); if (features.testFlag(DockWidget::DockWidgetFloatable) - || (features.testFlag(DockWidget::DockWidgetMovable))) { - // If we undock, we need to restore the initial position of this - // tab because it looks strange if it remains on its dragged position + || features.testFlag(DockWidget::DockWidgetMovable)) { + // If we undock, we need to restore the initial position of this tab because it looks + // strange if it remains on its dragged position if (d->isDraggingState(DraggingTab)) parentWidget()->layout()->update(); diff --git a/src/libs/advanceddockingsystem/floatingdockcontainer.cpp b/src/libs/advanceddockingsystem/floatingdockcontainer.cpp index 83f203c5e57..be645ebbcd3 100644 --- a/src/libs/advanceddockingsystem/floatingdockcontainer.cpp +++ b/src/libs/advanceddockingsystem/floatingdockcontainer.cpp @@ -496,8 +496,8 @@ void FloatingDockContainerPrivate::updateDropOverlays(const QPoint &globalPositi if (m_dockContainer == containerWidget) continue; - QPoint mappedPos = containerWidget->mapFromGlobal(globalPosition); - if (containerWidget->rect().contains(mappedPos)) { + QPoint mappedPosition = containerWidget->mapFromGlobal(globalPosition); + if (containerWidget->rect().contains(mappedPosition)) { if (!topContainer || containerWidget->isInFrontOf(topContainer)) topContainer = containerWidget; } @@ -580,7 +580,6 @@ FloatingDockContainer::FloatingDockContainer(DockManager *dockManager) // on linux are missing from floating dock widgets. setWindowFlags(Qt::Window | Qt::WindowMinMaxButtonsHint | Qt::FramelessWindowHint | Qt::Tool); QDockWidget::setWidget(d->m_dockContainer); - //QDockWidget::setFloating(true); QDockWidget::setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); @@ -639,6 +638,11 @@ FloatingDockContainer::~FloatingDockContainer() delete d; } +QSize FloatingDockContainer::minimumSizeHint() const +{ + return d->m_dockContainer->minimumSizeHint(); +} + DockContainerWidget *FloatingDockContainer::dockContainer() const { return d->m_dockContainer; diff --git a/src/libs/advanceddockingsystem/floatingdockcontainer.h b/src/libs/advanceddockingsystem/floatingdockcontainer.h index da2fc42138a..02fd003f217 100644 --- a/src/libs/advanceddockingsystem/floatingdockcontainer.h +++ b/src/libs/advanceddockingsystem/floatingdockcontainer.h @@ -187,6 +187,8 @@ public: */ ~FloatingDockContainer() override; + virtual QSize minimumSizeHint() const override; + /** * Access function for the internal dock container */ diff --git a/src/libs/advanceddockingsystem/workspace.cpp b/src/libs/advanceddockingsystem/workspace.cpp index d3b0785d90b..760e2c7dd1c 100644 --- a/src/libs/advanceddockingsystem/workspace.cpp +++ b/src/libs/advanceddockingsystem/workspace.cpp @@ -41,6 +41,16 @@ const QString &Workspace::name() const return m_name; } +void Workspace::setLocked(bool value) +{ + m_locked = value; +} + +bool Workspace::isLocked() const +{ + return m_locked; +} + const Utils::FilePath &Workspace::filePath() const { return m_filePath; diff --git a/src/libs/advanceddockingsystem/workspace.h b/src/libs/advanceddockingsystem/workspace.h index c23db55d6b6..5e96e5a9815 100644 --- a/src/libs/advanceddockingsystem/workspace.h +++ b/src/libs/advanceddockingsystem/workspace.h @@ -18,6 +18,9 @@ public: void setName(const QString &name); const QString &name() const; + void setLocked(bool value); + bool isLocked() const; + const Utils::FilePath &filePath() const; QString fileName() const; @@ -50,6 +53,7 @@ private: QString m_name; Utils::FilePath m_filePath; bool m_preset = false; + bool m_locked = false; }; } // namespace ADS diff --git a/src/libs/nanotrace/CMakeLists.txt b/src/libs/nanotrace/CMakeLists.txt index 17e2573829d..c504a112e37 100644 --- a/src/libs/nanotrace/CMakeLists.txt +++ b/src/libs/nanotrace/CMakeLists.txt @@ -1,8 +1,11 @@ add_qtc_library(Nanotrace BUILD_DEFAULT OFF PUBLIC_DEFINES NANOTRACE_ENABLED - SOURCES nanotrace.cpp nanotrace.h - PUBLIC_DEPENDS Qt::Core + SOURCES + nanotraceglobals.h + nanotrace.cpp nanotrace.h + nanotracehr.cpp nanotracehr.h + PUBLIC_DEPENDS Qt::Core Qt::Gui PROPERTIES CXX_VISIBILITY_PRESET default VISIBILITY_INLINES_HIDDEN OFF diff --git a/src/libs/nanotrace/nanotrace.h b/src/libs/nanotrace/nanotrace.h index 4003ae5feb0..ed17797e344 100644 --- a/src/libs/nanotrace/nanotrace.h +++ b/src/libs/nanotrace/nanotrace.h @@ -3,15 +3,8 @@ #pragma once -#include +#include "nanotraceglobals.h" -#if defined(NANOTRACE_LIBRARY) -# define NANOTRACESHARED_EXPORT Q_DECL_EXPORT -#elif defined(NANOTRACE_STATIC_LIBRARY) -# define NANOTRACESHARED_EXPORT -#else -# define NANOTRACESHARED_EXPORT Q_DECL_IMPORT -#endif #include #include diff --git a/src/libs/nanotrace/nanotraceglobals.h b/src/libs/nanotrace/nanotraceglobals.h new file mode 100644 index 00000000000..649408d69c9 --- /dev/null +++ b/src/libs/nanotrace/nanotraceglobals.h @@ -0,0 +1,16 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +#if defined(NANOTRACE_LIBRARY) +#define NANOTRACE_EXPORT Q_DECL_EXPORT +#elif defined(NANOTRACE_STATIC_LIBRARY) +#define NANOTRACE_EXPORT +#else +#define NANOTRACE_EXPORT Q_DECL_IMPORT +#endif + +#define NANOTRACESHARED_EXPORT NANOTRACE_EXPORT diff --git a/src/libs/nanotrace/nanotracehr.cpp b/src/libs/nanotrace/nanotracehr.cpp new file mode 100644 index 00000000000..3f02bd527d3 --- /dev/null +++ b/src/libs/nanotrace/nanotracehr.cpp @@ -0,0 +1,278 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "nanotracehr.h" + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#ifdef Q_OS_UNIX +# include +#endif + +namespace NanotraceHR { + +namespace { + +bool hasId(char phase) +{ + switch (phase) { + case 'b': + case 'n': + case 'e': + return true; + } + + return false; +} + +template +unsigned int getUnsignedIntegerHash(Id id) +{ + return static_cast(id & 0xFFFFFFFF); +} + +unsigned int getUnsignedIntegerHash(std::thread::id id) +{ + return static_cast(std::hash{}(id) & 0xFFFFFFFF); +} + +template +void printEvent(std::ostream &out, const TraceEvent &event, qint64 processId, std::thread::id threadId) +{ + out << R"({"ph":")" << event.type << R"(","name":")" << event.name << R"(","cat":")" + << event.category << R"(","ts":)" + << static_cast(event.time.time_since_epoch().count()) / 1000 << R"(,"pid":)" + << getUnsignedIntegerHash(processId) << R"(,"tid":)" << getUnsignedIntegerHash(threadId); + + if (event.type == 'X') + out << R"(,"dur":)" << static_cast(event.duration.count()) / 1000; + + if (hasId(event.type)) + out << R"(,"id":)" << event.id; + + if (event.bindId) { + out << R"(,"bind_id":)" << event.bindId; + if (event.flow & IsFlow::Out) + out << R"(,"flow_out":true)"; + if (event.flow & IsFlow::In) + out << R"(,"flow_in":true)"; + } + + if (event.arguments.size()) + out << R"(,"args":)" << event.arguments; + + out << "}"; +} + +void writeMetaEvent(TraceFile *file, std::string_view key, std::string_view value) +{ + std::lock_guard lock{file->fileMutex}; + auto &out = file->out; + + if (out.is_open()) { + file->out << R"({"name":")" << key << R"(","ph":"M", "pid":)" + << getUnsignedIntegerHash(QCoreApplication::applicationPid()) << R"(,"tid":)" + << getUnsignedIntegerHash(std::this_thread::get_id()) << R"(,"args":{"name":")" + << value << R"("}})" + << ",\n"; + } +} + +std::string getThreadName() +{ + std::array buffer; + buffer[0] = 0; +#ifdef Q_OS_UNIX + auto rc = pthread_getname_np(pthread_self(), buffer.data(), buffer.size()); + if (rc != 0) + return {}; + +#endif + + return buffer.data(); +} + +} // namespace + +namespace Internal { +template +void convertToString(String &string, const QImage &image) +{ + using namespace std::string_view_literals; + auto dict = dictonary(keyValue("width", image.width()), + keyValue("height", image.height()), + keyValue("bytes", image.sizeInBytes()), + keyValue("has alpha channel", image.hasAlphaChannel()), + keyValue("is color", !image.isGrayscale()), + keyValue("pixel format", + dictonary(keyValue("bits per pixel", + image.pixelFormat().bitsPerPixel()), + keyValue("byte order", + [&] { + if (image.pixelFormat().byteOrder() + == QPixelFormat::BigEndian) + return "big endian"sv; + else + return "little endian"sv; + }), + keyValue("premultiplied", [&] { + if (image.pixelFormat().premultiplied() + == QPixelFormat::Premultiplied) + return "premultiplied"sv; + else + return "alpha premultiplied"sv; + })))); + + Internal::convertToString(string, dict); +} + +template NANOTRACE_EXPORT void convertToString(std::string &string, const QImage &image); +template NANOTRACE_EXPORT void convertToString(ArgumentsString &string, const QImage &image); + +} // namespace Internal + +template +void flushEvents(const Utils::span events, + std::thread::id threadId, + EnabledEventQueue &eventQueue) +{ + if (events.empty()) + return; + + std::lock_guard lock{eventQueue.file->fileMutex}; + auto &out = eventQueue.file->out; + + if (out.is_open()) { + auto processId = QCoreApplication::applicationPid(); + for (const auto &event : events) { + printEvent(out, event, processId, threadId); + out << ",\n"; + } + } +} + +template NANOTRACE_EXPORT void flushEvents(const Utils::span events, + std::thread::id threadId, + EnabledEventQueue &eventQueue); +template NANOTRACE_EXPORT void flushEvents(const Utils::span events, + std::thread::id threadId, + EnabledEventQueue &eventQueue); +template NANOTRACE_EXPORT void flushEvents( + const Utils::span events, + std::thread::id threadId, + EnabledEventQueue &eventQueue); + +void openFile(EnabledTraceFile &file) +{ + std::lock_guard lock{file.fileMutex}; + + if (file.out = std::ofstream{file.filePath, std::ios::trunc}; file.out.good()) { + file.out << std::fixed << std::setprecision(3) << R"({"traceEvents": [)"; + file.out << R"({"name":"process_name","ph":"M", "pid":)" + << QCoreApplication::applicationPid() << R"(,"args":{"name":"QtCreator"}})" + << ",\n"; + } +} + +void finalizeFile(EnabledTraceFile &file) +{ + std::lock_guard lock{file.fileMutex}; + auto &out = file.out; + + if (out.is_open()) { + out.seekp(-2, std::ios_base::cur); // removes last comma and new line + out << R"(],"displayTimeUnit":"ns","otherData":{"version": "Qt Creator )"; + out << QCoreApplication::applicationVersion().toStdString(); + out << R"("}})"; + out.close(); + } +} + +template +void flushInThread(EnabledEventQueue &eventQueue) +{ + if (eventQueue.file->processing.valid()) + eventQueue.file->processing.wait(); + + auto flush = [&](const Utils::span &events, std::thread::id threadId) { + flushEvents(events, threadId, eventQueue); + }; + + eventQueue.file->processing = std::async(std::launch::async, + flush, + eventQueue.currentEvents.subspan(0, eventQueue.eventsIndex), + eventQueue.threadId); + eventQueue.currentEvents = eventQueue.currentEvents.data() == eventQueue.eventsOne.data() + ? eventQueue.eventsTwo + : eventQueue.eventsOne; + eventQueue.eventsIndex = 0; +} + +template NANOTRACE_EXPORT void flushInThread(EnabledEventQueue &eventQueue); +template NANOTRACE_EXPORT void flushInThread(EnabledEventQueue &eventQueue); +template NANOTRACE_EXPORT void flushInThread( + EnabledEventQueue &eventQueue); + +namespace { +TraceFile globalTraceFile{"global.json"}; +thread_local EventQueueData globalEventQueueData{ + globalTraceFile}; +thread_local EventQueue s_globalEventQueue = globalEventQueueData.createEventQueue(); +} // namespace + +EventQueue &globalEventQueue() +{ + return s_globalEventQueue; +} + +template +EventQueue::EventQueue(EnabledTraceFile *file, + TraceEventsSpan eventsOne, + TraceEventsSpan eventsTwo) + : file{file} + , eventsOne{eventsOne} + , eventsTwo{eventsTwo} + , currentEvents{eventsOne} + , threadId{std::this_thread::get_id()} +{ + Internal::EventQueueTracker::get().addQueue(this); + if (auto thread = QThread::currentThread()) { + auto name = getThreadName(); + if (name.size()) { + writeMetaEvent(file, "thread_name", name); + } + } +} + +template +EventQueue::~EventQueue() +{ + Internal::EventQueueTracker::get().removeQueue(this); + + flush(); +} + +template +void EventQueue::flush() +{ + std::lock_guard lock{mutex}; + if (isEnabled == IsEnabled::Yes && eventsIndex > 0) { + flushEvents(currentEvents.subspan(0, eventsIndex), threadId, *this); + eventsIndex = 0; + } +} + +template class EventQueue; +template class EventQueue; +template class EventQueue; + +} // namespace NanotraceHR diff --git a/src/libs/nanotrace/nanotracehr.h b/src/libs/nanotrace/nanotracehr.h new file mode 100644 index 00000000000..d49e12a87a0 --- /dev/null +++ b/src/libs/nanotrace/nanotracehr.h @@ -0,0 +1,1619 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "nanotraceglobals.h" + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace NanotraceHR { +using Clock = std::chrono::steady_clock; +using TimePoint = std::chrono::time_point; +using Duration = std::chrono::nanoseconds; + +static_assert(Clock::is_steady, "clock should be steady"); +static_assert(std::is_same_v, + "the steady clock should have nano second resolution"); + +enum class Tracing { IsDisabled, IsEnabled }; + +constexpr Tracing tracingStatus() +{ +#ifdef NANOTRACE_ENABLED + return Tracing::IsEnabled; +#else + return Tracing::IsDisabled; +#endif +} + +#if __cplusplus >= 202002L && __has_cpp_attribute(msvc::no_unique_address) +# define NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] +#elif __cplusplus >= 202002L && __has_cpp_attribute(no_unique_address) >= 201803L +# define NO_UNIQUE_ADDRESS [[no_unique_address]] +#else +# define NO_UNIQUE_ADDRESS +#endif + +using ArgumentsString = Utils::BasicSmallString<510>; + +namespace Literals { +struct TracerLiteral +{ + friend constexpr TracerLiteral operator""_t(const char *text, size_t size); + + constexpr operator std::string_view() const { return text; } + + operator std::string() const { return std::string{text}; } + +private: + constexpr TracerLiteral(std::string_view text) + : text{text} + {} + + std::string_view text; +}; + +constexpr TracerLiteral operator""_t(const char *text, size_t size) +{ + return {std::string_view{text, size}}; +} +} // namespace Literals + +using namespace Literals; + +struct IsArray +{}; + +inline constexpr IsArray isArray; + +struct IsDictonary +{}; + +inline constexpr IsDictonary isDictonary; + +namespace Internal { + +template +void convertToString(String &string, std::string_view text) +{ + string.append(R"(")"); + string.append(text); + string.append(R"(")"); +} + +template +void convertToString(String &string, const QImage &image); + +extern template NANOTRACE_EXPORT void convertToString(std::string &string, const QImage &image); +extern template NANOTRACE_EXPORT void convertToString(ArgumentsString &string, const QImage &image); + +template +void convertToString(String &string, const char (&text)[size]) +{ + string.append(R"(")"); + string.append(text); + string.append(R"(")"); +} + +template +void convertToString(String &string, QStringView text) +{ + string.append(R"(")"); + string.append(Utils::PathString{text}); + string.append(R"(")"); +} + +template +void convertToString(String &string, const QByteArray &text) +{ + string.append(R"(")"); + string.append(std::string_view(text.data(), Utils::usize(text))); + string.append(R"(")"); +} + +template +void convertToString(String &string, bool isTrue) +{ + if (isTrue) + string.append("true"); + else + string.append("false"); +} + +template>> +void convertToString(String &string, Callable &&callable) +{ + convertToString(string, callable()); +} + +template +void convertToString(String &string, int number) +{ + string.append(Utils::SmallString::number(number)); +} + +template +void convertToString(String &string, long long number) +{ + string.append(Utils::SmallString::number(number)); +} + +template +void convertToString(String &string, double number) +{ + string.append(Utils::SmallString::number(number)); +} + +template +void convertToString(String &string, const std::tuple &dictonary); + +template +void convertToString(String &string, const std::tuple &list); + +template typename Container, typename... Arguments> +void convertToString(String &string, const Container &container); + +template +void convertArrayEntryToString(String &string, const Value &value) +{ + convertToString(string, value); + string.append(","); +} + +template +void convertArrayToString(String &string, const IsArray &, Entries &...entries) +{ + string.append(R"([)"); + (convertArrayEntryToString(string, entries), ...); + if (sizeof...(entries)) + string.pop_back(); + string.append("]"); +} + +template +void convertToString(String &string, const std::tuple &list) +{ + std::apply([&](auto &&...entries) { convertArrayToString(string, entries...); }, list); +} + +template +void convertDictonaryEntryToString(String &string, const std::tuple &argument) +{ + const auto &[key, value] = argument; + convertToString(string, key); + string.append(":"); + convertToString(string, value); + string.append(","); +} + +template +void convertDictonaryToString(String &string, const IsDictonary &, Entries &...entries) +{ + string.append(R"({)"); + (convertDictonaryEntryToString(string, entries), ...); + if (sizeof...(entries)) + string.pop_back(); + string.append("}"); +} + +template +void convertToString(String &string, const std::tuple &dictonary) +{ + std::apply([&](auto &&...entries) { convertDictonaryToString(string, entries...); }, dictonary); +} + +template typename Container, typename... Arguments> +void convertToString(String &string, const Container &container) +{ + string.append("["); + for (const auto &entry : container) { + convertToString(string, entry); + string.append(","); + } + + if (container.size()) + string.pop_back(); + + string.append("]"); +} + +template +String toArguments(Arguments &&...arguments) +{ + if constexpr (tracingStatus() == Tracing::IsEnabled) { + String text; + constexpr auto argumentCount = sizeof...(Arguments); + text.append("{"); + (convertDictonaryEntryToString(text, arguments), ...); + if (argumentCount) + text.pop_back(); + + text.append("}"); + + return text; + } else { + return {}; + } +} + +inline std::string_view toArguments(std::string_view arguments) +{ + return arguments; +} + +template +void appendArguments(String &eventArguments) +{ + eventArguments = {}; +} + +template +void appendArguments(String &eventArguments, TracerLiteral arguments) +{ + eventArguments = arguments; +} + +template +[[maybe_unused]] void appendArguments(String &eventArguments, Arguments &&...arguments) +{ + static_assert( + !std::is_same_v, + R"(The arguments type of the tracing event queue is a string view. You can only provide trace token arguments as TracerLiteral (""_t).)"); + + eventArguments = Internal::toArguments(std::forward(arguments)...); +} + +} // namespace Internal + +template +auto keyValue(Key &&key, Value &&value) +{ + return std::forward_as_tuple(std::forward(key), std::forward(value)); +} + +template +auto dictonary(Entries &&...entries) +{ + return std::forward_as_tuple(isDictonary, std::forward(entries)...); +} + +template +auto array(Entries &&...entries) +{ + return std::forward_as_tuple(isArray, std::forward(entries)...); +} + +enum class IsFlow : std::size_t { No = 0, Out = 1 << 0, In = 1 << 1, InOut = In | Out }; + +inline bool operator&(IsFlow first, IsFlow second) +{ + return static_cast>(first) + & static_cast>(second); +} + +template +struct TraceEvent +{ + using StringType = String; + using ArgumentType = std::conditional_t, TracerLiteral, String>; + using ArgumentsStringType = ArgumentsString; + + TraceEvent() = default; + TraceEvent(const TraceEvent &) = delete; + TraceEvent(TraceEvent &&) = delete; + TraceEvent &operator=(const TraceEvent &) = delete; + TraceEvent &operator=(TraceEvent &&) = delete; + ~TraceEvent() = default; + + String name; + String category; + ArgumentsString arguments; + TimePoint time; + Duration duration; + std::size_t id = 0; + std::size_t bindId : 62; + IsFlow flow : 2; + char type = ' '; +}; + +using StringViewTraceEvent = TraceEvent; +using StringViewWithStringArgumentsTraceEvent = TraceEvent; +using StringTraceEvent = TraceEvent; + +enum class IsEnabled { No, Yes }; + +template +class EventQueue; + +template +using EnabledEventQueue = EventQueue; + +template +void flushEvents(const Utils::span events, + std::thread::id threadId, + EnabledEventQueue &eventQueue); +extern template NANOTRACE_EXPORT void flushEvents(const Utils::span events, + std::thread::id threadId, + EnabledEventQueue &eventQueue); +extern template NANOTRACE_EXPORT void flushEvents(const Utils::span events, + std::thread::id threadId, + EnabledEventQueue &eventQueue); +extern template NANOTRACE_EXPORT void flushEvents( + const Utils::span events, + std::thread::id threadId, + EnabledEventQueue &eventQueue); +template +void flushInThread(EnabledEventQueue &eventQueue); +extern template NANOTRACE_EXPORT void flushInThread(EnabledEventQueue &eventQueue); +extern template NANOTRACE_EXPORT void flushInThread(EnabledEventQueue &eventQueue); +extern template NANOTRACE_EXPORT void flushInThread( + EnabledEventQueue &eventQueue); + +template +class TraceFile; + +using EnabledTraceFile = TraceFile; + +NANOTRACE_EXPORT void openFile(EnabledTraceFile &file); +NANOTRACE_EXPORT void finalizeFile(EnabledTraceFile &file); + +template +class TraceFile +{ +public: + using IsActive = std::false_type; + + TraceFile(std::string_view) {} +}; + +template<> +class TraceFile +{ +public: + using IsActive = std::true_type; + + TraceFile([[maybe_unused]] std::string_view filePath) + : filePath{filePath} + { + openFile(*this); + } + + TraceFile(const TraceFile &) = delete; + TraceFile(TraceFile &&) = delete; + TraceFile &operator=(const TraceFile &) = delete; + TraceFile &operator=(TraceFile &&) = delete; + + ~TraceFile() { finalizeFile(*this); } + + std::string filePath; + std::mutex fileMutex; + std::future processing; + std::ofstream out; +}; + +template +class EventQueue +{ +public: + using IsActive = std::false_type; +}; + +namespace Internal { + +template +class EventQueueTracker +{ + using Queue = EventQueue; + +public: + EventQueueTracker() = default; + EventQueueTracker(const EventQueueTracker &) = delete; + EventQueueTracker(EventQueueTracker &&) = delete; + EventQueueTracker &operator=(const EventQueueTracker &) = delete; + EventQueueTracker &operator=(EventQueueTracker &&) = delete; + + ~EventQueueTracker() + { + std::lock_guard lock{mutex}; + + for (auto queue : queues) + queue->flush(); + } + + void addQueue(Queue *queue) + { + std::lock_guard lock{mutex}; + queues.push_back(queue); + } + + void removeQueue(Queue *queue) + { + std::lock_guard lock{mutex}; + queues.erase(std::remove(queues.begin(), queues.end(), queue), queues.end()); + } + + static EventQueueTracker &get() + { + static EventQueueTracker tracker; + + return tracker; + } + +private: + std::mutex mutex; + std::vector queues; +}; +} // namespace Internal + +template +class EventQueue +{ + using TraceEventsSpan = Utils::span; + +public: + using IsActive = std::true_type; + + EventQueue(EnabledTraceFile *file, TraceEventsSpan eventsOne, TraceEventsSpan eventsTwo); + + ~EventQueue(); + + void flush(); + + EventQueue(const EventQueue &) = delete; + EventQueue(EventQueue &&) = delete; + EventQueue &operator=(const EventQueue &) = delete; + EventQueue &operator=(EventQueue &&) = delete; + + EnabledTraceFile *file = nullptr; + TraceEventsSpan eventsOne; + TraceEventsSpan eventsTwo; + TraceEventsSpan currentEvents; + std::size_t eventsIndex = 0; + IsEnabled isEnabled = IsEnabled::Yes; + std::mutex mutex; + std::thread::id threadId; +}; + +template +using StringViewEventQueue = EventQueue; +template +using StringViewWithStringArgumentsEventQueue = EventQueue; +template +using StringEventQueue = EventQueue; + +extern template class NANOTRACE_EXPORT EventQueue; +extern template class NANOTRACE_EXPORT EventQueue; +extern template class NANOTRACE_EXPORT EventQueue; + +template +class EventQueueData +{ +public: + using IsActive = std::true_type; + + EventQueueData(TraceFile &) {} + + EventQueue createEventQueue() { return {}; } +}; + +template +class EventQueueData +{ + using TraceEvents = std::array; + +public: + using IsActive = std::true_type; + + EventQueueData(EnabledTraceFile &file) + : file{file} + {} + + EventQueue createEventQueue() + { + return {&file, eventsOne, eventsTwo}; + } + + EnabledTraceFile &file; + TraceEvents eventsOne; + TraceEvents eventsTwo; +}; + +NANOTRACE_EXPORT EventQueue &globalEventQueue(); + +template +TraceEvent &getTraceEvent(EnabledEventQueue &eventQueue) +{ + if (eventQueue.eventsIndex == eventQueue.currentEvents.size()) + flushInThread(eventQueue); + + return eventQueue.currentEvents[eventQueue.eventsIndex++]; +} + +class BasicDisabledToken +{ +public: + using IsActive = std::false_type; + + BasicDisabledToken() {} + + BasicDisabledToken(const BasicDisabledToken &) = default; + BasicDisabledToken &operator=(const BasicDisabledToken &) = default; + BasicDisabledToken(BasicDisabledToken &&other) noexcept = default; + BasicDisabledToken &operator=(BasicDisabledToken &&other) noexcept = default; + + ~BasicDisabledToken() {} + + constexpr explicit operator bool() const { return false; } + + static constexpr bool isActive() { return false; } +}; + +class BasicEnabledToken +{ +public: + using IsActive = std::true_type; + + BasicEnabledToken() {} + + BasicEnabledToken(const BasicEnabledToken &) = default; + BasicEnabledToken &operator=(const BasicEnabledToken &) = default; + BasicEnabledToken(BasicEnabledToken &&other) noexcept = default; + BasicEnabledToken &operator=(BasicEnabledToken &&other) noexcept = default; + ~BasicEnabledToken() {} + + constexpr explicit operator bool() const { return false; } + + static constexpr bool isActive() { return false; } +}; + +template +class Tracer; + +template +class Token : public BasicDisabledToken +{ +public: + using ArgumentType = typename Category::ArgumentType; + using FlowTokenType = typename Category::FlowTokenType; + using AsynchronousTokenType = typename Category::AsynchronousTokenType; + using TracerType = typename Category::TracerType; + + Token() {} + + ~Token() {} + + template + [[nodiscard]] AsynchronousTokenType beginAsynchronous(ArgumentType, Arguments &&...) + { + return {}; + } + + template + [[nodiscard]] std::pair beginAsynchronousWithFlow( + ArgumentType, Arguments &&...) + { + return {}; + } + + template + [[nodiscard]] TracerType beginDuration(ArgumentType, Arguments &&...) + { + return {}; + } + + template + [[nodiscard]] std::pair beginDurationWithFlow(ArgumentType, + Arguments &&...) + { + return {}; + } + + template + void tick(ArgumentType, Arguments &&...) + {} +}; + +template +class Token : public BasicEnabledToken + +{ + using CategoryFunctionPointer = typename Category::CategoryFunctionPointer; + + Token(std::size_t id, CategoryFunctionPointer category) + : m_id{id} + , m_category{category} + {} + + using PrivateTag = typename Category::PrivateTag; + +public: + using ArgumentType = typename Category::ArgumentType; + using FlowTokenType = typename Category::FlowTokenType; + using AsynchronousTokenType = typename Category::AsynchronousTokenType; + using TracerType = typename Category::TracerType; + + Token(PrivateTag, std::size_t id, CategoryFunctionPointer category) + : Token{id, category} + {} + + friend TracerType; + friend AsynchronousTokenType; + + ~Token() {} + + template + [[nodiscard]] AsynchronousTokenType beginAsynchronous(ArgumentType name, Arguments &&...arguments) + { + if (m_id) + m_category().begin('b', m_id, name, 0, IsFlow::No, std::forward(arguments)...); + + return {std::move(name), m_id, m_category}; + } + + template + [[nodiscard]] std::pair beginAsynchronousWithFlow( + ArgumentType name, Arguments &&...arguments) + { + std::size_t bindId = 0; + + if (m_id) { + auto category = m_category(); + bindId = category.createBindId(); + category.begin('b', m_id, name, bindId, IsFlow::Out, std::forward(arguments)...); + } + + return {std::piecewise_construct, + std::forward_as_tuple(std::move(name), m_id, m_category), + std::forward_as_tuple(std::move(name), bindId, m_category)}; + } + + template + [[nodiscard]] TracerType beginDuration(ArgumentType traceName, Arguments &&...arguments) + { + return {traceName, m_category, std::forward(arguments)...}; + } + + template + [[nodiscard]] std::pair beginDurationWithFlow(ArgumentType traceName, + Arguments &&...arguments) + { + std::size_t bindId = m_category().createBindId(); + + return {std::piecewise_construct, + std::forward_as_tuple(PrivateTag{}, + bindId, + IsFlow::Out, + traceName, + m_category, + std::forward(arguments)...), + std::forward_as_tuple(PrivateTag{}, traceName, bindId, m_category)}; + } + + template + void tick(ArgumentType name, Arguments &&...arguments) + { + m_category().begin('i', 0, name, 0, IsFlow::No, std::forward(arguments)...); + } + +private: + std::size_t m_id = 0; + CategoryFunctionPointer m_category = nullptr; +}; +template +class Category; + +template +using StringViewCategory = Category; +template +using StringCategory = Category; +template +using StringViewWithStringArgumentsCategory = Category; + +using DisabledToken = Token, Tracing::IsDisabled>; + +template +class ObjectToken : public BasicDisabledToken +{ +public: + using ArgumentType = typename Category::ArgumentType; + + ObjectToken() = default; + ObjectToken(const ObjectToken &) = delete; + ObjectToken &operator=(const ObjectToken &) = delete; + ObjectToken(ObjectToken &&other) noexcept = default; + ObjectToken &operator=(ObjectToken &&other) noexcept = default; + ~ObjectToken() = default; + + template + void change(ArgumentType, Arguments &&...) + {} +}; + +template +class ObjectToken : public BasicEnabledToken +{ + using CategoryFunctionPointer = typename Category::CategoryFunctionPointer; + + ObjectToken(std::string_view name, std::size_t id, CategoryFunctionPointer category) + : m_name{name} + , m_id{id} + , m_category{category} + {} + +public: + using StringType = typename Category::StringType; + using ArgumentType = typename Category::ArgumentType; + + friend Category; + + ObjectToken(const ObjectToken &other) + : m_name{other.m_name} + , m_category{other.m_category} + { + if (other.m_id) + m_id = m_category().beginObject(m_name).m_id; + } + + ObjectToken &operator=(const ObjectToken &other) + { + if (this != &other) { + ~ObjectToken(); + if (other.m_id) { + m_category = other.m_category; + m_name = other.m_name; + m_id = other.m_category->beginObject(other.m_name).m_id; + } + } + } + + ObjectToken(ObjectToken &&other) noexcept + : m_name{std::move(other.m_name)} + , m_id{std::exchange(other.m_id, 0)} + , m_category{std::exchange(other.m_category, nullptr)} + {} + + ObjectToken &operator=(ObjectToken &&other) noexcept + { + if (&other != this) { + m_name = std::move(other.m_name); + m_id = std::exchange(other.m_id, 0); + m_category = std::exchange(other.m_category, nullptr); + } + + return *this; + } + + ~ObjectToken() + { + if (m_id) + m_category().end('e', m_id, std::move(m_name)); + + m_id = 0; + } + + template + void change(ArgumentType name, Arguments &&...arguments) + { + if (m_id) { + m_category().tick( + 'n', m_id, std::move(name), 0, IsFlow::No, std::forward(arguments)...); + } + } + +private: + StringType m_name; + std::size_t m_id = 0; + CategoryFunctionPointer m_category = nullptr; +}; + +template +class AsynchronousToken : public BasicDisabledToken +{ +public: + using ArgumentType = typename Category::ArgumentType; + using FlowTokenType = typename Category::FlowTokenType; + using TokenType = typename Category::TokenType; + + AsynchronousToken() {} + + AsynchronousToken(const AsynchronousToken &) = delete; + AsynchronousToken &operator=(const AsynchronousToken &) = delete; + AsynchronousToken(AsynchronousToken &&other) noexcept = default; + AsynchronousToken &operator=(AsynchronousToken &&other) noexcept = default; + + ~AsynchronousToken() {} + + [[nodiscard]] TokenType createToken() { return {}; } + + template + [[nodiscard]] AsynchronousToken begin(ArgumentType, Arguments &&...) + { + return {}; + } + + template + [[nodiscard]] std::pair beginWithFlow(ArgumentType, + Arguments &&...) + { + return {}; + } + + template + void tick(ArgumentType, Arguments &&...) + {} + + template + FlowTokenType tickWithFlow(ArgumentType, Arguments &&...) + { + return {}; + } + + template + void end(Arguments &&...) + {} +}; + + +using DisabledAsynchronousToken = AsynchronousToken, + Tracing::IsDisabled>; + +template +class FlowToken; + +template +class AsynchronousToken : public BasicEnabledToken +{ + using CategoryFunctionPointer = typename Category::CategoryFunctionPointer; + + AsynchronousToken(std::string_view name, std::size_t id, CategoryFunctionPointer category) + : m_name{name} + , m_id{id} + , m_category{category} + {} + + using PrivateTag = typename Category::PrivateTag; + +public: + using StringType = typename Category::StringType; + using ArgumentType = typename Category::ArgumentType; + using FlowTokenType = typename Category::FlowTokenType; + using TokenType = typename Category::TokenType; + + friend Category; + friend FlowTokenType; + friend TokenType; + + AsynchronousToken(PrivateTag, std::string_view name, std::size_t id, CategoryFunctionPointer category) + : AsynchronousToken{name, id, category} + {} + + AsynchronousToken() {} + + AsynchronousToken(const AsynchronousToken &) = delete; + AsynchronousToken &operator=(const AsynchronousToken &) = delete; + + AsynchronousToken(AsynchronousToken &&other) noexcept + : m_name{std::move(other.m_name)} + , m_id{std::exchange(other.m_id, 0)} + , m_category{std::exchange(other.m_category, nullptr)} + {} + + AsynchronousToken &operator=(AsynchronousToken &&other) noexcept + { + if (&other != this) { + m_name = std::move(other.m_name); + m_id = std::exchange(other.m_id, 0); + m_category = std::exchange(other.m_category, nullptr); + } + + return *this; + } + + ~AsynchronousToken() { end(); } + + [[nodiscard]] TokenType createToken() { return {m_id, m_category}; } + + template + [[nodiscard]] AsynchronousToken begin(ArgumentType name, Arguments &&...arguments) + { + if (m_id) + m_category().begin('b', m_id, name, 0, IsFlow::No, std::forward(arguments)...); + + return AsynchronousToken{std::move(name), m_id, m_category}; + } + + template + [[nodiscard]] std::pair beginWithFlow(ArgumentType name, + Arguments &&...arguments) + { + std::size_t bindId = 0; + + if (m_id) { + auto category = m_category(); + bindId = category.createBindId(); + category.begin('b', m_id, name, bindId, IsFlow::Out, std::forward(arguments)...); + } + + return {std::piecewise_construct, + std::forward_as_tuple(std::move(name), m_id, m_category), + std::forward_as_tuple(PrivateTag{}, std::move(name), bindId, m_category)}; + } + + template + void tick(ArgumentType name, Arguments &&...arguments) + { + if (m_id) { + m_category().tick( + 'n', m_id, std::move(name), 0, IsFlow::No, std::forward(arguments)...); + } + } + + template + FlowTokenType tickWithFlow(ArgumentType name, Arguments &&...arguments) + { + std::size_t bindId = 0; + + if (m_id) { + auto category = m_category(); + bindId = category.createBindId(); + category.tick('n', m_id, name, bindId, IsFlow::Out, std::forward(arguments)...); + } + + return {std::move(name), bindId, m_category}; + } + + template + void end(Arguments &&...arguments) + { + if (m_id && m_name.size()) + m_category().end('e', m_id, std::move(m_name), std::forward(arguments)...); + + m_id = 0; + } + +private: + StringType m_name; + std::size_t m_id = 0; + CategoryFunctionPointer m_category = nullptr; +}; + +template +class FlowToken : public BasicDisabledToken +{ +public: + FlowToken() = default; + using AsynchronousTokenType = typename Category::AsynchronousTokenType; + using ArgumentType = typename Category::ArgumentType; + using TracerType = typename Category::TracerType; + using TokenType = typename Category::TokenType; + + friend TracerType; + friend AsynchronousTokenType; + friend TokenType; + friend Category; + + FlowToken(const FlowToken &) = default; + FlowToken &operator=(const FlowToken &) = default; + FlowToken(FlowToken &&other) noexcept = default; + FlowToken &operator=(FlowToken &&other) noexcept = default; + + ~FlowToken() {} + + template + [[nodiscard]] AsynchronousTokenType beginAsynchronous(ArgumentType, Arguments &&...) + { + return {}; + } + + template + [[nodiscard]] std::pair beginAsynchronousWithFlow(ArgumentType, + Arguments &&...) + { + return {}; + } + + template + void connectTo(const AsynchronousTokenType &, Arguments &&...) + {} + + template + [[nodiscard]] TracerType beginDuration(ArgumentType, Arguments &&...) + { + return {}; + } + + template + [[nodiscard]] std::pair beginDurationWithFlow(ArgumentType, Arguments &&...) + { + return std::pair(); + } + + template + void tick(ArgumentType, Arguments &&...) + {} +}; + +template +class FlowToken : public BasicDisabledToken +{ + using PrivateTag = typename Category::PrivateTag; + using CategoryFunctionPointer = typename Category::CategoryFunctionPointer; + +public: + FlowToken() = default; + + FlowToken(PrivateTag, std::string_view name, std::size_t bindId, CategoryFunctionPointer category) + : m_name{name} + , m_bindId{bindId} + , m_category{category} + {} + + using StringType = typename Category::StringType; + using AsynchronousTokenType = typename Category::AsynchronousTokenType; + using ArgumentType = typename Category::ArgumentType; + using TracerType = typename Category::TracerType; + using TokenType = typename Category::TokenType; + + friend AsynchronousTokenType; + friend TokenType; + friend TracerType; + friend Category; + + FlowToken(const FlowToken &) = default; + FlowToken &operator=(const FlowToken &) = default; + FlowToken(FlowToken &&other) noexcept = default; + FlowToken &operator=(FlowToken &&other) noexcept = default; + + ~FlowToken() {} + + template + [[nodiscard]] AsynchronousTokenType beginAsynchronous(ArgumentType name, Arguments &&...arguments) + { + std::size_t id = 0; + + if (m_bindId) { + auto category = m_category(); + id = category.createId(); + category->begin('b', id, name, m_bindId, IsFlow::In, std::forward(arguments)...); + } + + return {std::move(name), id, m_category}; + } + + template + [[nodiscard]] std::pair beginAsynchronousWithFlow( + ArgumentType name, Arguments &&...arguments) + { + std::size_t id = 0; + std::size_t bindId = 0; + + if (m_bindId) { + auto category = m_category(); + id = category.createId(); + bindId = category.createBindId(); + category->begin('b', id, name, bindId, IsFlow::InOut, std::forward(arguments)...); + } + + return {std::piecewise_construct, + std::forward_as_tuple(PrivateTag{}, std::move(name), id, m_category), + std::forward_as_tuple(PrivateTag{}, std::move(name), bindId, m_category)}; + } + + template + void connectTo(const AsynchronousTokenType &token, Arguments &&...arguments) + { + if (m_bindId && token.m_id) { + m_category().begin('b', + token.m_id, + token.m_name, + m_bindId, + IsFlow::In, + std::forward(arguments)...); + } + } + + template + [[nodiscard]] TracerType beginDuration(ArgumentType traceName, Arguments &&...arguments) + { + return {m_bindId, IsFlow::In, traceName, m_category, std::forward(arguments)...}; + } + + template + [[nodiscard]] std::pair beginDurationWithFlow(ArgumentType traceName, + Arguments &&...arguments) + { + return {std::piecewise_construct, + std::forward_as_tuple(PrivateTag{}, + m_bindId, + IsFlow::InOut, + traceName, + m_category, + std::forward(arguments)...), + std::forward_as_tuple(PrivateTag{}, traceName, m_bindId, m_category)}; + } + + template + void tick(ArgumentType name, Arguments &&...arguments) + { + m_category().tick('i', 0, name, m_bindId, IsFlow::In, std::forward(arguments)...); + } + +private: + StringType m_name; + std::size_t m_bindId = 0; + CategoryFunctionPointer m_category = nullptr; +}; + +template +class Category +{ +public: + using IsActive = std::false_type; + using ArgumentType = typename TraceEvent::ArgumentType; + using ArgumentsStringType = typename TraceEvent::ArgumentsStringType; + using AsynchronousTokenType = AsynchronousToken; + using ObjectTokenType = ObjectToken; + using FlowTokenType = FlowToken; + using TracerType = Tracer; + using TokenType = Token; + using CategoryFunctionPointer = Category &(*) (); + + Category(ArgumentType, EventQueue &, CategoryFunctionPointer) + {} + + Category(ArgumentType, EventQueue &, CategoryFunctionPointer) {} + + template + [[nodiscard]] AsynchronousTokenType beginAsynchronous(ArgumentType, Arguments &&...) + { + return {}; + } + + template + [[nodiscard]] std::pair beginAsynchronousWithFlow( + ArgumentType, Arguments &&...) + {} + + template + [[nodiscard]] ObjectTokenType beginObject(ArgumentType, Arguments &&...) + { + return {}; + } + + template + [[nodiscard]] TracerType beginDuration(ArgumentType, Arguments &&...) + { + return {}; + } + + template + [[nodiscard]] std::pair beginDurationWithFlow(ArgumentType, + Arguments &&...) + { + return std::pair(); + } + + static constexpr bool isActive() { return false; } +}; + +template +class Category +{ + class PrivateTag + {}; + +public: + using IsActive = std::true_type; + using ArgumentType = typename TraceEvent::ArgumentType; + using ArgumentsStringType = typename TraceEvent::ArgumentsStringType; + using StringType = typename TraceEvent::StringType; + using AsynchronousTokenType = AsynchronousToken; + using ObjectTokenType = ObjectToken; + using FlowTokenType = FlowToken; + using TracerType = Tracer; + using TokenType = Token; + using CategoryFunctionPointer = Category &(*) (); + + friend AsynchronousTokenType; + friend ObjectTokenType; + friend TokenType; + friend FlowTokenType; + friend TracerType; + + template + Category(ArgumentType name, EventQueue &queue, CategoryFunctionPointer self) + : m_name{std::move(name)} + , m_eventQueue{queue} + , m_self{self} + { + static_assert(std::is_same_v, + "A active category is not possible with an inactive event queue!"); + m_idCounter = m_globalIdCounter += 1ULL << 32; + m_bindIdCounter = m_globalBindIdCounter += 1ULL << 32; + } + + template + [[nodiscard]] AsynchronousTokenType beginAsynchronous(ArgumentType traceName, + Arguments &&...arguments) + { + std::size_t id = createId(); + + begin('b', id, std::move(traceName), 0, IsFlow::No, std::forward(arguments)...); + + return {traceName, id, m_self}; + } + + template + [[nodiscard]] std::pair beginAsynchronousWithFlow( + ArgumentType traceName, Arguments &&...arguments) + { + std::size_t id = createId(); + std::size_t bindId = createBindId(); + + begin('b', id, std::move(traceName), bindId, IsFlow::Out, std::forward(arguments)...); + + return {std::piecewise_construct, + std::forward_as_tuple(PrivateTag{}, traceName, id, m_self), + std::forward_as_tuple(PrivateTag{}, traceName, bindId, m_self)}; + } + + template + [[nodiscard]] ObjectTokenType beginObject(ArgumentType traceName, Arguments &&...arguments) + { + std::size_t id = createId(); + + begin('b', id, std::move(traceName), 0, IsFlow::No, std::forward(arguments)...); + + return {traceName, id, m_self}; + } + + template + [[nodiscard]] TracerType beginDuration(ArgumentType traceName, Arguments &&...arguments) + { + return {traceName, m_self, std::forward(arguments)...}; + } + + template + [[nodiscard]] std::pair beginDurationWithFlow(ArgumentType traceName, + Arguments &&...arguments) + { + std::size_t bindId = createBindId(); + + return {std::piecewise_construct, + std::forward_as_tuple(PrivateTag{}, + bindId, + IsFlow::Out, + traceName, + m_self, + std::forward(arguments)...), + std::forward_as_tuple(PrivateTag{}, traceName, bindId, m_self)}; + } + + EnabledEventQueue &eventQueue() const { return m_eventQueue; } + + std::string_view name() const { return m_name; } + + static constexpr bool isActive() { return true; } + + std::size_t createBindId() { return ++m_bindIdCounter; } + + std::size_t createId() { return ++m_idCounter; } + +public: + IsEnabled isEnabled = IsEnabled::Yes; + +private: + template + void begin(char type, + std::size_t id, + StringType traceName, + std::size_t bindId, + IsFlow flow, + Arguments &&...arguments) + { + if (isEnabled == IsEnabled::No) + return; + + auto &traceEvent = getTraceEvent(m_eventQueue); + + traceEvent.name = std::move(traceName); + traceEvent.category = m_name; + traceEvent.type = type; + traceEvent.id = id; + traceEvent.bindId = bindId; + traceEvent.flow = flow; + Internal::appendArguments(traceEvent.arguments, std::forward(arguments)...); + traceEvent.time = Clock::now(); + } + + template + void tick(char type, + std::size_t id, + StringType traceName, + std::size_t bindId, + IsFlow flow, + Arguments &&...arguments) + { + if (isEnabled == IsEnabled::No) + return; + + auto time = Clock::now(); + + auto &traceEvent = getTraceEvent(m_eventQueue); + + traceEvent.name = std::move(traceName); + traceEvent.category = m_name; + traceEvent.time = time; + traceEvent.type = type; + traceEvent.id = id; + traceEvent.bindId = bindId; + traceEvent.flow = flow; + Internal::appendArguments(traceEvent.arguments, std::forward(arguments)...); + } + + template + void end(char type, std::size_t id, StringType traceName, Arguments &&...arguments) + { + if (isEnabled == IsEnabled::No) + return; + + auto time = Clock::now(); + + auto &traceEvent = getTraceEvent(m_eventQueue); + + traceEvent.name = std::move(traceName); + traceEvent.category = m_name; + traceEvent.time = time; + traceEvent.type = type; + traceEvent.id = id; + traceEvent.bindId = 0; + traceEvent.flow = IsFlow::No; + Internal::appendArguments(traceEvent.arguments, std::forward(arguments)...); + } + +private: + StringType m_name; + EnabledEventQueue &m_eventQueue; + inline static std::atomic m_globalIdCounter; + std::size_t m_idCounter; + inline static std::atomic m_globalBindIdCounter; + std::size_t m_bindIdCounter; + CategoryFunctionPointer m_self; +}; + +template +using StringViewCategory = Category; +template +using StringCategory = Category; +template +using StringViewWithStringArgumentsCategory = Category; + +template +class Tracer +{ +public: + Tracer() = default; + using ArgumentType = typename Category::ArgumentType; + using TokenType = typename Category::TokenType; + using FlowTokenType = typename Category::FlowTokenType; + + friend TokenType; + friend FlowTokenType; + friend Category; + + template + [[nodiscard]] Tracer(ArgumentType, Category &, Arguments &&...) + {} + + Tracer(const Tracer &) = delete; + Tracer &operator=(const Tracer &) = delete; + Tracer(Tracer &&other) noexcept = default; + Tracer &operator=(Tracer &&other) noexcept = delete; + + TokenType createToken() { return {}; } + + template + Tracer beginDuration(ArgumentType, Arguments &&...) + { + return {}; + } + + template + void tick(ArgumentType, Arguments &&...) + {} + + template + void end(Arguments &&...) + {} + + ~Tracer() {} +}; + +template +class Tracer +{ + using ArgumentType = typename Category::ArgumentType; + using StringType = typename Category::StringType; + using ArgumentsStringType = typename Category::ArgumentsStringType; + using TokenType = typename Category::TokenType; + using FlowTokenType = typename Category::FlowTokenType; + using PrivateTag = typename Category::PrivateTag; + using CategoryFunctionPointer = typename Category::CategoryFunctionPointer; + + friend FlowTokenType; + friend TokenType; + friend Category; + + template + [[nodiscard]] Tracer(std::size_t bindId, + IsFlow flow, + ArgumentType name, + CategoryFunctionPointer category, + Arguments &&...arguments) + : m_name{name} + , m_bindId{bindId} + , flow{flow} + , m_category{category} + { + if (category().isEnabled == IsEnabled::Yes) { + Internal::appendArguments(m_arguments, + std::forward(arguments)...); + m_start = Clock::now(); + } + } + +public: + template + [[nodiscard]] Tracer(PrivateTag, + std::size_t bindId, + IsFlow flow, + ArgumentType name, + CategoryFunctionPointer category, + Arguments &&...arguments) + : Tracer{bindId, flow, std::move(name), category, std::forward(arguments)...} + {} + + template + [[nodiscard]] Tracer(ArgumentType name, CategoryFunctionPointer category, Arguments &&...arguments) + : m_name{name} + , m_category{category} + { + if (category().isEnabled == IsEnabled::Yes) { + Internal::appendArguments(m_arguments, + std::forward(arguments)...); + m_start = Clock::now(); + } + } + + Tracer(const Tracer &) = delete; + Tracer &operator=(const Tracer &) = delete; + Tracer(Tracer &&other) noexcept = delete; + Tracer &operator=(Tracer &&other) noexcept = delete; + + TokenType createToken() { return {0, m_category}; } + + ~Tracer() { sendTrace(); } + + template + Tracer beginDuration(ArgumentType name, Arguments &&...arguments) + { + return {std::move(name), m_category, std::forward(arguments)...}; + } + + template + void tick(ArgumentType name, Arguments &&...arguments) + { + m_category().begin('i', 0, name, 0, IsFlow::No, std::forward(arguments)...); + } + + template + void end(Arguments &&...arguments) + { + sendTrace(std::forward(arguments)...); + m_name = {}; + } + +private: + template + void sendTrace(Arguments &&...arguments) + { + if (m_name.size()) { + auto category = m_category(); + if (category.isEnabled == IsEnabled::Yes) { + auto duration = Clock::now() - m_start; + auto &traceEvent = getTraceEvent(category.eventQueue()); + traceEvent.name = m_name; + traceEvent.category = category.name(); + traceEvent.time = m_start; + traceEvent.duration = duration; + traceEvent.bindId = m_bindId; + traceEvent.flow = flow; + traceEvent.type = 'X'; + if (sizeof...(arguments)) { + m_arguments.clear(); + Internal::appendArguments(traceEvent.arguments, + std::forward( + arguments)...); + } else { + traceEvent.arguments = m_arguments; + } + } + } + } + +private: + TimePoint m_start; + StringType m_name; + ArgumentsStringType m_arguments; + std::size_t m_bindId; + IsFlow flow; + CategoryFunctionPointer m_category; +}; + +template +Tracer(typename Category::ArgumentType name, Category &category, Arguments &&...) + -> Tracer; + +#ifdef NANOTRACE_ENABLED +class GlobalTracer +{ +public: + template + [[nodiscard]] GlobalTracer(std::string name, std::string category, Arguments &&...arguments) + : m_name{std::move(name)} + , m_category{std::move(category)} + { + if (globalEventQueue().isEnabled == IsEnabled::Yes) { + Internal::appendArguments(m_arguments, std::forward(arguments)...); + m_start = Clock::now(); + } + } + + ~GlobalTracer() + { + if (globalEventQueue().isEnabled == IsEnabled::Yes) { + auto duration = Clock::now() - m_start; + auto &traceEvent = getTraceEvent(globalEventQueue()); + traceEvent.name = std::move(m_name); + traceEvent.category = std::move(m_category); + traceEvent.arguments = std::move(m_arguments); + traceEvent.time = std::move(m_start); + traceEvent.duration = std::move(duration); + traceEvent.type = 'X'; + } + } + +private: + TimePoint m_start; + std::string m_name; + std::string m_category; + std::string m_arguments; +}; +#else +class GlobalTracer +{ +public: + GlobalTracer(std::string_view, std::string_view, std::string_view) {} + + GlobalTracer(std::string_view, std::string_view) {} + + ~GlobalTracer() {} +}; +#endif + +} // namespace NanotraceHR diff --git a/src/libs/qmlpuppetcommunication/commands/inputeventcommand.cpp b/src/libs/qmlpuppetcommunication/commands/inputeventcommand.cpp index 0dc669406af..2ba1a651228 100644 --- a/src/libs/qmlpuppetcommunication/commands/inputeventcommand.cpp +++ b/src/libs/qmlpuppetcommunication/commands/inputeventcommand.cpp @@ -10,10 +10,16 @@ namespace QmlDesigner { InputEventCommand::InputEventCommand() = default; -InputEventCommand::InputEventCommand(QInputEvent *e) - : m_type(e->type()), - m_modifiers(e->modifiers()) +InputEventCommand::InputEventCommand(QEvent *e) + : m_type(e->type()) { + // Leave events are not actual input events + if (m_type == QEvent::Leave) + return; + + auto ie = static_cast(e); + m_modifiers = ie->modifiers(); + if (m_type == QEvent::Wheel) { auto we = static_cast(e); #if QT_VERSION <= QT_VERSION_CHECK(5, 15, 0) @@ -28,6 +34,11 @@ InputEventCommand::InputEventCommand(QInputEvent *e) m_key = ke->key(); m_count = ke->count(); m_autoRepeat = ke->isAutoRepeat(); + } else if (m_type == QEvent::Enter) { + auto spe = static_cast(e); + m_pos = spe->position().toPoint(); + m_button = spe->button(); + m_buttons = spe->buttons(); } else { auto me = static_cast(e); m_pos = me->pos(); diff --git a/src/libs/qmlpuppetcommunication/commands/inputeventcommand.h b/src/libs/qmlpuppetcommunication/commands/inputeventcommand.h index e0a220cd693..7cd2c898fe4 100644 --- a/src/libs/qmlpuppetcommunication/commands/inputeventcommand.h +++ b/src/libs/qmlpuppetcommunication/commands/inputeventcommand.h @@ -18,7 +18,7 @@ class InputEventCommand public: InputEventCommand(); - explicit InputEventCommand(QInputEvent *e); + explicit InputEventCommand(QEvent *e); QEvent::Type type() const { return m_type; } QPoint pos() const { return m_pos; } diff --git a/src/libs/qmlpuppetcommunication/commands/puppettocreatorcommand.h b/src/libs/qmlpuppetcommunication/commands/puppettocreatorcommand.h index 30b0cce9981..1e8d82e632b 100644 --- a/src/libs/qmlpuppetcommunication/commands/puppettocreatorcommand.h +++ b/src/libs/qmlpuppetcommunication/commands/puppettocreatorcommand.h @@ -16,6 +16,7 @@ public: Edit3DToolState, Render3DView, ActiveSceneChanged, + ActiveSplitChanged, RenderModelNodePreviewImage, Import3DSupport, NodeAtPos, diff --git a/src/libs/qmlpuppetcommunication/interfaces/nodeinstanceglobal.h b/src/libs/qmlpuppetcommunication/interfaces/nodeinstanceglobal.h index 58166516c80..aefaeb88e3f 100644 --- a/src/libs/qmlpuppetcommunication/interfaces/nodeinstanceglobal.h +++ b/src/libs/qmlpuppetcommunication/interfaces/nodeinstanceglobal.h @@ -46,7 +46,10 @@ enum class View3DActionType { ParticlesSeek, SyncEnvBackground, GetNodeAtPos, - SetBakeLightsView3D + SetBakeLightsView3D, + SplitViewToggle, + MaterialOverride, + ShowWireframe }; constexpr bool isNanotraceEnabled() diff --git a/src/libs/sqlite/CMakeLists.txt b/src/libs/sqlite/CMakeLists.txt index aa33171bab1..8d690f3bd46 100644 --- a/src/libs/sqlite/CMakeLists.txt +++ b/src/libs/sqlite/CMakeLists.txt @@ -1,3 +1,7 @@ +env_with_default("QTC_ENABLE_SQLITE_TRACING" ENV_QTC_ENABLE_SQLITE_TRACING OFF) +option(ENABLE_SQLITE_TRACING "Enable sqlite tarcing" ${ENV_QTC_ENABLE_SQLITE_TRACING}) +add_feature_info("Sqlite tracing" ${ENABLE_SQLITE_TRACING} "") + add_qtc_library(SqliteInternal OBJECT PROPERTIES AUTOMOC OFF AUTOUIC OFF QT_COMPILE_OPTIONS_DISABLE_WARNINGS ON DEFINES SQLITE_CORE SQLITE_CUSTOM_INCLUDE=config.h $<$:SQLITE_DEBUG> @@ -66,6 +70,13 @@ add_qtc_library(Sqlite sqliteids.h ) +extend_qtc_library(Sqlite + CONDITION TARGET Nanotrace + DEPENDS Nanotrace + PUBLIC_DEFINES + $<$:ENABLE_SQLITE_TRACING> +) + extend_qtc_library(Sqlite CONDITION QTC_STATIC_BUILD PROPERTIES COMPILE_OPTIONS $,/FIsqlite_static_config.h,-includesqlite_static_config.h> diff --git a/src/libs/sqlite/sqlitebasestatement.cpp b/src/libs/sqlite/sqlitebasestatement.cpp index ae0a60913d1..406d49d2489 100644 --- a/src/libs/sqlite/sqlitebasestatement.cpp +++ b/src/libs/sqlite/sqlitebasestatement.cpp @@ -26,6 +26,34 @@ extern "C" int sqlite3_carray_bind( namespace Sqlite { +namespace { +using TraceFile = NanotraceHR::TraceFile; + +TraceFile traceFile{"sqlite.json"}; + +thread_local NanotraceHR::EventQueueData + eventQueueData(traceFile); +thread_local NanotraceHR::EventQueue eventQueue = eventQueueData.createEventQueue(); + +NanotraceHR::StringViewCategory &sqliteLowLevelCategory(); + +thread_local NanotraceHR::StringViewCategory sqliteLowLevelCategory_{ + "sqlite low level"_t, eventQueue, sqliteLowLevelCategory}; + +NanotraceHR::StringViewCategory &sqliteLowLevelCategory() +{ + return sqliteLowLevelCategory_; +} + +thread_local NanotraceHR::StringViewCategory sqliteHighLevelCategory_{ + "sqlite high level"_t, eventQueue, sqliteHighLevelCategory}; +} // namespace + +NanotraceHR::StringViewCategory &sqliteHighLevelCategory() +{ + return sqliteHighLevelCategory_; +} + BaseStatement::BaseStatement(Utils::SmallStringView sqlStatement, Database &database) : m_database(database) { @@ -80,11 +108,14 @@ void BaseStatement::waitForUnlockNotify() const void BaseStatement::reset() const noexcept { + NanotraceHR::Tracer tracer{"reset"_t, sqliteLowLevelCategory()}; + sqlite3_reset(m_compiledStatement.get()); } bool BaseStatement::next() const { + NanotraceHR::Tracer tracer{"next"_t, sqliteLowLevelCategory()}; int resultCode; do { @@ -111,6 +142,8 @@ void BaseStatement::step() const void BaseStatement::bindNull(int index) { + NanotraceHR::Tracer tracer{"bind null"_t, sqliteLowLevelCategory()}; + int resultCode = sqlite3_bind_null(m_compiledStatement.get(), index); if (resultCode != SQLITE_OK) Sqlite::throwError(resultCode, sqliteDatabaseHandle()); @@ -123,6 +156,8 @@ void BaseStatement::bind(int index, NullValue) void BaseStatement::bind(int index, int value) { + NanotraceHR::Tracer tracer{"bind int"_t, sqliteLowLevelCategory()}; + int resultCode = sqlite3_bind_int(m_compiledStatement.get(), index, value); if (resultCode != SQLITE_OK) Sqlite::throwError(resultCode, sqliteDatabaseHandle()); @@ -130,6 +165,8 @@ void BaseStatement::bind(int index, int value) void BaseStatement::bind(int index, long long value) { + NanotraceHR::Tracer tracer{"bind long long"_t, sqliteLowLevelCategory()}; + int resultCode = sqlite3_bind_int64(m_compiledStatement.get(), index, value); if (resultCode != SQLITE_OK) Sqlite::throwError(resultCode, sqliteDatabaseHandle()); @@ -137,6 +174,8 @@ void BaseStatement::bind(int index, long long value) void BaseStatement::bind(int index, double value) { + NanotraceHR::Tracer tracer{"bind double"_t, sqliteLowLevelCategory()}; + int resultCode = sqlite3_bind_double(m_compiledStatement.get(), index, value); if (resultCode != SQLITE_OK) Sqlite::throwError(resultCode, sqliteDatabaseHandle()); @@ -144,17 +183,17 @@ void BaseStatement::bind(int index, double value) void BaseStatement::bind(int index, void *pointer) { - int resultCode = sqlite3_bind_pointer(m_compiledStatement.get(), - index, - pointer, - "carray", - nullptr); + NanotraceHR::Tracer tracer{"bind pointer"_t, sqliteLowLevelCategory()}; + + int resultCode = sqlite3_bind_pointer(m_compiledStatement.get(), index, pointer, "carray", nullptr); if (resultCode != SQLITE_OK) Sqlite::throwError(resultCode, sqliteDatabaseHandle()); } void BaseStatement::bind(int index, Utils::span values) { + NanotraceHR::Tracer tracer{"bind int span"_t, sqliteLowLevelCategory()}; + int resultCode = sqlite3_carray_bind(m_compiledStatement.get(), index, const_cast(values.data()), @@ -167,6 +206,8 @@ void BaseStatement::bind(int index, Utils::span values) void BaseStatement::bind(int index, Utils::span values) { + NanotraceHR::Tracer tracer{"bind long long span"_t, sqliteLowLevelCategory()}; + int resultCode = sqlite3_carray_bind(m_compiledStatement.get(), index, const_cast(values.data()), @@ -179,6 +220,8 @@ void BaseStatement::bind(int index, Utils::span values) void BaseStatement::bind(int index, Utils::span values) { + NanotraceHR::Tracer tracer{"bind double span"_t, sqliteLowLevelCategory()}; + int resultCode = sqlite3_carray_bind(m_compiledStatement.get(), index, const_cast(values.data()), @@ -191,6 +234,8 @@ void BaseStatement::bind(int index, Utils::span values) void BaseStatement::bind(int index, Utils::span values) { + NanotraceHR::Tracer tracer{"bind const char* span"_t, sqliteLowLevelCategory()}; + int resultCode = sqlite3_carray_bind(m_compiledStatement.get(), index, values.data(), @@ -203,6 +248,8 @@ void BaseStatement::bind(int index, Utils::span values) void BaseStatement::bind(int index, Utils::SmallStringView text) { + NanotraceHR::Tracer tracer{"bind string"_t, sqliteLowLevelCategory()}; + int resultCode = sqlite3_bind_text(m_compiledStatement.get(), index, text.data(), @@ -214,6 +261,8 @@ void BaseStatement::bind(int index, Utils::SmallStringView text) void BaseStatement::bind(int index, BlobView blobView) { + NanotraceHR::Tracer tracer{"bind blob"_t, sqliteLowLevelCategory()}; + int resultCode = SQLITE_OK; if (blobView.empty()) { @@ -232,6 +281,8 @@ void BaseStatement::bind(int index, BlobView blobView) void BaseStatement::bind(int index, const Value &value) { + NanotraceHR::Tracer tracer{"bind value"_t, sqliteLowLevelCategory()}; + switch (value.type()) { case ValueType::Integer: bind(index, value.toInteger()); @@ -253,6 +304,8 @@ void BaseStatement::bind(int index, const Value &value) void BaseStatement::bind(int index, ValueView value) { + NanotraceHR::Tracer tracer{"bind value"_t, sqliteLowLevelCategory()}; + switch (value.type()) { case ValueType::Integer: bind(index, value.toInteger()); @@ -274,6 +327,8 @@ void BaseStatement::bind(int index, ValueView value) void BaseStatement::prepare(Utils::SmallStringView sqlStatement) { + NanotraceHR::Tracer tracer{"prepare"_t, sqliteLowLevelCategory()}; + if (!m_database.isLocked()) throw DatabaseIsNotLocked{}; @@ -373,6 +428,8 @@ StringType convertToTextForColumn(sqlite3_stmt *sqlStatment, int column) Type BaseStatement::fetchType(int column) const { + NanotraceHR::Tracer tracer{"fetch type"_t, sqliteLowLevelCategory()}; + auto dataType = sqlite3_column_type(m_compiledStatement.get(), column); switch (dataType) { @@ -393,6 +450,8 @@ Type BaseStatement::fetchType(int column) const int BaseStatement::fetchIntValue(int column) const { + NanotraceHR::Tracer tracer{"fetch int"_t, sqliteLowLevelCategory()}; + return sqlite3_column_int(m_compiledStatement.get(), column); } @@ -415,6 +474,8 @@ long BaseStatement::fetchValue(int column) const long long BaseStatement::fetchLongLongValue(int column) const { + NanotraceHR::Tracer tracer{"fetch long long"_t, sqliteLowLevelCategory()}; + return sqlite3_column_int64(m_compiledStatement.get(), column); } @@ -426,11 +487,15 @@ long long BaseStatement::fetchValue(int column) const double BaseStatement::fetchDoubleValue(int column) const { + NanotraceHR::Tracer tracer{"fetch double"_t, sqliteLowLevelCategory()}; + return sqlite3_column_double(m_compiledStatement.get(), column); } BlobView BaseStatement::fetchBlobValue(int column) const { + NanotraceHR::Tracer tracer{"fetch blob"_t, sqliteLowLevelCategory()}; + return convertToBlobForColumn(m_compiledStatement.get(), column); } @@ -479,6 +544,8 @@ ValueView BaseStatement::fetchValueView(int column) const void BaseStatement::Deleter::operator()(sqlite3_stmt *statement) { + NanotraceHR::Tracer tracer{"finalize"_t, sqliteLowLevelCategory()}; + sqlite3_finalize(statement); } diff --git a/src/libs/sqlite/sqlitebasestatement.h b/src/libs/sqlite/sqlitebasestatement.h index 3c490293447..d2306c67a5e 100644 --- a/src/libs/sqlite/sqlitebasestatement.h +++ b/src/libs/sqlite/sqlitebasestatement.h @@ -14,6 +14,7 @@ #include +#include #include #include @@ -29,6 +30,8 @@ using std::int64_t; namespace Sqlite { +using namespace NanotraceHR::Literals; + class Database; class DatabaseBackend; @@ -41,6 +44,17 @@ constexpr static std::underlying_type_t to_underlying(Enumeration e return static_cast>(enumeration); } +constexpr NanotraceHR::Tracing sqliteTracingStatus() +{ +#ifdef ENABLE_SQLITE_TRACING + return NanotraceHR::tracingStatus(); +#else + return NanotraceHR::Tracing::IsDisabled; +#endif +} + +SQLITE_EXPORT NanotraceHR::StringViewCategory &sqliteHighLevelCategory(); + class SQLITE_EXPORT BaseStatement { public: @@ -152,6 +166,8 @@ public: void execute() { + NanotraceHR::Tracer tracer{"execute"_t, sqliteHighLevelCategory()}; + Resetter resetter{this}; BaseStatement::next(); } @@ -159,6 +175,8 @@ public: template void bindValues(const ValueType &...values) { + NanotraceHR::Tracer tracer{"bind"_t, sqliteHighLevelCategory()}; + static_assert(BindParameterCount == sizeof...(values), "Wrong binding parameter count!"); int index = 0; @@ -168,6 +186,8 @@ public: template void write(const ValueType&... values) { + NanotraceHR::Tracer tracer{"write"_t, sqliteHighLevelCategory()}; + Resetter resetter{this}; bindValues(values...); BaseStatement::next(); @@ -192,6 +212,8 @@ public: typename... QueryTypes> auto values(const QueryTypes &...queryValues) { + NanotraceHR::Tracer tracer{"values"_t, sqliteHighLevelCategory()}; + Resetter resetter{this}; Container resultValues; resultValues.reserve(std::max(capacity, m_maximumResultCount)); @@ -219,6 +241,8 @@ public: template auto value(const QueryTypes &...queryValues) { + NanotraceHR::Tracer tracer{"value"_t, sqliteHighLevelCategory()}; + Resetter resetter{this}; ResultType resultValue{}; @@ -233,6 +257,8 @@ public: template auto optionalValue(const QueryTypes &...queryValues) { + NanotraceHR::Tracer tracer{"optionalValue"_t, sqliteHighLevelCategory()}; + Resetter resetter{this}; std::optional resultValue; @@ -247,6 +273,8 @@ public: template static auto toValue(Utils::SmallStringView sqlStatement, Database &database) { + NanotraceHR::Tracer tracer{"toValue"_t, sqliteHighLevelCategory()}; + StatementImplementation statement(sqlStatement, database); statement.checkColumnCount(1); @@ -259,6 +287,8 @@ public: template void readCallback(Callable &&callable, const QueryTypes &...queryValues) { + NanotraceHR::Tracer tracer{"readCallback"_t, sqliteHighLevelCategory()}; + Resetter resetter{this}; bindValues(queryValues...); @@ -274,6 +304,8 @@ public: template void readTo(Container &container, const QueryTypes &...queryValues) { + NanotraceHR::Tracer tracer{"readTo"_t, sqliteHighLevelCategory()}; + Resetter resetter{this}; bindValues(queryValues...); @@ -369,6 +401,9 @@ public: const_iterator end() const & { return iterator{m_statement, false}; } private: + using TracerCategory = std::decay_t; + NanotraceHR::Tracer tracer{ + "range"_t, sqliteHighLevelCategory()}; StatementImplementation &m_statement; }; diff --git a/src/libs/sqlite/sqlitedatabase.cpp b/src/libs/sqlite/sqlitedatabase.cpp index bb92943776b..12a122030c4 100644 --- a/src/libs/sqlite/sqlitedatabase.cpp +++ b/src/libs/sqlite/sqlitedatabase.cpp @@ -215,6 +215,29 @@ void Database::sessionRollback() m_statements->rollbackBegin.execute(); } +void Database::resetDatabaseForTestsOnly() +{ + m_databaseBackend.resetDatabaseForTestsOnly(); + setIsInitialized(false); +} + +void Database::clearAllTablesForTestsOnly() +{ + m_databaseBackend.disableForeignKeys(); + { + Sqlite::ImmediateTransaction transaction{*this}; + + ReadStatement<1> tablesStatement{"SELECT name FROM sqlite_schema WHERE type='table'", *this}; + auto tables = tablesStatement.template values(); + for (const auto &table : tables) + execute("DELETE FROM " + table); + + transaction.commit(); + } + + m_databaseBackend.enableForeignKeys(); +} + DatabaseBackend &Database::backend() { return m_databaseBackend; diff --git a/src/libs/sqlite/sqlitedatabase.h b/src/libs/sqlite/sqlitedatabase.h index 70be8f43057..0db256c8162 100644 --- a/src/libs/sqlite/sqlitedatabase.h +++ b/src/libs/sqlite/sqlitedatabase.h @@ -161,6 +161,9 @@ public: void sessionCommit() override; void sessionRollback() override; + void resetDatabaseForTestsOnly(); + void clearAllTablesForTestsOnly(); + private: void initializeTables(); void registerTransactionStatements(); diff --git a/src/libs/sqlite/sqlitedatabasebackend.cpp b/src/libs/sqlite/sqlitedatabasebackend.cpp index 1cca1f61791..2f55853dd03 100644 --- a/src/libs/sqlite/sqlitedatabasebackend.cpp +++ b/src/libs/sqlite/sqlitedatabasebackend.cpp @@ -248,11 +248,30 @@ int busyHandlerCallback(void *userData, int counter) void DatabaseBackend::registerBusyHandler() { - int resultCode = sqlite3_busy_handler(sqliteDatabaseHandle(), &busyHandlerCallback, &m_busyHandler); + int resultCode = sqlite3_busy_handler(sqliteDatabaseHandle(), + &busyHandlerCallback, + &m_busyHandler); checkIfBusyTimeoutWasSet(resultCode); } +void DatabaseBackend::resetDatabaseForTestsOnly() +{ + sqlite3_db_config(sqliteDatabaseHandle(), SQLITE_DBCONFIG_RESET_DATABASE, 1, 0); + sqlite3_exec(sqliteDatabaseHandle(), "VACUUM", nullptr, nullptr, nullptr); + sqlite3_db_config(sqliteDatabaseHandle(), SQLITE_DBCONFIG_RESET_DATABASE, 0, 0); +} + +void DatabaseBackend::enableForeignKeys() +{ + sqlite3_exec(sqliteDatabaseHandle(), "PRAGMA foreign_keys=ON", nullptr, nullptr, nullptr); +} + +void DatabaseBackend::disableForeignKeys() +{ + sqlite3_exec(sqliteDatabaseHandle(), "PRAGMA foreign_keys=OFF", nullptr, nullptr, nullptr); +} + void DatabaseBackend::checkForOpenDatabaseWhichCanBeClosed() { if (m_databaseHandle == nullptr) diff --git a/src/libs/sqlite/sqlitedatabasebackend.h b/src/libs/sqlite/sqlitedatabasebackend.h index 9a1caa92d67..39d690c4f73 100644 --- a/src/libs/sqlite/sqlitedatabasebackend.h +++ b/src/libs/sqlite/sqlitedatabasebackend.h @@ -84,6 +84,10 @@ public: void registerBusyHandler(); + void resetDatabaseForTestsOnly(); + void enableForeignKeys(); + void disableForeignKeys(); + protected: bool databaseIsOpen() const; diff --git a/src/libs/sqlite/sqlitevalue.h b/src/libs/sqlite/sqlitevalue.h index 49ab76d89f0..fe576f3fec9 100644 --- a/src/libs/sqlite/sqlitevalue.h +++ b/src/libs/sqlite/sqlitevalue.h @@ -234,9 +234,9 @@ public: {} template - static ValueView create(Type &&value) + static ValueView create(Type &&value_) { - return ValueView{ValueBase{value}}; + return ValueView{ValueBase{value_}}; } }; @@ -267,6 +267,21 @@ public: : ValueBase(Utils::SmallStringView(value)) {} + explicit Value(long long value) + : ValueBase(value) + {} + explicit Value(int value) + : ValueBase(static_cast(value)) + {} + + explicit Value(uint value) + : ValueBase(static_cast(value)) + {} + + explicit Value(double value) + : ValueBase(value) + {} + explicit Value(const QString &value) : ValueBase(VariantType{Utils::SmallString(value)}) {} diff --git a/src/libs/utils/algorithm.h b/src/libs/utils/algorithm.h index 61c8f12817b..dd201b647c7 100644 --- a/src/libs/utils/algorithm.h +++ b/src/libs/utils/algorithm.h @@ -1410,13 +1410,13 @@ OutputContainer setUnionMerge(InputContainer1 &&input1, } template -auto usize(const Container &container) +constexpr auto usize(const Container &container) { return static_cast>(std::size(container)); } template -auto ssize(const Container &container) +constexpr auto ssize(const Container &container) { return static_cast>(std::size(container)); } diff --git a/src/libs/utils/array.h b/src/libs/utils/array.h index 0d9a6047281..c89778871ca 100644 --- a/src/libs/utils/array.h +++ b/src/libs/utils/array.h @@ -3,6 +3,8 @@ #pragma once +#include + namespace Utils { namespace Internal { diff --git a/src/libs/utils/smallstring.h b/src/libs/utils/smallstring.h index 54318795a71..e5c303430ca 100644 --- a/src/libs/utils/smallstring.h +++ b/src/libs/utils/smallstring.h @@ -11,6 +11,7 @@ #include "smallstringmemory.h" #include +#include #include #include @@ -240,6 +241,8 @@ public: setSize(newSize); } + void pop_back() noexcept { setSize(size() - 1); } + void clear() noexcept { this->~BasicSmallString(); @@ -562,7 +565,7 @@ public: { // 2 bytes for the sign and because digits10 returns the floor char buffer[std::numeric_limits::digits10 + 2]; - auto result = std::to_chars(buffer, buffer + sizeof(buffer), number, 10); + auto result = std::to_chars(buffer, buffer + sizeof(buffer), number); auto endOfConversionString = result.ptr; return BasicSmallString(buffer, endOfConversionString); } @@ -571,12 +574,24 @@ public: { // 2 bytes for the sign and because digits10 returns the floor char buffer[std::numeric_limits::digits10 + 2]; - auto result = std::to_chars(buffer, buffer + sizeof(buffer), number, 10); + auto result = std::to_chars(buffer, buffer + sizeof(buffer), number); auto endOfConversionString = result.ptr; return BasicSmallString(buffer, endOfConversionString); } - static BasicSmallString number(double number) noexcept { return std::to_string(number); } + static BasicSmallString number(double number) noexcept + { +#if defined(__cpp_lib_to_chars) && (__cpp_lib_to_chars >= 201611L) + // 2 bytes for the sign and because digits10 returns the floor + char buffer[std::numeric_limits::digits10 + 2]; + auto result = std::to_chars(buffer, buffer + sizeof(buffer), number); + auto endOfConversionString = result.ptr; + return BasicSmallString(buffer, endOfConversionString); +#else + QLocale locale{QLocale::Language::C}; + return BasicSmallString{locale.toString(number)}; +#endif + } char &operator[](std::size_t index) noexcept { return *(data() + index); } diff --git a/src/libs/utils/theme/theme.h b/src/libs/utils/theme/theme.h index c7bf0a5f3af..e7e82d59f72 100644 --- a/src/libs/utils/theme/theme.h +++ b/src/libs/utils/theme/theme.h @@ -356,7 +356,9 @@ public: DSpillOperatorBackgroundHover, DSpillLiteralBackgroundIdle, DSpillLiteralBackgroundHover, - + DSconnectionEditorMicroToolbar, + DSconnectionEditorButtonBackground_hover, + DSconnectionEditorButtonBorder_hover, /*Legacy QtDS*/ DSiconColor, diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index 5d7316dee61..15416d790d6 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -103,6 +103,7 @@ if (WITH_QMLDESIGNER) set(qmldesigner_builddir ${PROJECT_BINARY_DIR}/qmldsgnr) endif() add_subdirectory(qmldesigner ${qmldesigner_builddir}) + add_subdirectory(effectmakernew) add_subdirectory(studiowelcome) add_subdirectory(insight) endif() @@ -113,7 +114,4 @@ add_subdirectory(saferenderer) add_subdirectory(copilot) add_subdirectory(terminal) -if (WITH_QMLDESIGNER) - add_subdirectory(effectmakernew) -endif() add_subdirectory(compilerexplorer) diff --git a/src/plugins/clangcodemodel/clangdfindreferences.cpp b/src/plugins/clangcodemodel/clangdfindreferences.cpp index 19e1df4dd16..b7e62d6e055 100644 --- a/src/plugins/clangcodemodel/clangdfindreferences.cpp +++ b/src/plugins/clangcodemodel/clangdfindreferences.cpp @@ -252,10 +252,8 @@ void ClangdFindReferences::Private::handleRenameRequest( { const Utils::FilePaths filePaths = BaseFileFind::replaceAll(newSymbolName, checkedItems, preserveCase); - if (!filePaths.isEmpty()) { - DocumentManager::notifyFilesChangedInternally(filePaths); + if (!filePaths.isEmpty()) SearchResultWindow::instance()->hide(); - } const auto renameFilesCheckBox = qobject_cast(search->additionalReplaceWidget()); QTC_ASSERT(renameFilesCheckBox, return); diff --git a/src/plugins/effectmakernew/CMakeLists.txt b/src/plugins/effectmakernew/CMakeLists.txt index b8842687e5e..4c1474d8eb7 100644 --- a/src/plugins/effectmakernew/CMakeLists.txt +++ b/src/plugins/effectmakernew/CMakeLists.txt @@ -1,12 +1,10 @@ -find_package(Qt6 OPTIONAL_COMPONENTS Gui Quick ShaderTools) - add_qtc_plugin(EffectMakerNew - CONDITION TARGET QmlDesigner AND TARGET Qt::ShaderTools + CONDITION TARGET Qt::Quick AND TARGET QtCreator::QmlDesigner PLUGIN_DEPENDS - QtCreator::Core QtCreator::QmlDesigner + QtCreator::QmlDesigner QtCreator::ProjectExplorer QtCreator::QmlProjectManager DEPENDS - Qt::Core - QtCreator::Utils Qt::CorePrivate Qt::Widgets Qt::Qml Qt::QmlPrivate Qt::Quick Qt::ShaderTools Qt::ShaderToolsPrivate + Qt::Core Qt::CorePrivate Qt::Widgets Qt::Qml Qt::QmlPrivate Qt::Quick + QtCreator::Utils SOURCES effectmakerplugin.cpp effectmakerplugin.h effectmakerwidget.cpp effectmakerwidget.h @@ -22,5 +20,5 @@ add_qtc_plugin(EffectMakerNew effectmakercontextobject.cpp effectmakercontextobject.h shaderfeatures.cpp shaderfeatures.h syntaxhighlighterdata.cpp syntaxhighlighterdata.h - BUILD_DEFAULT OFF + propertyhandler.cpp propertyhandler.h ) diff --git a/src/plugins/effectmakernew/EffectMakerNew.json.in b/src/plugins/effectmakernew/EffectMakerNew.json.in index 46c5e12247f..c48476418b5 100644 --- a/src/plugins/effectmakernew/EffectMakerNew.json.in +++ b/src/plugins/effectmakernew/EffectMakerNew.json.in @@ -1,15 +1,16 @@ { - \"Name\" : \"EffectMakerNew\", - \"Version\" : \"$$QTCREATOR_VERSION\", - \"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\", - \"Revision\" : \"$$QTC_PLUGIN_REVISION\", - \"Vendor\" : \"The Qt Company Ltd\", - \"Copyright\" : \"(C) $$QTCREATOR_COPYRIGHT_YEAR The Qt Company Ltd\", - \"License\" : [ \"Commercial Usage\", - \"\", - \"Licensees holding valid Qt Enterprise licenses may use this plugin in accordance with the Qt Enterprise License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company.\" + "Name" : "EffectMakerNew", + "Version" : "${IDE_VERSION}", + "CompatVersion" : "${IDE_VERSION_COMPAT}", + "Vendor" : "The Qt Company Ltd", + "Copyright" : "(C) $$QTCREATOR_COPYRIGHT_YEAR The Qt Company Ltd", + "License" : [ "Commercial Usage", + "", + "Licensees holding valid Qt Enterprise licenses may use this plugin in accordance with the Qt Enterprise License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company." ], - \"Description\" : \"Plugin for Effect Maker.\", - \"Url\" : \"http://www.qt.io\", - $$dependencyList + "Description" : "Plugin for Effect Maker.", + "Url" : "http://www.qt.io", + "DisabledByDefault" : true, + ${IDE_PLUGIN_DEPENDENCIES} } + diff --git a/src/plugins/effectmakernew/compositionnode.cpp b/src/plugins/effectmakernew/compositionnode.cpp index 74e43c76d56..8cd5bda971e 100644 --- a/src/plugins/effectmakernew/compositionnode.cpp +++ b/src/plugins/effectmakernew/compositionnode.cpp @@ -5,18 +5,42 @@ #include "effectutils.h" #include "effectmakeruniformsmodel.h" +#include "propertyhandler.h" #include "uniform.h" #include #include #include -#include namespace EffectMaker { -CompositionNode::CompositionNode(const QString &qenPath) +CompositionNode::CompositionNode(const QString &effectName, const QString &qenPath, const QJsonObject &jsonObject) { - parse(qenPath); + QJsonObject json; + if (jsonObject.isEmpty()) { + QFile qenFile(qenPath); + if (!qenFile.open(QIODevice::ReadOnly)) { + qWarning("Couldn't open effect file."); + return; + } + + QByteArray loadData = qenFile.readAll(); + QJsonParseError parseError; + QJsonDocument jsonDoc(QJsonDocument::fromJson(loadData, &parseError)); + + if (parseError.error != QJsonParseError::NoError) { + QString error = QString("Error parsing effect node"); + QString errorDetails = QString("%1: %2").arg(parseError.offset).arg(parseError.errorString()); + qWarning() << error; + qWarning() << errorDetails; + return; + } + json = jsonDoc.object().value("QEN").toObject(); + parse(effectName, qenPath, json); + } + else { + parse(effectName, "", jsonObject); + } } QString CompositionNode::fragmentCode() const @@ -62,28 +86,8 @@ CompositionNode::NodeType CompositionNode::type() const return m_type; } -void CompositionNode::parse(const QString &qenPath) +void CompositionNode::parse(const QString &effectName, const QString &qenPath, const QJsonObject &json) { - QFile qenFile(qenPath); - - if (!qenFile.open(QIODevice::ReadOnly)) { - qWarning("Couldn't open effect file."); - return; - } - - QByteArray loadData = qenFile.readAll(); - QJsonParseError parseError; - QJsonDocument jsonDoc(QJsonDocument::fromJson(loadData, &parseError)); - if (parseError.error != QJsonParseError::NoError) { - QString error = QString("Error parsing the effect node: %1:").arg(qenPath); - QString errorDetails = QString("%1: %2").arg(parseError.offset).arg(parseError.errorString()); - qWarning() << qPrintable(error); - qWarning() << qPrintable(errorDetails); - return; - } - - QJsonObject json = jsonDoc.object().value("QEN").toObject(); - int version = -1; if (json.contains("version")) version = json["version"].toInt(-1); @@ -100,8 +104,12 @@ void CompositionNode::parse(const QString &qenPath) // parse properties QJsonArray jsonProps = json.value("properties").toArray(); - for (const auto /*QJsonValueRef*/ &prop : jsonProps) - m_unifomrsModel.addUniform(new Uniform(prop.toObject())); + for (const auto /*QJsonValueRef*/ &prop : jsonProps) { + const auto uniform = new Uniform(effectName, prop.toObject(), qenPath); + m_unifomrsModel.addUniform(uniform); + m_uniforms.append(uniform); + g_propertyData.insert(uniform->name(), uniform->value()); + } // Seek through code to get tags QStringList shaderCodeLines; @@ -119,5 +127,15 @@ void CompositionNode::parse(const QString &qenPath) } } +QList CompositionNode::uniforms() const +{ + return m_uniforms; +} + +QString CompositionNode::name() const +{ + return m_name; +} + } // namespace EffectMaker diff --git a/src/plugins/effectmakernew/compositionnode.h b/src/plugins/effectmakernew/compositionnode.h index 2637bfb3879..4736f1d8afa 100644 --- a/src/plugins/effectmakernew/compositionnode.h +++ b/src/plugins/effectmakernew/compositionnode.h @@ -5,6 +5,7 @@ #include "effectmakeruniformsmodel.h" +#include #include namespace EffectMaker { @@ -24,7 +25,7 @@ public: CustomNode }; - CompositionNode(const QString &qenPath); + CompositionNode(const QString &effectName, const QString &qenPath, const QJsonObject &json = {}); QString fragmentCode() const; QString vertexCode() const; @@ -39,12 +40,16 @@ public: bool isEnabled() const; void setIsEnabled(bool newIsEnabled); + QString name() const; + + QList uniforms() const; + signals: void uniformsModelChanged(); void isEnabledChanged(); private: - void parse(const QString &qenPath); + void parse(const QString &effectName, const QString &qenPath, const QJsonObject &json); QString m_name; NodeType m_type = CustomNode; @@ -54,6 +59,8 @@ private: QStringList m_requiredNodes; bool m_isEnabled = true; + QList m_uniforms; + EffectMakerUniformsModel m_unifomrsModel; }; diff --git a/src/plugins/effectmakernew/effectmakermodel.cpp b/src/plugins/effectmakernew/effectmakermodel.cpp index 9da7d97ef60..2c9dd051638 100644 --- a/src/plugins/effectmakernew/effectmakermodel.cpp +++ b/src/plugins/effectmakernew/effectmakermodel.cpp @@ -7,10 +7,21 @@ #include "syntaxhighlighterdata.h" #include "uniform.h" -#include -#include +#include + +#include +#include +#include + +#include #include +#include + +#include + +#include +#include namespace EffectMaker { @@ -38,17 +49,6 @@ static bool writeToFile(const QByteArray &buf, const QString &filename, FileType EffectMakerModel::EffectMakerModel(QObject *parent) : QAbstractListModel{parent} { - m_vertexShaderFile.setFileTemplate(QDir::tempPath() + "/dsem_XXXXXX.vert.qsb"); - m_fragmentShaderFile.setFileTemplate(QDir::tempPath() + "/dsem_XXXXXX.frag.qsb"); - // TODO: Will be revisted later when saving output files - if (m_vertexShaderFile.open()) - qInfo() << "Using temporary vs file:" << m_vertexShaderFile.fileName(); - if (m_fragmentShaderFile.open()) - qInfo() << "Using temporary fs file:" << m_fragmentShaderFile.fileName(); - - // Prepare baker - m_baker.setGeneratedShaderVariants({ QShader::StandardShader }); - updateBakedShaderVersions(); } QHash EffectMakerModel::roleNames() const @@ -82,6 +82,8 @@ bool EffectMakerModel::setData(const QModelIndex &index, const QVariant &value, if (role == EnabledRole) { m_nodes.at(index.row())->setIsEnabled(value.toBool()); + bakeShaders(); + emit dataChanged(index, index, {role}); } @@ -93,13 +95,16 @@ void EffectMakerModel::setIsEmpty(bool val) if (m_isEmpty != val) { m_isEmpty = val; emit isEmptyChanged(); + + if (m_isEmpty) + bakeShaders(); } } void EffectMakerModel::addNode(const QString &nodeQenPath) { beginInsertRows({}, m_nodes.size(), m_nodes.size()); - auto *node = new CompositionNode(nodeQenPath); + auto *node = new CompositionNode("", nodeQenPath); m_nodes.append(node); endInsertRows(); @@ -135,21 +140,6 @@ void EffectMakerModel::removeNode(int idx) bakeShaders(); } -void EffectMakerModel::updateBakedShaderVersions() -{ - QList targets; - targets.append({ QShader::SpirvShader, QShaderVersion(100) }); // Vulkan 1.0 - targets.append({ QShader::HlslShader, QShaderVersion(50) }); // Shader Model 5.0 - targets.append({ QShader::MslShader, QShaderVersion(12) }); // Metal 1.2 - targets.append({ QShader::GlslShader, QShaderVersion(300, QShaderVersion::GlslEs) }); // GLES 3.0+ - targets.append({ QShader::GlslShader, QShaderVersion(410) }); // OpenGL 4.1+ - targets.append({ QShader::GlslShader, QShaderVersion(330) }); // OpenGL 3.3 - targets.append({ QShader::GlslShader, QShaderVersion(140) }); // OpenGL 3.1 - //TODO: Do we need support for legacy shaders 100, 120? - - m_baker.setGeneratedShaders(targets); -} - QString EffectMakerModel::fragmentShader() const { return m_fragmentShader; @@ -181,6 +171,24 @@ const QString &EffectMakerModel::qmlComponentString() const return m_qmlComponentString; } +void EffectMakerModel::clear() +{ + if (m_nodes.isEmpty()) + return; + + beginRemoveRows({}, 0, m_nodes.count()); + + for (CompositionNode *node : std::as_const(m_nodes)) + delete node; + + m_nodes.clear(); + + endRemoveRows(); + + setIsEmpty(true); + bakeShaders(); +} + const QList EffectMakerModel::allUniforms() { QList uniforms = {}; @@ -207,7 +215,7 @@ const QString EffectMakerModel::getBufUniform() for (const auto uniform : uniforms) { // TODO: Check if uniform is already added. if (uniform->type() != Uniform::Type::Sampler && uniform->type() != Uniform::Type::Define) { - QString type = Uniform::stringFromType(uniform->type()); + QString type = Uniform::stringFromType(uniform->type(), true); QString props = " " + type + " " + uniform->name() + ";\n"; s += props; } @@ -341,6 +349,375 @@ void EffectMakerModel::setEffectError(const QString &errorMessage, int type, int Q_EMIT effectErrorChanged(); } +QString variantAsDataString(const Uniform::Type type, const QVariant &variant) +{ + QString s; + switch (type) { + case Uniform::Type::Bool: + s = variant.toBool() ? QString("true") : QString("false"); + break; + case Uniform::Type::Int: + s = QString::number(variant.toInt()); + break; + case Uniform::Type::Float: + s = QString::number(variant.toDouble()); + break; + case Uniform::Type::Vec2: { + QStringList list; + QVector2D v2 = variant.value(); + list << QString::number(v2.x()); + list << QString::number(v2.y()); + s = list.join(", "); + break; + } + case Uniform::Type::Vec3: { + QStringList list; + QVector3D v3 = variant.value(); + list << QString::number(v3.x()); + list << QString::number(v3.y()); + list << QString::number(v3.z()); + s = list.join(", "); + break; + } + case Uniform::Type::Vec4: { + QStringList list; + QVector4D v4 = variant.value(); + list << QString::number(v4.x()); + list << QString::number(v4.y()); + list << QString::number(v4.z()); + list << QString::number(v4.w()); + s = list.join(", "); + break; + } + case Uniform::Type::Color: { + QStringList list; + QColor c = variant.value(); + list << QString::number(c.redF(), 'g', 3); + list << QString::number(c.greenF(), 'g', 3); + list << QString::number(c.blueF(), 'g', 3); + list << QString::number(c.alphaF(), 'g', 3); + s = list.join(", "); + break; + } + case Uniform::Type::Sampler: + case Uniform::Type::Define: { + s = variant.toString(); + break; + } + } + return s; +} + +QJsonObject nodeToJson(const CompositionNode &node) +{ + QJsonObject nodeObject; + nodeObject.insert("name", node.name()); + if (!node.description().isEmpty()) + nodeObject.insert("description", node.description()); + nodeObject.insert("enabled", node.isEnabled()); + nodeObject.insert("version", 1); + // Add properties + QJsonArray propertiesArray; + const QList uniforms = node.uniforms(); + for (const Uniform *uniform : uniforms) { + QJsonObject uniformObject; + uniformObject.insert("name", QString(uniform->name())); + QString type = Uniform::stringFromType(uniform->type()); + uniformObject.insert("type", type); + + QString value = variantAsDataString(uniform->type(), uniform->value()); + if (uniform->type() == Uniform::Type::Sampler) + value = QFileInfo(value).fileName(); + uniformObject.insert("value", value); + + QString defaultValue = variantAsDataString(uniform->type(), uniform->defaultValue()); + if (uniform->type() == Uniform::Type::Sampler) { + defaultValue = QFileInfo(value).fileName(); + if (uniform->enableMipmap()) + uniformObject.insert("enableMipmap", uniform->enableMipmap()); + } + uniformObject.insert("defaultValue", defaultValue); + if (!uniform->description().isEmpty()) + uniformObject.insert("description", uniform->description()); + if (uniform->type() == Uniform::Type::Float + || uniform->type() == Uniform::Type::Int + || uniform->type() == Uniform::Type::Vec2 + || uniform->type() == Uniform::Type::Vec3 + || uniform->type() == Uniform::Type::Vec4) { + uniformObject.insert("minValue", variantAsDataString(uniform->type(), uniform->minValue())); + uniformObject.insert("maxValue", variantAsDataString(uniform->type(), uniform->maxValue())); + } + if (!uniform->customValue().isEmpty()) + uniformObject.insert("customValue", uniform->customValue()); + if (uniform->useCustomValue()) + uniformObject.insert("useCustomValue", true); + + propertiesArray.append(uniformObject); + } + if (!propertiesArray.isEmpty()) + nodeObject.insert("properties", propertiesArray); + + // Add shaders + if (!node.fragmentCode().trimmed().isEmpty()) { + QJsonArray fragmentCodeArray; + const QStringList fsLines = node.fragmentCode().split('\n'); + for (const QString &line : fsLines) + fragmentCodeArray.append(line); + + if (!fragmentCodeArray.isEmpty()) + nodeObject.insert("fragmentCode", fragmentCodeArray); + } + if (!node.vertexCode().trimmed().isEmpty()) { + QJsonArray vertexCodeArray; + const QStringList vsLines = node.vertexCode().split('\n'); + for (const QString &line : vsLines) + vertexCodeArray.append(line); + + if (!vertexCodeArray.isEmpty()) + nodeObject.insert("vertexCode", vertexCodeArray); + } + + return nodeObject; +} + +QString EffectMakerModel::getQmlEffectString() +{ + QString s; + + s += QString("// Created with Qt Design Studio (version %1), %2\n\n") + .arg(qApp->applicationVersion(), QDateTime::currentDateTime().toString()); + s += "import QtQuick\n"; + s += '\n'; + s += "Item {\n"; + s += " id: rootItem\n"; + s += '\n'; + if (m_shaderFeatures.enabled(ShaderFeatures::Source)) { + s += " // This is the main source for the effect\n"; + s += " property Item source: null\n"; + } + if (m_shaderFeatures.enabled(ShaderFeatures::Time) + || m_shaderFeatures.enabled(ShaderFeatures::Frame)) { + s += " // Enable this to animate iTime property\n"; + s += " property bool timeRunning: false\n"; + } + if (m_shaderFeatures.enabled(ShaderFeatures::Time)) { + s += " // When timeRunning is false, this can be used to control iTime manually\n"; + s += " property real animatedTime: frameAnimation.elapsedTime\n"; + } + if (m_shaderFeatures.enabled(ShaderFeatures::Frame)) { + s += " // When timeRunning is false, this can be used to control iFrame manually\n"; + s += " property int animatedFrame: frameAnimation.currentFrame\n"; + } + s += '\n'; + // Custom properties + if (!m_exportedRootPropertiesString.isEmpty()) { + s += m_exportedRootPropertiesString; + s += '\n'; + } + if (m_shaderFeatures.enabled(ShaderFeatures::Time) + || m_shaderFeatures.enabled(ShaderFeatures::Frame)) { + s += " FrameAnimation {\n"; + s += " id: frameAnimation\n"; + s += " running: rootItem.timeRunning\n"; + s += " }\n"; + s += '\n'; + } + + //TODO: Blue stuff goes here + + s += getQmlComponentString(true); + s += "}\n"; + return s; +} + +void EffectMakerModel::exportComposition(const QString &name) +{ + const QString effectsAssetsDir = QmlDesigner::ModelNodeOperations::getEffectsDefaultDirectory(); + const QString path = effectsAssetsDir + QDir::separator() + name + ".qep"; + auto saveFile = QFile(path); + if (!saveFile.open(QIODevice::WriteOnly)) { + QString error = QString("Error: Couldn't save composition file: '%1'").arg(path); + qWarning() << error; + return; + } + + QJsonObject json; + // File format version + json.insert("version", 1); + + // Add nodes + QJsonArray nodesArray; + for (const CompositionNode *node : std::as_const(m_nodes)) { + QJsonObject nodeObject = nodeToJson(*node); + nodesArray.append(nodeObject); + } + + if (!nodesArray.isEmpty()) + json.insert("nodes", nodesArray); + + QJsonObject rootJson; + rootJson.insert("QEP", json); + QJsonDocument jsonDoc(rootJson); + + saveFile.write(jsonDoc.toJson()); + saveFile.close(); +} + +void EffectMakerModel::openComposition(const QString &path) +{ + clear(); + + QFile compFile(path); + if (!compFile.open(QIODevice::ReadOnly)) { + QString error = QString("Couldn't open composition file: '%1'").arg(path); + qWarning() << qPrintable(error); + setEffectError(error); + return; + } + + QByteArray data = compFile.readAll(); + QJsonParseError parseError; + QJsonDocument jsonDoc(QJsonDocument::fromJson(data, &parseError)); + if (parseError.error != QJsonParseError::NoError) { + QString error = QString("Error parsing the project file: %1").arg(parseError.errorString()); + qWarning() << error; + setEffectError(error); + return; + } + QJsonObject rootJson = jsonDoc.object(); + if (!rootJson.contains("QEP")) { + QString error = QStringLiteral("Error: Invalid project file"); + qWarning() << error; + setEffectError(error); + return; + } + + QJsonObject json = rootJson["QEP"].toObject(); + + int version = -1; + if (json.contains("version")) + version = json["version"].toInt(-1); + + if (version != 1) { + QString error = QString("Error: Unknown project version (%1)").arg(version); + qWarning() << error; + setEffectError(error); + return; + } + + // Get effects dir + const QString effectName = QFileInfo(path).baseName(); + const Utils::FilePath effectsResDir = QmlDesigner::ModelNodeOperations::getEffectsImportDirectory(); + const QString effectsResPath = effectsResDir.pathAppended(effectName).toString(); + + if (json.contains("nodes") && json["nodes"].isArray()) { + const QJsonArray nodesArray = json["nodes"].toArray(); + for (const auto &nodeElement : nodesArray) { + beginInsertRows({}, m_nodes.size(), m_nodes.size()); + auto *node = new CompositionNode(effectName, "", nodeElement.toObject()); + m_nodes.append(node); + endInsertRows(); + } + + setIsEmpty(m_nodes.isEmpty()); + bakeShaders(); + } + + setCurrentComposition(effectName); +} + +void EffectMakerModel::exportResources(const QString &name) +{ + // Make sure that uniforms are up-to-date + updateCustomUniforms(); + + QString qmlFilename = name + ".qml"; + QString vsFilename = name + ".vert.qsb"; + QString fsFilename = name + ".frag.qsb"; + + // Shaders should be all lowercase + vsFilename = vsFilename.toLower(); + fsFilename = fsFilename.toLower(); + + // Get effects dir + const Utils::FilePath effectsResDir = QmlDesigner::ModelNodeOperations::getEffectsImportDirectory(); + const QString effectsResPath = effectsResDir.pathAppended(name).toString() + QDir::separator(); + + // Create the qmldir for effects + Utils::FilePath qmldirPath = effectsResDir.resolvePath(QStringLiteral("qmldir")); + QString qmldirContent = QString::fromUtf8(qmldirPath.fileContents().value_or(QByteArray())); + if (qmldirContent.isEmpty()) { + qmldirContent.append("module Effects\n"); + qmldirPath.writeFileContents(qmldirContent.toUtf8()); + } + + // Create effect folder if not created + Utils::FilePath effectPath = Utils::FilePath::fromString(effectsResPath); + if (!effectPath.exists()) { + QDir effectDir(effectsResDir.toString()); + effectDir.mkdir(name); + } + + // Create effect qmldir + qmldirPath = effectPath.resolvePath(QStringLiteral("qmldir")); + qmldirContent = QString::fromUtf8(qmldirPath.fileContents().value_or(QByteArray())); + if (qmldirContent.isEmpty()) { + qmldirContent.append("module Effects."); + qmldirContent.append(name); + qmldirContent.append('\n'); + qmldirContent.append(name); + qmldirContent.append(" 1.0 "); + qmldirContent.append(name); + qmldirContent.append(".qml\n"); + qmldirPath.writeFileContents(qmldirContent.toUtf8()); + } + + // Create the qml file + QString qmlComponentString = getQmlEffectString(); + QStringList qmlStringList = qmlComponentString.split('\n'); + + // Replace shaders with local versions + for (int i = 1; i < qmlStringList.size(); i++) { + QString line = qmlStringList.at(i).trimmed(); + if (line.startsWith("vertexShader")) { + QString vsLine = " vertexShader: '" + vsFilename + "'"; + qmlStringList[i] = vsLine; + } else if (line.startsWith("fragmentShader")) { + QString fsLine = " fragmentShader: '" + fsFilename + "'"; + qmlStringList[i] = fsLine; + } + } + + const QString qmlString = qmlStringList.join('\n'); + QString qmlFilePath = effectsResPath + qmlFilename; + writeToFile(qmlString.toUtf8(), qmlFilePath, FileType::Text); + + // Export shaders and images + QStringList sources = {m_vertexShaderFilename, m_fragmentShaderFilename}; + QStringList dests = {vsFilename, fsFilename}; + + const QList uniforms = allUniforms(); + for (const Uniform *uniform : uniforms) { + if (uniform->type() == Uniform::Type::Sampler && !uniform->value().toString().isEmpty()) { + QString imagePath = uniform->value().toString(); + QFileInfo fi(imagePath); + QString imageFilename = fi.fileName(); + sources.append(imagePath.remove(0, 7)); // Removes "file://" + dests.append(imageFilename); + } + } + + for (int i = 0; i < sources.count(); ++i) { + Utils::FilePath source = Utils::FilePath::fromString(sources[i]); + Utils::FilePath target = Utils::FilePath::fromString(effectsResPath + dests[i]); + if (target.exists() && source.fileName() != target.fileName()) + target.removeFile(); // Remove existing file for update + + if (!source.copyFile(target)) + qWarning() << __FUNCTION__ << " Failed to copy file: " << source; + } +} + void EffectMakerModel::resetEffectError(int type) { if (m_effectErrors.contains(type)) { @@ -367,12 +744,9 @@ QString EffectMakerModel::valueAsString(const Uniform &uniform) } else if (uniform.type() == Uniform::Type::Vec4) { QVector4D v4 = uniform.value().value(); return QString("Qt.vector4d(%1, %2, %3, %4)").arg(v4.x(), v4.y(), v4.z(), v4.w()); - } else if (uniform.type() == Uniform::Type::Color) { - QColor c = uniform.value().value(); - return QString("Qt.rgba(%1, %2, %3, %4)").arg(c.redF(), c.greenF(), c.blueF(), c.alphaF()); } else if (uniform.type() == Uniform::Type::Sampler) { return getImageElementName(uniform); - } else if (uniform.type() == Uniform::Type::Define) { + } else if (uniform.type() == Uniform::Type::Define || uniform.type() == Uniform::Type::Color) { return uniform.value().toString(); } else { qWarning() << QString("Unhandled const variable type: %1").arg(int(uniform.type())).toLatin1(); @@ -383,8 +757,11 @@ QString EffectMakerModel::valueAsString(const Uniform &uniform) // Get value in QML binding that used for previews QString EffectMakerModel::valueAsBinding(const Uniform &uniform) { - if (uniform.type() == Uniform::Type::Bool || uniform.type() == Uniform::Type::Int - || uniform.type() == Uniform::Type::Float || uniform.type() == Uniform::Type::Define) { + if (uniform.type() == Uniform::Type::Bool + || uniform.type() == Uniform::Type::Int + || uniform.type() == Uniform::Type::Float + || uniform.type() == Uniform::Type::Color + || uniform.type() == Uniform::Type::Define) { return "g_propertyData." + uniform.name(); } else if (uniform.type() == Uniform::Type::Vec2) { QString sx = QString("g_propertyData.%1.x").arg(uniform.name()); @@ -401,12 +778,6 @@ QString EffectMakerModel::valueAsBinding(const Uniform &uniform) QString sz = QString("g_propertyData.%1.z").arg(uniform.name()); QString sw = QString("g_propertyData.%1.w").arg(uniform.name()); return QString("Qt.vector4d(%1, %2, %3, %4)").arg(sx, sy, sz, sw); - } else if (uniform.type() == Uniform::Type::Color) { - QString sr = QString("g_propertyData.%1.r").arg(uniform.name()); - QString sg = QString("g_propertyData.%1.g").arg(uniform.name()); - QString sb = QString("g_propertyData.%1.b").arg(uniform.name()); - QString sa = QString("g_propertyData.%1.a").arg(uniform.name()); - return QString("Qt.rgba(%1, %2, %3, %4)").arg(sr, sg, sb, sa); } else if (uniform.type() == Uniform::Type::Sampler) { return getImageElementName(uniform); } else { @@ -445,9 +816,11 @@ QString EffectMakerModel::valueAsVariable(const Uniform &uniform) // Return name for the image property Image element QString EffectMakerModel::getImageElementName(const Uniform &uniform) { - // TODO - Q_UNUSED(uniform) - return {}; + if (uniform.value().toString().isEmpty()) + return QStringLiteral("null"); + QString simplifiedName = uniform.name().simplified(); + simplifiedName = simplifiedName.remove(' '); + return QStringLiteral("imageItem") + simplifiedName; } const QString EffectMakerModel::getConstVariables() @@ -457,7 +830,7 @@ const QString EffectMakerModel::getConstVariables() for (Uniform *uniform : uniforms) { // TODO: Check if uniform is already added. QString constValue = valueAsVariable(*uniform); - QString type = Uniform::stringFromType(uniform->type()); + QString type = Uniform::stringFromType(uniform->type(), true); s += QString("const %1 %2 = %3;\n").arg(type, uniform->name(), constValue); } if (!s.isEmpty()) @@ -622,19 +995,15 @@ QString EffectMakerModel::generateVertexShader(bool includeUniforms) m_shaderVaryingVariables.clear(); for (const CompositionNode *n : std::as_const(m_nodes)) { if (!n->vertexCode().isEmpty() && n->isEnabled()) { - if (n->type() == CompositionNode::NodeType::SourceNode) { - s_sourceCode = n->vertexCode().split('\n'); - } else if (n->type() == CompositionNode::NodeType::CustomNode) { - const QStringList vertexCode = n->vertexCode().split('\n'); - int mainIndex = getTagIndex(vertexCode, QStringLiteral("main")); - int line = 0; - for (const QString &ss : vertexCode) { - if (mainIndex == -1 || line > mainIndex) - s_main += QStringLiteral(" ") + ss + '\n'; - else if (line < mainIndex) - s_root += processVertexRootLine(ss); - line++; - } + const QStringList vertexCode = n->vertexCode().split('\n'); + int mainIndex = getTagIndex(vertexCode, "main"); + int line = 0; + for (const QString &ss : vertexCode) { + if (mainIndex == -1 || line > mainIndex) + s_main += " " + ss + '\n'; + else if (line < mainIndex) + s_root += processVertexRootLine(ss); + line++; } } } @@ -681,19 +1050,15 @@ QString EffectMakerModel::generateFragmentShader(bool includeUniforms) QStringList s_sourceCode; for (const CompositionNode *n : std::as_const(m_nodes)) { if (!n->fragmentCode().isEmpty() && n->isEnabled()) { - if (n->type() == CompositionNode::NodeType::SourceNode) { - s_sourceCode = n->fragmentCode().split('\n'); - } else if (n->type() == CompositionNode::NodeType::CustomNode) { - const QStringList fragmentCode = n->fragmentCode().split('\n'); - int mainIndex = getTagIndex(fragmentCode, QStringLiteral("main")); - int line = 0; - for (const QString &ss : fragmentCode) { - if (mainIndex == -1 || line > mainIndex) - s_main += QStringLiteral(" ") + ss + '\n'; - else if (line < mainIndex) - s_root += processFragmentRootLine(ss); - line++; - } + const QStringList fragmentCode = n->fragmentCode().split('\n'); + int mainIndex = getTagIndex(fragmentCode, QStringLiteral("main")); + int line = 0; + for (const QString &ss : fragmentCode) { + if (mainIndex == -1 || line > mainIndex) + s_main += QStringLiteral(" ") + ss + '\n'; + else if (line < mainIndex) + s_root += processFragmentRootLine(ss); + line++; } } } @@ -720,6 +1085,29 @@ QString EffectMakerModel::generateFragmentShader(bool includeUniforms) return s; } +void EffectMakerModel::handleQsbProcessExit(Utils::Process *qsbProcess, const QString &shader) +{ + --m_remainingQsbTargets; + + const QString errStr = qsbProcess->errorString(); + const QByteArray errStd = qsbProcess->readAllRawStandardError(); + if (!errStr.isEmpty()) + qWarning() << QString("Failed to generate QSB file for: %1 %2").arg(shader, errStr); + + if (!errStd.isEmpty()) + qWarning() << QString("Failed to generate QSB file for: %1 %2") + .arg(shader, QString::fromUtf8(errStd)); + + if (m_remainingQsbTargets <= 0) { + Q_EMIT shadersBaked(); + setShadersUpToDate(true); + + // TODO: Mark shaders as baked, required by export later + } + + qsbProcess->deleteLater(); +} + // Generates string of the custom properties (uniforms) into ShaderEffect component // Also generates QML images elements for samplers. void EffectMakerModel::updateCustomUniforms() @@ -732,7 +1120,7 @@ void EffectMakerModel::updateCustomUniforms() for (Uniform *uniform : uniforms) { // TODO: Check if uniform is already added. const bool isDefine = uniform->type() == Uniform::Type::Define; - QString type = Uniform::typeToProperty(uniform->type()); + QString propertyType = Uniform::typeToProperty(uniform->type()); QString value = valueAsString(*uniform); QString bindedValue = valueAsBinding(*uniform); // When user has set custom uniform value, use it as-is @@ -754,11 +1142,11 @@ void EffectMakerModel::updateCustomUniforms() } } QString valueString = value.isEmpty() ? QString() : QString(": %1").arg(value); - QString bindedValueString = bindedValue.isEmpty() ? QString() : QString(": %1").arg(bindedValue); + QString boundValueString = bindedValue.isEmpty() ? QString() : QString(": %1").arg(bindedValue); // Custom values are not readonly, others inside the effect can be QString readOnly = uniform->useCustomValue() ? QString() : QStringLiteral("readonly "); - previewEffectPropertiesString += " " + readOnly + "property " + type + " " - + propertyName + bindedValueString + '\n'; + previewEffectPropertiesString += " " + readOnly + "property " + propertyType + " " + + propertyName + boundValueString + '\n'; // Define type properties are not added into exports if (!isDefine) { if (uniform->useCustomValue()) { @@ -769,11 +1157,11 @@ void EffectMakerModel::updateCustomUniforms() exportedEffectPropertiesString += QStringLiteral(" // ") + line + '\n'; } exportedEffectPropertiesString += QStringLiteral(" ") + readOnly - + "property " + type + " " + propertyName - + bindedValueString + '\n'; + + "property " + propertyType + " " + propertyName + + boundValueString + '\n'; } else { // Custom values are not added into root - exportedRootPropertiesString += " property " + type + " " + propertyName + exportedRootPropertiesString += " property " + propertyType + " " + propertyName + valueString + '\n'; exportedEffectPropertiesString += QStringLiteral(" ") + readOnly + "property alias " + propertyName @@ -783,11 +1171,47 @@ void EffectMakerModel::updateCustomUniforms() } // See if any of the properties changed - // TODO + m_exportedRootPropertiesString = exportedRootPropertiesString; + m_previewEffectPropertiesString = previewEffectPropertiesString; + m_exportedEffectPropertiesString = exportedEffectPropertiesString; +} + +void EffectMakerModel::createFiles() +{ + if (QFileInfo(m_vertexShaderFilename).exists()) + QFile(m_vertexShaderFilename).remove(); + if (QFileInfo(m_fragmentShaderFilename).exists()) + QFile(m_fragmentShaderFilename).remove(); + + auto vertexShaderFile = QTemporaryFile(QDir::tempPath() + "/dsem_XXXXXX.vert.qsb"); + auto fragmentShaderFile = QTemporaryFile(QDir::tempPath() + "/dsem_XXXXXX.frag.qsb"); + + m_vertexSourceFile.setFileTemplate(QDir::tempPath() + "/dsem_XXXXXX.vert"); + m_fragmentSourceFile.setFileTemplate(QDir::tempPath() + "/dsem_XXXXXX.frag"); + + if (!m_vertexSourceFile.open() || !m_fragmentSourceFile.open() + || !vertexShaderFile.open() || !fragmentShaderFile.open()) { + qWarning() << "Unable to open temporary files"; + } else { + m_vertexSourceFilename = m_vertexSourceFile.fileName(); + m_fragmentSourceFilename = m_fragmentSourceFile.fileName(); + m_vertexShaderFilename = vertexShaderFile.fileName(); + m_fragmentShaderFilename = fragmentShaderFile.fileName(); + } } void EffectMakerModel::bakeShaders() { + const QString failMessage = "Shader baking failed: "; + + const ProjectExplorer::Target *target = ProjectExplorer::ProjectTree::currentTarget(); + if (!target) { + qWarning() << failMessage << "Target not found"; + return; + } + + createFiles(); + resetEffectError(ErrorPreprocessor); if (m_vertexShader == generateVertexShader() && m_fragmentShader == generateFragmentShader()) { setShadersUpToDate(true); @@ -805,37 +1229,41 @@ void EffectMakerModel::bakeShaders() setVertexShader(generateVertexShader()); QString vs = m_vertexShader; - m_baker.setSourceString(vs.toUtf8(), QShader::VertexStage); - QShader vertShader = m_baker.bake(); - if (!vertShader.isValid()) { - qWarning() << "Shader baking failed:" << qPrintable(m_baker.errorMessage()); - setEffectError(m_baker.errorMessage().split('\n').first(), ErrorVert); - } else { - QString filename = m_vertexShaderFile.fileName(); - writeToFile(vertShader.serialized(), filename, FileType::Binary); - resetEffectError(ErrorVert); - } + writeToFile(vs.toUtf8(), m_vertexSourceFile.fileName(), FileType::Text); setFragmentShader(generateFragmentShader()); QString fs = m_fragmentShader; - m_baker.setSourceString(fs.toUtf8(), QShader::FragmentStage); + writeToFile(fs.toUtf8(), m_fragmentSourceFile.fileName(), FileType::Text); - QShader fragShader = m_baker.bake(); - if (!fragShader.isValid()) { - qWarning() << "Shader baking failed:" << qPrintable(m_baker.errorMessage()); - setEffectError(m_baker.errorMessage().split('\n').first(), ErrorFrag); - } else { - QString filename = m_fragmentShaderFile.fileName(); - writeToFile(fragShader.serialized(), filename, FileType::Binary); - resetEffectError(ErrorFrag); + QtSupport::QtVersion *qtVer = QtSupport::QtKitAspect::qtVersion(target->kit()); + if (!qtVer) { + qWarning() << failMessage << "Qt version not found"; + return; } - if (vertShader.isValid() && fragShader.isValid()) { - Q_EMIT shadersBaked(); - setShadersUpToDate(true); + Utils::FilePath qsbPath = qtVer->binPath().pathAppended("qsb").withExecutableSuffix(); + if (!qsbPath.exists()) { + qWarning() << failMessage << "QSB tool not found"; + return; } - // TODO: Mark shaders as baked, required by export later + m_remainingQsbTargets = 2; // We only have 2 shaders + const QStringList srcPaths = {m_vertexSourceFilename, m_fragmentSourceFilename}; + const QStringList outPaths = {m_vertexShaderFilename, m_fragmentShaderFilename}; + for (int i = 0; i < 2; ++i) { + const auto workDir = Utils::FilePath::fromString(outPaths[i]); + // TODO: Optional legacy glsl support like standalone effect maker needs to add "100es,120" + QStringList args = {"-s", "--glsl", "300es,140,330,410", "--hlsl", "50", "--msl", "12"}; + args << "-o" << outPaths[i] << srcPaths[i]; + + auto qsbProcess = new Utils::Process(this); + connect(qsbProcess, &Utils::Process::done, this, [=] { + handleQsbProcessExit(qsbProcess, srcPaths[i]); + }); + qsbProcess->setWorkingDirectory(workDir.absolutePath()); + qsbProcess->setCommand({qsbPath, args}); + qsbProcess->start(); + } } bool EffectMakerModel::shadersUpToDate() const @@ -851,12 +1279,48 @@ void EffectMakerModel::setShadersUpToDate(bool UpToDate) emit shadersUpToDateChanged(); } +// Returns name for image mipmap property. +// e.g. "myImage" -> "myImageMipmap". +QString EffectMakerModel::mipmapPropertyName(const QString &name) const +{ + QString simplifiedName = name.simplified(); + simplifiedName = simplifiedName.remove(' '); + simplifiedName += "Mipmap"; + return simplifiedName; +} + QString EffectMakerModel::getQmlImagesString(bool localFiles) { - Q_UNUSED(localFiles) + QString imagesString; + const QList uniforms = allUniforms(); + for (Uniform *uniform : uniforms) { + if (uniform->type() == Uniform::Type::Sampler) { + QString imagePath = uniform->value().toString(); + if (imagePath.isEmpty()) + continue; + imagesString += " Image {\n"; + QString simplifiedName = getImageElementName(*uniform); + imagesString += QString(" id: %1\n").arg(simplifiedName); + imagesString += " anchors.fill: parent\n"; + // File paths are absolute, return as local when requested + if (localFiles) { + QFileInfo fi(imagePath); + imagePath = fi.fileName(); + imagesString += QString(" source: \"%1\"\n").arg(imagePath); + } else { + imagesString += QString(" source: g_propertyData.%1\n").arg(uniform->name()); - // TODO - return QString(); + if (uniform->enableMipmap()) + imagesString += " mipmap: true\n"; + else + QString mipmapProperty = mipmapPropertyName(uniform->name()); + } + + imagesString += " visible: false\n"; + imagesString += " }\n"; + } + } + return imagesString; } QString EffectMakerModel::getQmlComponentString(bool localFiles) @@ -874,8 +1338,6 @@ QString EffectMakerModel::getQmlComponentString(bool localFiles) }; QString customImagesString = getQmlImagesString(localFiles); - QString vertexShaderFilename = "file:///" + m_fragmentShaderFile.fileName(); - QString fragmentShaderFilename = "file:///" + m_vertexShaderFile.fileName(); QString s; QString l1 = localFiles ? QStringLiteral(" ") : QStringLiteral(""); QString l2 = localFiles ? QStringLiteral(" ") : QStringLiteral(" "); @@ -908,15 +1370,17 @@ QString EffectMakerModel::getQmlComponentString(bool localFiles) // When used in preview component, we need property with value // and when in exported component, property with binding to root value. s += localFiles ? m_exportedEffectPropertiesString : m_previewEffectPropertiesString; + if (!customImagesString.isEmpty()) s += '\n' + customImagesString; s += '\n'; - s += l2 + "vertexShader: '" + vertexShaderFilename + "'\n"; - s += l2 + "fragmentShader: '" + fragmentShaderFilename + "'\n"; + s += l2 + "vertexShader: 'file:///" + m_vertexShaderFilename + "'\n"; + s += l2 + "fragmentShader: 'file:///" + m_fragmentShaderFilename + "'\n"; s += l2 + "anchors.fill: parent\n"; if (m_shaderFeatures.enabled(ShaderFeatures::GridMesh)) { - QString gridSize = QString("%1, %2").arg(m_shaderFeatures.gridMeshWidth()).arg(m_shaderFeatures.gridMeshHeight()); + QString gridSize = QString("%1, %2").arg(m_shaderFeatures.gridMeshWidth()) + .arg(m_shaderFeatures.gridMeshHeight()); s += l2 + "mesh: GridMesh {\n"; s += l3 + QString("resolution: Qt.size(%1)\n").arg(gridSize); s += l2 + "}\n"; @@ -925,6 +1389,19 @@ QString EffectMakerModel::getQmlComponentString(bool localFiles) return s; } +QString EffectMakerModel::currentComposition() const +{ + return m_currentComposition; +} + +void EffectMakerModel::setCurrentComposition(const QString &newCurrentComposition) +{ + if (m_currentComposition == newCurrentComposition) + return; + m_currentComposition = newCurrentComposition; + emit currentCompositionChanged(); +} + void EffectMakerModel::updateQmlComponent() { // Clear possible QML runtime errors @@ -932,5 +1409,13 @@ void EffectMakerModel::updateQmlComponent() m_qmlComponentString = getQmlComponentString(false); } -} // namespace EffectMaker +// Removes "file:" from the URL path. +// So e.g. "file:///C:/myimages/steel1.jpg" -> "C:/myimages/steel1.jpg" +QString EffectMakerModel::stripFileFromURL(const QString &urlString) const +{ + QUrl url(urlString); + QString filePath = (url.scheme() == QStringLiteral("file")) ? url.toLocalFile() : url.toString(); + return filePath; +} +} // namespace EffectMaker diff --git a/src/plugins/effectmakernew/effectmakermodel.h b/src/plugins/effectmakernew/effectmakermodel.h index 395a9063c53..96eb0e19b46 100644 --- a/src/plugins/effectmakernew/effectmakermodel.h +++ b/src/plugins/effectmakernew/effectmakermodel.h @@ -5,12 +5,21 @@ #include "shaderfeatures.h" +#include + +#include #include #include #include #include -#include +namespace ProjectExplorer { +class Target; +} + +namespace Utils { +class Process; +} namespace EffectMaker { @@ -37,7 +46,7 @@ class EffectMakerModel : public QAbstractListModel Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged) Q_PROPERTY(bool shadersUpToDate READ shadersUpToDate WRITE setShadersUpToDate NOTIFY shadersUpToDateChanged) Q_PROPERTY(QString qmlComponentString READ qmlComponentString) - + Q_PROPERTY(QString currentComposition READ currentComposition WRITE setCurrentComposition NOTIFY currentCompositionChanged) public: EffectMakerModel(QObject *parent = nullptr); @@ -60,14 +69,27 @@ public: QString fragmentShader() const; void setFragmentShader(const QString &newFragmentShader); + QString vertexShader() const; void setVertexShader(const QString &newVertexShader); const QString &qmlComponentString() const; - void setQmlComponentString(const QString &string); + + void clear(); Q_INVOKABLE void updateQmlComponent(); + Q_INVOKABLE void resetEffectError(int type); + Q_INVOKABLE void setEffectError(const QString &errorMessage, int type = -1, int lineNumber = -1); + + Q_INVOKABLE void exportComposition(const QString &name); + Q_INVOKABLE void exportResources(const QString &name); + + void openComposition(const QString &path); + + QString currentComposition() const; + void setCurrentComposition(const QString &newCurrentComposition); + signals: void isEmptyChanged(); void selectedIndexChanged(int idx); @@ -75,6 +97,8 @@ signals: void shadersUpToDateChanged(); void shadersBaked(); + void currentCompositionChanged(); + private: enum Roles { NameRole = Qt::UserRole + 1, @@ -99,11 +123,8 @@ private: const QString getVSUniforms(); const QString getFSUniforms(); - void updateBakedShaderVersions(); QString detectErrorMessage(const QString &errorMessage); EffectError effectError() const; - void setEffectError(const QString &errorMessage, int type = -1, int lineNumber = -1); - void resetEffectError(int type); QString valueAsString(const Uniform &uniform); QString valueAsBinding(const Uniform &uniform); @@ -121,9 +142,15 @@ private: QString getCustomShaderVaryings(bool outState); QString generateVertexShader(bool includeUniforms = true); QString generateFragmentShader(bool includeUniforms = true); + void handleQsbProcessExit(Utils::Process *qsbProcess, const QString &shader); + QString stripFileFromURL(const QString &urlString) const; + QString getQmlEffectString(); + void updateCustomUniforms(); + void createFiles(); void bakeShaders(); + QString mipmapPropertyName(const QString &name) const; QString getQmlImagesString(bool localFiles); QString getQmlComponentString(bool localFiles); @@ -133,6 +160,7 @@ private: bool m_isEmpty = true; // True when shaders haven't changed since last baking bool m_shadersUpToDate = true; + int m_remainingQsbTargets = 0; QMap m_effectErrors; ShaderFeatures m_shaderFeatures; QStringList m_shaderVaryingVariables; @@ -140,9 +168,13 @@ private: QString m_vertexShader; QStringList m_defaultRootVertexShader; QStringList m_defaultRootFragmentShader; - QShaderBaker m_baker; - QTemporaryFile m_fragmentShaderFile; - QTemporaryFile m_vertexShaderFile; + // Temp files to store shaders sources and binary data + QTemporaryFile m_fragmentSourceFile; + QTemporaryFile m_vertexSourceFile; + QString m_fragmentSourceFilename; + QString m_vertexSourceFilename; + QString m_fragmentShaderFilename; + QString m_vertexShaderFilename; // Used in exported QML, at root of the file QString m_exportedRootPropertiesString; // Used in exported QML, at ShaderEffect component of the file @@ -150,6 +182,8 @@ private: // Used in preview QML, at ShaderEffect component of the file QString m_previewEffectPropertiesString; QString m_qmlComponentString; + bool m_loadComponentImages = true; + QString m_currentComposition; const QRegularExpression m_spaceReg = QRegularExpression("\\s+"); }; diff --git a/src/plugins/effectmakernew/effectmakernodesmodel.cpp b/src/plugins/effectmakernew/effectmakernodesmodel.cpp index 3d3bb9f95d6..dc028aef8e8 100644 --- a/src/plugins/effectmakernew/effectmakernodesmodel.cpp +++ b/src/plugins/effectmakernew/effectmakernodesmodel.cpp @@ -97,6 +97,11 @@ void EffectMakerNodesModel::loadModel() m_categories.push_back(category); } + std::sort(m_categories.begin(), m_categories.end(), + [](EffectNodesCategory *a, EffectNodesCategory *b) { + return a->name() < b->name(); + }); + resetModel(); } diff --git a/src/plugins/effectmakernew/effectmakeruniformsmodel.cpp b/src/plugins/effectmakernew/effectmakeruniformsmodel.cpp index 9313b986404..cfbbf3f5778 100644 --- a/src/plugins/effectmakernew/effectmakeruniformsmodel.cpp +++ b/src/plugins/effectmakernew/effectmakeruniformsmodel.cpp @@ -3,6 +3,7 @@ #include "effectmakeruniformsmodel.h" +#include "propertyhandler.h" #include "uniform.h" #include @@ -48,7 +49,22 @@ bool EffectMakerUniformsModel::setData(const QModelIndex &index, const QVariant if (!index.isValid() || !roleNames().contains(role)) return false; - m_uniforms.at(index.row())->setValue(value); + auto uniform = m_uniforms.at(index.row()); + + if (uniform->type() == Uniform::Type::Sampler) { + QString updatedValue = value.toString(); + int idx = value.toString().indexOf("file:"); + + QString path = idx > 0 ? updatedValue.right(updatedValue.size() - idx - 5) : updatedValue; + updatedValue = QUrl::fromLocalFile(path).toString(); + + uniform->setValue(updatedValue); + g_propertyData.insert(uniform->name(), updatedValue); + } else { + uniform->setValue(value); + g_propertyData.insert(uniform->name(), value); + } + emit dataChanged(index, index, {role}); return true; diff --git a/src/plugins/effectmakernew/effectmakerview.cpp b/src/plugins/effectmakernew/effectmakerview.cpp index 4bb68f358a4..637c12f6d5a 100644 --- a/src/plugins/effectmakernew/effectmakerview.cpp +++ b/src/plugins/effectmakernew/effectmakerview.cpp @@ -3,8 +3,9 @@ #include "effectmakerview.h" -#include "effectmakerwidget.h" +#include "effectmakermodel.h" #include "effectmakernodesmodel.h" +#include "effectmakerwidget.h" #include "nodeinstanceview.h" #include "qmldesignerconstants.h" @@ -58,12 +59,15 @@ QmlDesigner::WidgetInfo EffectMakerView::widgetInfo() QmlDesigner::WidgetInfo::LeftPane, 0, tr("Effect Maker")); } -void EffectMakerView::customNotification(const AbstractView * /*view*/, - const QString & /*identifier*/, - const QList & /*nodeList*/, - const QList & /*data*/) +void EffectMakerView::customNotification([[maybe_unused]] const AbstractView *view, + const QString &identifier, + [[maybe_unused]] const QList &nodeList, + const QList &data) { - // TODO + if (identifier == "open_effectmaker_composition" && data.count() > 0) { + const QString compositionPath = data[0].toString(); + m_widget->effectMakerModel()->openComposition(compositionPath); + } } void EffectMakerView::modelAttached(QmlDesigner::Model *model) @@ -71,6 +75,7 @@ void EffectMakerView::modelAttached(QmlDesigner::Model *model) AbstractView::modelAttached(model); m_widget->effectMakerNodesModel()->loadModel(); + m_widget->effectMakerModel()->clear(); m_widget->initView(); } diff --git a/src/plugins/effectmakernew/effectmakerwidget.cpp b/src/plugins/effectmakernew/effectmakerwidget.cpp index 72d4c1b2cd6..738c0a00168 100644 --- a/src/plugins/effectmakernew/effectmakerwidget.cpp +++ b/src/plugins/effectmakernew/effectmakerwidget.cpp @@ -7,6 +7,9 @@ #include "effectmakermodel.h" #include "effectmakernodesmodel.h" #include "effectmakerview.h" +#include "propertyhandler.h" + +//#include "qmldesigner/designercore/imagecache/midsizeimagecacheprovider.h" #include "qmldesignerconstants.h" #include "qmldesignerplugin.h" #include "qqmlcontext.h" @@ -14,6 +17,8 @@ #include +#include +#include #include #include @@ -63,10 +68,14 @@ EffectMakerWidget::EffectMakerWidget(EffectMakerView *view) QmlDesigner::QmlDesignerPlugin::trackWidgetFocusTime(this, QmlDesigner::Constants::EVENT_EFFECTMAKER_TIME); + m_quickWidget->rootContext()->setContextProperty("g_propertyData", &g_propertyData); + auto map = m_quickWidget->registerPropertyMap("EffectMakerBackend"); map->setProperties({{"effectMakerNodesModel", QVariant::fromValue(m_effectMakerNodesModel.data())}, {"effectMakerModel", QVariant::fromValue(m_effectMakerModel.data())}, {"rootView", QVariant::fromValue(this)}}); + QmlDesigner::QmlDesignerPlugin::trackWidgetFocusTime( + this, QmlDesigner::Constants::EVENT_NEWEFFECTMAKER_TIME); } @@ -130,7 +139,14 @@ void EffectMakerWidget::initView() m_quickWidget->rootContext()->setContextObject(ctxObj); m_backendModelNode.setup(m_effectMakerView->rootModelNode()); + m_quickWidget->rootContext()->setContextProperty("anchorBackend", &m_backendAnchorBinding); m_quickWidget->rootContext()->setContextProperty("modelNodeBackend", &m_backendModelNode); + m_quickWidget->rootContext()->setContextProperty("activeDragSuffix", ""); + + //TODO: Fix crash on macos +// m_quickWidget->engine()->addImageProvider("qmldesigner_thumbnails", +// new QmlDesigner::AssetImageProvider( +// QmlDesigner::QmlDesignerPlugin::imageCache())); // init the first load of the QML UI elements reloadQmlSource(); diff --git a/src/plugins/effectmakernew/effectmakerwidget.h b/src/plugins/effectmakernew/effectmakerwidget.h index 6f55cbc786e..24efac7b586 100644 --- a/src/plugins/effectmakernew/effectmakerwidget.h +++ b/src/plugins/effectmakernew/effectmakerwidget.h @@ -3,6 +3,7 @@ #pragma once +#include "qmldesigner/components/propertyeditor/qmlanchorbindingproxy.h" #include "qmldesigner/components/propertyeditor/qmlmodelnodeproxy.h" #include @@ -54,6 +55,7 @@ private: QPointer m_effectMakerView; QPointer m_quickWidget; QmlDesigner::QmlModelNodeProxy m_backendModelNode; + QmlDesigner::QmlAnchorBindingProxy m_backendAnchorBinding; }; } // namespace EffectMaker diff --git a/src/plugins/effectmakernew/propertyhandler.cpp b/src/plugins/effectmakernew/propertyhandler.cpp new file mode 100644 index 00000000000..9382c9a1f67 --- /dev/null +++ b/src/plugins/effectmakernew/propertyhandler.cpp @@ -0,0 +1,10 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "propertyhandler.h" + +namespace EffectMaker { + +QQmlPropertyMap g_propertyData; + +} diff --git a/src/plugins/effectmakernew/propertyhandler.h b/src/plugins/effectmakernew/propertyhandler.h new file mode 100644 index 00000000000..82e2f7212d0 --- /dev/null +++ b/src/plugins/effectmakernew/propertyhandler.h @@ -0,0 +1,15 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#pragma once + +#include + +namespace EffectMaker { + +// This will be used for binding dynamic properties +// changes between C++ and QML. +extern QQmlPropertyMap g_propertyData; + +} + diff --git a/src/plugins/effectmakernew/uniform.cpp b/src/plugins/effectmakernew/uniform.cpp index 5cad43e0c95..be10cc7f421 100644 --- a/src/plugins/effectmakernew/uniform.cpp +++ b/src/plugins/effectmakernew/uniform.cpp @@ -1,9 +1,12 @@ -// Copyright (C) 2023 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "uniform.h" #include +#include "propertyhandler.h" + +#include #include #include @@ -11,7 +14,8 @@ namespace EffectMaker { -Uniform::Uniform(const QJsonObject &propObj) +Uniform::Uniform(const QString &effectName, const QJsonObject &propObj, const QString &qenPath) + : m_qenPath(qenPath) { QString value, defaultValue, minValue, maxValue; @@ -24,22 +28,29 @@ Uniform::Uniform(const QJsonObject &propObj) if (m_displayName.isEmpty()) m_displayName = m_name; + QString resPath; if (m_type == Type::Sampler) { + resPath = getResourcePath(effectName, defaultValue, qenPath); if (!defaultValue.isEmpty()) - defaultValue = getResourcePath(defaultValue); + defaultValue = resPath; if (propObj.contains("enableMipmap")) m_enableMipmap = getBoolValue(propObj.value("enableMipmap"), false); // Update the mipmap property QString mipmapProperty = mipmapPropertyName(m_name); + g_propertyData[mipmapProperty] = m_enableMipmap; } + if (propObj.contains("value")) { value = propObj.value("value").toString(); - if (m_type == Type::Sampler && !value.isEmpty()) - value = getResourcePath(value); + if (m_type == Type::Sampler) + value = resPath; } else { // QEN files don't store the current value, so with those use default value value = defaultValue; } + m_customValue = propObj.value("customValue").toString(); + m_useCustomValue = getBoolValue(propObj.value("useCustomValue"), false); + minValue = propObj.value("minValue").toString(); maxValue = propObj.value("maxValue").toString(); @@ -159,11 +170,20 @@ bool Uniform::getBoolValue(const QJsonValue &jsonValue, bool defaultValue) // Returns the path for a shader resource // Used with sampler types -QString Uniform::getResourcePath(const QString &value) const +QString Uniform::getResourcePath(const QString &effectName, const QString &value, const QString &qenPath) const { - Q_UNUSED(value) - //TODO - return {}; + QString filePath = value; + if (qenPath.isEmpty()) { + const Utils::FilePath effectsResDir = QmlDesigner::ModelNodeOperations::getEffectsImportDirectory(); + return effectsResDir.pathAppended(effectName).pathAppended(value).toString(); + } else { + QDir dir(m_qenPath); + dir.cdUp(); + QString absPath = dir.absoluteFilePath(filePath); + absPath = QDir::cleanPath(absPath); + absPath = QUrl::fromLocalFile(absPath).toString(); + return absPath; + } } // Validation and setting values @@ -248,7 +268,7 @@ QVariant Uniform::valueStringToVariant(const QString &value) return variant; } -QString Uniform::stringFromType(Uniform::Type type) +QString Uniform::stringFromType(Uniform::Type type, bool isShader) { if (type == Type::Bool) return "bool"; @@ -263,7 +283,7 @@ QString Uniform::stringFromType(Uniform::Type type) else if (type == Type::Vec4) return "vec4"; else if (type == Type::Color) - return "color"; + return isShader ? QString("vec4") : QString("color"); else if (type == Type::Sampler) return "sampler2D"; else if (type == Type::Define) @@ -289,7 +309,7 @@ Uniform::Type Uniform::typeFromString(const QString &typeString) return Uniform::Type::Vec4; else if (typeString == "color") return Uniform::Type::Color; - else if (typeString == "sampler2D") + else if (typeString == "sampler2D" || typeString == "image") //TODO: change image to sample2D in all QENs return Uniform::Type::Sampler; else if (typeString == "define") return Uniform::Type::Define; diff --git a/src/plugins/effectmakernew/uniform.h b/src/plugins/effectmakernew/uniform.h index 7bad706cb3e..f5731af00a8 100644 --- a/src/plugins/effectmakernew/uniform.h +++ b/src/plugins/effectmakernew/uniform.h @@ -14,8 +14,6 @@ QT_FORWARD_DECLARE_CLASS(QVector2D) namespace EffectMaker { - - class Uniform : public QObject { Q_OBJECT @@ -42,7 +40,7 @@ public: Define }; - Uniform(const QJsonObject &props); + Uniform(const QString &effectName, const QJsonObject &props, const QString &qenPath); Type type() const; QString typeName() const; @@ -69,7 +67,7 @@ public: bool enableMipmap() const; - static QString stringFromType(Uniform::Type type); + static QString stringFromType(Uniform::Type type, bool isShader = false); static Uniform::Type typeFromString(const QString &typeString); static QString typeToProperty(Uniform::Type type); @@ -80,13 +78,14 @@ signals: private: QString mipmapPropertyName(const QString &name) const; bool getBoolValue(const QJsonValue &jsonValue, bool defaultValue); - QString getResourcePath(const QString &value) const; + QString getResourcePath(const QString &effectName, const QString &value, const QString &qenPath) const; void setValueData(const QString &value, const QString &defaultValue, const QString &minValue, const QString &maxValue); QVariant getInitializedVariant(bool maxValue); QVariant valueStringToVariant(const QString &value); + QString m_qenPath; Type m_type; QVariant m_value; QVariant m_defaultValue; diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp index def0016f3ab..5a1fcc44668 100644 --- a/src/plugins/languageclient/client.cpp +++ b/src/plugins/languageclient/client.cpp @@ -1661,6 +1661,7 @@ void Client::setLogTarget(LogTarget target) void Client::start() { + d->m_shutdownTimer.stop(); LanguageClientManager::addClient(this); d->m_clientInterface->start(); } diff --git a/src/plugins/languageclient/languageclientsymbolsupport.cpp b/src/plugins/languageclient/languageclientsymbolsupport.cpp index 2631a853996..4f93e537f70 100644 --- a/src/plugins/languageclient/languageclientsymbolsupport.cpp +++ b/src/plugins/languageclient/languageclientsymbolsupport.cpp @@ -691,13 +691,10 @@ void SymbolSupport::handleRenameResponse(Core::SearchResult *search, void SymbolSupport::applyRename(const Utils::SearchResultItems &checkedItems, Core::SearchResult *search) { - QSet affectedNonOpenFilePaths; QMap> editsForDocuments; QList changes; for (const Utils::SearchResultItem &item : checkedItems) { const auto filePath = Utils::FilePath::fromUserInput(item.path().value(0)); - if (!m_client->documentForFilePath(filePath)) - affectedNonOpenFilePaths << filePath; const QJsonObject jsonObject = item.userData().toJsonObject(); if (const TextEdit edit(jsonObject); edit.isValid()) editsForDocuments[filePath] << edit; @@ -715,10 +712,6 @@ void SymbolSupport::applyRename(const Utils::SearchResultItems &checkedItems, for (auto it = editsForDocuments.begin(), end = editsForDocuments.end(); it != end; ++it) applyTextEdits(m_client, it.key(), it.value()); - if (!affectedNonOpenFilePaths.isEmpty()) { - Core::DocumentManager::notifyFilesChangedInternally(Utils::toList(affectedNonOpenFilePaths)); - } - const auto extraWidget = qobject_cast(search->additionalReplaceWidget()); QTC_ASSERT(extraWidget, return); if (!extraWidget->shouldRenameFiles()) diff --git a/src/plugins/mcusupport/mcubuildstep.cpp b/src/plugins/mcusupport/mcubuildstep.cpp index f49c7a5209a..102ee306fef 100644 --- a/src/plugins/mcusupport/mcubuildstep.cpp +++ b/src/plugins/mcusupport/mcubuildstep.cpp @@ -7,6 +7,8 @@ #include "mculegacyconstants.h" #include "mcusupportconstants.h" +#include + #include #include @@ -27,6 +29,7 @@ #include +#include #include #include @@ -89,7 +92,8 @@ DeployMcuProcessStep::DeployMcuProcessStep(ProjectExplorer::BuildStepList *bc, I const FilePath qulIncludeDir = FilePath::fromVariant(kit->value(importPathConstant)); QStringList includeDirs { ProcessArgs::quoteArg(qulIncludeDir.toString()), - ProcessArgs::quoteArg(qulIncludeDir.pathAppended("Timeline").toString()) + ProcessArgs::quoteArg(qulIncludeDir.pathAppended("Timeline").toString()), + ProcessArgs::quoteArg(qulIncludeDir.pathAppended("Shapes").toString()) }; const char *toolChainConstant = Internal::Constants::KIT_MCUTARGET_TOOLCHAIN_KEY; @@ -161,6 +165,27 @@ void MCUBuildStepFactory::updateDeployStep(ProjectExplorer::Target *target, bool return; ProjectExplorer::DeployConfiguration *deployConfiguration = target->activeDeployConfiguration(); + + // Return if the kit is currupted or is an MCU kit (unsupported in Design Studio) + if (!deployConfiguration + || (target->kit() && target->kit()->hasValue(Constants::KIT_MCUTARGET_KITVERSION_KEY))) { + // This branch is called multiple times when selecting the run configuration + // Show the message only once when the kit changes (avoid repitition) + static ProjectExplorer::Kit *previousSelectedKit = nullptr; + if (previousSelectedKit && previousSelectedKit == target->kit()) + return; + previousSelectedKit = target->kit(); + + //TODO use DeployMcuProcessStep::showError instead when the Issues panel + // supports poping up on new entries + QMessageBox::warning( + Core::ICore::dialogParent(), + QmlProjectManager::Tr::tr("The Selected Kit Is Not Supported"), + QmlProjectManager::Tr::tr( + "You cannot use the selected kit to preview Qt for MCUs applications.")); + return; + } + ProjectExplorer::BuildStepList *stepList = deployConfiguration->stepList(); ProjectExplorer::BuildStep *step = stepList->firstStepWithId(DeployMcuProcessStep::id); if (!step && enabled) { diff --git a/src/plugins/mcusupport/mcusupportplugin.cpp b/src/plugins/mcusupport/mcusupportplugin.cpp index 5805f8caeba..20edc2dd5db 100644 --- a/src/plugins/mcusupport/mcusupportplugin.cpp +++ b/src/plugins/mcusupport/mcusupportplugin.cpp @@ -43,6 +43,7 @@ #include #include +#include #include using namespace Core; @@ -51,6 +52,7 @@ using namespace ProjectExplorer; namespace McuSupport::Internal { const char setupMcuSupportKits[] = "SetupMcuSupportKits"; +const char qdsMcuDocInfoEntry[] = "McuDocInfoEntry"; void printMessage(const QString &message, bool important) { @@ -114,6 +116,24 @@ McuSupportPlugin::~McuSupportPlugin() dd = nullptr; } +static bool isQtMCUsProject(ProjectExplorer::Project *p) +{ + if (!Core::ICore::isQtDesignStudio()) + // should be unreachable + printMessage("Testing if the QDS project is an MCU project outside the QDS", true); + + if (!p || !p->rootProjectNode()) + return false; + + ProjectExplorer::Target *target = p->activeTarget(); + if (!target) + return false; + + const bool isMcuProject = target->additionalData("CustomQtForMCUs").toBool(); + + return isMcuProject; +} + void McuSupportPlugin::initialize() { setObjectName("McuSupportPlugin"); @@ -160,6 +180,23 @@ void McuSupportPlugin::initialize() ->action() ->trigger(); }); + } else { + // Only in design studio + connect(ProjectManager::instance(), + &ProjectManager::projectFinishedParsing, + [&](ProjectExplorer::Project *p) { + if (!isQtMCUsProject(p) || !ICore::infoBar()->canInfoBeAdded(qdsMcuDocInfoEntry)) + return; + Utils::InfoBarEntry docInfo(qdsMcuDocInfoEntry, + Tr::tr("Read about Using QtMCUs in the Qt Design Studio"), + Utils::InfoBarEntry::GlobalSuppression::Enabled); + docInfo.addCustomButton(Tr::tr("Go to the Documentation"), [] { + ICore::infoBar()->suppressInfo(qdsMcuDocInfoEntry); + QDesktopServices::openUrl( + QUrl("https://doc.qt.io/qtdesignstudio/studio-on-mcus.html")); + }); + ICore::infoBar()->addInfo(docInfo); + }); } dd->m_options.registerQchFiles(); @@ -270,3 +307,4 @@ void McuSupportPlugin::updateDeployStep(ProjectExplorer::Target *target, bool en } } // namespace McuSupport::Internal + diff --git a/src/plugins/projectexplorer/project.cpp b/src/plugins/projectexplorer/project.cpp index c0e245baf1c..d75f21dd767 100644 --- a/src/plugins/projectexplorer/project.cpp +++ b/src/plugins/projectexplorer/project.cpp @@ -852,6 +852,8 @@ void Project::createTargetFromMap(const Store &map, int index) "kit \"%2\" with id %3, which does not exist anymore. The new kit \"%4\" was " "created in its place, in an attempt not to lose custom project settings.") .arg(displayName(), formerKitName, id.toString(), k->displayName()))); + } else { + return; } auto t = std::make_unique(this, k, Target::_constructor_tag{}); diff --git a/src/plugins/projectexplorer/project.h b/src/plugins/projectexplorer/project.h index 40b26d6faa0..e1bf1eb7b02 100644 --- a/src/plugins/projectexplorer/project.h +++ b/src/plugins/projectexplorer/project.h @@ -49,7 +49,8 @@ public: enum ModelRoles { // Absolute file path FilePathRole = QFileSystemModel::FilePathRole, - isParsingRole + isParsingRole, + UseUnavailableMarkerRole, }; Project(const QString &mimeType, const Utils::FilePath &fileName); diff --git a/src/plugins/projectexplorer/projectmodels.cpp b/src/plugins/projectexplorer/projectmodels.cpp index 0af76f1bbef..f824a19cc91 100644 --- a/src/plugins/projectexplorer/projectmodels.cpp +++ b/src/plugins/projectexplorer/projectmodels.cpp @@ -193,6 +193,7 @@ QVariant FlatModel::data(const QModelIndex &index, int role) const return {}; const FolderNode * const folderNode = node->asFolderNode(); + const FileNode * const fileNode = node->asFileNode(); const ContainerNode * const containerNode = node->asContainerNode(); const Project * const project = containerNode ? containerNode->project() : nullptr; const Target * const target = project ? project->activeTarget() : nullptr; @@ -246,6 +247,8 @@ QVariant FlatModel::data(const QModelIndex &index, int role) const return node->filePath().toString(); case Project::isParsingRole: return project && bs ? bs->isParsing() && !project->needsConfiguration() : false; + case Project::UseUnavailableMarkerRole: + return fileNode ? fileNode->useUnavailableMarker() : false; } return {}; } @@ -923,4 +926,3 @@ const QLoggingCategory &FlatModel::logger() } // namespace Internal } // namespace ProjectExplorer - diff --git a/src/plugins/projectexplorer/projectnodes.cpp b/src/plugins/projectexplorer/projectnodes.cpp index 0ef47153fb9..b03716fd6ff 100644 --- a/src/plugins/projectexplorer/projectnodes.cpp +++ b/src/plugins/projectexplorer/projectnodes.cpp @@ -261,6 +261,16 @@ void FileNode::setHasError(bool error) const m_hasError = error; } +bool FileNode::useUnavailableMarker() const +{ + return m_useUnavailableMarker; +} + +void FileNode::setUseUnavailableMarker(bool useUnavailableMarker) +{ + m_useUnavailableMarker = useUnavailableMarker; +} + /*! Returns \c true if the file is automatically generated by a compile step. */ @@ -382,6 +392,7 @@ FileNode::FileNode(const Utils::FilePath &filePath, const FileType fileType) : m_fileType(fileType) { setFilePath(filePath); + setUseUnavailableMarker(!filePath.needsDevice() && !filePath.exists()); setListInProject(true); if (fileType == FileType::Project) setPriority(DefaultProjectFilePriority); diff --git a/src/plugins/projectexplorer/projectnodes.h b/src/plugins/projectexplorer/projectnodes.h index 5e424b62bbc..9d159aeea35 100644 --- a/src/plugins/projectexplorer/projectnodes.h +++ b/src/plugins/projectexplorer/projectnodes.h @@ -204,10 +204,14 @@ public: QIcon icon() const; void setIcon(const QIcon icon); + bool useUnavailableMarker() const; + void setUseUnavailableMarker(bool useUnavailableMarker); + private: FileType m_fileType; mutable QIcon m_icon; mutable bool m_hasError = false; + bool m_useUnavailableMarker = false; }; // Documentation inside. diff --git a/src/plugins/projectexplorer/projecttreewidget.cpp b/src/plugins/projectexplorer/projecttreewidget.cpp index e850d43e7ea..c63dc87a51f 100644 --- a/src/plugins/projectexplorer/projecttreewidget.cpp +++ b/src/plugins/projectexplorer/projecttreewidget.cpp @@ -72,8 +72,18 @@ public: void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override { - QStyledItemDelegate::paint(painter, option, index); + const bool useUnavailableMarker = index.data(Project::UseUnavailableMarkerRole).toBool(); + if (useUnavailableMarker) { + QStyleOptionViewItem opt = option; + opt.palette.setColor(QPalette::Text, creatorTheme()->color(Theme::TextColorDisabled)); + QStyledItemDelegate::paint(painter, opt, index); + static const QPixmap pixmap + = QApplication::style()->standardIcon(QStyle::SP_BrowserStop).pixmap(10); + painter->drawPixmap(option.rect.topLeft(), pixmap); + return; + } + QStyledItemDelegate::paint(painter, option, index); if (index.data(Project::isParsingRole).toBool()) { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 3c41fffd3c9..8f2b2d22187 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -15,6 +15,20 @@ env_with_default("QDS_USE_PROJECTSTORAGE" ENV_QDS_USE_PROJECTSTORAGE OFF) option(USE_PROJECTSTORAGE "Use ProjectStorage" ${ENV_QDS_USE_PROJECTSTORAGE}) add_feature_info("ProjectStorage" ${USE_PROJECTSTORAGE} "") +env_with_default("QTC_ENABLE_PROJECT_STORAGE_TRACING" ENV_QTC_ENABLE_PROJECT_STORAGE_TRACING OFF) +option(ENABLE_PROJECT_STORAGE_TRACING "Enable sqlite tracing" ${ENV_QTC_ENABLE_PROJECT_STORAGE_TRACING}) +add_feature_info("Sqlite tracing" ${ENABLE_PROJECT_STORAGE_TRACING} "") + +env_with_default("QTC_ENABLE_IMAGE_CACHE_TRACING" ENV_QTC_ENABLE_IMAGE_CACHE_TRACING OFF) +option(ENABLE_IMAGE_CACHE_TRACING "Enable image cache tracing" ${ENV_QTC_ENABLE_IMAGE_CACHE_TRACING}) +add_feature_info("Image cache tracing" ${ENABLE_IMAGE_CACHE_TRACING} "") + +env_with_default("QTC_ENABLE_MODEL_TRACING" ENV_QTC_ENABLE_MODEL_TRACING OFF) +option(ENABLE_MODEL_TRACING "Enable model tracing" ${ENV_QTC_ENABLE_MODEL_TRACING}) +add_feature_info("Model tracing" ${ENABLE_MODEL_TRACING} "") + + + add_qtc_library(QmlDesignerUtils STATIC DEPENDS Qt::Gui Utils Qt::QmlPrivate Core @@ -79,6 +93,16 @@ add_qtc_library(QmlDesignerCore STATIC rewritertransaction.h ) +extend_qtc_library(QmlDesignerCore + CONDITION TARGET Nanotrace + DEPENDS Nanotrace + DEFINES + ENABLE_QMLDESIGNER_TRACING + $<$:ENABLE_PROJECT_STORAGE_TRACING> + $<$:ENABLE_IMAGE_CACHE_TRACING> + $<$:ENABLE_MODEL_TRACING> +) + extend_qtc_library(QmlDesignerCore CONDITION ENABLE_COMPILE_WARNING_AS_ERROR PROPERTIES COMPILE_WARNING_AS_ERROR ON @@ -180,6 +204,12 @@ extend_qtc_library(QmlDesignerCore timestampproviderinterface.h ) +extend_qtc_library(QmlDesignerCore + SOURCES_PREFIX ${CMAKE_CURRENT_LIST_DIR}/designercore/tracing + SOURCES + qmldesignertracing.cpp qmldesignertracing.h +) + extend_qtc_library(QmlDesignerCore SOURCES_PREFIX ${CMAKE_CURRENT_LIST_DIR}/designercore/include SOURCES @@ -188,18 +218,26 @@ extend_qtc_library(QmlDesignerCore bytearraymodifier.h componenttextmodifier.h forwardview.h - itemlibraryinfo.h - metainforeader.h + itemlibraryentry.h model.h nodehints.h plaintexteditmodifier.h nodeinstanceview.h propertyparser.h rewriterview.h - subcomponentmanager.h textmodifier.h ) +extend_qtc_library(QmlDesignerCore + CONDITION NOT USE_PROJECTSTORAGE + SOURCES_PREFIX ${CMAKE_CURRENT_LIST_DIR}/designercore/include + SOURCES + itemlibraryinfo.h + metainforeader.h + subcomponentmanager.h + metainfo.h +) + extend_qtc_library(QmlDesignerCore SOURCES_PROPERTIES SKIP_AUTOGEN ON SOURCES_PREFIX ${CMAKE_CURRENT_LIST_DIR}/designercore/include @@ -229,7 +267,6 @@ extend_qtc_library(QmlDesignerCore invalidreparentingexception.h invalidslideindexexception.h mathutils.h - metainfo.h modelfwd.h modelmerger.h modelnode.h @@ -269,15 +306,23 @@ extend_qtc_library(QmlDesignerCore ${CMAKE_CURRENT_LIST_DIR}/designercore/metainfo SOURCES_PREFIX ${CMAKE_CURRENT_LIST_DIR}/designercore/metainfo DEFINES SHARE_QML_PATH="${CMAKE_CURRENT_SOURCE_DIR}/../../../share/qtcreator/qmldesigner" + SOURCES + itemlibraryentry.cpp + nodehints.cpp + nodemetainfo.cpp +) + +extend_qtc_library(QmlDesignerCore + CONDITION NOT USE_PROJECTSTORAGE + SOURCES_PREFIX ${CMAKE_CURRENT_LIST_DIR}/designercore/metainfo SOURCES itemlibraryinfo.cpp metainfo.cpp metainforeader.cpp - nodehints.cpp - nodemetainfo.cpp subcomponentmanager.cpp ) + extend_qtc_library(QmlDesignerCore PUBLIC_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/designercore/instances SOURCES_PREFIX ${CMAKE_CURRENT_LIST_DIR}/designercore/instances @@ -401,6 +446,7 @@ extend_qtc_library(QmlDesignerCore projectstorageinterface.h projectstoragefwd.h projectstorageinfotypes.h + projectstorageobserver.h projectstoragepathwatcher.h projectstoragepathwatcherinterface.h projectstoragepathwatchernotifierinterface.h @@ -418,6 +464,7 @@ extend_qtc_library(QmlDesignerCore storagecache.h storagecacheentry.h storagecachefwd.h + typeannotationreader.cpp typeannotationreader.h qmldocumentparserinterface.h qmltypesparserinterface.h qmltypesparser.cpp qmltypesparser.h @@ -788,12 +835,14 @@ extend_qtc_plugin(QmlDesigner SOURCES_PREFIX components/collectioneditor SOURCES collectiondetails.cpp collectiondetails.h + collectiondetailsmodel.cpp collectiondetailsmodel.h + collectiondetailssortfiltermodel.cpp collectiondetailssortfiltermodel.h collectioneditorconstants.h + collectioneditorutils.cpp collectioneditorutils.h collectionlistmodel.cpp collectionlistmodel.h collectionsourcemodel.cpp collectionsourcemodel.h collectionview.cpp collectionview.h collectionwidget.cpp collectionwidget.h - singlecollectionmodel.cpp singlecollectionmodel.h ) extend_qtc_plugin(QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp index 48d10a34d20..a141c696978 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp @@ -13,6 +13,9 @@ #include "qmldesignerplugin.h" #include "theme.h" +#include +#include + #include #include @@ -364,9 +367,22 @@ QSet AssetsLibraryWidget::supportedAssetSuffixes(bool complex) return suffixes; } +bool isEffectMakerActivated() +{ + const QVector specs = ExtensionSystem::PluginManager::plugins(); + return std::find_if(specs.begin(), specs.end(), + [](ExtensionSystem::PluginSpec *spec) { + return spec->name() == "EffectMakerNew" && spec->isEffectivelyEnabled(); + }) + != specs.end(); +} + void AssetsLibraryWidget::openEffectMaker(const QString &filePath) { - ModelNodeOperations::openEffectMaker(filePath); + if (isEffectMakerActivated()) + m_assetsView->emitCustomNotification("open_effectmaker_composition", {}, {filePath}); + else + ModelNodeOperations::openEffectMaker(filePath); } QString AssetsLibraryWidget::qmlSourcesPath() diff --git a/src/plugins/qmldesigner/components/bindingeditor/actioneditor.cpp b/src/plugins/qmldesigner/components/bindingeditor/actioneditor.cpp index 8480115a3b7..256bfac1e9f 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/actioneditor.cpp +++ b/src/plugins/qmldesigner/components/bindingeditor/actioneditor.cpp @@ -8,7 +8,6 @@ #include #include -#include #include #include #include diff --git a/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.cpp b/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.cpp index 76f5cc7e211..b605a77191b 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.cpp +++ b/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.cpp @@ -9,7 +9,6 @@ #include #include -#include #include #include #include diff --git a/src/plugins/qmldesigner/components/bindingeditor/signallist.cpp b/src/plugins/qmldesigner/components/bindingeditor/signallist.cpp index 82c37aa3c41..fa175123c79 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/signallist.cpp +++ b/src/plugins/qmldesigner/components/bindingeditor/signallist.cpp @@ -8,8 +8,6 @@ #include #include -#include - #include #include #include diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.cpp index 4b8a297140f..cd8296ca2de 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.cpp @@ -3,27 +3,83 @@ #include "collectiondetails.h" +#include +#include + +#include #include +#include #include namespace QmlDesigner { +struct CollectionProperty +{ + using DataType = CollectionDetails::DataType; + + QString name; + DataType type; +}; + class CollectionDetails::Private { using SourceFormat = CollectionEditor::SourceFormat; public: - QStringList headers; + QList properties; QList elements; SourceFormat sourceFormat = SourceFormat::Unknown; CollectionReference reference; bool isChanged = false; - bool isValidColumnId(int column) const { return column > -1 && column < headers.size(); } + bool isValidColumnId(int column) const { return column > -1 && column < properties.size(); } bool isValidRowId(int row) const { return row > -1 && row < elements.size(); } }; +static CollectionProperty::DataType collectionDataTypeFromJsonValue(const QJsonValue &value) +{ + using DataType = CollectionDetails::DataType; + using JsonType = QJsonValue::Type; + + switch (value.type()) { + case JsonType::Null: + case JsonType::Undefined: + return DataType::Unknown; + case JsonType::Bool: + return DataType::Boolean; + case JsonType::Double: + return DataType::Number; + case JsonType::String: { + // TODO: Image, Color, Url + return DataType::String; + } break; + default: + return DataType::Unknown; + } +} + +static QVariant valueToVariant(const QJsonValue &value, CollectionDetails::DataType type) +{ + using DataType = CollectionDetails::DataType; + QVariant variantValue = value.toVariant(); + + switch (type) { + case DataType::String: + return variantValue.toString(); + case DataType::Number: + return variantValue.toDouble(); + case DataType::Boolean: + return variantValue.toBool(); + case DataType::Color: + return variantValue.value(); + case DataType::Url: + return variantValue.value(); + default: + return variantValue; + } +} + CollectionDetails::CollectionDetails() : d(new Private()) {} @@ -38,54 +94,67 @@ CollectionDetails::CollectionDetails(const CollectionDetails &other) = default; CollectionDetails::~CollectionDetails() = default; -void CollectionDetails::resetDetails(const QStringList &headers, +void CollectionDetails::resetDetails(const QStringList &propertyNames, const QList &elements, CollectionEditor::SourceFormat format) { if (!isValid()) return; - d->headers = headers; + d->properties = Utils::transform(propertyNames, [](const QString &name) -> CollectionProperty { + return {name, DataType::Unknown}; + }); + d->elements = elements; d->sourceFormat = format; + resetPropertyTypes(); markSaved(); } -void CollectionDetails::insertHeader(const QString &header, int place, const QVariant &defaultValue) +void CollectionDetails::insertColumn(const QString &propertyName, + int colIdx, + const QVariant &defaultValue, + DataType type) { if (!isValid()) return; - if (d->headers.contains(header)) + if (containsPropertyName(propertyName)) return; - if (d->isValidColumnId(place)) - d->headers.insert(place, header); + CollectionProperty property = {propertyName, type}; + if (d->isValidColumnId(colIdx)) + d->properties.insert(colIdx, property); else - d->headers.append(header); + d->properties.append(property); QJsonValue defaultJsonValue = QJsonValue::fromVariant(defaultValue); for (QJsonObject &element : d->elements) - element.insert(header, defaultJsonValue); + element.insert(propertyName, defaultJsonValue); markChanged(); } -void CollectionDetails::removeHeader(int place) +bool CollectionDetails::removeColumns(int colIdx, int count) { - if (!isValid()) - return; + if (count < 1 || !isValid() || !d->isValidColumnId(colIdx)) + return false; - if (!d->isValidColumnId(place)) - return; + int maxCount = d->properties.count() - colIdx; + count = std::min(maxCount, count); - const QString header = d->headers.takeAt(place); + const QList removedProperties = d->properties.mid(colIdx, count); + d->properties.remove(colIdx, count); - for (QJsonObject &element : d->elements) - element.remove(header); + for (const CollectionProperty &property : removedProperties) { + for (QJsonObject &element : d->elements) + element.remove(property.name); + } markChanged(); + + return true; } void CollectionDetails::insertElementAt(std::optional object, int row) @@ -104,14 +173,119 @@ void CollectionDetails::insertElementAt(std::optional object, int r insertJson(object.value()); } else { QJsonObject defaultObject; - for (const QString &header : std::as_const(d->headers)) - defaultObject.insert(header, {}); + for (const CollectionProperty &property : std::as_const(d->properties)) + defaultObject.insert(property.name, {}); insertJson(defaultObject); } markChanged(); } +void CollectionDetails::insertEmptyElements(int row, int count) +{ + if (!isValid()) + return; + + if (count < 1) + return; + + row = qBound(0, row, rows()); + d->elements.insert(row, count, {}); + + markChanged(); +} + +bool CollectionDetails::removeElements(int row, int count) +{ + if (count < 1 || !isValid() || !d->isValidRowId(row)) + return false; + + int maxCount = d->elements.count() - row; + count = std::min(maxCount, count); + + QSet removedProperties; + Utils::span elementsSpan{std::as_const(d->elements)}; + for (const QJsonObject &element : elementsSpan.subspan(row, count)) { + const QStringList elementPropertyNames = element.keys(); + for (const QString &removedProperty : elementPropertyNames) + removedProperties.insert(removedProperty); + } + + d->elements.remove(row, count); + + for (const QString &removedProperty : removedProperties) + resetPropertyType(removedProperty); + + markChanged(); + + return true; +} + +bool CollectionDetails::setPropertyValue(int row, int column, const QVariant &value) +{ + if (!d->isValidRowId(row) || !d->isValidColumnId(column)) + return false; + + QJsonObject &element = d->elements[row]; + QVariant currentValue = data(row, column); + + if (value == currentValue) + return false; + + element.insert(d->properties.at(column).name, QJsonValue::fromVariant(value)); + return true; +} + +bool CollectionDetails::setPropertyName(int column, const QString &value) +{ + if (!d->isValidColumnId(column)) + return false; + + const CollectionProperty &oldProperty = d->properties.at(column); + const QString oldColumnName = oldProperty.name; + if (oldColumnName == value) + return false; + + d->properties.replace(column, {value, oldProperty.type}); + for (QJsonObject &element : d->elements) { + if (element.contains(oldColumnName)) { + element.insert(value, element.value(oldColumnName)); + element.remove(oldColumnName); + } + } + + markChanged(); + return true; +} + +bool CollectionDetails::forcePropertyType(int column, DataType type, bool force) +{ + if (!isValid() || !d->isValidColumnId(column)) + return false; + + bool changed = false; + CollectionProperty &property = d->properties[column]; + if (property.type != type) + changed = true; + + property.type = type; + + if (force) { + for (QJsonObject &element : d->elements) { + if (element.contains(property.name)) { + QJsonValue value = element.value(property.name); + element.insert(property.name, valueToVariant(value, type).toJsonValue()); + changed = true; + } + } + } + + if (changed) + markChanged(); + + return changed; +} + CollectionReference CollectionDetails::reference() const { return d->reference; @@ -133,7 +307,7 @@ QVariant CollectionDetails::data(int row, int column) const if (!d->isValidColumnId(column)) return {}; - const QString &propertyName = d->headers.at(column); + const QString &propertyName = d->properties.at(column).name; const QJsonObject &elementNode = d->elements.at(row); if (elementNode.contains(propertyName)) @@ -142,12 +316,44 @@ QVariant CollectionDetails::data(int row, int column) const return {}; } -QString CollectionDetails::headerAt(int column) const +QString CollectionDetails::propertyAt(int column) const { if (!d->isValidColumnId(column)) return {}; - return d->headers.at(column); + return d->properties.at(column).name; +} + +CollectionDetails::DataType CollectionDetails::typeAt(int column) const +{ + if (!d->isValidColumnId(column)) + return {}; + + return d->properties.at(column).type; +} + +CollectionDetails::DataType CollectionDetails::typeAt(int row, int column) const +{ + if (!d->isValidRowId(row) || !d->isValidColumnId(column)) + return {}; + + const QString &propertyName = d->properties.at(column).name; + const QJsonObject &element = d->elements.at(row); + + if (element.contains(propertyName)) + return collectionDataTypeFromJsonValue(element.value(propertyName)); + + return {}; +} + +bool CollectionDetails::containsPropertyName(const QString &propertyName) +{ + if (!isValid()) + return false; + + return Utils::anyOf(d->properties, [&propertyName](const CollectionProperty &property) { + return property.name == propertyName; + }); } bool CollectionDetails::isValid() const @@ -162,7 +368,7 @@ bool CollectionDetails::isChanged() const int CollectionDetails::columns() const { - return d->headers.size(); + return d->properties.size(); } int CollectionDetails::rows() const @@ -184,6 +390,13 @@ void CollectionDetails::swap(CollectionDetails &other) d.swap(other.d); } +void CollectionDetails::registerDeclarativeType() +{ + typedef CollectionDetails::DataType DataType; + qRegisterMetaType("DataType"); + qmlRegisterUncreatableType("CollectionDetails", 1, 0, "DataType", "Enum type"); +} + CollectionDetails &CollectionDetails::operator=(const CollectionDetails &other) { CollectionDetails value(other); @@ -196,4 +409,68 @@ void CollectionDetails::markChanged() d->isChanged = true; } +void CollectionDetails::resetPropertyType(const QString &propertyName) +{ + for (CollectionProperty &property : d->properties) { + if (property.name == propertyName) + resetPropertyType(property); + } +} + +void CollectionDetails::resetPropertyType(CollectionProperty &property) +{ + const QString &propertyName = property.name; + DataType type = DataType::Unknown; + for (const QJsonObject &element : std::as_const(d->elements)) { + if (element.contains(propertyName)) { + type = collectionDataTypeFromJsonValue(element.value(propertyName)); + if (type != DataType::Unknown) + break; + } + } + + property.type = type; +} + +void CollectionDetails::resetPropertyTypes() +{ + for (CollectionProperty &property : d->properties) + resetPropertyType(property); +} + +QJsonArray CollectionDetails::getJsonCollection() const +{ + QJsonArray collectionArray; + for (const QJsonObject &element : std::as_const(d->elements)) + collectionArray.push_back(element); + + return collectionArray; +} + +QString CollectionDetails::getCsvCollection() const +{ + QString content; + if (d->properties.count() <= 0) + return ""; + + for (const CollectionProperty &property : std::as_const(d->properties)) + content += property.name + ','; + + content.back() = '\n'; + + for (const QJsonObject &elementsRow : std::as_const(d->elements)) { + for (const CollectionProperty &property : std::as_const(d->properties)) { + const QJsonValue &value = elementsRow.value(property.name); + + if (value.isDouble()) + content += QString::number(value.toDouble()) + ','; + else + content += value.toString() + ','; + } + content.back() = '\n'; + } + + return content; +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.h b/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.h index c3ec59f5d8f..33e5552884f 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.h @@ -33,26 +33,45 @@ struct CollectionReference bool operator!=(const CollectionReference &other) const { return !(*this == other); } }; +struct CollectionProperty; + class CollectionDetails { + Q_GADGET + public: + enum class DataType { Unknown, String, Url, Number, Boolean, Image, Color }; + Q_ENUM(DataType) + explicit CollectionDetails(); CollectionDetails(const CollectionReference &reference); CollectionDetails(const CollectionDetails &other); ~CollectionDetails(); - void resetDetails(const QStringList &headers, + void resetDetails(const QStringList &propertyNames, const QList &elements, CollectionEditor::SourceFormat format); - void insertHeader(const QString &header, int place = -1, const QVariant &defaultValue = {}); - void removeHeader(int place); + void insertColumn(const QString &propertyName, + int colIdx = -1, + const QVariant &defaultValue = {}, + DataType type = DataType::Unknown); + bool removeColumns(int colIdx, int count = 1); void insertElementAt(std::optional object, int row = -1); + void insertEmptyElements(int row = 0, int count = 1); + bool removeElements(int row, int count = 1); + bool setPropertyValue(int row, int column, const QVariant &value); + + bool setPropertyName(int column, const QString &value); + bool forcePropertyType(int column, DataType type, bool force = false); CollectionReference reference() const; CollectionEditor::SourceFormat sourceFormat() const; QVariant data(int row, int column) const; - QString headerAt(int column) const; + QString propertyAt(int column) const; + DataType typeAt(int column) const; + DataType typeAt(int row, int column) const; + bool containsPropertyName(const QString &propertyName); bool isValid() const; bool isChanged() const; @@ -63,14 +82,21 @@ public: bool markSaved(); void swap(CollectionDetails &other); + QJsonArray getJsonCollection() const; + QString getCsvCollection() const; + + static void registerDeclarativeType(); + CollectionDetails &operator=(const CollectionDetails &other); private: void markChanged(); + void resetPropertyType(const QString &propertyName); + void resetPropertyType(CollectionProperty &property); + void resetPropertyTypes(); // The private data is supposed to be shared between the copies class Private; QSharedPointer d; }; - } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.cpp new file mode 100644 index 00000000000..2374726f92c --- /dev/null +++ b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.cpp @@ -0,0 +1,638 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "collectiondetailsmodel.h" + +#include "collectioneditorconstants.h" +#include "modelnode.h" +#include "variantproperty.h" + +#include + +#include +#include +#include +#include + +namespace { + +QStringList getJsonHeaders(const QJsonArray &collectionArray) +{ + QSet result; + for (const QJsonValue &value : collectionArray) { + if (value.isObject()) { + const QJsonObject object = value.toObject(); + const QStringList headers = object.toVariantMap().keys(); + for (const QString &header : headers) + result.insert(header); + } + } + + return result.values(); +} + +class CollectionDataTypeHelper +{ +public: + using DataType = QmlDesigner::CollectionDetails::DataType; + + static QString typeToString(DataType dataType) + { + static const QHash typeStringHash = typeToStringHash(); + return typeStringHash.value(dataType); + } + + static DataType typeFromString(const QString &dataType) + { + static const QHash stringTypeHash = stringToTypeHash(); + return stringTypeHash.value(dataType, DataType::Unknown); + } + + static QStringList typesStringList() + { + static const QStringList typesList = typeToStringHash().values(); + return typesList; + } + +private: + CollectionDataTypeHelper() = delete; + + static QHash typeToStringHash() + { + return { + {DataType::Unknown, "Unknown"}, + {DataType::String, "String"}, + {DataType::Url, "Url"}, + {DataType::Number, "Number"}, + {DataType::Boolean, "Boolean"}, + {DataType::Image, "Image"}, + {DataType::Color, "Color"}, + }; + } + + static QHash stringToTypeHash() + { + QHash stringTypeHash; + const QHash typeStringHash = typeToStringHash(); + for (const auto &transferItem : typeStringHash.asKeyValueRange()) + stringTypeHash.insert(transferItem.second, transferItem.first); + + return stringTypeHash; + } +}; + +} // namespace + +namespace QmlDesigner { + +CollectionDetailsModel::CollectionDetailsModel(QObject *parent) + : QAbstractTableModel(parent) +{ + connect(this, &CollectionDetailsModel::modelReset, this, &CollectionDetailsModel::updateEmpty); + connect(this, &CollectionDetailsModel::rowsInserted, this, &CollectionDetailsModel::updateEmpty); + connect(this, &CollectionDetailsModel::rowsRemoved, this, &CollectionDetailsModel::updateEmpty); +} + +QHash CollectionDetailsModel::roleNames() const +{ + static QHash roles; + if (roles.isEmpty()) { + roles.insert(QAbstractTableModel::roleNames()); + roles.insert(SelectedRole, "itemSelected"); + roles.insert(DataTypeRole, "dataType"); + roles.insert(ColumnDataTypeRole, "columnType"); + } + return roles; +} + +int CollectionDetailsModel::rowCount([[maybe_unused]] const QModelIndex &parent) const +{ + return m_currentCollection.rows(); +} + +int CollectionDetailsModel::columnCount([[maybe_unused]] const QModelIndex &parent) const +{ + return m_currentCollection.columns(); +} + +QVariant CollectionDetailsModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return {}; + + if (role == SelectedRole) + return (index.column() == m_selectedColumn || index.row() == m_selectedRow); + + if (role == DataTypeRole) + return QVariant::fromValue(m_currentCollection.typeAt(index.row(), index.column())); + + if (role == ColumnDataTypeRole) + return QVariant::fromValue(m_currentCollection.typeAt(index.column())); + + if (role == Qt::EditRole) + return m_currentCollection.data(index.row(), index.column()); + + return m_currentCollection.data(index.row(), index.column()).toString(); +} + +bool CollectionDetailsModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid()) + return {}; + + if (role == Qt::EditRole) { + bool changed = m_currentCollection.setPropertyValue(index.row(), index.column(), value); + if (changed) { + emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole}); + return true; + } + } + + return false; +} + +bool CollectionDetailsModel::setHeaderData(int section, + Qt::Orientation orientation, + const QVariant &value, + [[maybe_unused]] int role) +{ + if (orientation == Qt::Vertical) + return false; + + bool headerChanged = m_currentCollection.setPropertyName(section, value.toString()); + if (headerChanged) + emit this->headerDataChanged(orientation, section, section); + + return headerChanged; +} + +bool CollectionDetailsModel::insertRows(int row, int count, const QModelIndex &parent) +{ + if (count < 1) + return false; + + row = qBound(0, row, rowCount()); + beginInsertRows(parent, row, row + count); + m_currentCollection.insertEmptyElements(row, count); + endInsertRows(); + + selectRow(row); + return true; +} + +bool CollectionDetailsModel::removeColumns(int column, int count, const QModelIndex &parent) +{ + if (column < 0 || column >= columnCount(parent) || count < 1) + return false; + + count = std::min(count, columnCount(parent) - column); + beginRemoveColumns(parent, column, column + count - 1); + bool columnsRemoved = m_currentCollection.removeColumns(column, count); + endRemoveColumns(); + + int nextColumn = column - 1; + if (nextColumn < 0 && columnCount(parent) > 0) + nextColumn = 0; + + selectColumn(nextColumn); + return columnsRemoved; +} + +bool CollectionDetailsModel::removeRows(int row, int count, const QModelIndex &parent) +{ + if (row < 0 || row >= rowCount(parent) || count < 1) + return false; + + count = std::min(count, rowCount(parent) - row); + beginRemoveRows(parent, row, row + count - 1); + bool rowsRemoved = m_currentCollection.removeElements(row, count); + endRemoveRows(); + + return rowsRemoved; +} + +Qt::ItemFlags CollectionDetailsModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return {}; + + return {Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable}; +} + +QVariant CollectionDetailsModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal) { + if (role == DataTypeRole) { + return CollectionDataTypeHelper::typeToString(m_currentCollection.typeAt(section)); + } else if (role == Qt::DisplayRole) { + return QString("%1 <%2>").arg(m_currentCollection.propertyAt(section), + CollectionDataTypeHelper::typeToString( + m_currentCollection.typeAt(section))); + } else { + return m_currentCollection.propertyAt(section); + } + } + + if (orientation == Qt::Vertical) + return section + 1; + + return {}; +} + +CollectionDetails::DataType CollectionDetailsModel::propertyDataType(int column) const +{ + return m_currentCollection.typeAt(column); +} + +int CollectionDetailsModel::selectedColumn() const +{ + return m_selectedColumn; +} + +int CollectionDetailsModel::selectedRow() const +{ + return m_selectedRow; +} + +QString CollectionDetailsModel::propertyName(int column) const +{ + return m_currentCollection.propertyAt(column); +} + +QString CollectionDetailsModel::propertyType(int column) const +{ + return CollectionDataTypeHelper::typeToString(m_currentCollection.typeAt(column)); +} + +bool CollectionDetailsModel::isPropertyAvailable(const QString &name) +{ + return m_currentCollection.containsPropertyName(name); +} + +bool CollectionDetailsModel::addColumn(int column, const QString &name, const QString &propertyType) +{ + if (m_currentCollection.containsPropertyName(name)) + return false; + + if (column < 0 || column > columnCount()) + column = columnCount(); + + beginInsertColumns({}, column, column); + m_currentCollection.insertColumn(name, + column, + {}, + CollectionDataTypeHelper::typeFromString(propertyType)); + endInsertColumns(); + return m_currentCollection.containsPropertyName(name); +} + +bool CollectionDetailsModel::selectColumn(int section) +{ + if (m_selectedColumn == section) + return false; + + const int columns = columnCount(); + + if (m_selectedColumn >= columns) + return false; + + selectRow(-1); + + const int rows = rowCount(); + const int previousColumn = m_selectedColumn; + + m_selectedColumn = section; + emit this->selectedColumnChanged(m_selectedColumn); + + auto notifySelectedDataChanged = [this, columns, rows](int notifyingColumn) { + if (notifyingColumn > -1 && notifyingColumn < columns && rows) { + emit dataChanged(index(0, notifyingColumn), + index(rows - 1, notifyingColumn), + {SelectedRole}); + } + }; + + notifySelectedDataChanged(previousColumn); + notifySelectedDataChanged(m_selectedColumn); + + return true; +} + +bool CollectionDetailsModel::renameColumn(int section, const QString &newValue) +{ + return setHeaderData(section, Qt::Horizontal, newValue); +} + +bool CollectionDetailsModel::setPropertyType(int column, const QString &newValue, bool force) +{ + bool changed = m_currentCollection.forcePropertyType(column, + CollectionDataTypeHelper::typeFromString( + newValue), + force); + if (changed) { + emit headerDataChanged(Qt::Horizontal, column, column); + + if (force) { + emit dataChanged(index(0, column), + index(rowCount() - 1, column), + {Qt::DisplayRole, DataTypeRole}); + } + } + + return changed; +} + +bool CollectionDetailsModel::selectRow(int row) +{ + if (m_selectedRow == row) + return false; + + const int rows = rowCount(); + + if (m_selectedRow >= rows) + return false; + + selectColumn(-1); + + const int columns = columnCount(); + const int previousRow = m_selectedRow; + + m_selectedRow = row; + emit this->selectedRowChanged(m_selectedRow); + + auto notifySelectedDataChanged = [this, rows, columns](int notifyingRow) { + if (notifyingRow > -1 && notifyingRow < rows && columns) + emit dataChanged(index(notifyingRow, 0), index(notifyingRow, columns - 1), {SelectedRole}); + }; + + notifySelectedDataChanged(previousRow); + notifySelectedDataChanged(m_selectedRow); + + return true; +} + +void CollectionDetailsModel::deselectAll() +{ + selectColumn(-1); + selectRow(-1); +} + +QStringList CollectionDetailsModel::typesList() +{ + return CollectionDataTypeHelper::typesStringList(); +} + +void CollectionDetailsModel::loadCollection(const ModelNode &sourceNode, const QString &collection) +{ + QString fileName = sourceNode.variantProperty(CollectionEditor::SOURCEFILE_PROPERTY).value().toString(); + + CollectionReference newReference{sourceNode, collection}; + bool alreadyOpen = m_openedCollections.contains(newReference); + + if (alreadyOpen) { + if (m_currentCollection.reference() != newReference) { + deselectAll(); + beginResetModel(); + switchToCollection(newReference); + endResetModel(); + } + } else { + deselectAll(); + switchToCollection(newReference); + if (sourceNode.type() == CollectionEditor::JSONCOLLECTIONMODEL_TYPENAME) + loadJsonCollection(fileName, collection); + else if (sourceNode.type() == CollectionEditor::CSVCOLLECTIONMODEL_TYPENAME) + loadCsvCollection(fileName, collection); + } +} + +bool CollectionDetailsModel::exportCollection(const QString &path, const QString &collectionName, const QString &exportType) +{ + QUrl url(path); + QString fileAddress = url.isLocalFile() ? url.toLocalFile() : path; + + if (exportType == "JSON") { + QJsonArray content = m_currentCollection.getJsonCollection(); + return saveCollectionAsJson(fileAddress, content, collectionName); + } else if (exportType == "CSV") { + QString content = m_currentCollection.getCsvCollection(); + return saveCollectionAsCsv(fileAddress, content); + } + + return false; +} + +void CollectionDetailsModel::updateEmpty() +{ + bool isEmptyNow = rowCount() == 0; + if (m_isEmpty != isEmptyNow) { + m_isEmpty = isEmptyNow; + emit isEmptyChanged(m_isEmpty); + } +} + +void CollectionDetailsModel::switchToCollection(const CollectionReference &collection) +{ + if (m_currentCollection.reference() == collection) + return; + + closeCurrentCollectionIfSaved(); + + if (!m_openedCollections.contains(collection)) + m_openedCollections.insert(collection, CollectionDetails(collection)); + + m_currentCollection = m_openedCollections.value(collection); + + setCollectionName(collection.name); +} + +void CollectionDetailsModel::closeCollectionIfSaved(const CollectionReference &collection) +{ + if (!m_openedCollections.contains(collection)) + return; + + const CollectionDetails &collectionDetails = m_openedCollections.value(collection); + + if (!collectionDetails.isChanged()) + m_openedCollections.remove(collection); + + m_currentCollection = CollectionDetails{}; +} + +void CollectionDetailsModel::closeCurrentCollectionIfSaved() +{ + if (m_currentCollection.isValid()) + closeCollectionIfSaved(m_currentCollection.reference()); +} + +void CollectionDetailsModel::loadJsonCollection(const QString &source, const QString &collection) +{ + using CollectionEditor::SourceFormat; + + QFile sourceFile(source); + QJsonArray collectionNodes; + bool jsonFileIsOk = false; + if (sourceFile.open(QFile::ReadOnly)) { + QJsonParseError jpe; + QJsonDocument document = QJsonDocument::fromJson(sourceFile.readAll(), &jpe); + if (jpe.error == QJsonParseError::NoError) { + jsonFileIsOk = true; + if (document.isObject()) { + QJsonObject collectionMap = document.object(); + if (collectionMap.contains(collection)) { + QJsonValue collectionVal = collectionMap.value(collection); + if (collectionVal.isArray()) + collectionNodes = collectionVal.toArray(); + else + collectionNodes.append(collectionVal); + } + } + } + } + + if (collectionNodes.isEmpty()) { + endResetModel(); + return; + }; + + QList elements; + for (const QJsonValue &value : std::as_const(collectionNodes)) { + if (value.isObject()) { + QJsonObject object = value.toObject(); + elements.append(object); + } + } + + SourceFormat sourceFormat = jsonFileIsOk ? SourceFormat::Json : SourceFormat::Unknown; + beginResetModel(); + m_currentCollection.resetDetails(getJsonHeaders(collectionNodes), elements, sourceFormat); + endResetModel(); +} + +void CollectionDetailsModel::loadCsvCollection(const QString &source, + [[maybe_unused]] const QString &collectionName) +{ + using CollectionEditor::SourceFormat; + + QFile sourceFile(source); + QStringList headers; + QList elements; + bool csvFileIsOk = false; + + if (sourceFile.open(QFile::ReadOnly)) { + QTextStream stream(&sourceFile); + + if (!stream.atEnd()) + headers = stream.readLine().split(','); + + if (!headers.isEmpty()) { + while (!stream.atEnd()) { + const QStringList recordDataList = stream.readLine().split(','); + int column = -1; + QJsonObject recordData; + for (const QString &cellData : recordDataList) { + if (++column == headers.size()) + break; + recordData.insert(headers.at(column), cellData); + } + if (recordData.count()) + elements.append(recordData); + } + csvFileIsOk = true; + } + } + + for (const QString &header : std::as_const(headers)) { + for (QJsonObject &element: elements) { + QVariant variantValue; + if (element.contains(header)) { + variantValue = variantFromString(element.value(header).toString()); + element[header] = variantValue.toJsonValue(); + + if (variantValue.isValid()) + break; + } + } + } + + SourceFormat sourceFormat = csvFileIsOk ? SourceFormat::Csv : SourceFormat::Unknown; + beginResetModel(); + m_currentCollection.resetDetails(headers, elements, sourceFormat); + endResetModel(); +} + +QVariant CollectionDetailsModel::variantFromString(const QString &value) +{ + constexpr QStringView typesPattern{u"(?^(?:true|false)$)|" + u"(?^(?:-?(?:0|[1-9]\\d*)?(?:\\.\\d*)?(?<=\\d|\\.)" + u"(?:e-?(?:0|[1-9]\\d*))?|0x[0-9a-f]+)$)|" + u"(?^(?:#(?:(?:[0-9a-fA-F]{2}){3,4}|" + u"(?:[0-9a-fA-F]){3,4}))$)|" + u"(?[A-Za-z][A-Za-z0-9_ -]*)"}; + static QRegularExpression validator(typesPattern.toString()); + const QString trimmedValue = value.trimmed(); + QRegularExpressionMatch match = validator.match(trimmedValue); + QVariant variantValue = value; + + if (value.isEmpty()) + return QVariant(); + if (!match.captured(u"boolean").isEmpty()) + return variantValue.toBool(); + if (!match.captured(u"number").isEmpty()) + return variantValue.toDouble(); + if (!match.captured(u"color").isEmpty()) + return variantValue.value(); + if (!match.captured(u"string").isEmpty()) + return variantValue.toString(); + + return QVariant::fromValue(value); +} + +void CollectionDetailsModel::setCollectionName(const QString &newCollectionName) +{ + if (m_collectionName != newCollectionName) { + m_collectionName = newCollectionName; + emit this->collectionNameChanged(m_collectionName); + } +} + +bool CollectionDetailsModel::saveCollectionAsJson(const QString &path, const QJsonArray &content, const QString &collectionName) +{ + QFile sourceFile(path); + QJsonDocument document; + + if (sourceFile.exists() && sourceFile.open(QFile::ReadWrite)) { + QJsonParseError jpe; + document = QJsonDocument::fromJson(sourceFile.readAll(), &jpe); + + if (jpe.error == QJsonParseError::NoError) { + QJsonObject collectionMap = document.object(); + collectionMap[collectionName] = content; + document.setObject(collectionMap); + } + + sourceFile.resize(0); + + } else if (sourceFile.open(QFile::WriteOnly)) { + QJsonObject collection; + collection[collectionName] = content; + document.setObject(collection); + } + + if (sourceFile.write(document.toJson())) + return true; + + return false; +} + +bool CollectionDetailsModel::saveCollectionAsCsv(const QString &path, const QString &content) +{ + QFile file(path); + + if (file.open(QFile::WriteOnly) && file.write(content.toUtf8())) + return true; + + return false; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.h b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.h new file mode 100644 index 00000000000..38c59d8fe92 --- /dev/null +++ b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.h @@ -0,0 +1,97 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "collectiondetails.h" + +#include +#include + +namespace QmlDesigner { + +class ModelNode; + +class CollectionDetailsModel : public QAbstractTableModel +{ + Q_OBJECT + + Q_PROPERTY(QString collectionName MEMBER m_collectionName NOTIFY collectionNameChanged) + Q_PROPERTY(int selectedColumn READ selectedColumn WRITE selectColumn NOTIFY selectedColumnChanged) + Q_PROPERTY(int selectedRow READ selectedRow WRITE selectRow NOTIFY selectedRowChanged) + Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) + +public: + enum DataRoles { SelectedRole = Qt::UserRole + 1, DataTypeRole, ColumnDataTypeRole }; + + explicit CollectionDetailsModel(QObject *parent = nullptr); + + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = {}) const override; + int columnCount(const QModelIndex &parent = {}) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + bool setHeaderData(int section, + Qt::Orientation orientation, + const QVariant &value, + int role = Qt::EditRole) override; + bool insertRows(int row, int count, const QModelIndex &parent = {}) override; + bool removeColumns(int column, int count, const QModelIndex &parent = {}) override; + bool removeRows(int row, int count, const QModelIndex &parent = {}) override; + + Qt::ItemFlags flags(const QModelIndex &index) const override; + QVariant headerData(int section, + Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; + + CollectionDetails::DataType propertyDataType(int column) const; + + int selectedColumn() const; + int selectedRow() const; + Q_INVOKABLE QString propertyName(int column) const; + Q_INVOKABLE QString propertyType(int column) const; + + Q_INVOKABLE bool isPropertyAvailable(const QString &name); + Q_INVOKABLE bool addColumn(int column, const QString &name, const QString &propertyType = {}); + Q_INVOKABLE bool selectColumn(int section); + Q_INVOKABLE bool renameColumn(int section, const QString &newValue); + Q_INVOKABLE bool setPropertyType(int column, const QString &newValue, bool force = false); + Q_INVOKABLE bool selectRow(int row); + Q_INVOKABLE void deselectAll(); + + static Q_INVOKABLE QStringList typesList(); + + void loadCollection(const ModelNode &sourceNode, const QString &collection); + + Q_INVOKABLE bool exportCollection(const QString &path, const QString &collectionName, const QString &exportType); + +signals: + void collectionNameChanged(const QString &collectionName); + void selectedColumnChanged(int); + void selectedRowChanged(int); + void isEmptyChanged(bool); + +private slots: + void updateEmpty(); + +private: + void switchToCollection(const CollectionReference &collection); + void closeCollectionIfSaved(const CollectionReference &collection); + void closeCurrentCollectionIfSaved(); + void setCollectionName(const QString &newCollectionName); + void loadJsonCollection(const QString &source, const QString &collection); + void loadCsvCollection(const QString &source, const QString &collectionName); + bool saveCollectionAsJson(const QString &path, const QJsonArray &content, const QString &collectionName); + bool saveCollectionAsCsv(const QString &path, const QString &content); + QVariant variantFromString(const QString &value); + + QHash m_openedCollections; + CollectionDetails m_currentCollection; + bool m_isEmpty = true; + int m_selectedColumn = -1; + int m_selectedRow = -1; + + QString m_collectionName; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.cpp new file mode 100644 index 00000000000..50fcadd4944 --- /dev/null +++ b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.cpp @@ -0,0 +1,155 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "collectiondetailssortfiltermodel.h" + +#include "collectiondetailsmodel.h" +#include "collectioneditorutils.h" + +#include + +namespace QmlDesigner { + +CollectionDetailsSortFilterModel::CollectionDetailsSortFilterModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ + connect(this, &CollectionDetailsSortFilterModel::rowsInserted, + this, &CollectionDetailsSortFilterModel::updateRowCountChanges); + connect(this, &CollectionDetailsSortFilterModel::rowsRemoved, + this, &CollectionDetailsSortFilterModel::updateRowCountChanges); + connect(this, &CollectionDetailsSortFilterModel::modelReset, + this, &CollectionDetailsSortFilterModel::updateRowCountChanges); + + setDynamicSortFilter(true); +} + +void CollectionDetailsSortFilterModel::setSourceModel(CollectionDetailsModel *model) +{ + m_source = model; + Super::setSourceModel(model); + connect(m_source, &CollectionDetailsModel::selectedColumnChanged, + this, &CollectionDetailsSortFilterModel::updateSelectedColumn); + + connect(m_source, &CollectionDetailsModel::selectedRowChanged, + this, &CollectionDetailsSortFilterModel::updateSelectedRow); +} + +int CollectionDetailsSortFilterModel::selectedRow() const +{ + QTC_ASSERT(m_source, return -1); + + return mapFromSource(m_source->index(m_source->selectedRow(), 0)).row(); +} + +int CollectionDetailsSortFilterModel::selectedColumn() const +{ + QTC_ASSERT(m_source, return -1); + + return mapFromSource(m_source->index(0, m_source->selectedColumn())).column(); +} + +bool CollectionDetailsSortFilterModel::selectRow(int row) +{ + QTC_ASSERT(m_source, return false); + + return m_source->selectRow(mapToSource(index(row, 0)).row()); +} + +bool CollectionDetailsSortFilterModel::selectColumn(int column) +{ + QTC_ASSERT(m_source, return false); + + return m_source->selectColumn(mapToSource(index(0, column)).column()); +} + +CollectionDetailsSortFilterModel::~CollectionDetailsSortFilterModel() = default; + +bool CollectionDetailsSortFilterModel::filterAcceptsRow(int sourceRow, + const QModelIndex &sourceParent) const +{ + QTC_ASSERT(m_source, return false); + QModelIndex sourceIndex(m_source->index(sourceRow, 0, sourceParent)); + return sourceIndex.isValid(); +} + +bool CollectionDetailsSortFilterModel::lessThan(const QModelIndex &sourceleft, + const QModelIndex &sourceRight) const +{ + QTC_ASSERT(m_source, return false); + + if (sourceleft.column() == sourceRight.column()) { + int column = sourceleft.column(); + CollectionDetails::DataType columnType = m_source->propertyDataType(column); + return CollectionEditor::variantIslessThan(sourceleft.data(), sourceRight.data(), columnType); + } + + return false; +} + +void CollectionDetailsSortFilterModel::updateEmpty() +{ + bool newValue = rowCount() == 0; + if (m_isEmpty != newValue) { + m_isEmpty = newValue; + emit isEmptyChanged(m_isEmpty); + } +} + +void CollectionDetailsSortFilterModel::updateSelectedRow() +{ + const int upToDateSelectedRow = selectedRow(); + if (m_selectedRow == upToDateSelectedRow) + return; + + const int rows = rowCount(); + const int columns = columnCount(); + const int previousRow = m_selectedRow; + + m_selectedRow = upToDateSelectedRow; + emit this->selectedRowChanged(m_selectedRow); + + auto notifySelectedDataChanged = [this, rows, columns](int notifyingRow) { + if (notifyingRow > -1 && notifyingRow < rows && columns) { + emit dataChanged(index(notifyingRow, 0), + index(notifyingRow, columns - 1), + {CollectionDetailsModel::SelectedRole}); + } + }; + + notifySelectedDataChanged(previousRow); + notifySelectedDataChanged(m_selectedRow); +} + +void CollectionDetailsSortFilterModel::updateSelectedColumn() +{ + const int upToDateSelectedColumn = selectedColumn(); + if (m_selectedColumn == upToDateSelectedColumn) + return; + + const int rows = rowCount(); + const int columns = columnCount(); + const int previousColumn = m_selectedColumn; + + m_selectedColumn = upToDateSelectedColumn; + emit this->selectedColumnChanged(m_selectedColumn); + + auto notifySelectedDataChanged = [this, rows, columns](int notifyingCol) { + if (notifyingCol > -1 && notifyingCol < columns && rows) { + emit dataChanged(index(0, notifyingCol), + index(rows - 1, notifyingCol), + {CollectionDetailsModel::SelectedRole}); + } + }; + + notifySelectedDataChanged(previousColumn); + notifySelectedDataChanged(m_selectedColumn); +} + +void CollectionDetailsSortFilterModel::updateRowCountChanges() +{ + updateEmpty(); + updateSelectedRow(); + invalidate(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.h b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.h new file mode 100644 index 00000000000..93305f3ca20 --- /dev/null +++ b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.h @@ -0,0 +1,57 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include +#include + +namespace QmlDesigner { + +class CollectionDetailsModel; + +class CollectionDetailsSortFilterModel : public QSortFilterProxyModel +{ + Q_OBJECT + + Q_PROPERTY(int selectedColumn READ selectedColumn WRITE selectColumn NOTIFY selectedColumnChanged) + Q_PROPERTY(int selectedRow READ selectedRow WRITE selectRow NOTIFY selectedRowChanged) + Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) + + using Super = QSortFilterProxyModel; + +public: + explicit CollectionDetailsSortFilterModel(QObject *parent = nullptr); + virtual ~CollectionDetailsSortFilterModel(); + + void setSourceModel(CollectionDetailsModel *model); + + int selectedRow() const; + int selectedColumn() const; + + Q_INVOKABLE bool selectRow(int row); + Q_INVOKABLE bool selectColumn(int column); + +signals: + void selectedColumnChanged(int); + void selectedRowChanged(int); + void isEmptyChanged(bool); + +protected: + using Super::setSourceModel; + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + bool lessThan(const QModelIndex &sourceleft, const QModelIndex &sourceRight) const override; + +private: + void updateEmpty(); + void updateSelectedRow(); + void updateSelectedColumn(); + void updateRowCountChanges(); + + QPointer m_source; + int m_selectedColumn = -1; + int m_selectedRow = -1; + bool m_isEmpty = true; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorconstants.h b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorconstants.h index f5e18a93ec3..11ceb034fa5 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorconstants.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorconstants.h @@ -7,10 +7,10 @@ namespace QmlDesigner::CollectionEditor { enum class SourceFormat { Unknown, Json, Csv }; -inline constexpr char SOURCEFILE_PROPERTY[] = "sourceFile"; +inline constexpr char SOURCEFILE_PROPERTY[] = "source"; -inline constexpr char COLLECTIONMODEL_IMPORT[] = "QtQuick.Studio.Models"; -inline constexpr char JSONCOLLECTIONMODEL_TYPENAME[] = "QtQuick.Studio.Models.JsonSourceModel"; -inline constexpr char CSVCOLLECTIONMODEL_TYPENAME[] = "QtQuick.Studio.Models.CsvSourceModel"; +inline constexpr char COLLECTIONMODEL_IMPORT[] = "QtQuick.Studio.Utils"; +inline constexpr char JSONCOLLECTIONMODEL_TYPENAME[] = "QtQuick.Studio.Utils.JsonListModel"; +inline constexpr char CSVCOLLECTIONMODEL_TYPENAME[] = "QtQuick.Studio.Utils.CsvTableModel"; } // namespace QmlDesigner::CollectionEditor diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.cpp new file mode 100644 index 00000000000..1e47f6460f6 --- /dev/null +++ b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.cpp @@ -0,0 +1,114 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "collectioneditorutils.h" + +#include "abstractview.h" +#include "bindingproperty.h" +#include "nodemetainfo.h" +#include "propertymetainfo.h" + +#include + +#include + +#include +#include + +namespace { + +using CollectionDataVariant = std::variant; + +inline bool operator<(const QColor &a, const QColor &b) +{ + return a.name(QColor::HexArgb) < b.name(QColor::HexArgb); +} + +inline CollectionDataVariant valueToVariant(const QVariant &value, + QmlDesigner::CollectionDetails::DataType type) +{ + using DataType = QmlDesigner::CollectionDetails::DataType; + switch (type) { + case DataType::String: + return value.toString(); + case DataType::Number: + return value.toDouble(); + case DataType::Boolean: + return value.toBool(); + case DataType::Color: + return value.value(); + case DataType::Url: + return value.value(); + default: + return false; + } +} + +struct LessThanVisitor +{ + template + bool operator()(const T1 &a, const T2 &b) const + { + return CollectionDataVariant(a).index() < CollectionDataVariant(b).index(); + } + + template + bool operator()(const T &a, const T &b) const + { + return a < b; + } +}; + +} // namespace + +namespace QmlDesigner::CollectionEditor { + +bool variantIslessThan(const QVariant &a, const QVariant &b, CollectionDetails::DataType type) +{ + return std::visit(LessThanVisitor{}, valueToVariant(a, type), valueToVariant(b, type)); +} + +QString getSourceCollectionType(const ModelNode &node) +{ + using namespace QmlDesigner; + if (node.type() == CollectionEditor::JSONCOLLECTIONMODEL_TYPENAME) + return "json"; + + if (node.type() == CollectionEditor::CSVCOLLECTIONMODEL_TYPENAME) + return "csv"; + + return {}; +} + +void assignCollectionSourceToNode(AbstractView *view, + const ModelNode &modelNode, + const ModelNode &collectionSourceNode) +{ + QTC_ASSERT(modelNode.isValid() && collectionSourceNode.isValid(), return); + + if (collectionSourceNode.id().isEmpty() || !canAcceptCollectionAsModel(modelNode)) + return; + + BindingProperty modelProperty = modelNode.bindingProperty("model"); + + view->executeInTransaction("CollectionEditor::assignCollectionSourceToNode", + [&modelProperty, &collectionSourceNode]() { + modelProperty.setExpression(collectionSourceNode.id()); + }); +} + +bool canAcceptCollectionAsModel(const ModelNode &node) +{ + const NodeMetaInfo nodeMetaInfo = node.metaInfo(); + if (!nodeMetaInfo.isValid()) + return false; + + const PropertyMetaInfo modelProperty = nodeMetaInfo.property("model"); + if (!modelProperty.isValid()) + return false; + + return modelProperty.isWritable() && !modelProperty.isPrivate() + && modelProperty.propertyType().isVariant(); +} + +} // namespace QmlDesigner::CollectionEditor diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.h b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.h new file mode 100644 index 00000000000..464e9e49689 --- /dev/null +++ b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.h @@ -0,0 +1,20 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "collectiondetails.h" + +namespace QmlDesigner::CollectionEditor { + +bool variantIslessThan(const QVariant &a, const QVariant &b, CollectionDetails::DataType type); + +QString getSourceCollectionType(const QmlDesigner::ModelNode &node); + +void assignCollectionSourceToNode(AbstractView *view, + const ModelNode &modelNode, + const ModelNode &collectionSourceNode = {}); + +bool canAcceptCollectionAsModel(const ModelNode &node); + +} // namespace QmlDesigner::CollectionEditor diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.cpp index 4c49d5e4954..bc0e1dc6662 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.cpp @@ -4,6 +4,7 @@ #include "collectionlistmodel.h" #include "collectioneditorconstants.h" +#include "collectioneditorutils.h" #include "variantproperty.h" #include @@ -20,6 +21,7 @@ bool containsItem(const std::initializer_list &container, const Value auto it = std::find(begin, end, value); return it != end; } + } // namespace namespace QmlDesigner { @@ -27,6 +29,7 @@ namespace QmlDesigner { CollectionListModel::CollectionListModel(const ModelNode &sourceModel) : QStringListModel() , m_sourceNode(sourceModel) + , m_sourceType(CollectionEditor::getSourceCollectionType(sourceModel)) { connect(this, &CollectionListModel::modelReset, this, &CollectionListModel::updateEmpty); connect(this, &CollectionListModel::rowsRemoved, this, &CollectionListModel::updateEmpty); @@ -52,8 +55,17 @@ bool CollectionListModel::setData(const QModelIndex &index, const QVariant &valu if (!index.isValid()) return false; - if (containsItem({IdRole, Qt::EditRole, Qt::DisplayRole}, role)) { - return Super::setData(index, value); + if (containsItem({Qt::EditRole, Qt::DisplayRole, NameRole}, role)) { + if (contains(value.toString())) + return false; + + QString oldName = collectionNameAt(index.row()); + bool nameChanged = Super::setData(index, value); + if (nameChanged) { + QString newName = collectionNameAt(index.row()); + emit this->collectionNameChanged(oldName, newName); + } + return nameChanged; } else if (role == SelectedRole) { if (value.toBool() != index.data(SelectedRole).toBool()) { setSelectedIndex(value.toBool() ? index.row() : -1); @@ -63,6 +75,24 @@ bool CollectionListModel::setData(const QModelIndex &index, const QVariant &valu return false; } +bool CollectionListModel::removeRows(int row, int count, const QModelIndex &parent) +{ + const int rows = rowCount(parent); + if (count < 1 || row >= rows) + return false; + + row = qBound(0, row, rows - 1); + count = qBound(1, count, rows - row); + + QStringList removedCollections = stringList().mid(row, count); + + bool itemsRemoved = Super::removeRows(row, count, parent); + if (itemsRemoved) + emit collectionsRemoved(removedCollections); + + return itemsRemoved; +} + QVariant CollectionListModel::data(const QModelIndex &index, int role) const { QTC_ASSERT(index.isValid(), return {}); @@ -94,6 +124,11 @@ QString CollectionListModel::sourceAddress() const return m_sourceNode.variantProperty(CollectionEditor::SOURCEFILE_PROPERTY).value().toString(); } +bool CollectionListModel::contains(const QString &collectionName) const +{ + return stringList().contains(collectionName); +} + void CollectionListModel::selectCollectionIndex(int idx, bool selectAtLeastOne) { int collectionCount = stringList().size(); @@ -108,6 +143,13 @@ void CollectionListModel::selectCollectionIndex(int idx, bool selectAtLeastOne) setSelectedIndex(preferredIndex); } +void CollectionListModel::selectCollectionName(const QString &collectionName) +{ + int idx = stringList().indexOf(collectionName); + if (idx > -1) + selectCollectionIndex(idx); +} + QString CollectionListModel::collectionNameAt(int idx) const { return index(idx).data(NameRole).toString(); @@ -144,4 +186,5 @@ void CollectionListModel::updateEmpty() setSelectedIndex(-1); } } + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.h b/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.h index e30b16a5ff7..c65af750d80 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.h @@ -13,8 +13,10 @@ namespace QmlDesigner { class CollectionListModel : public QStringListModel { Q_OBJECT + Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged) Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) + Q_PROPERTY(QString sourceType MEMBER m_sourceType CONSTANT) public: enum Roles { IdRole = Qt::UserRole + 1, NameRole, SourceRole, SelectedRole, CollectionsRole }; @@ -23,18 +25,23 @@ public: virtual QHash roleNames() const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; + bool removeRows(int row, int count, const QModelIndex &parent = {}) override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; Q_INVOKABLE int selectedIndex() const; Q_INVOKABLE ModelNode sourceNode() const; Q_INVOKABLE QString sourceAddress() const; + Q_INVOKABLE bool contains(const QString &collectionName) const; void selectCollectionIndex(int idx, bool selectAtLeastOne = false); + void selectCollectionName(const QString &collectionName); QString collectionNameAt(int idx) const; signals: void selectedIndexChanged(int idx); void isEmptyChanged(bool); + void collectionNameChanged(const QString &oldName, const QString &newName); + void collectionsRemoved(const QStringList &names); private: void setSelectedIndex(int idx); @@ -45,6 +52,7 @@ private: int m_selectedIndex = -1; bool m_isEmpty = false; const ModelNode m_sourceNode; + const QString m_sourceType; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.cpp index 999ebe449ba..74cf8d41b43 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.cpp @@ -5,12 +5,16 @@ #include "abstractview.h" #include "collectioneditorconstants.h" +#include "collectioneditorutils.h" #include "collectionlistmodel.h" #include "variantproperty.h" #include +#include #include +#include +#include #include #include #include @@ -58,11 +62,14 @@ QSharedPointer loadCollection( } return collectionsList; } + } // namespace namespace QmlDesigner { -CollectionSourceModel::CollectionSourceModel() {} +CollectionSourceModel::CollectionSourceModel(QObject *parent) + : Super(parent) +{} int CollectionSourceModel::rowCount(const QModelIndex &) const { @@ -76,10 +83,12 @@ QVariant CollectionSourceModel::data(const QModelIndex &index, int role) const const ModelNode *collectionSource = &m_collectionSources.at(index.row()); switch (role) { - case IdRole: - return collectionSource->id(); case NameRole: return collectionSource->variantProperty("objectName").value(); + case NodeRole: + return QVariant::fromValue(*collectionSource); + case CollectionTypeRole: + return CollectionEditor::getSourceCollectionType(*collectionSource); case SourceRole: return collectionSource->variantProperty(CollectionEditor::SOURCEFILE_PROPERTY).value(); case SelectedRole: @@ -98,20 +107,6 @@ bool CollectionSourceModel::setData(const QModelIndex &index, const QVariant &va ModelNode collectionSource = m_collectionSources.at(index.row()); switch (role) { - case IdRole: { - if (collectionSource.id() == value) - return false; - - bool duplicatedId = Utils::anyOf(std::as_const(m_collectionSources), - [&collectionSource, &value](const ModelNode &otherCollection) { - return (otherCollection.id() == value - && otherCollection != collectionSource); - }); - if (duplicatedId) - return false; - - collectionSource.setIdWithRefactoring(value.toString()); - } break; case Qt::DisplayRole: case NameRole: { auto collectionName = collectionSource.variantProperty("objectName"); @@ -186,11 +181,12 @@ QHash CollectionSourceModel::roleNames() const static QHash roles; if (roles.isEmpty()) { roles.insert(Super::roleNames()); - roles.insert({{IdRole, "sourceId"}, - {NameRole, "sourceName"}, + roles.insert({{NameRole, "sourceName"}, + {NodeRole, "sourceNode"}, + {CollectionTypeRole, "sourceCollectionType"}, {SelectedRole, "sourceIsSelected"}, {SourceRole, "sourceAddress"}, - {CollectionsRole, "collections"}}); + {CollectionsRole, "internalModels"}}); } return roles; } @@ -208,11 +204,7 @@ void CollectionSourceModel::setSources(const ModelNodes &sources) auto loadedCollection = loadCollection(collectionSource); m_collectionList.append(loadedCollection); - connect(loadedCollection.data(), - &CollectionListModel::selectedIndexChanged, - this, - &CollectionSourceModel::onSelectedCollectionChanged, - Qt::UniqueConnection); + registerCollection(loadedCollection); } updateEmpty(); @@ -245,11 +237,7 @@ void CollectionSourceModel::addSource(const ModelNode &node) auto loadedCollection = loadCollection(node); m_collectionList.append(loadedCollection); - connect(loadedCollection.data(), - &CollectionListModel::selectedIndexChanged, - this, - &CollectionSourceModel::onSelectedCollectionChanged, - Qt::UniqueConnection); + registerCollection(loadedCollection); updateEmpty(); endInsertRows(); @@ -265,6 +253,85 @@ void CollectionSourceModel::selectSource(const ModelNode &node) selectSourceIndex(nodePlace, true); } +bool CollectionSourceModel::collectionExists(const ModelNode &node, const QString &collectionName) const +{ + int idx = sourceIndex(node); + if (idx < 0) + return false; + + auto collections = m_collectionList.at(idx); + if (collections.isNull()) + return false; + + return collections->contains(collectionName); +} + +bool CollectionSourceModel::addCollectionToSource(const ModelNode &node, + const QString &collectionName, + QString *errorString) +{ + auto returnError = [errorString](const QString &msg) -> bool { + if (errorString) + *errorString = msg; + return false; + }; + + int idx = sourceIndex(node); + if (idx < 0) + return returnError(tr("Node is not indexed in the models.")); + + if (node.type() != CollectionEditor::JSONCOLLECTIONMODEL_TYPENAME) + return returnError(tr("Node should be a JSON model.")); + + if (collectionExists(node, collectionName)) + return returnError(tr("Model does not exist.")); + + QString sourceFileAddress = node.variantProperty(CollectionEditor::SOURCEFILE_PROPERTY) + .value() + .toString(); + + QFileInfo sourceFileInfo(sourceFileAddress); + if (!sourceFileInfo.isFile()) + return returnError(tr("Selected node must have a valid source file address")); + + QFile jsonFile(sourceFileAddress); + if (!jsonFile.open(QFile::ReadWrite)) + return returnError(tr("Can't read or write \"%1\".\n%2") + .arg(sourceFileInfo.absoluteFilePath(), jsonFile.errorString())); + + QJsonParseError parseError; + QJsonDocument document = QJsonDocument::fromJson(jsonFile.readAll(), &parseError); + if (parseError.error != QJsonParseError::NoError) + return returnError(tr("\"%1\" is corrupted.\n%2") + .arg(sourceFileInfo.absoluteFilePath(), parseError.errorString())); + + if (document.isObject()) { + QJsonObject sourceObject = document.object(); + sourceObject.insert(collectionName, QJsonArray{}); + document.setObject(sourceObject); + if (!jsonFile.resize(0)) + return returnError(tr("Can't clean \"%1\".").arg(sourceFileInfo.absoluteFilePath())); + + QByteArray jsonData = document.toJson(); + auto writtenBytes = jsonFile.write(jsonData); + jsonFile.close(); + + if (writtenBytes != jsonData.size()) + return returnError(tr("Can't write to \"%1\".").arg(sourceFileInfo.absoluteFilePath())); + + updateCollectionList(index(idx)); + + auto collections = m_collectionList.at(idx); + if (collections.isNull()) + return returnError(tr("No model is available for the JSON model group.")); + + collections->selectCollectionName(collectionName); + return true; + } else { + return returnError(tr("JSON document type should be an object containing models.")); + } +} + QmlDesigner::ModelNode CollectionSourceModel::sourceNodeAt(int idx) { QModelIndex data = index(idx); @@ -309,6 +376,11 @@ void CollectionSourceModel::updateSelectedSource(bool selectAtLeastOne) selectSourceIndex(idx, selectAtLeastOne); } +bool CollectionSourceModel::collectionExists(const QVariant &node, const QString &collectionName) const +{ + return collectionExists(node.value(), collectionName); +} + void CollectionSourceModel::updateNodeName(const ModelNode &node) { QModelIndex index = indexOfNode(node); @@ -323,12 +395,6 @@ void CollectionSourceModel::updateNodeSource(const ModelNode &node) updateCollectionList(index); } -void CollectionSourceModel::updateNodeId(const ModelNode &node) -{ - QModelIndex index = indexOfNode(node); - emit dataChanged(index, index, {IdRole}); -} - QString CollectionSourceModel::selectedSourceAddress() const { return index(m_selectedIndex).data(SourceRole).toString(); @@ -338,13 +404,189 @@ void CollectionSourceModel::onSelectedCollectionChanged(int collectionIndex) { CollectionListModel *collectionList = qobject_cast(sender()); if (collectionIndex > -1 && collectionList) { - if (_previousSelectedList && _previousSelectedList != collectionList) - _previousSelectedList->selectCollectionIndex(-1); + if (m_previousSelectedList && m_previousSelectedList != collectionList) + m_previousSelectedList->selectCollectionIndex(-1); + + m_previousSelectedList = collectionList; emit collectionSelected(collectionList->sourceNode(), collectionList->collectionNameAt(collectionIndex)); - _previousSelectedList = collectionList; + selectSourceIndex(sourceIndex(collectionList->sourceNode())); + } +} + +void CollectionSourceModel::onCollectionNameChanged(const QString &oldName, const QString &newName) +{ + CollectionListModel *collectionList = qobject_cast(sender()); + QTC_ASSERT(collectionList, return); + + auto emitRenameWarning = [this](const QString &msg) -> void { + emit this->warning(tr("Rename Model"), msg); + }; + + const ModelNode node = collectionList->sourceNode(); + const QModelIndex nodeIndex = indexOfNode(node); + + if (!nodeIndex.isValid()) { + emitRenameWarning(tr("Invalid node")); + return; + } + + if (node.type() == CollectionEditor::CSVCOLLECTIONMODEL_TYPENAME) { + if (!setData(nodeIndex, newName, NameRole)) + emitRenameWarning(tr("Can't rename the node")); + return; + } else if (node.type() != CollectionEditor::JSONCOLLECTIONMODEL_TYPENAME) { + emitRenameWarning(tr("Invalid node type")); + return; + } + + QString sourceFileAddress = node.variantProperty(CollectionEditor::SOURCEFILE_PROPERTY) + .value() + .toString(); + + QFileInfo sourceFileInfo(sourceFileAddress); + if (!sourceFileInfo.isFile()) { + emitRenameWarning(tr("Selected node must have a valid source file address")); + return; + } + + QFile jsonFile(sourceFileAddress); + if (!jsonFile.open(QFile::ReadWrite)) { + emitRenameWarning(tr("Can't read or write \"%1\".\n%2") + .arg(sourceFileInfo.absoluteFilePath(), jsonFile.errorString())); + return; + } + + QJsonParseError parseError; + QJsonDocument document = QJsonDocument::fromJson(jsonFile.readAll(), &parseError); + if (parseError.error != QJsonParseError::NoError) { + emitRenameWarning(tr("\"%1\" is corrupted.\n%2") + .arg(sourceFileInfo.absoluteFilePath(), parseError.errorString())); + return; + } + + if (document.isObject()) { + QJsonObject rootObject = document.object(); + + bool collectionContainsOldName = rootObject.contains(oldName); + bool collectionContainsNewName = rootObject.contains(newName); + + if (!collectionContainsOldName) { + emitRenameWarning( + tr("The model group doesn't contain the old model name (%1).").arg(oldName)); + return; + } + + if (collectionContainsNewName) { + emitRenameWarning( + tr("The model name \"%1\" already exists in the model group.").arg(newName)); + return; + } + + QJsonValue oldValue = rootObject.value(oldName); + rootObject.insert(newName, oldValue); + rootObject.remove(oldName); + + document.setObject(rootObject); + if (!jsonFile.resize(0)) { + emitRenameWarning(tr("Can't clean \"%1\".").arg(sourceFileInfo.absoluteFilePath())); + return; + } + + QByteArray jsonData = document.toJson(); + auto writtenBytes = jsonFile.write(jsonData); + jsonFile.close(); + + if (writtenBytes != jsonData.size()) { + emitRenameWarning(tr("Can't write to \"%1\".").arg(sourceFileInfo.absoluteFilePath())); + return; + } + + updateCollectionList(nodeIndex); + } +} + +void CollectionSourceModel::onCollectionsRemoved(const QStringList &removedCollections) +{ + CollectionListModel *collectionList = qobject_cast(sender()); + QTC_ASSERT(collectionList, return); + + auto emitDeleteWarning = [this](const QString &msg) -> void { + emit warning(tr("Delete Model"), msg); + }; + + const ModelNode node = collectionList->sourceNode(); + const QModelIndex nodeIndex = indexOfNode(node); + + if (!nodeIndex.isValid()) { + emitDeleteWarning(tr("Invalid node")); + return; + } + + if (node.type() == CollectionEditor::CSVCOLLECTIONMODEL_TYPENAME) { + removeSource(node); + return; + } else if (node.type() != CollectionEditor::JSONCOLLECTIONMODEL_TYPENAME) { + emitDeleteWarning(tr("Invalid node type")); + return; + } + + QString sourceFileAddress = node.variantProperty(CollectionEditor::SOURCEFILE_PROPERTY) + .value() + .toString(); + + QFileInfo sourceFileInfo(sourceFileAddress); + if (!sourceFileInfo.isFile()) { + emitDeleteWarning(tr("The selected node has an invalid source address")); + return; + } + + QFile jsonFile(sourceFileAddress); + if (!jsonFile.open(QFile::ReadWrite)) { + emitDeleteWarning(tr("Can't read or write \"%1\".\n%2") + .arg(sourceFileInfo.absoluteFilePath(), jsonFile.errorString())); + return; + } + + QJsonParseError parseError; + QJsonDocument document = QJsonDocument::fromJson(jsonFile.readAll(), &parseError); + if (parseError.error != QJsonParseError::NoError) { + emitDeleteWarning(tr("\"%1\" is corrupted.\n%2") + .arg(sourceFileInfo.absoluteFilePath(), parseError.errorString())); + return; + } + + if (document.isObject()) { + QJsonObject rootObject = document.object(); + + for (const QString &collectionName : removedCollections) { + bool sourceContainsCollection = rootObject.contains(collectionName); + if (sourceContainsCollection) { + rootObject.remove(collectionName); + } else { + emitDeleteWarning(tr("The model group doesn't contain the model name (%1).") + .arg(sourceContainsCollection)); + } + } + + document.setObject(rootObject); + if (!jsonFile.resize(0)) { + emitDeleteWarning(tr("Can't clean \"%1\".").arg(sourceFileInfo.absoluteFilePath())); + return; + } + + QByteArray jsonData = document.toJson(); + auto writtenBytes = jsonFile.write(jsonData); + jsonFile.close(); + + if (writtenBytes != jsonData.size()) { + emitDeleteWarning(tr("Can't write to \"%1\".").arg(sourceFileInfo.absoluteFilePath())); + return; + } + + updateCollectionList(nodeIndex); } } @@ -365,6 +607,18 @@ void CollectionSourceModel::setSelectedIndex(int idx) emit dataChanged(newIndex, newIndex, {SelectedRole}); emit selectedIndexChanged(idx); + + if (idx > -1) { + QPointer relatedCollectionList = m_collectionList.at(idx).data(); + if (relatedCollectionList) { + if (relatedCollectionList->selectedIndex() < 0) + relatedCollectionList->selectCollectionIndex(0, true); + } else if (m_previousSelectedList) { + m_previousSelectedList->selectCollectionIndex(-1); + m_previousSelectedList = {}; + emit this->collectionSelected(sourceNodeAt(idx), {}); + } + } } } @@ -394,8 +648,46 @@ void CollectionSourceModel::updateCollectionList(QModelIndex index) } } +void CollectionSourceModel::registerCollection(const QSharedPointer &collection) +{ + connect(collection.data(), + &CollectionListModel::selectedIndexChanged, + this, + &CollectionSourceModel::onSelectedCollectionChanged, + Qt::UniqueConnection); + + connect(collection.data(), + &CollectionListModel::collectionNameChanged, + this, + &CollectionSourceModel::onCollectionNameChanged, + Qt::UniqueConnection); + + connect(collection.data(), + &CollectionListModel::collectionsRemoved, + this, + &CollectionSourceModel::onCollectionsRemoved, + Qt::UniqueConnection); +} + QModelIndex CollectionSourceModel::indexOfNode(const ModelNode &node) const { return index(m_sourceIndexHash.value(node.internalId(), -1)); } + +void CollectionJsonSourceFilterModel::registerDeclarativeType() +{ + qmlRegisterType("CollectionEditor", + 1, + 0, + "CollectionJsonSourceFilterModel"); +} + +bool CollectionJsonSourceFilterModel::filterAcceptsRow(int source_row, const QModelIndex &) const +{ + if (!sourceModel()) + return false; + QModelIndex sourceItem = sourceModel()->index(source_row, 0, {}); + return sourceItem.data(CollectionSourceModel::Roles::CollectionTypeRole).toString() == "json"; +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.h b/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.h index bd22d833ad9..39c82ec5b31 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionsourcemodel.h @@ -7,9 +7,13 @@ #include #include +#include namespace QmlDesigner { + +class CollectionJsonSourceFilterModel; class CollectionListModel; + class CollectionSourceModel : public QAbstractListModel { Q_OBJECT @@ -18,9 +22,16 @@ class CollectionSourceModel : public QAbstractListModel Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) public: - enum Roles { IdRole = Qt::UserRole + 1, NameRole, SourceRole, SelectedRole, CollectionsRole }; + enum Roles { + NameRole = Qt::UserRole + 1, + NodeRole, + CollectionTypeRole, + SourceRole, + SelectedRole, + CollectionsRole + }; - explicit CollectionSourceModel(); + explicit CollectionSourceModel(QObject *parent = nullptr); virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; @@ -40,40 +51,61 @@ public: void addSource(const ModelNode &node); void selectSource(const ModelNode &node); + bool collectionExists(const ModelNode &node, const QString &collectionName) const; + bool addCollectionToSource(const ModelNode &node, + const QString &collectionName, + QString *errorString = nullptr); + ModelNode sourceNodeAt(int idx); CollectionListModel *selectedCollectionList(); Q_INVOKABLE void selectSourceIndex(int idx, bool selectAtLeastOne = false); Q_INVOKABLE void deselect(); Q_INVOKABLE void updateSelectedSource(bool selectAtLeastOne = false); + Q_INVOKABLE bool collectionExists(const QVariant &node, const QString &collectionName) const; + void updateNodeName(const ModelNode &node); void updateNodeSource(const ModelNode &node); - void updateNodeId(const ModelNode &node); - QString selectedSourceAddress() const; + Q_INVOKABLE QString selectedSourceAddress() const; signals: void selectedIndexChanged(int idx); void collectionSelected(const ModelNode &sourceNode, const QString &collectionName); void isEmptyChanged(bool); + void warning(const QString &title, const QString &body); private slots: void onSelectedCollectionChanged(int collectionIndex); + void onCollectionNameChanged(const QString &oldName, const QString &newName); + void onCollectionsRemoved(const QStringList &removedCollections); private: void setSelectedIndex(int idx); void updateEmpty(); void updateCollectionList(QModelIndex index); + void registerCollection(const QSharedPointer &collection); + QModelIndex indexOfNode(const ModelNode &node) const; using Super = QAbstractListModel; - QModelIndex indexOfNode(const ModelNode &node) const; ModelNodes m_collectionSources; QHash m_sourceIndexHash; // internalId -> index QList> m_collectionList; - QPointer _previousSelectedList; + QPointer m_previousSelectedList; int m_selectedIndex = -1; bool m_isEmpty = true; }; +class CollectionJsonSourceFilterModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + static void registerDeclarativeType(); + +protected: + bool filterAcceptsRow(int source_row, const QModelIndex &) const override; +}; + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp index 53cf76312d4..e3c8d26519c 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp @@ -3,14 +3,15 @@ #include "collectionview.h" +#include "collectiondetailsmodel.h" #include "collectioneditorconstants.h" +#include "collectioneditorutils.h" #include "collectionsourcemodel.h" #include "collectionwidget.h" #include "designmodecontext.h" #include "nodeabstractproperty.h" #include "nodemetainfo.h" #include "qmldesignerplugin.h" -#include "singlecollectionmodel.h" #include "variantproperty.h" #include @@ -55,7 +56,7 @@ QmlDesigner::WidgetInfo CollectionView::widgetInfo() &CollectionSourceModel::collectionSelected, this, [this](const ModelNode &sourceNode, const QString &collection) { - m_widget->singleCollectionModel()->loadCollection(sourceNode, collection); + m_widget->collectionDetailsModel()->loadCollection(sourceNode, collection); }); } @@ -63,8 +64,8 @@ QmlDesigner::WidgetInfo CollectionView::widgetInfo() "CollectionEditor", WidgetInfo::LeftPane, 0, - tr("Collection Editor"), - tr("Collection Editor view")); + tr("Model Editor"), + tr("Model Editor view")); } void CollectionView::modelAttached(Model *model) @@ -88,7 +89,7 @@ void CollectionView::nodeReparented(const ModelNode &node, void CollectionView::nodeAboutToBeRemoved(const ModelNode &removedNode) { - // removing the collections lib node + // removing the model lib node if (isStudioCollectionModel(removedNode)) m_widget->sourceModel()->removeSource(removedNode); } @@ -111,8 +112,6 @@ void CollectionView::variantPropertiesChanged(const QList &prop m_widget->sourceModel()->updateNodeName(node); else if (property.name() == CollectionEditor::SOURCEFILE_PROPERTY) m_widget->sourceModel()->updateNodeSource(node); - else if (property.name() == "id") - m_widget->sourceModel()->updateNodeId(node); } } } @@ -120,15 +119,26 @@ void CollectionView::variantPropertiesChanged(const QList &prop void CollectionView::selectedNodesChanged(const QList &selectedNodeList, [[maybe_unused]] const QList &lastSelectedNodeList) { - QList selectedJsonCollections = Utils::filtered(selectedNodeList, + QList selectedCollectionNodes = Utils::filtered(selectedNodeList, &isStudioCollectionModel); - // More than one collections are selected. So ignore them - if (selectedJsonCollections.size() > 1) + bool singleNonCollectionNodeSelected = selectedNodeList.size() == 1 + && selectedCollectionNodes.isEmpty(); + + bool singleSelectedHasModelProperty = false; + if (singleNonCollectionNodeSelected) { + const ModelNode selectedNode = selectedNodeList.first(); + singleSelectedHasModelProperty = CollectionEditor::canAcceptCollectionAsModel(selectedNode); + } + + m_widget->setTargetNodeSelected(singleSelectedHasModelProperty); + + // More than one model is selected. So ignore them + if (selectedCollectionNodes.size() > 1) return; - if (selectedJsonCollections.size() == 1) { // If exactly one collection is selected - m_widget->sourceModel()->selectSource(selectedJsonCollections.first()); + if (selectedCollectionNodes.size() == 1) { // If exactly one model is selected + m_widget->sourceModel()->selectSource(selectedCollectionNodes.first()); return; } } @@ -149,18 +159,28 @@ void CollectionView::addResource(const QUrl &url, const QString &name, const QSt VariantProperty nameProperty = resourceNode.variantProperty("objectName"); sourceProperty.setValue(sourceAddress); nameProperty.setValue(name); + resourceNode.setIdWithoutRefactoring(model()->generateIdFromName(name, "model")); rootModelNode().defaultNodeAbstractProperty().reparentHere(resourceNode); }); } +void CollectionView::registerDeclarativeType() +{ + CollectionDetails::registerDeclarativeType(); + CollectionJsonSourceFilterModel::registerDeclarativeType(); +} + void CollectionView::refreshModel() { if (!model()) return; - // Load Json Collections - const ModelNodes jsonSourceNodes = rootModelNode().subModelNodesOfType(jsonCollectionMetaInfo()); - m_widget->sourceModel()->setSources(jsonSourceNodes); + // Load Model Groups + const ModelNodes collectionSourceNodes = rootModelNode().subModelNodesOfType( + jsonCollectionMetaInfo()) + + rootModelNode().subModelNodesOfType( + csvCollectionMetaInfo()); + m_widget->sourceModel()->setSources(collectionSourceNodes); } NodeMetaInfo CollectionView::jsonCollectionMetaInfo() const diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionview.h b/src/plugins/qmldesigner/components/collectioneditor/collectionview.h index 6d81d00f064..994105f9745 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionview.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionview.h @@ -43,6 +43,8 @@ public: void addResource(const QUrl &url, const QString &name, const QString &type); + static void registerDeclarativeType(); + private: void refreshModel(); NodeMetaInfo jsonCollectionMetaInfo() const; diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp index c4d9631cefe..8f6168dfb2d 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp @@ -2,19 +2,25 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "collectionwidget.h" + +#include "collectiondetailsmodel.h" +#include "collectiondetailssortfiltermodel.h" +#include "collectioneditorutils.h" #include "collectionsourcemodel.h" #include "collectionview.h" #include "qmldesignerconstants.h" #include "qmldesignerplugin.h" -#include "singlecollectionmodel.h" #include "theme.h" -#include #include +#include +#include #include #include +#include #include +#include #include #include #include @@ -23,6 +29,7 @@ #include namespace { + QString collectionViewResourcesPath() { #ifdef SHARE_QML_PATH @@ -31,6 +38,32 @@ QString collectionViewResourcesPath() #endif return Core::ICore::resourcePath("qmldesigner/collectionEditorQmlSource").toString(); } + +QString urlToLocalPath(const QUrl &url) +{ + QString localPath; + + if (url.isLocalFile()) + localPath = url.toLocalFile(); + + if (url.scheme() == QLatin1String("qrc")) { + const QString &path = url.path(); + localPath = QStringLiteral(":") + path; + } + + return localPath; +} + +QString getPreferredCollectionName(const QUrl &url, const QString &collectionName) +{ + if (collectionName.isEmpty()) { + QFileInfo fileInfo(url.isLocalFile() ? url.toLocalFile() : url.toString()); + return fileInfo.completeBaseName(); + } + + return collectionName; +} + } // namespace namespace QmlDesigner { @@ -38,10 +71,11 @@ CollectionWidget::CollectionWidget(CollectionView *view) : QFrame() , m_view(view) , m_sourceModel(new CollectionSourceModel) - , m_singleCollectionModel(new SingleCollectionModel) + , m_collectionDetailsModel(new CollectionDetailsModel) + , m_collectionDetailsSortFilterModel(std::make_unique()) , m_quickWidget(new StudioQuickWidget(this)) { - setWindowTitle(tr("Collection View", "Title of collection view widget")); + setWindowTitle(tr("Model Editor", "Title of model editor widget")); Core::IContext *icontext = nullptr; Core::Context context(Constants::C_QMLMATERIALBROWSER); @@ -49,6 +83,10 @@ CollectionWidget::CollectionWidget(CollectionView *view) icontext->setContext(context); icontext->setWidget(this); + connect(m_sourceModel, &CollectionSourceModel::warning, this, &CollectionWidget::warn); + + m_collectionDetailsSortFilterModel->setSourceModel(m_collectionDetailsModel); + m_quickWidget->quickWidget()->setObjectName(Constants::OBJECT_NAME_COLLECTION_EDITOR); m_quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); m_quickWidget->engine()->addImportPath(collectionViewResourcesPath() + "/imports"); @@ -64,15 +102,20 @@ CollectionWidget::CollectionWidget(CollectionView *view) qmlRegisterAnonymousType("CollectionEditorBackend", 1); auto map = m_quickWidget->registerPropertyMap("CollectionEditorBackend"); - map->setProperties( - {{"rootView", QVariant::fromValue(this)}, - {"model", QVariant::fromValue(m_sourceModel.data())}, - {"singleCollectionModel", QVariant::fromValue(m_singleCollectionModel.data())}}); + map->setProperties({ + {"rootView", QVariant::fromValue(this)}, + {"model", QVariant::fromValue(m_sourceModel.data())}, + {"collectionDetailsModel", QVariant::fromValue(m_collectionDetailsModel.data())}, + {"collectionDetailsSortFilterModel", + QVariant::fromValue(m_collectionDetailsSortFilterModel.get())}, + }); auto hotReloadShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F4), this); connect(hotReloadShortcut, &QShortcut::activated, this, &CollectionWidget::reloadQmlSource); reloadQmlSource(); + + QmlDesignerPlugin::trackWidgetFocusTime(this, Constants::EVENT_MODELEDITOR_TIME); } void CollectionWidget::contextHelp(const Core::IContext::HelpCallback &callback) const @@ -88,9 +131,9 @@ QPointer CollectionWidget::sourceModel() const return m_sourceModel; } -QPointer CollectionWidget::singleCollectionModel() const +QPointer CollectionWidget::collectionDetailsModel() const { - return m_singleCollectionModel; + return m_collectionDetailsModel; } void CollectionWidget::reloadQmlSource() @@ -100,25 +143,42 @@ void CollectionWidget::reloadQmlSource() QTC_ASSERT(QFileInfo::exists(collectionViewQmlPath), return); m_quickWidget->setSource(QUrl::fromLocalFile(collectionViewQmlPath)); + + if (!m_quickWidget->rootObject()) { + QString errorString; + const auto errors = m_quickWidget->errors(); + for (const QQmlError &error : errors) + errorString.append("\n" + error.toString()); + + Core::AsynchronousMessageBox::warning(tr("Cannot Create QtQuick View"), + tr("StatesEditorWidget: %1 cannot be created.%2") + .arg(collectionViewQmlPath, errorString)); + return; + } } -bool CollectionWidget::loadJsonFile(const QString &jsonFileAddress) +QSize CollectionWidget::minimumSizeHint() const +{ + return {300, 400}; +} + +bool CollectionWidget::loadJsonFile(const QString &jsonFileAddress, const QString &collectionName) { if (!isJsonFile(jsonFileAddress)) return false; - QUrl jsonUrl(jsonFileAddress); - QFileInfo fileInfo(jsonUrl.isLocalFile() ? jsonUrl.toLocalFile() : jsonUrl.toString()); - - m_view->addResource(jsonUrl, fileInfo.completeBaseName(), "json"); + m_view->addResource(jsonFileAddress, + getPreferredCollectionName(jsonFileAddress, collectionName), + "json"); return true; } -bool CollectionWidget::loadCsvFile(const QString &collectionName, const QString &csvFileAddress) +bool CollectionWidget::loadCsvFile(const QString &csvFileAddress, const QString &collectionName) { - QUrl csvUrl(csvFileAddress); - m_view->addResource(csvUrl, collectionName, "csv"); + m_view->addResource(csvFileAddress, + getPreferredCollectionName(csvFileAddress, collectionName), + "csv"); return true; } @@ -140,25 +200,99 @@ bool CollectionWidget::isJsonFile(const QString &jsonFileAddress) const return true; } -bool CollectionWidget::isCsvFile(const QString &csvFileAddress) const +bool CollectionWidget::isCsvFile(const QString &csvFilePath) const { - QUrl csvUrl(csvFileAddress); - QString fileAddress = csvUrl.isLocalFile() ? csvUrl.toLocalFile() : csvUrl.toString(); - QFile file(fileAddress); + QUrl csvUrl(csvFilePath); + QString filePath = csvUrl.isLocalFile() ? csvUrl.toLocalFile() : csvUrl.toString(); + QFile file(filePath); - if (!file.exists()) - return false; - - // TODO: Evaluate the csv file - return true; + return file.exists() && file.fileName().endsWith(".csv"); } -bool CollectionWidget::addCollection([[maybe_unused]] const QString &collectionName) const +bool CollectionWidget::addCollection(const QString &collectionName, + const QString &collectionType, + const QString &sourceAddress, + const QVariant &sourceNode) { - // TODO + const ModelNode node = sourceNode.value(); + bool isNewCollection = !node.isValid(); + + if (isNewCollection) { + QString sourcePath = ::urlToLocalPath(sourceAddress); + if (collectionType == "json") { + QJsonObject jsonObject; + QJsonObject initialObject; + QJsonArray initialCollection; + + initialObject.insert("Column1", ""); + initialCollection.append(initialObject); + jsonObject.insert(collectionName, initialCollection); + + QFile sourceFile(sourcePath); + if (!sourceFile.open(QFile::WriteOnly)) { + warn(tr("File error"), + tr("Can not open the file to write.\n") + sourceFile.errorString()); + return false; + } + + sourceFile.write(QJsonDocument(jsonObject).toJson()); + sourceFile.close(); + + bool loaded = loadJsonFile(sourcePath, collectionName); + if (!loaded) + sourceFile.remove(); + + return loaded; + } else if (collectionType == "csv") { + QFile sourceFile(sourcePath); + if (!sourceFile.open(QFile::WriteOnly)) { + warn(tr("File error"), + tr("Can not open the file to write.\n") + sourceFile.errorString()); + return false; + } + + sourceFile.write("Column1\n\n"); + sourceFile.close(); + + bool loaded = loadCsvFile(sourcePath, collectionName); + if (!loaded) + sourceFile.remove(); + + return loaded; + } else if (collectionType == "existing") { + QFileInfo fileInfo(sourcePath); + if (fileInfo.suffix() == "json") + return loadJsonFile(sourcePath, collectionName); + else if (fileInfo.suffix() == "csv") + return loadCsvFile(sourcePath, collectionName); + } + } else if (collectionType == "json") { + QString errorMsg; + bool added = m_sourceModel->addCollectionToSource(node, collectionName, &errorMsg); + if (!added) + warn(tr("Can not add a model to the JSON file"), errorMsg); + return added; + } + return false; } +void CollectionWidget::assignSourceNodeToSelectedItem(const QVariant &sourceNode) +{ + ModelNode sourceModel = sourceNode.value(); + ModelNode targetNode = m_view->singleSelectedModelNode(); + + QTC_ASSERT(sourceModel.isValid() && targetNode.isValid(), return); + + if (sourceModel.id().isEmpty()) { + warn(tr("Assigning the model group"), + tr("The model group must have a valid id to be assigned.")); + return; + } + + CollectionEditor::assignCollectionSourceToNode(m_view, targetNode, sourceModel); +} + void CollectionWidget::warn(const QString &title, const QString &body) { QMetaObject::invokeMethod(m_quickWidget->rootObject(), @@ -166,4 +300,14 @@ void CollectionWidget::warn(const QString &title, const QString &body) Q_ARG(QVariant, title), Q_ARG(QVariant, body)); } + +void CollectionWidget::setTargetNodeSelected(bool selected) +{ + if (m_targetNodeSelected == selected) + return; + + m_targetNodeSelected = selected; + emit targetNodeSelectedChanged(m_targetNodeSelected); +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h index de2b4d8d9fc..5bf728660cb 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h @@ -11,36 +11,53 @@ class StudioQuickWidget; namespace QmlDesigner { +class CollectionDetailsModel; +class CollectionDetailsSortFilterModel; class CollectionSourceModel; class CollectionView; -class SingleCollectionModel; +class ModelNode; class CollectionWidget : public QFrame { Q_OBJECT + Q_PROPERTY(bool targetNodeSelected MEMBER m_targetNodeSelected NOTIFY targetNodeSelectedChanged) + public: CollectionWidget(CollectionView *view); void contextHelp(const Core::IContext::HelpCallback &callback) const; QPointer sourceModel() const; - QPointer singleCollectionModel() const; + QPointer collectionDetailsModel() const; void reloadQmlSource(); - Q_INVOKABLE bool loadJsonFile(const QString &jsonFileAddress); - Q_INVOKABLE bool loadCsvFile(const QString &collectionName, const QString &csvFileAddress); + virtual QSize minimumSizeHint() const; + + Q_INVOKABLE bool loadJsonFile(const QString &jsonFileAddress, const QString &collectionName = {}); + Q_INVOKABLE bool loadCsvFile(const QString &csvFileAddress, const QString &collectionName = {}); Q_INVOKABLE bool isJsonFile(const QString &jsonFileAddress) const; Q_INVOKABLE bool isCsvFile(const QString &csvFileAddress) const; - Q_INVOKABLE bool addCollection(const QString &collectionName) const; + Q_INVOKABLE bool addCollection(const QString &collectionName, + const QString &collectionType, + const QString &sourceAddress, + const QVariant &sourceNode); + + Q_INVOKABLE void assignSourceNodeToSelectedItem(const QVariant &sourceNode); void warn(const QString &title, const QString &body); + void setTargetNodeSelected(bool selected); + +signals: + void targetNodeSelectedChanged(bool); private: QPointer m_view; QPointer m_sourceModel; - QPointer m_singleCollectionModel; + QPointer m_collectionDetailsModel; + std::unique_ptr m_collectionDetailsSortFilterModel; QScopedPointer m_quickWidget; + bool m_targetNodeSelected = false; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/singlecollectionmodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/singlecollectionmodel.cpp deleted file mode 100644 index a86b42cd169..00000000000 --- a/src/plugins/qmldesigner/components/collectioneditor/singlecollectionmodel.cpp +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "singlecollectionmodel.h" - -#include "collectioneditorconstants.h" -#include "modelnode.h" -#include "variantproperty.h" - -#include - -#include -#include -#include -#include - -namespace { - -QStringList getJsonHeaders(const QJsonArray &collectionArray) -{ - QSet result; - for (const QJsonValue &value : collectionArray) { - if (value.isObject()) { - const QJsonObject object = value.toObject(); - const QStringList headers = object.toVariantMap().keys(); - for (const QString &header : headers) - result.insert(header); - } - } - - return result.values(); -} -} // namespace - -namespace QmlDesigner { - -SingleCollectionModel::SingleCollectionModel(QObject *parent) - : QAbstractTableModel(parent) -{} - -int SingleCollectionModel::rowCount([[maybe_unused]] const QModelIndex &parent) const -{ - return m_currentCollection.rows(); -} - -int SingleCollectionModel::columnCount([[maybe_unused]] const QModelIndex &parent) const -{ - return m_currentCollection.columns(); -} - -QVariant SingleCollectionModel::data(const QModelIndex &index, int) const -{ - if (!index.isValid()) - return {}; - return m_currentCollection.data(index.row(), index.column()); -} - -bool SingleCollectionModel::setData(const QModelIndex &, const QVariant &, int) -{ - return false; -} - -Qt::ItemFlags SingleCollectionModel::flags(const QModelIndex &index) const -{ - if (!index.isValid()) - return {}; - - return {Qt::ItemIsSelectable | Qt::ItemIsEnabled}; -} - -QVariant SingleCollectionModel::headerData(int section, - Qt::Orientation orientation, - [[maybe_unused]] int role) const -{ - if (orientation == Qt::Horizontal) - return m_currentCollection.headerAt(section); - - return {}; -} - -void SingleCollectionModel::loadCollection(const ModelNode &sourceNode, const QString &collection) -{ - QString fileName = sourceNode.variantProperty(CollectionEditor::SOURCEFILE_PROPERTY).value().toString(); - - CollectionReference newReference{sourceNode, collection}; - bool alreadyOpen = m_openedCollections.contains(newReference); - - if (alreadyOpen) { - if (m_currentCollection.reference() != newReference) { - beginResetModel(); - switchToCollection(newReference); - endResetModel(); - } - } else { - switchToCollection(newReference); - if (sourceNode.type() == CollectionEditor::JSONCOLLECTIONMODEL_TYPENAME) - loadJsonCollection(fileName, collection); - else if (sourceNode.type() == CollectionEditor::CSVCOLLECTIONMODEL_TYPENAME) - loadCsvCollection(fileName, collection); - } -} - -void SingleCollectionModel::switchToCollection(const CollectionReference &collection) -{ - if (m_currentCollection.reference() == collection) - return; - - closeCurrentCollectionIfSaved(); - - if (!m_openedCollections.contains(collection)) - m_openedCollections.insert(collection, CollectionDetails(collection)); - - m_currentCollection = m_openedCollections.value(collection); - - setCollectionName(collection.name); -} - -void SingleCollectionModel::closeCollectionIfSaved(const CollectionReference &collection) -{ - if (!m_openedCollections.contains(collection)) - return; - - const CollectionDetails &collectionDetails = m_openedCollections.value(collection); - - if (!collectionDetails.isChanged()) - m_openedCollections.remove(collection); - - m_currentCollection = CollectionDetails{}; -} - -void SingleCollectionModel::closeCurrentCollectionIfSaved() -{ - if (m_currentCollection.isValid()) - closeCollectionIfSaved(m_currentCollection.reference()); -} - -void SingleCollectionModel::loadJsonCollection(const QString &source, const QString &collection) -{ - using CollectionEditor::SourceFormat; - - QFile sourceFile(source); - QJsonArray collectionNodes; - bool jsonFileIsOk = false; - if (sourceFile.open(QFile::ReadOnly)) { - QJsonParseError jpe; - QJsonDocument document = QJsonDocument::fromJson(sourceFile.readAll(), &jpe); - if (jpe.error == QJsonParseError::NoError) { - jsonFileIsOk = true; - if (document.isObject()) { - QJsonObject collectionMap = document.object(); - if (collectionMap.contains(collection)) { - QJsonValue collectionVal = collectionMap.value(collection); - if (collectionVal.isArray()) - collectionNodes = collectionVal.toArray(); - else - collectionNodes.append(collectionVal); - } - } - } - } - - if (collectionNodes.isEmpty()) { - closeCurrentCollectionIfSaved(); - endResetModel(); - return; - }; - - QList elements; - for (const QJsonValue &value : std::as_const(collectionNodes)) { - if (value.isObject()) { - QJsonObject object = value.toObject(); - elements.append(object); - } - } - - SourceFormat sourceFormat = jsonFileIsOk ? SourceFormat::Json : SourceFormat::Unknown; - - beginResetModel(); - m_currentCollection.resetDetails(getJsonHeaders(collectionNodes), elements, sourceFormat); - endResetModel(); -} - -void SingleCollectionModel::loadCsvCollection(const QString &source, - [[maybe_unused]] const QString &collectionName) -{ - using CollectionEditor::SourceFormat; - - QFile sourceFile(source); - QStringList headers; - QList elements; - bool csvFileIsOk = false; - - if (sourceFile.open(QFile::ReadOnly)) { - QTextStream stream(&sourceFile); - - if (!stream.atEnd()) - headers = stream.readLine().split(','); - - if (!headers.isEmpty()) { - while (!stream.atEnd()) { - const QStringList recordDataList = stream.readLine().split(','); - int column = -1; - QJsonObject recordData; - for (const QString &cellData : recordDataList) { - if (++column == headers.size()) - break; - recordData.insert(headers.at(column), cellData); - } - if (recordData.count()) - elements.append(recordData); - } - csvFileIsOk = true; - } - } - - SourceFormat sourceFormat = csvFileIsOk ? SourceFormat::Csv : SourceFormat::Unknown; - - beginResetModel(); - m_currentCollection.resetDetails(headers, elements, sourceFormat); - endResetModel(); -} - -void SingleCollectionModel::setCollectionName(const QString &newCollectionName) -{ - if (m_collectionName != newCollectionName) { - m_collectionName = newCollectionName; - emit this->collectionNameChanged(m_collectionName); - } -} - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/singlecollectionmodel.h b/src/plugins/qmldesigner/components/collectioneditor/singlecollectionmodel.h deleted file mode 100644 index a545f4b0e78..00000000000 --- a/src/plugins/qmldesigner/components/collectioneditor/singlecollectionmodel.h +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "collectiondetails.h" - -#include -#include - -namespace QmlDesigner { - -class ModelNode; - -class SingleCollectionModel : public QAbstractTableModel -{ - Q_OBJECT - - Q_PROPERTY(QString collectionName MEMBER m_collectionName NOTIFY collectionNameChanged) - -public: - explicit SingleCollectionModel(QObject *parent = nullptr); - - int rowCount(const QModelIndex &parent) const override; - int columnCount(const QModelIndex &parent) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - Qt::ItemFlags flags(const QModelIndex &index) const override; - QVariant headerData(int section, - Qt::Orientation orientation, - int role = Qt::DisplayRole) const override; - - void loadCollection(const ModelNode &sourceNode, const QString &collection); - -signals: - void collectionNameChanged(const QString &collectionName); - -private: - void switchToCollection(const CollectionReference &collection); - void closeCollectionIfSaved(const CollectionReference &collection); - void closeCurrentCollectionIfSaved(); - void setCollectionName(const QString &newCollectionName); - void loadJsonCollection(const QString &source, const QString &collection); - void loadCsvCollection(const QString &source, const QString &collectionName); - - QHash m_openedCollections; - CollectionDetails m_currentCollection; - - QString m_collectionName; -}; - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/changestyleaction.cpp b/src/plugins/qmldesigner/components/componentcore/changestyleaction.cpp index 6ef8bbb6c94..aa386929400 100644 --- a/src/plugins/qmldesigner/components/componentcore/changestyleaction.cpp +++ b/src/plugins/qmldesigner/components/componentcore/changestyleaction.cpp @@ -6,6 +6,9 @@ #include #include +#include + +#include #include #include @@ -14,14 +17,31 @@ namespace QmlDesigner { static QString styleConfigFileName(const QString &qmlFileName) { - ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectManager::projectForFile(Utils::FilePath::fromString(qmlFileName)); + ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectManager::projectForFile( + Utils::FilePath::fromString(qmlFileName)); - if (currentProject) { - const QList fileNames = currentProject->files( - ProjectExplorer::Project::SourceFiles); - for (const Utils::FilePath &fileName : fileNames) - if (fileName.endsWith("qtquickcontrols2.conf")) - return fileName.toString(); + if (currentProject && currentProject->activeTarget()) { + const auto *qmlBuild = qobject_cast( + currentProject->activeTarget()->buildSystem()); + + if (qmlBuild) { + const auto &environment = qmlBuild->environment(); + const auto &envVar = std::find_if( + std::begin(environment), std::end(environment), [](const auto &envVar) { + return (envVar.name == u"QT_QUICK_CONTROLS_CONF" + && envVar.operation != Utils::EnvironmentItem::SetDisabled); + }); + if (envVar != std::end(environment)) { + const auto &fileNames = currentProject->files(ProjectExplorer::Project::SourceFiles); + const auto &foundFile = std::find_if(std::begin(fileNames), + std::end(fileNames), + [&](const auto &fileName) { + return fileName.fileName() == envVar->value; + }); + if (foundFile != std::end(fileNames)) + return foundFile->toString(); + } + } } return QString(); @@ -29,9 +49,6 @@ static QString styleConfigFileName(const QString &qmlFileName) ChangeStyleWidgetAction::ChangeStyleWidgetAction(QObject *parent) : QWidgetAction(parent) { - // The Default style was renamed to Basic in Qt 6. In Qt 6, "Default" - // will result in a platform-specific style being chosen. - items = getAllStyleItems(); } @@ -48,7 +65,6 @@ const QList ChangeStyleWidgetAction::styleItems() const QList ChangeStyleWidgetAction::getAllStyleItems() { QList items = {{"Basic", "Basic", {}}, - {"Default", "Default", {}}, {"Fusion", "Fusion", {}}, {"Imagine", "Imagine", {}}, {"Material Light", "Material", "Light"}, @@ -62,6 +78,11 @@ QList ChangeStyleWidgetAction::getAllStyleItems() if (Utils::HostOsInfo::isWindowsHost()) items.append({"Windows", "Windows", {}}); + if (DesignerMcuManager::instance().isMCUProject()) + items.append({"MCUDefaultStyle", "MCUDefaultStyle", {}}); + + //what if we have a custom style set in .conf? + return items; } @@ -159,7 +180,6 @@ QWidget *ChangeStyleWidgetAction::createWidget(QWidget *parent) comboBox->setCurrentIndex(0); } else if (DesignerMcuManager::instance().isMCUProject()) { comboBox->setDisabled(true); - //TODO: add tooltip regarding MCU limitations, however we are behind string freeze comboBox->setEditText(style); } else { comboBox->setDisabled(false); diff --git a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h index 5644648336f..44015d59fce 100644 --- a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h +++ b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h @@ -64,6 +64,7 @@ const char layoutGridLayoutCommandId[] = "LayoutGridLayout"; const char layoutFillWidthCommandId[] = "LayoutFillWidth"; const char layoutFillHeightCommandId[] = "LayoutFillHeight"; const char goIntoComponentCommandId[] = "GoIntoComponent"; +const char jumpToCodeCommandId[] = "JumpToCode"; const char mergeTemplateCommandId[] = "MergeTemplate"; const char goToImplementationCommandId[] = "GoToImplementation"; const char makeComponentCommandId[] = "MakeComponent"; @@ -120,6 +121,7 @@ const char copyFormatDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", const char applyFormatDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Apply Formatting"); const char enterComponentDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit Component"); +const char JumpToCodeDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Jump to the Code"); const char mergeTemplateDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Merge with Template"); const char goToImplementationDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Go to Implementation"); const char makeComponentDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Create Component"); @@ -220,6 +222,7 @@ enum PrioritiesEnum : int { EventListCategory, /******** Section *****************************/ AdditionsSection = 4000, + JumpToCode, EditAnnotations, AddMouseArea, MergeWithTemplate, diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp index 4f800bf939f..6ad7ed994b7 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -15,6 +15,7 @@ #include "qmleditormenu.h" #include "rewritingexception.h" #include +#include #include #include #include @@ -740,11 +741,23 @@ public: activeSignalHandlerGroup->addMenu(editSlotGroup); } - //add an action to open Connection Form from here: + ActionTemplate *openEditorAction = new ActionTemplate( + (propertyName + "OpenEditorId").toLatin1(), + QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit the Connection")), + [=](const SelectionContext &) { + signalHandler.view() + ->emitCustomNotification(EditConnectionNotification, + {signalHandler.parentModelNode()}, + {signalHandler.name()}); + //ActionEditor::invokeEditor(signalHandler, removeSignal); + }); + + activeSignalHandlerGroup->addAction(openEditorAction); ActionTemplate *removeSignalHandlerAction = new ActionTemplate( (propertyName + "RemoveSignalHandlerId").toLatin1(), - QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Remove This Handler")), + QString( + QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Remove the Connection")), [signalHandler](const SelectionContext &) { signalHandler.parentModelNode().view()->executeInTransaction( "ConnectionsModelNodeActionGroup::" @@ -761,7 +774,7 @@ public: //singular add connection: QMenu *addConnection = new QmlEditorMenu(QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", - "Add Signal Handler")), + "Add new Connection")), menu()); for (const auto &signalStr : signalsList) { @@ -809,7 +822,15 @@ public: } } - //add an action to open Connection Form from here + ActionTemplate *openEditorAction = new ActionTemplate( + (signalStr + "OpenEditorId").toLatin1(), + QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Add new Connection")), + [=](const SelectionContext &) { + currentNode.view()->emitCustomNotification(AddConnectionNotification, + {currentNode}, + {signalStr}); + }); + newSignal->addAction(openEditorAction); addConnection->addMenu(newSignal); } @@ -1876,15 +1897,23 @@ void DesignerActionManager::createDefaultDesignerActions() addDesignerAction(new SeparatorDesignerAction(rootCategory, Priorities::ViewOprionsSection)); addDesignerAction(new SeparatorDesignerAction(rootCategory, Priorities::CustomActionsSection)); - addDesignerAction(new ModelNodeContextMenuAction( - goIntoComponentCommandId, - enterComponentDisplayName, - contextIcon(DesignerIcons::EnterComponentIcon), - rootCategory, - QKeySequence(Qt::Key_F2), - Priorities::ComponentActions + 2, - &goIntoComponentOperation, - &selectionIsComponent)); + addDesignerAction(new ModelNodeContextMenuAction(goIntoComponentCommandId, + enterComponentDisplayName, + contextIcon(DesignerIcons::EnterComponentIcon), + rootCategory, + QKeySequence(Qt::Key_F2), + Priorities::ComponentActions + 2, + &goIntoComponentOperation, + &selectionIsComponent)); + + addDesignerAction(new ModelNodeContextMenuAction(jumpToCodeCommandId, + JumpToCodeDisplayName, + contextIcon(DesignerIcons::JumpToCodeIcon), + rootCategory, + QKeySequence(Qt::Key_F4), + Priorities::JumpToCode, + &jumpToCodeOperation, + &singleSelection)); addDesignerAction(new ModelNodeContextMenuAction( editAnnotationsCommandId, diff --git a/src/plugins/qmldesigner/components/componentcore/designericons.h b/src/plugins/qmldesigner/components/componentcore/designericons.h index 6539462f8f7..3cb5a1e9731 100644 --- a/src/plugins/qmldesigner/components/componentcore/designericons.h +++ b/src/plugins/qmldesigner/components/componentcore/designericons.h @@ -54,6 +54,7 @@ public: AnnotationIcon, ArrangeIcon, BackspaceIcon, + BakeLightIcon, CameraIcon, CameraOrthographicIcon, CameraPerspectiveIcon, @@ -67,6 +68,7 @@ public: EditIcon, EditLightIcon, EnterComponentIcon, + JumpToCodeIcon, EventListIcon, FitSelectedIcon, FitToViewIcon, @@ -103,6 +105,7 @@ public: SimpleCheckIcon, SnappingIcon, SnappingConfIcon, + SplitViewIcon, TimelineIcon, ToggleGroupIcon, VisibilityIcon diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp index 9aac781e8ba..ffa51710f6f 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp @@ -12,20 +12,22 @@ #include "addsignalhandlerdialog.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 @@ -1673,7 +1675,7 @@ Utils::FilePath getEffectsImportDirectory() if (!effectsPath.exists()) { QDir dir(projectPath.toString()); - dir.mkpath(defaultDir); + dir.mkpath(effectsPath.toString()); } return effectsPath; @@ -1681,6 +1683,12 @@ Utils::FilePath getEffectsImportDirectory() QString getEffectsDefaultDirectory(const QString &defaultDir) { + if (defaultDir.isEmpty()) { + return Utils::FilePath::fromString(getAssetDefaultDirectory( + "effects", + QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath().toString())).toString(); + } + return getAssetDefaultDirectory("effects", defaultDir); } @@ -1724,9 +1732,422 @@ bool validateEffect(const QString &effectPath) Utils::FilePath getImagesDefaultDirectory() { - return Utils::FilePath::fromString( - getAssetDefaultDirectory( - "images", QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath().toString())); + return Utils::FilePath::fromString(getAssetDefaultDirectory( + "images", + QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath().toString())); +} + +void jumpToCode(const ModelNode &modelNode) +{ + QmlDesignerPlugin::instance()->viewManager().jumpToCodeInTextEditor(modelNode); +} + +void jumpToCodeOperation(const SelectionContext &selectionState) +{ + jumpToCode(selectionState.currentSingleSelectedNode()); +} + +static bool moveNodeToParent(const NodeAbstractProperty &targetProperty, const ModelNode &node) +{ + NodeAbstractProperty parentProp = targetProperty.parentProperty(); + if (parentProp.isValid()) { + ModelNode targetModel = parentProp.parentModelNode(); + parentProp.reparentHere(node); + return true; + } + return false; +} + +ModelNode createTextureNode(const NodeAbstractProperty &targetProp, const QString &imagePath) +{ + AbstractView *view = targetProp.view(); + QTC_ASSERT(view, return {}); + + if (targetProp.isValid()) { + // create a texture item lib + ItemLibraryEntry itemLibraryEntry; + itemLibraryEntry.setName("Texture"); + itemLibraryEntry.setType("QtQuick3D.Texture", 1, 0); + + // set texture source + PropertyName prop = "source"; + QString type = "QUrl"; + QVariant val = imagePath; + itemLibraryEntry.addProperty(prop, type, val); + + // create a texture + ModelNode newModelNode = QmlItemNode::createQmlObjectNode(view, + itemLibraryEntry, + {}, + targetProp, + false); + + // Rename the node based on source image + QFileInfo fi(imagePath); + newModelNode.setIdWithoutRefactoring( + view->model()->generateNewId(fi.baseName(), "textureImage")); + return newModelNode; + } + return {}; +} + +bool dropAsImage3dTexture(const ModelNode &targetNode, + const NodeAbstractProperty &targetProp, + const QString &imagePath, + ModelNode &newNode, + bool &outMoveNodesAfter) +{ + AbstractView *view = targetNode.view(); + QTC_ASSERT(view, return {}); + + auto bindToProperty = [&](const PropertyName &propName, bool sibling) { + view->executeInTransaction("NavigatorTreeModel::dropAsImage3dTexture", [&] { + newNode = createTextureNode(targetProp, imagePath); + if (newNode.isValid()) { + targetNode.bindingProperty(propName).setExpression(newNode.validId()); + + // If dropping an image on e.g. TextureInput, create a texture on the same level as + // target, as the target doesn't support Texture children (QTBUG-86219) + if (sibling) + outMoveNodesAfter = !moveNodeToParent(targetProp, newNode); + } + }); + }; + + if (targetNode.metaInfo().isQtQuick3DDefaultMaterial() + || targetNode.metaInfo().isQtQuick3DPrincipledMaterial() + || targetNode.metaInfo().isQtQuick3DSpecularGlossyMaterial()) { + // if dropping an image on a material, create a texture instead of image + // Show texture property selection dialog + auto dialog = ChooseFromPropertyListDialog::createIfNeeded(targetNode, + view->model()->metaInfo( + "QtQuick3D.Texture"), + Core::ICore::dialogParent()); + if (!dialog) + return false; + + dialog->exec(); + + if (dialog->result() == QDialog::Accepted) { + view->executeInTransaction("NavigatorTreeModel::dropAsImage3dTexture", [&] { + newNode = createTextureNode(targetProp, imagePath); + if (newNode.isValid()) // Automatically set the texture to selected property + targetNode.bindingProperty(dialog->selectedProperty()) + .setExpression(newNode.validId()); + }); + } + + delete dialog; + return true; + } else if (targetNode.metaInfo().isQtQuick3DTextureInput()) { + bindToProperty("texture", true); + return newNode.isValid(); + } else if (targetNode.metaInfo().isQtQuick3DParticles3DSpriteParticle3D()) { + bindToProperty("sprite", false); + return newNode.isValid(); + } else if (targetNode.metaInfo().isQtQuick3DSceneEnvironment()) { + bindToProperty("lightProbe", false); + return newNode.isValid(); + } else if (targetNode.metaInfo().isQtQuick3DTexture()) { + // if dropping an image on an existing texture, set the source + targetNode.variantProperty("source").setValue(imagePath); + return true; + } else if (targetNode.metaInfo().isQtQuick3DModel()) { + QTimer::singleShot(0, view, [targetNode, imagePath, view]() { + if (view && targetNode.isValid()) { + // To MaterialBrowserView. Done async to avoid custom notification in transaction + view->emitCustomNotification("apply_asset_to_model3D", + {targetNode}, + {DocumentManager::currentFilePath() + .absolutePath() + .pathAppended(imagePath) + .cleanPath() + .toString()}); + } + }); + return true; + } + + return false; +} + +ModelNode handleItemLibraryEffectDrop(const QString &effectPath, const ModelNode &targetNode) +{ + AbstractView *view = targetNode.view(); + QTC_ASSERT(view, return {}); + + ModelNode newModelNode; + + if ((targetNode.hasParentProperty() && targetNode.parentProperty().name() == "layer.effect") + || !targetNode.metaInfo().isQtQuickItem()) { + return newModelNode; + } + + if (ModelNodeOperations::validateEffect(effectPath)) { + bool layerEffect = ModelNodeOperations::useLayerEffect(); + newModelNode = QmlItemNode::createQmlItemNodeForEffect(view, + targetNode, + effectPath, + layerEffect); + } + + return newModelNode; +} + +void handleTextureDrop(const QMimeData *mimeData, const ModelNode &targetModelNode) +{ + AbstractView *view = targetModelNode.view(); + QTC_ASSERT(view, return ); + + QmlObjectNode targetNode(targetModelNode); + + if (!targetNode.isValid()) + return; + + qint32 internalId = mimeData->data(Constants::MIME_TYPE_TEXTURE).toInt(); + ModelNode texNode = view->modelNodeForInternalId(internalId); + QTC_ASSERT(texNode.isValid(), return ); + + if (targetNode.modelNode().metaInfo().isQtQuick3DModel()) { + view->emitCustomNotification("apply_texture_to_model3D", {targetNode, texNode}); + } else { + auto *dialog = ChooseFromPropertyListDialog::createIfNeeded(targetNode, + texNode, + Core::ICore::dialogParent()); + if (dialog) { + bool soloProperty = dialog->isSoloProperty(); + if (!soloProperty) + dialog->exec(); + + if (soloProperty || dialog->result() == QDialog::Accepted) + targetNode.setBindingProperty(dialog->selectedProperty(), texNode.id()); + + delete dialog; + } + } +} + +void handleMaterialDrop(const QMimeData *mimeData, const ModelNode &targetNode) +{ + AbstractView *view = targetNode.view(); + QTC_ASSERT(view, return ); + + if (!targetNode.metaInfo().isQtQuick3DModel()) + return; + + qint32 internalId = mimeData->data(Constants::MIME_TYPE_MATERIAL).toInt(); + ModelNode matNode = view->modelNodeForInternalId(internalId); + + view->executeInTransaction(__FUNCTION__, [&] { + MaterialUtils::assignMaterialTo3dModel(view, targetNode, matNode); + }); +} + +ModelNode handleItemLibraryImageDrop(const QString &imagePath, + NodeAbstractProperty targetProperty, + const ModelNode &targetNode, + bool &outMoveNodesAfter) +{ + AbstractView *view = targetNode.view(); + QTC_ASSERT(view, return {}); + + const QString imagePathRelative + = DocumentManager::currentFilePath().toFileInfo().dir().relativeFilePath( + imagePath); // relative to .ui.qml file + + ModelNode newModelNode; + + if (!dropAsImage3dTexture(targetNode, + targetProperty, + imagePathRelative, + newModelNode, + outMoveNodesAfter)) { + if (targetNode.metaInfo().isQtQuickImage() || targetNode.metaInfo().isQtQuickBorderImage()) { + // if dropping an image on an existing image, set the source + targetNode.variantProperty("source").setValue(imagePathRelative); + } else { + // create an image + QmlItemNode newItemNode = QmlItemNode::createQmlItemNodeFromImage(view, + imagePath, + QPointF(), + targetProperty, + false); + if (NodeHints::fromModelNode(targetProperty.parentModelNode()) + .canBeContainerFor(newItemNode.modelNode())) { + newModelNode = newItemNode.modelNode(); + } else { + newItemNode.destroy(); + } + } + } + + return newModelNode; +} + +ModelNode handleItemLibraryFontDrop(const QString &fontFamily, + NodeAbstractProperty targetProperty, + const ModelNode &targetNode) +{ + AbstractView *view = targetNode.view(); + QTC_ASSERT(view, return {}); + + ModelNode newModelNode; + + if (targetNode.metaInfo().isQtQuickText()) { + // if dropping into an existing Text, update font + targetNode.variantProperty("font.family").setValue(fontFamily); + } else { + // create a Text node + QmlItemNode newItemNode = QmlItemNode::createQmlItemNodeFromFont(view, + fontFamily, + QPointF(), + targetProperty, + false); + if (NodeHints::fromModelNode(targetProperty.parentModelNode()) + .canBeContainerFor(newItemNode.modelNode())) { + newModelNode = newItemNode.modelNode(); + } else { + newItemNode.destroy(); + } + } + + return newModelNode; +} + +ModelNode handleItemLibraryShaderDrop(const QString &shaderPath, + bool isFragShader, + NodeAbstractProperty targetProperty, + const ModelNode &targetNode, + bool &outMoveNodesAfter) +{ + AbstractView *view = targetNode.view(); + QTC_ASSERT(view, return {}); + + ModelNode newModelNode; + + const QString relPath = DocumentManager::currentFilePath().toFileInfo().dir().relativeFilePath( + shaderPath); + + if (targetNode.metaInfo().isQtQuick3DShader()) { + // if dropping into an existing Shader, update + targetNode.variantProperty("stage").setEnumeration(isFragShader ? "Shader.Fragment" + : "Shader.Vertex"); + targetNode.variantProperty("shader").setValue(relPath); + } else { + view->executeInTransaction("NavigatorTreeModel::handleItemLibraryShaderDrop", [&] { + // create a new Shader + ItemLibraryEntry itemLibraryEntry; + itemLibraryEntry.setName("Shader"); + itemLibraryEntry.setType("QtQuick3D.Shader", 1, 0); + + // set shader properties + PropertyName prop = "shader"; + QString type = "QUrl"; + QVariant val = relPath; + itemLibraryEntry.addProperty(prop, type, val); + prop = "stage"; + type = "enum"; + val = isFragShader ? "Shader.Fragment" : "Shader.Vertex"; + itemLibraryEntry.addProperty(prop, type, val); + + // create a texture + newModelNode = QmlItemNode::createQmlObjectNode(view, + itemLibraryEntry, + {}, + targetProperty, + false); + + // Rename the node based on shader source + QFileInfo fi(relPath); + newModelNode.setIdWithoutRefactoring( + view->model()->generateNewId(fi.baseName(), "shader")); + // Passes can't have children, so move shader node under parent + if (targetProperty.parentModelNode().metaInfo().isQtQuick3DPass()) { + BindingProperty listProp = targetNode.bindingProperty("shaders"); + listProp.addModelNodeToArray(newModelNode); + outMoveNodesAfter = !moveNodeToParent(targetProperty, newModelNode); + } + }); + } + + return newModelNode; +} + +ModelNode handleItemLibrarySoundDrop(const QString &soundPath, + NodeAbstractProperty targetProperty, + const ModelNode &targetNode) +{ + AbstractView *view = targetNode.view(); + QTC_ASSERT(view, return {}); + + ModelNode newModelNode; + + const QString relPath = DocumentManager::currentFilePath().toFileInfo().dir().relativeFilePath( + soundPath); + + if (targetNode.metaInfo().isQtMultimediaSoundEffect()) { + // if dropping into on an existing SoundEffect, update + targetNode.variantProperty("source").setValue(relPath); + } else { + // create a new SoundEffect + ItemLibraryEntry itemLibraryEntry; + itemLibraryEntry.setName("SoundEffect"); + itemLibraryEntry.setType("QtMultimedia.SoundEffect", 1, 0); + + // set source property + PropertyName prop = "source"; + QString type = "QUrl"; + QVariant val = relPath; + itemLibraryEntry.addProperty(prop, type, val); + + // create a texture + newModelNode = QmlItemNode::createQmlObjectNode(view, + itemLibraryEntry, + {}, + targetProperty, + false); + + // Rename the node based on source + QFileInfo fi(relPath); + newModelNode.setIdWithoutRefactoring( + view->model()->generateNewId(fi.baseName(), "soundEffect")); + } + + return newModelNode; +} + +ModelNode handleItemLibraryTexture3dDrop(const QString &tex3DPath, + NodeAbstractProperty targetProperty, + const ModelNode &targetNode, + bool &outMoveNodesAfter) +{ + AbstractView *view = targetNode.view(); + QTC_ASSERT(view, return {}); + + Import import = Import::createLibraryImport(QStringLiteral("QtQuick3D")); + if (!view->model()->hasImport(import, true, true)) + return {}; + + const QString imagePath = DocumentManager::currentFilePath().toFileInfo().dir().relativeFilePath( + tex3DPath); // relative to qml file + + ModelNode newModelNode; + + if (!dropAsImage3dTexture(targetNode, + targetProperty, + imagePath, + newModelNode, + outMoveNodesAfter)) { + view->executeInTransaction("NavigatorTreeModel::handleItemLibraryTexture3dDrop", [&] { + // create a standalone Texture3D at drop location + newModelNode = createTextureNode(targetProperty, imagePath); + if (!NodeHints::fromModelNode(targetProperty.parentModelNode()) + .canBeContainerFor(newModelNode)) + newModelNode.destroy(); + }); + } + + return newModelNode; } } // namespace ModelNodeOperations diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h index 94cd54c1ea3..7d7a985283a 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h @@ -56,6 +56,7 @@ private: namespace ModelNodeOperations { bool goIntoComponent(const ModelNode &modelNode); +void jumpToCode(const ModelNode &modelNode); void select(const SelectionContext &selectionState); void deSelect(const SelectionContext &selectionState); @@ -75,6 +76,7 @@ void setFillHeight(const SelectionContext &selectionState); void resetSize(const SelectionContext &selectionState); void resetPosition(const SelectionContext &selectionState); void goIntoComponentOperation(const SelectionContext &selectionState); +void jumpToCodeOperation(const SelectionContext &selectionState); void setId(const SelectionContext &selectionState); void resetZ(const SelectionContext &selectionState); void reverse(const SelectionContext &selectionState); @@ -123,7 +125,7 @@ void openSignalDialog(const SelectionContext &selectionContext); void updateImported3DAsset(const SelectionContext &selectionContext); QMLDESIGNERCOMPONENTS_EXPORT Utils::FilePath getEffectsImportDirectory(); -QMLDESIGNERCOMPONENTS_EXPORT QString getEffectsDefaultDirectory(const QString &defaultDir); +QMLDESIGNERCOMPONENTS_EXPORT QString getEffectsDefaultDirectory(const QString &defaultDir = {}); void openEffectMaker(const QString &filePath); QString getEffectIcon(const QString &effectPath); bool useLayerEffect(); @@ -131,6 +133,30 @@ bool validateEffect(const QString &effectPath); Utils::FilePath getImagesDefaultDirectory(); +//Item Library and Assets related drop operations +ModelNode handleItemLibraryEffectDrop(const QString &effectPath, const ModelNode &targetNode); +void handleTextureDrop(const QMimeData *mimeData, const ModelNode &targetModelNode); +void handleMaterialDrop(const QMimeData *mimeData, const ModelNode &targetNode); +ModelNode handleItemLibraryImageDrop(const QString &imagePath, + NodeAbstractProperty targetProperty, + const ModelNode &targetNode, + bool &outMoveNodesAfter); +ModelNode handleItemLibraryFontDrop(const QString &fontFamily, + NodeAbstractProperty targetProperty, + const ModelNode &targetNode); +ModelNode handleItemLibraryShaderDrop(const QString &shaderPath, + bool isFragShader, + NodeAbstractProperty targetProperty, + const ModelNode &targetNode, + bool &outMoveNodesAfter); +ModelNode handleItemLibrarySoundDrop(const QString &soundPath, + NodeAbstractProperty targetProperty, + const ModelNode &targetNode); +ModelNode handleItemLibraryTexture3dDrop(const QString &tex3DPath, + NodeAbstractProperty targetProperty, + const ModelNode &targetNode, + bool &outMoveNodesAfter); + // ModelNodePreviewImageOperations QVariant previewImageDataForGenericNode(const ModelNode &modelNode); QVariant previewImageDataForImageNode(const ModelNode &modelNode); diff --git a/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp b/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp index 0fe7e534acb..27dd56a685b 100644 --- a/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp +++ b/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp @@ -63,7 +63,13 @@ bool Navigation2dFilter::gestureEvent(QGestureEvent *event) bool Navigation2dFilter::wheelEvent(QWheelEvent *event) { - if (!event->modifiers().testFlag(Qt::ControlModifier)) { + bool isMac = Utils::HostOsInfo::isMacHost(); + bool controlPressed = event->modifiers().testFlag(Qt::ControlModifier); + + if (isMac) + controlPressed = controlPressed || event->modifiers().testFlag(Qt::MetaModifier); + + if (!controlPressed) { if (event->source() == Qt::MouseEventSynthesizedBySystem) { emit panChanged(QPointF(event->pixelDelta())); event->accept(); @@ -77,7 +83,6 @@ bool Navigation2dFilter::wheelEvent(QWheelEvent *event) if (zoomChangedConnected) { double speed = 1.0 / 200.0; - bool isMac = Utils::HostOsInfo::isMacHost(); if (QPointF delta = event->pixelDelta(); !delta.isNull() && isMac) { double dist = std::abs(delta.x()) > std::abs(delta.y()) ? -delta.x() : delta.y(); emit zoomChanged(dist * speed, event->position()); diff --git a/src/plugins/qmldesigner/components/componentcore/svgpasteaction.cpp b/src/plugins/qmldesigner/components/componentcore/svgpasteaction.cpp index 738b1affedd..7bcd6050869 100644 --- a/src/plugins/qmldesigner/components/componentcore/svgpasteaction.cpp +++ b/src/plugins/qmldesigner/components/componentcore/svgpasteaction.cpp @@ -3,9 +3,9 @@ #include "svgpasteaction.h" -#include -#include +#include #include +#include #include diff --git a/src/plugins/qmldesigner/components/componentcore/theme.h b/src/plugins/qmldesigner/components/componentcore/theme.h index 6edd3338f5c..c18f5a9b91d 100644 --- a/src/plugins/qmldesigner/components/componentcore/theme.h +++ b/src/plugins/qmldesigner/components/componentcore/theme.h @@ -26,6 +26,7 @@ public: addColumnAfter, addColumnBefore, addFile, + addGroup_medium, addRowAfter, addRowBefore, addTable, @@ -51,6 +52,7 @@ public: alignToObject_small, alignToView_medium, alignTop, + alphabetical_medium, anchorBaseline, anchorBottom, anchorFill, @@ -69,9 +71,12 @@ public: arrange_small, arrow_small, assign, + assignTo, + assignTo_medium, attach_medium, back_medium, backspace_small, + bakeLights_medium, bevelAll, bevelCorner, bezier_medium, @@ -92,6 +97,7 @@ public: colorPopupClose, colorSelection_medium, columnsAndRows, + comboBox_medium, cone_medium, cone_small, connection_small, @@ -104,6 +110,7 @@ public: cornersAll, createComponent_large, createComponent_small, + createObject_medium, create_medium, create_small, cube_medium, @@ -122,6 +129,7 @@ public: delete_medium, delete_small, deletecolumn_medium, + deletepermanently_medium, deleterow_medium, designMode_large, detach, @@ -141,11 +149,7 @@ public: download, downloadUnavailable, downloadUpdate, - downloadcsv_large, - downloadcsv_medium, downloaded, - downloadjson_large, - downloadjson_medium, dragmarks, duplicate_small, edit, @@ -186,6 +190,8 @@ public: infinity, invisible_medium, invisible_small, + jumpToCode_medium, + jumpToCode_small, keyframe, languageList_medium, layouts_small, @@ -194,6 +200,7 @@ public: linkTriangle, linked, listView, + listView_medium, list_medium, localOrient_medium, lockOff, @@ -229,6 +236,7 @@ public: pasteStyle, paste_small, pause, + pause_medium, perspectiveCam_medium, perspectiveCam_small, pin, @@ -245,6 +253,7 @@ public: promote, properties_medium, readOnly, + recent_medium, recordFill_medium, recordOutline_medium, redo, @@ -299,11 +308,13 @@ public: sphere_small, splitColumns, splitRows, + splitScreen_medium, spotLight_small, stackedContainer_small, startNode, step_medium, stop_medium, + tableView_medium, testIcon, textAlignBottom, textAlignCenter, @@ -351,14 +362,11 @@ public: upDownSquare2, updateAvailable_medium, updateContent_medium, - uploadcsv_large, - uploadcsv_medium, - uploadjson_large, - uploadjson_medium, visibilityOff, visibilityOn, visible_medium, visible_small, + warning_medium, wildcard, wizardsAutomotive, wizardsDesktop, diff --git a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp index 26f9a974211..9ba4bfd26b1 100644 --- a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp @@ -31,6 +31,8 @@ #include +#include + #include #include #include @@ -201,7 +203,8 @@ QList ViewManager::standardViews() const &d->materialBrowserView, &d->textureEditorView, &d->statesEditorView, - &d->designerActionManagerView}; + &d->designerActionManagerView, + &d->collectionView}; if (QmlDesignerPlugin::instance() ->settings() @@ -209,9 +212,6 @@ QList ViewManager::standardViews() const .toBool()) list.append(&d->debugView); - if (qEnvironmentVariableIsSet("ENABLE_QDS_COLLECTIONVIEW")) - list.append(&d->collectionView); - #ifdef CHECK_LICENSE if (checkLicense() == FoundLicense::enterprise) list.append(&d->contentLibraryView); @@ -386,9 +386,7 @@ QList ViewManager::widgetInfos() const widgetInfoList.append(d->materialBrowserView.widgetInfo()); widgetInfoList.append(d->textureEditorView.widgetInfo()); widgetInfoList.append(d->statesEditorView.widgetInfo()); - - if (qEnvironmentVariableIsSet("ENABLE_QDS_COLLECTIONVIEW")) - widgetInfoList.append(d->collectionView.widgetInfo()); + widgetInfoList.append(d->collectionView.widgetInfo()); #ifdef CHECK_LICENSE if (checkLicense() == FoundLicense::enterprise) @@ -516,6 +514,15 @@ void ViewManager::enableStandardViews() attachViewsExceptRewriterAndComponetView(); } +void ViewManager::jumpToCodeInTextEditor(const ModelNode &modelNode) +{ + ADS::DockWidget *dockWidget = qobject_cast( + d->textEditorView.widgetInfo().widget->parentWidget()); + if (dockWidget) + dockWidget->toggleView(true); + d->textEditorView.jumpToModelNode(modelNode); +} + void ViewManager::addView(std::unique_ptr &&view) { d->additionalViews.push_back(std::move(view)); diff --git a/src/plugins/qmldesigner/components/componentcore/viewmanager.h b/src/plugins/qmldesigner/components/componentcore/viewmanager.h index a3cacbe907e..935ff54cfdb 100644 --- a/src/plugins/qmldesigner/components/componentcore/viewmanager.h +++ b/src/plugins/qmldesigner/components/componentcore/viewmanager.h @@ -89,6 +89,7 @@ public: void disableStandardViews(); void enableStandardViews(); + void jumpToCodeInTextEditor(const ModelNode &modelNode); QList views() const; private: // functions diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp index 5791b8ad6c6..dd6350f4e56 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp @@ -9,16 +9,18 @@ #include #include #include +#include #include #include #include #include +#include +#include #include #include #include #include -#include -#include +#include #include @@ -344,7 +346,7 @@ static PropertyName getFirstSignalForTarget(const NodeMetaInfo &target) return ret; } -void ConnectionModel::addConnection() +void ConnectionModel::addConnection(const PropertyName &signalName) { QmlDesignerPlugin::emitUsageStatistics(Constants::EVENT_CONNECTION_ADDED); @@ -356,10 +358,13 @@ void ConnectionModel::addConnection() if (nodeMetaInfo.isValid()) { ModelNode selectedNode = connectionView()->selectedModelNodes().constFirst(); - const PropertyName signalHandlerName = addOnToSignalName( - QString::fromUtf8(getFirstSignalForTarget( - selectedNode.metaInfo()))) - .toUtf8(); + + PropertyName signalHandlerName = signalName; + if (signalHandlerName.isEmpty()) { + signalHandlerName = addOnToSignalName(QString::fromUtf8(getFirstSignalForTarget( + selectedNode.metaInfo()))) + .toUtf8(); + } connectionView() ->executeInTransaction("ConnectionModel::addConnection", [=, &rootModelNode]() { @@ -918,6 +923,17 @@ void ConnectionModelBackendDelegate::update() QTC_ASSERT(model, return ); } +void ConnectionModelBackendDelegate::jumpToCode() +{ + ConnectionModel *model = qobject_cast(parent()); + + QTC_ASSERT(model, return ); + QTC_ASSERT(model->connectionView()->isAttached(), return ); + SignalHandlerProperty signalHandlerProperty = model->signalHandlerPropertyForRow(currentRow()); + + ModelNodeOperations::jumpToCode(signalHandlerProperty.parentModelNode()); +} + void ConnectionModelBackendDelegate::handleException() { QMessageBox::warning(nullptr, tr("Error"), m_exceptionError); @@ -2114,4 +2130,9 @@ void QmlDesigner::ConnectionModel::modelAboutToBeDetached() emit m_delegate->popupShouldClose(); } +void ConnectionModel::showPopup() +{ + emit m_delegate->popupShouldOpen(); +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h index d25d5aa2d3b..d45014914d4 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h @@ -54,7 +54,7 @@ public: QStringList getflowActionTriggerForRow(int row) const; ModelNode getTargetNodeForConnection(const ModelNode &connection) const; - void addConnection(); + void addConnection(const PropertyName &signalName = {}); void bindingPropertyChanged(const BindingProperty &bindingProperty); void variantPropertyChanged(const VariantProperty &variantProperty); @@ -74,6 +74,8 @@ public: void nodeAboutToBeRemoved(const ModelNode &removedNode); void modelAboutToBeDetached(); + void showPopup(); + signals: void currentIndexChanged(); @@ -293,6 +295,8 @@ public: void setCurrentRow(int i); void update(); + Q_INVOKABLE void jumpToCode(); + signals: void currentRowChanged(); void actionTypeChanged(); @@ -300,6 +304,7 @@ signals: void hasElseChanged(); void sourceChanged(); void popupShouldClose(); + void popupShouldOpen(); private: int currentRow() const; diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp index cd63231d5bd..ab010e3f355 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp @@ -11,13 +11,14 @@ #include "theme.h" #include +#include #include -#include -#include -#include #include #include #include +#include +#include +#include #include @@ -99,8 +100,7 @@ public: Theme::setupTheme(engine()); - setMinimumWidth(195); - setMinimumHeight(195); + setMinimumSize(QSize(195, 195)); // init the first load of the QML UI elements reloadQmlSource(); @@ -353,4 +353,30 @@ ConnectionView *ConnectionView::instance() return s_instance; } +void ConnectionView::customNotification(const AbstractView *, + const QString &identifier, + const QList &nodeList, + const QList &data) +{ + if (identifier == AddConnectionNotification) { + QTC_ASSERT(data.count() == 1, return ); + + const PropertyName name = data.first().toString().toUtf8(); + m_connectionModel->addConnection(name); + m_connectionModel->showPopup(); + } else if (identifier == EditConnectionNotification) { + QTC_ASSERT(nodeList.count() == 1, return ); + ModelNode modelNode = nodeList.first(); + + QTC_ASSERT(data.count() == 1, return ); + + const PropertyName name = data.first().toByteArray(); + + QTC_ASSERT(modelNode.hasSignalHandlerProperty(name), return ); + + m_connectionModel->selectProperty(modelNode.signalHandlerProperty(name)); + m_connectionModel->showPopup(); + } +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionview.h b/src/plugins/qmldesigner/components/connectioneditor/connectionview.h index 338d1fb14ed..12a53d43a9e 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionview.h +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionview.h @@ -72,6 +72,11 @@ public: static ConnectionView *instance(); + void customNotification(const AbstractView *view, + const QString &identifier, + const QList &nodeList, + const QList &data) override; + signals: void currentIndexChanged(); diff --git a/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.cpp index 073b8214927..3ae89a3185f 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.cpp @@ -494,7 +494,9 @@ const std::vector PropertyTreeModel::sortedAndFilteredPropertyName return returnValue; if (m_type == SignalType) { - returnValue = sortedAndFilteredSignalNames(modelNode.metaInfo()); + auto list = sortedAndFilteredSignalNames(modelNode.metaInfo()); + returnValue = getDynamicSignals(modelNode); + std::move(list.begin(), list.end(), std::back_inserter(returnValue)); } else if (m_type == SlotType) { returnValue = sortedAndFilteredSlotNames(modelNode.metaInfo()); } else { @@ -549,6 +551,18 @@ const std::vector PropertyTreeModel::getDynamicProperties( return Utils::sorted(std::vector(filtered.begin(), filtered.end())); } +const std::vector PropertyTreeModel::getDynamicSignals(const ModelNode &modelNode) const +{ + QList list = Utils::transform(modelNode.dynamicProperties(), + [](const AbstractProperty &property) { + if (property.isSignalDeclarationProperty()) + return property.name(); + + return PropertyName(property.name() + "Changed"); + }); + return Utils::sorted(std::vector(list.begin(), list.end())); +} + const std::vector PropertyTreeModel::sortedAndFilteredPropertyNames( const NodeMetaInfo &metaInfo, bool recursive) const { diff --git a/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.h b/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.h index b93820ac7b9..df17c112da9 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.h +++ b/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.h @@ -93,6 +93,7 @@ private: const ModelNode &modelNode) const; const std::vector getDynamicProperties(const ModelNode &modelNode) const; + const std::vector getDynamicSignals(const ModelNode &modelNode) const; const std::vector sortedAndFilteredPropertyNames(const NodeMetaInfo &metaInfo, bool recursive = false) const; diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.cpp index 60cba025840..6afe4fa8164 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.cpp @@ -155,6 +155,18 @@ void Edit3DCanvas::focusInEvent(QFocusEvent *focusEvent) QWidget::focusInEvent(focusEvent); } +void Edit3DCanvas::enterEvent(QEnterEvent *e) +{ + m_parent->view()->sendInputEvent(e); + QWidget::enterEvent(e); +} + +void Edit3DCanvas::leaveEvent(QEvent *e) +{ + m_parent->view()->sendInputEvent(e); + QWidget::leaveEvent(e); +} + void Edit3DCanvas::positionBusyInidicator() { m_busyIndicator->move(width() / 2 - 32, height() / 2 - 32); diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.h b/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.h index d575e68c139..810e246f8a3 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.h @@ -38,6 +38,8 @@ protected: void resizeEvent(QResizeEvent *e) override; void focusOutEvent(QFocusEvent *focusEvent) override; void focusInEvent(QFocusEvent *focusEvent) override; + void enterEvent(QEnterEvent *e) override; + void leaveEvent(QEvent *e) override; private: void positionBusyInidicator(); diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp index 028732cebca..9cf849bc4ce 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp @@ -58,6 +58,9 @@ Edit3DView::Edit3DView(ExternalDependenciesInterface &externalDependencies) m_compressionTimer.setInterval(1000); m_compressionTimer.setSingleShot(true); connect(&m_compressionTimer, &QTimer::timeout, this, &Edit3DView::handleEntriesChanged); + + for (int i = 0; i < 4; ++i) + m_splitToolStates.append({0, false}); } void Edit3DView::createEdit3DWidget() @@ -108,6 +111,18 @@ void Edit3DView::renderImage3DChanged(const QImage &img) void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState) { + const QString activeSplitKey = QStringLiteral("activeSplit"); + if (sceneState.contains(activeSplitKey)) { + m_activeSplit = sceneState[activeSplitKey].toInt(); + + // If the sceneState contained just activeSplit key, then this is simply an active split + // change rather than entire active scene change, and we don't need to process further. + if (sceneState.size() == 1) + return; + } else { + m_activeSplit = 0; + } + const QString sceneKey = QStringLiteral("sceneInstanceId"); const QString selectKey = QStringLiteral("selectionMode"); const QString transformKey = QStringLiteral("transformMode"); @@ -121,6 +136,9 @@ void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState) const QString particleEmitterKey = QStringLiteral("showParticleEmitter"); const QString particlesPlayKey = QStringLiteral("particlePlay"); const QString syncEnvBgKey = QStringLiteral("syncEnvBackground"); + const QString splitViewKey = QStringLiteral("splitView"); + const QString matOverrideKey = QStringLiteral("matOverride"); + const QString showWireframeKey = QStringLiteral("showWireframe"); if (sceneState.contains(sceneKey)) { qint32 newActiveScene = sceneState[sceneKey].value(); @@ -191,6 +209,29 @@ void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState) else m_particlesPlayAction->action()->setChecked(true); + if (sceneState.contains(splitViewKey)) + m_splitViewAction->action()->setChecked(sceneState[splitViewKey].toBool()); + else + m_splitViewAction->action()->setChecked(false); + + if (sceneState.contains(matOverrideKey)) { + const QVariantList overrides = sceneState[matOverrideKey].toList(); + for (int i = 0; i < 4; ++i) + m_splitToolStates[i].matOverride = i < overrides.size() ? overrides[i].toInt() : 0; + } else { + for (SplitToolState &state : m_splitToolStates) + state.matOverride = 0; + } + + if (sceneState.contains(showWireframeKey)) { + const QVariantList showList = sceneState[showWireframeKey].toList(); + for (int i = 0; i < 4; ++i) + m_splitToolStates[i].showWireframe = i < showList.size() ? showList[i].toBool() : false; + } else { + for (SplitToolState &state : m_splitToolStates) + state.showWireframe = false; + } + // Syncing background color only makes sense for children of View3D instances bool syncValue = false; bool syncEnabled = false; @@ -257,9 +298,13 @@ void Edit3DView::modelAttached(Model *model) if (QtSupport::QtVersion *qtVer = QtSupport::QtKitAspect::qtVersion(target->kit())) m_isBakingLightsSupported = qtVer->qtVersion() >= QVersionNumber(6, 5, 0); } - - connect(model->metaInfo().itemLibraryInfo(), &ItemLibraryInfo::entriesChanged, this, - &Edit3DView::onEntriesChanged, Qt::UniqueConnection); +#ifndef QDS_USE_PROJECTSTORAGE + connect(model->metaInfo().itemLibraryInfo(), + &ItemLibraryInfo::entriesChanged, + this, + &Edit3DView::onEntriesChanged, + Qt::UniqueConnection); +#endif } void Edit3DView::onEntriesChanged() @@ -285,24 +330,43 @@ void Edit3DView::handleEntriesChanged() EK_importedModels }; - QMap entriesMap { + QMap entriesMap{ {EK_cameras, {tr("Cameras"), contextIcon(DesignerIcons::CameraIcon)}}, {EK_lights, {tr("Lights"), contextIcon(DesignerIcons::LightIcon)}}, {EK_primitives, {tr("Primitives"), contextIcon(DesignerIcons::PrimitivesIcon)}}, - {EK_importedModels, {tr("Imported Models"), contextIcon(DesignerIcons::ImportedModelsIcon)}} + {EK_importedModels, {tr("Imported Models"), contextIcon(DesignerIcons::ImportedModelsIcon)}}}; + +#ifdef QDS_USE_PROJECTSTORAGE + const auto &projectStorage = *model()->projectStorage(); + auto append = [&](const NodeMetaInfo &metaInfo, ItemLibraryEntryKeys key) { + auto entries = metaInfo.itemLibrariesEntries(); + if (entries.size()) + entriesMap[key].entryList.append(toItemLibraryEntries(entries, projectStorage)); }; + append(model()->qtQuick3DModelMetaInfo(), EK_primitives); + append(model()->qtQuick3DDirectionalLightMetaInfo(), EK_lights); + append(model()->qtQuick3DSpotLightMetaInfo(), EK_lights); + append(model()->qtQuick3DPointLightMetaInfo(), EK_lights); + append(model()->qtQuick3DOrthographicCameraMetaInfo(), EK_cameras); + append(model()->qtQuick3DPerspectiveCameraMetaInfo(), EK_cameras); + + auto assetsModule = model()->module("Quick3DAssets"); + + for (const auto &metaInfo : model()->metaInfosForModule(assetsModule)) + append(metaInfo, EK_importedModels); +#else const QList itemLibEntries = model()->metaInfo().itemLibraryInfo()->entries(); for (const ItemLibraryEntry &entry : itemLibEntries) { ItemLibraryEntryKeys entryKey; if (entry.typeName() == "QtQuick3D.Model" && entry.name() != "Empty") { entryKey = EK_primitives; } else if (entry.typeName() == "QtQuick3D.DirectionalLight" - || entry.typeName() == "QtQuick3D.PointLight" - || entry.typeName() == "QtQuick3D.SpotLight") { + || entry.typeName() == "QtQuick3D.PointLight" + || entry.typeName() == "QtQuick3D.SpotLight") { entryKey = EK_lights; } else if (entry.typeName() == "QtQuick3D.OrthographicCamera" - || entry.typeName() == "QtQuick3D.PerspectiveCamera") { + || entry.typeName() == "QtQuick3D.PerspectiveCamera") { entryKey = EK_cameras; } else if (entry.typeName().startsWith("Quick3DAssets.") && NodeHints::fromItemLibraryEntry(entry).canBeDroppedInView3D()) { @@ -312,6 +376,7 @@ void Edit3DView::handleEntriesChanged() } entriesMap[entryKey].entryList.append(entry); } +#endif m_edit3DWidget->updateCreateSubMenu(entriesMap.values()); } @@ -431,7 +496,7 @@ void Edit3DView::nodeRemoved(const ModelNode &, updateAlignActionStates(); } -void Edit3DView::sendInputEvent(QInputEvent *e) const +void Edit3DView::sendInputEvent(QEvent *e) const { if (nodeInstanceView()) nodeInstanceView()->sendInputEvent(e); @@ -633,6 +698,24 @@ void Edit3DView::syncSnapAuxPropsToSettings() Edit3DViewConfig::load(DesignerSettingsKey::EDIT3DVIEW_SNAP_SCALE_INTERVAL)); } +const QList &Edit3DView::splitToolStates() const +{ + return m_splitToolStates; +} + +void Edit3DView::setSplitToolState(int splitIndex, const SplitToolState &state) +{ + if (splitIndex >= m_splitToolStates.size()) + return; + + m_splitToolStates[splitIndex] = state; +} + +int Edit3DView::activeSplit() const +{ + return m_activeSplit; +} + void Edit3DView::createEdit3DActions() { m_selectionModeAction = std::make_unique( @@ -946,7 +1029,7 @@ void Edit3DView::createEdit3DActions() createSeekerSliderAction(); m_bakeLightsAction = std::make_unique( - toolbarIcon(DesignerIcons::EditLightIcon), //: TODO placeholder icon + toolbarIcon(DesignerIcons::BakeLightIcon), this, bakeLightsTrigger); @@ -991,6 +1074,17 @@ void Edit3DView::createEdit3DActions() this, snapConfigTrigger); + m_splitViewAction = std::make_unique( + QmlDesigner::Constants::EDIT3D_SPLIT_VIEW, + View3DActionType::SplitViewToggle, + QCoreApplication::translate("SplitViewToggleAction", + "Toggle Split View On/Off"), + QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Q), + true, + false, + toolbarIcon(DesignerIcons::SplitViewIcon), + this); + m_leftActions << m_selectionModeAction.get(); m_leftActions << nullptr; // Null indicates separator m_leftActions << nullptr; // Second null after separator indicates an exclusive group @@ -1012,6 +1106,7 @@ void Edit3DView::createEdit3DActions() m_leftActions << nullptr; m_leftActions << m_visibilityTogglesAction.get(); m_leftActions << m_backgroundColorMenuAction.get(); + m_leftActions << m_splitViewAction.get(); m_rightActions << m_particleViewModeAction.get(); m_rightActions << m_particlesPlayAction.get(); diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.h b/src/plugins/qmldesigner/components/edit3d/edit3dview.h index 2fb1e0451cd..56248d9587c 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.h @@ -3,7 +3,7 @@ #pragma once #include "edit3dactions.h" -#include "itemlibraryinfo.h" +#include #include #include @@ -32,6 +32,12 @@ class QMLDESIGNERCOMPONENTS_EXPORT Edit3DView : public AbstractView Q_OBJECT public: + struct SplitToolState + { + int matOverride = 0; + bool showWireframe = false; + }; + Edit3DView(ExternalDependenciesInterface &externalDependencies); WidgetInfo widgetInfo() override; @@ -52,7 +58,7 @@ public: void nodeRemoved(const ModelNode &removedNode, const NodeAbstractProperty &parentProperty, PropertyChangeFlags propertyChange) override; - void sendInputEvent(QInputEvent *e) const; + void sendInputEvent(QEvent *e) const; void edit3DViewResized(const QSize &size) const; QSize canvasSize() const; @@ -78,6 +84,11 @@ public: void syncSnapAuxPropsToSettings(); + const QList &splitToolStates() const; + void setSplitToolState(int splitIndex, const SplitToolState &state); + + int activeSplit() const; + private slots: void onEntriesChanged(); @@ -139,6 +150,7 @@ private: std::unique_ptr m_selectBackgroundColorAction; std::unique_ptr m_selectGridColorAction; std::unique_ptr m_resetColorAction; + std::unique_ptr m_splitViewAction; // View3DActionType::Empty actions std::unique_ptr m_resetAction; @@ -159,6 +171,9 @@ private: QPointer m_bakeLights; bool m_isBakingLightsSupported = false; QPointer m_snapConfiguration; + int m_activeSplit = 0; + + QList m_splitToolStates; friend class Edit3DAction; }; diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index c054b712ce3..b56986e79df 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -281,6 +281,83 @@ void Edit3DWidget::createContextMenu() m_toggleGroupAction->setChecked(defaultToggleGroupAction->isChecked()); m_contextMenu->addSeparator(); + + auto overridesSubMenu = new QmlEditorMenu(tr("Viewport Shading"), m_contextMenu); + overridesSubMenu->setToolTipsVisible(true); + m_contextMenu->addMenu(overridesSubMenu); + + m_wireFrameAction = overridesSubMenu->addAction( + tr("Wireframe"), this, &Edit3DWidget::onWireframeAction); + m_wireFrameAction->setCheckable(true); + m_wireFrameAction->setToolTip(tr("Show models as wireframe.")); + + overridesSubMenu->addSeparator(); + + // The type enum order must match QQuick3DDebugSettings::QQuick3DMaterialOverrides enums + enum class MaterialOverrideType { + None, + BaseColor, + Roughness, + Metalness, + Diffuse, + Specular, + ShadowOcclusion, + Emission, + AmbientOcclusion, + Normals, + Tangents, + Binormals, + F0 + }; + + auto addOverrideMenuAction = [&](const QString &label, const QString &toolTip, + MaterialOverrideType type) { + QAction *action = overridesSubMenu->addAction( + label, this, &Edit3DWidget::onMatOverrideAction); + action->setData(int(type)); + action->setCheckable(true); + action->setToolTip(toolTip); + m_matOverrideActions.insert(int(type), action); + }; + + addOverrideMenuAction(tr("Default"), + tr("Rendering occurs as normal."), + MaterialOverrideType::None); + addOverrideMenuAction(tr("Base Color"), + tr("The base or diffuse color of a material is passed through without any lighting."), + MaterialOverrideType::BaseColor); + addOverrideMenuAction(tr("Roughness"), + tr("The roughness of a material is passed through as an unlit greyscale value."), + MaterialOverrideType::Roughness); + addOverrideMenuAction(tr("Metalness"), + tr("The metalness of a material is passed through as an unlit greyscale value."), + MaterialOverrideType::Metalness); + addOverrideMenuAction(tr("Normals"), + tr("The interpolated world space normal value of the material mapped to an RGB color."), + MaterialOverrideType::Normals); + addOverrideMenuAction(tr("Ambient Occlusion"), + tr("Only the ambient occlusion of the material."), + MaterialOverrideType::AmbientOcclusion); + addOverrideMenuAction(tr("Emission"), + tr("Only the emissive contribution of the material."), + MaterialOverrideType::Emission); + addOverrideMenuAction(tr("Shadow Occlusion"), + tr("The occlusion caused by shadows as a greyscale value."), + MaterialOverrideType::ShadowOcclusion); + addOverrideMenuAction(tr("Diffuse"), + tr("Only the diffuse contribution of the material after all lighting."), + MaterialOverrideType::Diffuse); + addOverrideMenuAction(tr("Specular"), + tr("Only the specular contribution of the material after all lighting."), + MaterialOverrideType::Specular); + + overridesSubMenu->addSeparator(); + + QAction *resetAction = overridesSubMenu->addAction( + tr("Reset All Viewports"), this, &Edit3DWidget::onResetAllOverridesAction); + resetAction->setToolTip(tr("Reset all shading options for all viewports.")); + + m_contextMenu->addSeparator(); } bool Edit3DWidget::isPasteAvailable() const @@ -422,6 +499,69 @@ void Edit3DWidget::onCreateAction() }); } +void Edit3DWidget::onMatOverrideAction() +{ + QAction *action = qobject_cast(sender()); + if (!action || !m_view || !m_view->model()) + return; + + QVariantList list; + for (int i = 0; i < m_view->splitToolStates().size(); ++i) { + Edit3DView::SplitToolState state = m_view->splitToolStates()[i]; + if (i == m_view->activeSplit()) { + state.matOverride = action->data().toInt(); + m_view->setSplitToolState(i, state); + list.append(action->data()); + } else { + list.append(state.matOverride); + } + } + + view()->emitView3DAction(View3DActionType::MaterialOverride, list); +} + +void Edit3DWidget::onWireframeAction() +{ + QAction *action = qobject_cast(sender()); + if (!action || !m_view || !m_view->model()) + return; + + QVariantList list; + for (int i = 0; i < m_view->splitToolStates().size(); ++i) { + Edit3DView::SplitToolState state = m_view->splitToolStates()[i]; + if (i == m_view->activeSplit()) { + state.showWireframe = action->isChecked(); + m_view->setSplitToolState(i, state); + list.append(action->isChecked()); + } else { + list.append(state.showWireframe); + } + } + + view()->emitView3DAction(View3DActionType::ShowWireframe, list); +} + +void Edit3DWidget::onResetAllOverridesAction() +{ + if (!m_view || !m_view->model()) + return; + + QVariantList wList; + QVariantList mList; + + for (int i = 0; i < m_view->splitToolStates().size(); ++i) { + Edit3DView::SplitToolState state; + state.showWireframe = false; + state.matOverride = 0; + m_view->setSplitToolState(i, state); + wList.append(state.showWireframe); + mList.append(state.matOverride); + } + + view()->emitView3DAction(View3DActionType::ShowWireframe, wList); + view()->emitView3DAction(View3DActionType::MaterialOverride, mList); +} + void Edit3DWidget::contextHelp(const Core::IContext::HelpCallback &callback) const { if (m_view) @@ -502,6 +642,15 @@ void Edit3DWidget::showContextMenu(const QPoint &pos, const ModelNode &modelNode m_bakeLightsAction->setVisible(view()->bakeLightsAction()->action()->isVisible()); m_bakeLightsAction->setEnabled(view()->bakeLightsAction()->action()->isEnabled()); + if (m_view) { + int idx = m_view->activeSplit(); + m_wireFrameAction->setChecked(m_view->splitToolStates()[idx].showWireframe); + for (QAction *a : std::as_const(m_matOverrideActions)) + a->setChecked(false); + int type = m_view->splitToolStates()[idx].matOverride; + m_matOverrideActions[type]->setChecked(true); + } + m_contextMenu->popup(mapToGlobal(pos)); } @@ -618,20 +767,39 @@ void Edit3DWidget::dropEvent(QDropEvent *dropEvent) QHash addedAssets = actionManager.handleExternalAssetsDrop(dropEvent->mimeData()); view()->executeInTransaction("Edit3DWidget::dropEvent", [&] { - // add 3D assets to 3d editor (QtQuick3D import will be added if missing) - ItemLibraryInfo *itemLibInfo = m_view->model()->metaInfo().itemLibraryInfo(); - + // add 3D assets to 3d editor (QtQuick3D import will be added if missing) +#ifdef QDS_USE_PROJECTSTORAGE const QStringList added3DAssets = addedAssets.value(ComponentCoreConstants::add3DAssetsDisplayString); for (const QString &assetPath : added3DAssets) { QString fileName = QFileInfo(assetPath).baseName(); fileName = fileName.at(0).toUpper() + fileName.mid(1); // capitalize first letter - QString type = QString("Quick3DAssets.%1.%1").arg(fileName); - QList entriesForType = itemLibInfo->entriesForType(type.toLatin1()); - if (!entriesForType.isEmpty()) { // should always be true, but just in case - QmlVisualNode::createQml3DNode(view(), entriesForType.at(0), - m_canvas->activeScene(), {}, false).modelNode(); + auto model = m_view->model(); + auto metaInfo = model->metaInfo(model->module("Quick3DAssets"), fileName.toUtf8()); + if (auto entries = metaInfo.itemLibrariesEntries(); entries.size()) { + auto entry = ItemLibraryEntry{entries.front(), *model->projectStorage()}; + QmlVisualNode::createQml3DNode(view(), entry, m_canvas->activeScene(), {}, false); } } +#else + ItemLibraryInfo *itemLibInfo = m_view->model()->metaInfo().itemLibraryInfo(); + + const QStringList added3DAssets = addedAssets.value( + ComponentCoreConstants::add3DAssetsDisplayString); + for (const QString &assetPath : added3DAssets) { + QString fileName = QFileInfo(assetPath).baseName(); + fileName = fileName.at(0).toUpper() + fileName.mid(1); // capitalize first letter + QString type = QString("Quick3DAssets.%1.%1").arg(fileName); + QList entriesForType = itemLibInfo->entriesForType(type.toUtf8()); + if (!entriesForType.isEmpty()) { // should always be true, but just in case + QmlVisualNode::createQml3DNode(view(), + entriesForType.at(0), + m_canvas->activeScene(), + {}, + false) + .modelNode(); + } + } +#endif }); m_view->model()->endDrag(); diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h index b9826ca07b3..67e23b0b98c 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h @@ -9,7 +9,7 @@ #include #include -#include "itemlibraryinfo.h" +#include #include namespace QmlDesigner { @@ -54,6 +54,9 @@ public: private slots: void onCreateAction(); + void onMatOverrideAction(); + void onWireframeAction(); + void onResetAllOverridesAction(); protected: void dragEnterEvent(QDragEnterEvent *dragEnterEvent) override; @@ -90,6 +93,8 @@ private: QPointer m_alignViewAction; QPointer m_selectParentAction; QPointer m_toggleGroupAction; + QPointer m_wireFrameAction; + QHash> m_matOverrideActions; QPointer m_createSubMenu; ModelNode m_contextMenuTarget; QVector3D m_contextMenuPos3d; diff --git a/src/plugins/qmldesigner/components/eventlist/eventlist.cpp b/src/plugins/qmldesigner/components/eventlist/eventlist.cpp index da3ba2981f2..5ba29bb0dff 100644 --- a/src/plugins/qmldesigner/components/eventlist/eventlist.cpp +++ b/src/plugins/qmldesigner/components/eventlist/eventlist.cpp @@ -6,7 +6,6 @@ #include "nodelistview.h" #include "bindingproperty.h" -#include "metainfo.h" #include "projectexplorer/project.h" #include "projectexplorer/projectmanager.h" #include "qmldesignerplugin.h" @@ -14,6 +13,7 @@ #include "utils/fileutils.h" #include "utils/qtcassert.h" #include "variantproperty.h" +#include #include #include diff --git a/src/plugins/qmldesigner/components/formeditor/dragtool.cpp b/src/plugins/qmldesigner/components/formeditor/dragtool.cpp index 9a51c9f3722..e363e9bb11d 100644 --- a/src/plugins/qmldesigner/components/formeditor/dragtool.cpp +++ b/src/plugins/qmldesigner/components/formeditor/dragtool.cpp @@ -3,16 +3,16 @@ #include "dragtool.h" +#include "assetslibrarymodel.h" +#include "assetslibrarywidget.h" #include "formeditorscene.h" #include "formeditorview.h" -#include "assetslibrarywidget.h" -#include "assetslibrarymodel.h" #include "materialutils.h" -#include +#include "qmldesignerconstants.h" +#include #include #include #include -#include "qmldesignerconstants.h" #include diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp index 2a433e6764a..9ddec21f350 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp @@ -42,6 +42,10 @@ namespace QmlDesigner { +namespace { +constexpr AuxiliaryDataKeyView autoSizeProperty{AuxiliaryDataType::Temporary, "autoSize"}; +} + FormEditorView::FormEditorView(ExternalDependenciesInterface &externalDependencies) : AbstractView{externalDependencies} {} @@ -233,6 +237,10 @@ void FormEditorView::nodeCreated(const ModelNode &node) void FormEditorView::cleanupToolsAndScene() { + QTC_ASSERT(m_scene, return ); + QTC_ASSERT(m_formEditorWidget, return ); + QTC_ASSERT(m_currentTool, return ); + m_currentTool->setItems(QList()); m_selectionTool->clear(); m_rotationTool->clear(); @@ -251,6 +259,11 @@ void FormEditorView::cleanupToolsAndScene() void FormEditorView::modelAboutToBeDetached(Model *model) { + rootModelNode().removeAuxiliaryData(contextImageProperty); + rootModelNode().removeAuxiliaryData(widthProperty); + rootModelNode().removeAuxiliaryData(heightProperty); + rootModelNode().removeAuxiliaryData(autoSizeProperty); + cleanupToolsAndScene(); AbstractView::modelAboutToBeDetached(model); } @@ -681,7 +694,6 @@ void FormEditorView::instancesCompleted(const QVector &completedNodeL const bool isFlow = QmlItemNode(rootModelNode()).isFlowView(); QList itemNodeList; for (const ModelNode &node : completedNodeList) { - ; if (const QmlItemNode qmlItemNode = (node)) { if (FormEditorItem *item = scene()->itemForQmlItemNode(qmlItemNode)) { scene()->synchronizeParent(qmlItemNode); @@ -694,10 +706,6 @@ void FormEditorView::instancesCompleted(const QVector &completedNodeL currentTool()->instancesCompleted(itemNodeList); } -namespace { -constexpr AuxiliaryDataKeyView autoSizeProperty{AuxiliaryDataType::Temporary, "autoSize"}; -} - void FormEditorView::instanceInformationsChanged(const QMultiHash &informationChangedHash) { QList changedItems; @@ -935,34 +943,45 @@ void FormEditorView::setupFormEditor3DView() void FormEditorView::setupRootItemSize() { if (const QmlItemNode rootQmlNode = rootModelNode()) { - const int rootElementInitWidth = QmlDesignerPlugin::settings() + int rootElementInitWidth = QmlDesignerPlugin::settings() .value(DesignerSettingsKey::ROOT_ELEMENT_INIT_WIDTH) .toInt(); - const int rootElementInitHeight = QmlDesignerPlugin::settings() + int rootElementInitHeight = QmlDesignerPlugin::settings() .value(DesignerSettingsKey::ROOT_ELEMENT_INIT_HEIGHT) .toInt(); - if (rootQmlNode.instanceBoundingRect().isEmpty() - && !(rootQmlNode.propertyAffectedByCurrentState("width") - && rootQmlNode.propertyAffectedByCurrentState("height"))) { + if (rootModelNode().hasAuxiliaryData(defaultWidthProperty)) + rootElementInitWidth = rootModelNode().auxiliaryData(defaultWidthProperty).value().toInt(); + if (rootModelNode().hasAuxiliaryData(defaultHeightProperty)) + rootElementInitHeight = rootModelNode().auxiliaryData(defaultHeightProperty).value().toInt(); + + bool affectedByCurrentState = + rootQmlNode.propertyAffectedByCurrentState("width") || + rootQmlNode.propertyAffectedByCurrentState("height"); + + QRectF rootRect = rootQmlNode.instanceBoundingRect(); + if (rootRect.isEmpty() && !affectedByCurrentState) { + if (!rootModelNode().hasAuxiliaryData(widthProperty)) rootModelNode().setAuxiliaryData(widthProperty, rootElementInitWidth); + if (!rootModelNode().hasAuxiliaryData(heightProperty)) rootModelNode().setAuxiliaryData(heightProperty, rootElementInitHeight); + rootModelNode().setAuxiliaryData(autoSizeProperty, true); + + formEditorWidget()->updateActions(); + rootRect.setWidth(rootModelNode().auxiliaryData(widthProperty).value().toFloat() ); + rootRect.setHeight(rootModelNode().auxiliaryData(heightProperty).value().toFloat() ); + + } else if (rootModelNode().hasAuxiliaryData(autoSizeProperty) && affectedByCurrentState ) { + rootModelNode().removeAuxiliaryData(widthProperty); + rootModelNode().removeAuxiliaryData(heightProperty); + rootModelNode().removeAuxiliaryData(autoSizeProperty); formEditorWidget()->updateActions(); - } else { - if (rootModelNode().hasAuxiliaryData(autoSizeProperty) - && (rootQmlNode.propertyAffectedByCurrentState("width") - || rootQmlNode.propertyAffectedByCurrentState("height"))) { - rootModelNode().removeAuxiliaryData(widthProperty); - rootModelNode().removeAuxiliaryData(heightProperty); - rootModelNode().removeAuxiliaryData(autoSizeProperty); - formEditorWidget()->updateActions(); - } } - formEditorWidget()->setRootItemRect(rootQmlNode.instanceBoundingRect()); + formEditorWidget()->setRootItemRect(rootRect); formEditorWidget()->centerScene(); auto contextImage = rootModelNode().auxiliaryData(contextImageProperty); diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp index 2e7a5b2c887..e75a95cfb06 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp @@ -305,6 +305,7 @@ void FormEditorWidget::changeRootItemWidth(const QString &widthText) bool canConvert; int width = widthText.toInt(&canConvert); if (canConvert) { + m_formEditorView->rootModelNode().setAuxiliaryData(defaultWidthProperty, width); m_formEditorView->rootModelNode().setAuxiliaryData(widthProperty, width); } else { m_formEditorView->rootModelNode().removeAuxiliaryData(widthProperty); @@ -316,6 +317,7 @@ void FormEditorWidget::changeRootItemHeight(const QString &heighText) bool canConvert; int height = heighText.toInt(&canConvert); if (canConvert) { + m_formEditorView->rootModelNode().setAuxiliaryData(defaultHeightProperty, height); m_formEditorView->rootModelNode().setAuxiliaryData(heightProperty, height); } else { m_formEditorView->rootModelNode().removeAuxiliaryData(heightProperty); diff --git a/src/plugins/qmldesigner/components/integration/designdocument.cpp b/src/plugins/qmldesigner/components/integration/designdocument.cpp index 638c57e0d90..91ac4d8f160 100644 --- a/src/plugins/qmldesigner/components/integration/designdocument.cpp +++ b/src/plugins/qmldesigner/components/integration/designdocument.cpp @@ -8,7 +8,9 @@ #include "qmlvisualnode.h" #include -#include +#ifndef QDS_USE_PROJECTSTORAGE +# include +#endif #include #include #include @@ -72,8 +74,8 @@ DesignDocument::DesignDocument(ProjectStorageDependencies projectStorageDependen #else : m_documentModel( Model::create("QtQuick.Item", 1, 0, nullptr, std::make_unique())) -#endif , m_subComponentManager(new SubComponentManager(m_documentModel.get(), externalDependencies)) +#endif , m_rewriterView(new RewriterView(externalDependencies, RewriterView::Amend)) , m_documentLoaded(false) , m_currentTarget(nullptr) @@ -165,7 +167,9 @@ ModelPointer DesignDocument::createInFileComponentModel() nullptr, std::make_unique()); model->setFileUrl(m_documentModel->fileUrl()); +#ifndef QDS_USE_PROJECTSTORAGE model->setMetaInfo(m_documentModel->metaInfo()); +#endif return model; } @@ -520,6 +524,7 @@ void DesignDocument::close() emit designDocumentClosed(); } +#ifndef QDS_USE_PROJECTSTORAGE void DesignDocument::updateSubcomponentManager() { Q_ASSERT(m_subComponentManager); @@ -531,6 +536,7 @@ void DesignDocument::addSubcomponentManagerImport(const Import &import) { m_subComponentManager->addAndParseImport(import); } +#endif void DesignDocument::deleteSelected() { diff --git a/src/plugins/qmldesigner/components/integration/designdocument.h b/src/plugins/qmldesigner/components/integration/designdocument.h index 7c0fe3941f8..c5c1bab27f9 100644 --- a/src/plugins/qmldesigner/components/integration/designdocument.h +++ b/src/plugins/qmldesigner/components/integration/designdocument.h @@ -9,7 +9,9 @@ #include #include #include -#include +#ifndef QDS_USE_PROJECTSTORAGE +# include +#endif #include #include @@ -49,9 +51,10 @@ public: void loadDocument(QPlainTextEdit *edit); void attachRewriterToModel(); void close(); +#ifndef QDS_USE_PROJECTSTORAGE void updateSubcomponentManager(); void addSubcomponentManagerImport(const Import &import); - +#endif bool isUndoAvailable() const; bool isRedoAvailable() const; @@ -138,8 +141,9 @@ private: // variables QPointer m_textEditor; QScopedPointer m_documentTextModifier; QScopedPointer m_inFileComponentTextModifier; +#ifndef QDS_USE_PROJECTSTORAGE QScopedPointer m_subComponentManager; - +#endif QScopedPointer m_rewriterView; bool m_documentLoaded; ProjectExplorer::Target *m_currentTarget; diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.h index 449103976b1..1bc091d0054 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.h @@ -8,7 +8,7 @@ #include #include -#include "itemlibraryinfo.h" +#include namespace QmlDesigner { diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp index 3f611a0dbba..c9aafc48fb7 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp @@ -3,10 +3,10 @@ #include "itemlibrarymodel.h" #include "itemlibrarycategoriesmodel.h" -#include "itemlibraryimport.h" #include "itemlibrarycategory.h" +#include "itemlibraryentry.h" +#include "itemlibraryimport.h" #include "itemlibraryitem.h" -#include "itemlibraryinfo.h" #include #include @@ -304,7 +304,7 @@ Import ItemLibraryModel::entryToImport(const ItemLibraryEntry &entry) } -void ItemLibraryModel::update(ItemLibraryInfo *itemLibraryInfo, Model *model) +void ItemLibraryModel::update([[maybe_unused]] ItemLibraryInfo *itemLibraryInfo, Model *model) { if (!model) return; @@ -365,9 +365,14 @@ void ItemLibraryModel::update(ItemLibraryInfo *itemLibraryInfo, Model *model) } const bool blockNewImports = document->inFileComponentModelActive(); - const QList itemLibEntries = itemLibraryInfo->entries(); + const QList itemLibEntries = model->itemLibraryEntries(); for (const ItemLibraryEntry &entry : itemLibEntries) { - NodeMetaInfo metaInfo = model->metaInfo(entry.typeName()); + NodeMetaInfo metaInfo; + + if constexpr (useProjectStorage()) + metaInfo = entry.metaInfo(); + else + metaInfo = model->metaInfo(entry.typeName()); bool valid = metaInfo.isValid() && (metaInfo.majorVersion() >= entry.majorVersion() diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp index 3082ee442ac..feff523b9c6 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -16,12 +17,12 @@ #include #include #include -#include -#include -#include #include #include #include +#include +#include +#include namespace QmlDesigner { @@ -76,9 +77,11 @@ void ItemLibraryView::modelAboutToBeDetached(Model *model) void ItemLibraryView::importsChanged(const Imports &addedImports, const Imports &removedImports) { +#ifndef QDS_USE_PROJECTSTORAGE DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument(); for (const auto &import : addedImports) document->addSubcomponentManagerImport(import); +#endif updateImports(); m_widget->updatePossibleImports(model()->possibleImports()); @@ -114,9 +117,11 @@ void ItemLibraryView::importsChanged(const Imports &addedImports, const Imports void ItemLibraryView::possibleImportsChanged(const Imports &possibleImports) { +#ifndef QDS_USE_PROJECTSTORAGE DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument(); for (const auto &import : possibleImports) document->addSubcomponentManagerImport(import); +#endif m_widget->updatePossibleImports(possibleImports); } @@ -193,8 +198,12 @@ void ItemLibraryView::customNotification(const AbstractView *view, const QString const QList &nodeList, const QList &data) { if (identifier == "UpdateImported3DAsset" && nodeList.size() > 0) { - ItemLibraryAssetImportDialog::updateImport(nodeList[0], m_importableExtensions3DMap, + ItemLibraryAssetImportDialog::updateImport(nodeList[0], + m_importableExtensions3DMap, m_importOptions3DMap); + + } else if (identifier == UpdateItemlibrary) { + updateImports(); } else { AbstractView::customNotification(view, identifier, nodeList, data); } diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp index 8d3603ea841..7703f352b0e 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp @@ -14,15 +14,20 @@ #include #include #include +#include #include -#include +#ifndef QDS_USE_PROJECTSTORAGE +# include +#endif #include -#include #include #include #include #include #include +#ifndef QDS_USE_PROJECTSTORAGE +# include +#endif #include #include @@ -176,6 +181,7 @@ ItemLibraryWidget::ItemLibraryWidget(AsynchronousImageCache &imageCache) ItemLibraryWidget::~ItemLibraryWidget() = default; +#ifndef QDS_USE_PROJECTSTORAGE void ItemLibraryWidget::setItemLibraryInfo(ItemLibraryInfo *itemLibraryInfo) { if (m_itemLibraryInfo.data() == itemLibraryInfo) @@ -192,6 +198,7 @@ void ItemLibraryWidget::setItemLibraryInfo(ItemLibraryInfo *itemLibraryInfo) } delayedUpdateModel(); } +#endif QList ItemLibraryWidget::createToolBarWidgets() { @@ -271,8 +278,9 @@ void ItemLibraryWidget::setModel(Model *model) m_itemToDrag = {}; return; } - +#ifndef QDS_USE_PROJECTSTORAGE setItemLibraryInfo(model->metaInfo().itemLibraryInfo()); +#endif if (DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument()) { const bool subCompEditMode = document->inFileComponentModelActive(); @@ -321,7 +329,9 @@ void ItemLibraryWidget::updateModel() m_compressionTimer.stop(); } +#ifndef QDS_USE_PROJECTSTORAGE m_itemLibraryModel->update(m_itemLibraryInfo.data(), m_model.data()); +#endif if (m_itemLibraryModel->rowCount() == 0 && !m_updateRetry) { m_updateRetry = true; // Only retry once to avoid endless loops diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.h index e791fceb698..b56532b2185 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.h @@ -3,7 +3,9 @@ #pragma once -#include "itemlibraryinfo.h" +#ifndef QDS_USE_PROJECTSTORAGE +# include +#endif #include "import.h" #include @@ -51,7 +53,9 @@ public: ItemLibraryWidget(AsynchronousImageCache &imageCache); ~ItemLibraryWidget(); +#ifndef QDS_USE_PROJECTSTORAGE void setItemLibraryInfo(ItemLibraryInfo *itemLibraryInfo); +#endif QList createToolBarWidgets(); static QString qmlSourcesPath(); @@ -97,8 +101,9 @@ private: QTimer m_compressionTimer; QSize m_itemIconSize; +#ifndef QDS_USE_PROJECTSTORAGE QPointer m_itemLibraryInfo; - +#endif QPointer m_itemLibraryModel; QPointer m_addModuleModel; diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.cpp index fe84c0c1841..f0ce34a0e62 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.cpp @@ -206,7 +206,7 @@ void MaterialEditorQmlBackend::setSource(const QUrl &url) m_view->setSource(url); } -Internal::QmlAnchorBindingProxy &MaterialEditorQmlBackend::backendAnchorBinding() +QmlAnchorBindingProxy &MaterialEditorQmlBackend::backendAnchorBinding() { return m_backendAnchorBinding; } diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.h b/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.h index c405334bd0e..65649329a23 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.h +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.h @@ -38,7 +38,7 @@ public: MaterialEditorContextObject *contextObject() const; QQuickWidget *widget() const; void setSource(const QUrl &url); - Internal::QmlAnchorBindingProxy &backendAnchorBinding(); + QmlAnchorBindingProxy &backendAnchorBinding(); void updateMaterialPreview(const QPixmap &pixmap); DesignerPropertyMap &backendValuesPropertyMap(); MaterialEditorTransaction *materialEditorTransaction() const; @@ -59,7 +59,7 @@ private: PropertyName auxNamePostFix(const PropertyName &propertyName); QQuickWidget *m_view = nullptr; - Internal::QmlAnchorBindingProxy m_backendAnchorBinding; + QmlAnchorBindingProxy m_backendAnchorBinding; QmlModelNodeProxy m_backendModelNode; DesignerPropertyMap m_backendValuesPropertyMap; QScopedPointer m_materialEditorTransaction; diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp index 487d300ef97..992ef2574f1 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp @@ -4,16 +4,15 @@ #include "materialeditorview.h" #include "asset.h" -#include "bindingproperty.h" #include "auxiliarydataproperties.h" +#include "bindingproperty.h" #include "designdocument.h" #include "designmodewidget.h" #include "dynamicpropertiesmodel.h" #include "externaldependenciesinterface.h" -#include "itemlibraryinfo.h" -#include "materialeditorqmlbackend.h" #include "materialeditorcontextobject.h" #include "materialeditordynamicpropertiesproxymodel.h" +#include "materialeditorqmlbackend.h" #include "materialeditortransaction.h" #include "metainfo.h" #include "nodeinstanceview.h" @@ -25,6 +24,7 @@ #include "qmldesignerplugin.h" #include "qmltimeline.h" #include "variantproperty.h" +#include #include #include @@ -39,20 +39,6 @@ #include #include -namespace { -QSize maxSize(const std::initializer_list &sizeList) -{ - QSize result; - for (const QSize &size : sizeList) { - if (size.width() > result.width()) - result.setWidth(size.width()); - if (size.height() > result.height()) - result.setHeight(size.height()); - } - return result; -} -} - namespace QmlDesigner { MaterialEditorView::MaterialEditorView(ExternalDependenciesInterface &externalDependencies) @@ -612,14 +598,7 @@ void MaterialEditorView::setupQmlBackend() initPreviewData(); m_stackedWidget->setCurrentWidget(m_qmlBackEnd->widget()); - if (m_qmlBackEnd->widget()) { - m_stackedWidget->setMinimumSize(maxSize({m_qmlBackEnd->widget()->sizeHint(), - m_qmlBackEnd->widget()->initialSize(), - m_qmlBackEnd->widget()->minimumSizeHint(), - m_qmlBackEnd->widget()->minimumSize()})); - } else { - m_stackedWidget->setMinimumSize({400, 300}); - } + m_stackedWidget->setMinimumSize({400, 300}); } void MaterialEditorView::commitVariantValueToModel(const PropertyName &propertyName, const QVariant &value) @@ -704,7 +683,7 @@ void MaterialEditorView::delayedTypeUpdate() m_typeUpdateTimer.start(); } -static Import entryToImport(const ItemLibraryEntry &entry) +[[maybe_unused]] static Import entryToImport(const ItemLibraryEntry &entry) { if (entry.majorVersion() == -1 && entry.minorVersion() == -1) return Import::createFileImport(entry.requiredImport()); @@ -721,7 +700,15 @@ void MaterialEditorView::updatePossibleTypes() if (!m_qmlBackEnd) return; - // Ensure basic types are always first +#ifdef QDS_USE_PROJECTSTORAGE + auto heirs = model()->qtQuick3DMaterialMetaInfo().heirs(); + heirs.push_back(model()->qtQuick3DMaterialMetaInfo()); + auto entries = Utils::transform(heirs, [&](const auto &heir) { + return toItemLibraryEntries(heir.itemLibrariesEntries(), *model()->projectStorage()); + }); + + // I am unsure about the code intention here +#else // Ensure basic types are always first QStringList nonQuick3dTypes; QStringList allTypes; @@ -755,6 +742,7 @@ void MaterialEditorView::updatePossibleTypes() allTypes.append(nonQuick3dTypes); m_qmlBackEnd->contextObject()->setPossibleTypes(allTypes); +#endif } void MaterialEditorView::modelAttached(Model *model) @@ -774,6 +762,7 @@ void MaterialEditorView::modelAttached(Model *model) m_ensureMatLibTimer.start(500); } +#ifndef QDS_USE_PROJECTSTORAGE if (m_itemLibraryInfo.data() != model->metaInfo().itemLibraryInfo()) { if (m_itemLibraryInfo) { disconnect(m_itemLibraryInfo.data(), &ItemLibraryInfo::entriesChanged, @@ -785,6 +774,7 @@ void MaterialEditorView::modelAttached(Model *model) this, &MaterialEditorView::delayedTypeUpdate); } } +#endif if (!m_setupCompleted) { reloadQml(); @@ -801,6 +791,7 @@ void MaterialEditorView::modelAboutToBeDetached(Model *model) m_dynamicPropertiesModel->reset(); m_qmlBackEnd->materialEditorTransaction()->end(); m_qmlBackEnd->contextObject()->setHasMaterialLibrary(false); + m_selectedMaterial = {}; } void MaterialEditorView::propertiesRemoved(const QList &propertyList) diff --git a/src/plugins/qmldesigner/components/navigator/iconcheckboxitemdelegate.cpp b/src/plugins/qmldesigner/components/navigator/iconcheckboxitemdelegate.cpp index d5701ac5691..c24aa1933bf 100644 --- a/src/plugins/qmldesigner/components/navigator/iconcheckboxitemdelegate.cpp +++ b/src/plugins/qmldesigner/components/navigator/iconcheckboxitemdelegate.cpp @@ -10,7 +10,6 @@ #include "navigatortreemodel.h" #include "qproxystyle.h" -#include #include #include diff --git a/src/plugins/qmldesigner/components/navigator/nameitemdelegate.cpp b/src/plugins/qmldesigner/components/navigator/nameitemdelegate.cpp index 1a3814f9618..aacaf6dc0d9 100644 --- a/src/plugins/qmldesigner/components/navigator/nameitemdelegate.cpp +++ b/src/plugins/qmldesigner/components/navigator/nameitemdelegate.cpp @@ -11,7 +11,6 @@ #include "choosefrompropertylistdialog.h" #include "qproxystyle.h" -#include #include #include #include diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp index 6abda00dbac..ed54fc4c52a 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp @@ -16,8 +16,8 @@ #include #include #include +#include #include -#include #include #include #include @@ -554,9 +554,13 @@ bool NavigatorTreeModel::dropMimeData(const QMimeData *mimeData, if (mimeData->hasFormat(Constants::MIME_TYPE_ITEM_LIBRARY_INFO)) { handleItemLibraryItemDrop(mimeData, rowNumber, dropModelIndex); } else if (mimeData->hasFormat(Constants::MIME_TYPE_TEXTURE)) { - handleTextureDrop(mimeData, dropModelIndex); + const QModelIndex rowModelIndex = dropModelIndex.sibling(dropModelIndex.row(), 0); + ModelNode targetNode = modelNodeForIndex(rowModelIndex); + ModelNodeOperations::handleTextureDrop(mimeData, targetNode); } else if (mimeData->hasFormat(Constants::MIME_TYPE_MATERIAL)) { - handleMaterialDrop(mimeData, dropModelIndex); + const QModelIndex rowModelIndex = dropModelIndex.sibling(dropModelIndex.row(), 0); + ModelNode targetNode = modelNodeForIndex(rowModelIndex); + ModelNodeOperations::handleMaterialDrop(mimeData, targetNode); } else if (mimeData->hasFormat(Constants::MIME_TYPE_BUNDLE_TEXTURE)) { QByteArray filePath = mimeData->data(Constants::MIME_TYPE_BUNDLE_TEXTURE); ModelNode targetNode(modelNodeForIndex(dropModelIndex)); @@ -607,23 +611,36 @@ bool NavigatorTreeModel::dropMimeData(const QMimeData *mimeData, QString assetType = assetTypeAndData.first; QString assetData = QString::fromUtf8(assetTypeAndData.second); if (assetType == Constants::MIME_TYPE_ASSET_IMAGE) { - currNode = handleItemLibraryImageDrop(assetPath, targetProperty, - rowModelIndex, moveNodesAfter); + currNode + = ModelNodeOperations::handleItemLibraryImageDrop(assetPath, + targetProperty, + modelNodeForIndex( + rowModelIndex), + moveNodesAfter); } else if (assetType == Constants::MIME_TYPE_ASSET_FONT) { - currNode = handleItemLibraryFontDrop(assetData, // assetData is fontFamily - targetProperty, rowModelIndex); + currNode = ModelNodeOperations::handleItemLibraryFontDrop( + assetData, // assetData is fontFamily + targetProperty, + modelNodeForIndex(rowModelIndex)); } else if (assetType == Constants::MIME_TYPE_ASSET_SHADER) { - currNode = handleItemLibraryShaderDrop(assetPath, assetData == "f", - targetProperty, rowModelIndex, - moveNodesAfter); + currNode = ModelNodeOperations::handleItemLibraryShaderDrop( + assetPath, + assetData == "f", + targetProperty, + modelNodeForIndex(rowModelIndex), + moveNodesAfter); } else if (assetType == Constants::MIME_TYPE_ASSET_SOUND) { - currNode = handleItemLibrarySoundDrop(assetPath, targetProperty, - rowModelIndex); + currNode = ModelNodeOperations::handleItemLibrarySoundDrop( + assetPath, targetProperty, modelNodeForIndex(rowModelIndex)); } else if (assetType == Constants::MIME_TYPE_ASSET_TEXTURE3D) { - currNode = handleItemLibraryTexture3dDrop(assetPath, targetProperty, - rowModelIndex, moveNodesAfter); + currNode = ModelNodeOperations::handleItemLibraryTexture3dDrop( + assetPath, + targetProperty, + modelNodeForIndex(rowModelIndex), + moveNodesAfter); } else if (assetType == Constants::MIME_TYPE_ASSET_EFFECT) { - currNode = handleItemLibraryEffectDrop(assetPath, rowModelIndex); + currNode = ModelNodeOperations::handleItemLibraryEffectDrop( + assetPath, modelNodeForIndex(rowModelIndex)); moveNodesAfter = false; } @@ -718,55 +735,57 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in return; } MaterialUtils::assignMaterialTo3dModel(m_view, targetNode, newModelNode); - } + } else { + ChooseFromPropertyListDialog *dialog = ChooseFromPropertyListDialog::createIfNeeded( + targetNode, newModelNode, Core::ICore::dialogParent()); + if (dialog) { + bool soloProperty = dialog->isSoloProperty(); + if (!soloProperty) + dialog->exec(); + if (soloProperty || dialog->result() == QDialog::Accepted) { + TypeName selectedProp = dialog->selectedProperty(); - ChooseFromPropertyListDialog *dialog = ChooseFromPropertyListDialog::createIfNeeded( - targetNode, newModelNode, Core::ICore::dialogParent()); - if (dialog) { - bool soloProperty = dialog->isSoloProperty(); - if (!soloProperty) - dialog->exec(); - if (soloProperty || dialog->result() == QDialog::Accepted) { - TypeName selectedProp = dialog->selectedProperty(); + // Pass and TextureInput can't have children, so we have to move nodes under parent + if (((newModelNode.metaInfo().isQtQuick3DShader() + || newModelNode.metaInfo().isQtQuick3DCommand() + || newModelNode.metaInfo().isQtQuick3DBuffer()) + && targetProperty.parentModelNode().metaInfo().isQtQuick3DPass()) + || (newModelNode.metaInfo().isQtQuick3DTexture() + && targetProperty.parentModelNode().metaInfo().isQtQuick3DTextureInput())) { + if (moveNodeToParent(targetProperty, newQmlObjectNode)) { + targetProperty = targetProperty.parentProperty(); + moveNodesAfter = false; + } + } - // Pass and TextureInput can't have children, so we have to move nodes under parent - if (((newModelNode.metaInfo().isQtQuick3DShader() - || newModelNode.metaInfo().isQtQuick3DCommand() - || newModelNode.metaInfo().isQtQuick3DBuffer()) - && targetProperty.parentModelNode().metaInfo().isQtQuick3DPass()) - || (newModelNode.metaInfo().isQtQuick3DTexture() - && targetProperty.parentModelNode().metaInfo().isQtQuick3DTextureInput())) { - if (moveNodeToParent(targetProperty, newQmlObjectNode)) { - targetProperty = targetProperty.parentProperty(); - moveNodesAfter = false; + if (targetNode.metaInfo().property(selectedProp).isListProperty()) { + BindingProperty listProp = targetNode.bindingProperty(selectedProp); + listProp.addModelNodeToArray(newModelNode); + validContainer = true; + } else { + targetNode.bindingProperty(dialog->selectedProperty()).setExpression( + newModelNode.validId()); + validContainer = true; } } - - if (targetNode.metaInfo().property(selectedProp).isListProperty()) { - BindingProperty listProp = targetNode.bindingProperty(selectedProp); - listProp.addModelNodeToArray(newModelNode); - validContainer = true; - } else { - targetNode.bindingProperty(dialog->selectedProperty()).setExpression(newModelNode.validId()); - validContainer = true; - } + delete dialog; } - delete dialog; - } - if (newModelNode.metaInfo().isQtQuick3DView3D()) { - const QList models = newModelNode.subModelNodesOfType( - m_view->model()->qtQuick3DModelMetaInfo()); - QTC_ASSERT(models.size() == 1, return); - MaterialUtils::assignMaterialTo3dModel(m_view, models.at(0)); - } else if (newModelNode.metaInfo().isQtQuick3DModel()) { - MaterialUtils::assignMaterialTo3dModel(m_view, newModelNode); - } + if (newModelNode.metaInfo().isQtQuick3DView3D()) { + const QList models = newModelNode.subModelNodesOfType( + m_view->model()->qtQuick3DModelMetaInfo()); + QTC_ASSERT(models.size() == 1, return); + MaterialUtils::assignMaterialTo3dModel(m_view, models.at(0)); + } else if (newModelNode.metaInfo().isQtQuick3DModel()) { + MaterialUtils::assignMaterialTo3dModel(m_view, newModelNode); + } - if (!validContainer) { - validContainer = NodeHints::fromModelNode(targetProperty.parentModelNode()).canBeContainerFor(newModelNode); - if (!validContainer) - newQmlObjectNode.destroy(); + if (!validContainer) { + validContainer = NodeHints::fromModelNode(targetProperty.parentModelNode()) + .canBeContainerFor(newModelNode); + if (!validContainer) + newQmlObjectNode.destroy(); + } } } }); @@ -785,109 +804,6 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in } } -void NavigatorTreeModel::handleTextureDrop(const QMimeData *mimeData, const QModelIndex &dropModelIndex) -{ - QTC_ASSERT(m_view, return); - - const QModelIndex rowModelIndex = dropModelIndex.sibling(dropModelIndex.row(), 0); - QmlObjectNode targetNode = modelNodeForIndex(rowModelIndex); - if (!targetNode.isValid()) - return; - - qint32 internalId = mimeData->data(Constants::MIME_TYPE_TEXTURE).toInt(); - ModelNode texNode = m_view->modelNodeForInternalId(internalId); - QTC_ASSERT(texNode.isValid(), return); - - if (targetNode.modelNode().metaInfo().isQtQuick3DModel()) { - m_view->emitCustomNotification("apply_texture_to_model3D", {targetNode, texNode}); - } else { - auto *dialog = ChooseFromPropertyListDialog::createIfNeeded(targetNode, texNode, Core::ICore::dialogParent()); - if (dialog) { - bool soloProperty = dialog->isSoloProperty(); - if (!soloProperty) - dialog->exec(); - - if (soloProperty || dialog->result() == QDialog::Accepted) - targetNode.setBindingProperty(dialog->selectedProperty(), texNode.id()); - - delete dialog; - } - } -} - -void NavigatorTreeModel::handleMaterialDrop(const QMimeData *mimeData, const QModelIndex &dropModelIndex) -{ - QTC_ASSERT(m_view, return); - - const QModelIndex rowModelIndex = dropModelIndex.sibling(dropModelIndex.row(), 0); - ModelNode targetNode = modelNodeForIndex(rowModelIndex); - if (!targetNode.metaInfo().isQtQuick3DModel()) - return; - - qint32 internalId = mimeData->data(Constants::MIME_TYPE_MATERIAL).toInt(); - ModelNode matNode = m_view->modelNodeForInternalId(internalId); - - m_view->executeInTransaction(__FUNCTION__, [&] { - MaterialUtils::assignMaterialTo3dModel(m_view, targetNode, matNode); - }); -} - -ModelNode NavigatorTreeModel::handleItemLibraryImageDrop(const QString &imagePath, - NodeAbstractProperty targetProperty, - const QModelIndex &rowModelIndex, - bool &outMoveNodesAfter) -{ - QTC_ASSERT(m_view, return {}); - - ModelNode targetNode(modelNodeForIndex(rowModelIndex)); - - const QString imagePathRelative = DocumentManager::currentFilePath().toFileInfo().dir().relativeFilePath(imagePath); // relative to .ui.qml file - - ModelNode newModelNode; - - if (!dropAsImage3dTexture(targetNode, targetProperty, imagePathRelative, newModelNode, outMoveNodesAfter)) { - if (targetNode.metaInfo().isQtQuickImage() || targetNode.metaInfo().isQtQuickBorderImage()) { - // if dropping an image on an existing image, set the source - targetNode.variantProperty("source").setValue(imagePathRelative); - } else { - // create an image - QmlItemNode newItemNode = QmlItemNode::createQmlItemNodeFromImage(m_view, imagePath, QPointF(), targetProperty, false); - if (NodeHints::fromModelNode(targetProperty.parentModelNode()).canBeContainerFor(newItemNode.modelNode())) - newModelNode = newItemNode.modelNode(); - else - newItemNode.destroy(); - } - } - - return newModelNode; -} - -ModelNode NavigatorTreeModel::handleItemLibraryFontDrop(const QString &fontFamily, - NodeAbstractProperty targetProperty, - const QModelIndex &rowModelIndex) -{ - QTC_ASSERT(m_view, return {}); - - ModelNode targetNode(modelNodeForIndex(rowModelIndex)); - - ModelNode newModelNode; - - if (targetNode.metaInfo().isQtQuickText()) { - // if dropping into an existing Text, update font - targetNode.variantProperty("font.family").setValue(fontFamily); - } else { - // create a Text node - QmlItemNode newItemNode = QmlItemNode::createQmlItemNodeFromFont( - m_view, fontFamily, QPointF(), targetProperty, false); - if (NodeHints::fromModelNode(targetProperty.parentModelNode()).canBeContainerFor(newItemNode.modelNode())) - newModelNode = newItemNode.modelNode(); - else - newItemNode.destroy(); - } - - return newModelNode; -} - void NavigatorTreeModel::addImport(const QString &importName) { if (!ModelUtils::addImportWithCheck(importName, m_view->model())) @@ -900,227 +816,12 @@ bool QmlDesigner::NavigatorTreeModel::moveNodeToParent(const NodeAbstractPropert NodeAbstractProperty parentProp = targetProperty.parentProperty(); if (parentProp.isValid()) { ModelNode targetModel = parentProp.parentModelNode(); - int targetRowNumber = rowCount(indexForModelNode(targetModel)); - moveNodesInteractive(parentProp, {node}, targetRowNumber, false); + parentProp.reparentHere(node); return true; } return false; } -ModelNode NavigatorTreeModel::handleItemLibraryShaderDrop(const QString &shaderPath, bool isFragShader, - NodeAbstractProperty targetProperty, - const QModelIndex &rowModelIndex, - bool &outMoveNodesAfter) -{ - QTC_ASSERT(m_view, return {}); - - ModelNode targetNode(modelNodeForIndex(rowModelIndex)); - ModelNode newModelNode; - - const QString relPath = DocumentManager::currentFilePath().toFileInfo().dir().relativeFilePath(shaderPath); - - if (targetNode.metaInfo().isQtQuick3DShader()) { - // if dropping into an existing Shader, update - targetNode.variantProperty("stage").setEnumeration(isFragShader ? "Shader.Fragment" - : "Shader.Vertex"); - targetNode.variantProperty("shader").setValue(relPath); - } else { - m_view->executeInTransaction("NavigatorTreeModel::handleItemLibraryShaderDrop", [&] { - // create a new Shader - ItemLibraryEntry itemLibraryEntry; - itemLibraryEntry.setName("Shader"); - itemLibraryEntry.setType("QtQuick3D.Shader", 1, 0); - - // set shader properties - PropertyName prop = "shader"; - QString type = "QUrl"; - QVariant val = relPath; - itemLibraryEntry.addProperty(prop, type, val); - prop = "stage"; - type = "enum"; - val = isFragShader ? "Shader.Fragment" : "Shader.Vertex"; - itemLibraryEntry.addProperty(prop, type, val); - - // create a texture - newModelNode = QmlItemNode::createQmlObjectNode(m_view, itemLibraryEntry, {}, - targetProperty, false); - - // Rename the node based on shader source - QFileInfo fi(relPath); - newModelNode.setIdWithoutRefactoring( - m_view->model()->generateNewId(fi.baseName(), "shader")); - // Passes can't have children, so move shader node under parent - if (targetProperty.parentModelNode().metaInfo().isQtQuick3DPass()) { - BindingProperty listProp = targetNode.bindingProperty("shaders"); - listProp.addModelNodeToArray(newModelNode); - outMoveNodesAfter = !moveNodeToParent(targetProperty, newModelNode); - } - }); - } - - return newModelNode; -} - -ModelNode NavigatorTreeModel::handleItemLibrarySoundDrop(const QString &soundPath, - NodeAbstractProperty targetProperty, - const QModelIndex &rowModelIndex) -{ - QTC_ASSERT(m_view, return {}); - - ModelNode targetNode(modelNodeForIndex(rowModelIndex)); - ModelNode newModelNode; - - const QString relPath = DocumentManager::currentFilePath().toFileInfo().dir().relativeFilePath(soundPath); - - if (targetNode.metaInfo().isQtMultimediaSoundEffect()) { - // if dropping into on an existing SoundEffect, update - targetNode.variantProperty("source").setValue(relPath); - } else { - // create a new SoundEffect - ItemLibraryEntry itemLibraryEntry; - itemLibraryEntry.setName("SoundEffect"); - itemLibraryEntry.setType("QtMultimedia.SoundEffect", 1, 0); - - // set source property - PropertyName prop = "source"; - QString type = "QUrl"; - QVariant val = relPath; - itemLibraryEntry.addProperty(prop, type, val); - - // create a texture - newModelNode = QmlItemNode::createQmlObjectNode(m_view, itemLibraryEntry, {}, - targetProperty, false); - - // Rename the node based on source - QFileInfo fi(relPath); - newModelNode.setIdWithoutRefactoring( - m_view->model()->generateNewId(fi.baseName(), "soundEffect")); - } - - return newModelNode; -} - -ModelNode NavigatorTreeModel::handleItemLibraryTexture3dDrop(const QString &tex3DPath, - NodeAbstractProperty targetProperty, - const QModelIndex &rowModelIndex, - bool &outMoveNodesAfter) -{ - QTC_ASSERT(m_view, return {}); - - Import import = Import::createLibraryImport(QStringLiteral("QtQuick3D")); - if (!m_view->model()->hasImport(import, true, true)) - return {}; - - ModelNode targetNode(modelNodeForIndex(rowModelIndex)); - - const QString imagePath = DocumentManager::currentFilePath().toFileInfo().dir() - .relativeFilePath(tex3DPath); // relative to qml file - - ModelNode newModelNode; - - if (!dropAsImage3dTexture(targetNode, targetProperty, imagePath, newModelNode, outMoveNodesAfter)) { - m_view->executeInTransaction("NavigatorTreeModel::handleItemLibraryTexture3dDrop", [&] { - // create a standalone Texture3D at drop location - newModelNode = createTextureNode(targetProperty, imagePath); - if (!NodeHints::fromModelNode(targetProperty.parentModelNode()).canBeContainerFor(newModelNode)) - newModelNode.destroy(); - }); - } - - 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") - || !targetNode.metaInfo().isQtQuickItem()) - 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, - ModelNode &newNode, - bool &outMoveNodesAfter) -{ - auto bindToProperty = [&](const PropertyName &propName, bool sibling) { - m_view->executeInTransaction("NavigatorTreeModel::dropAsImage3dTexture", [&] { - newNode = createTextureNode(targetProp, imagePath); - if (newNode.isValid()) { - targetNode.bindingProperty(propName).setExpression(newNode.validId()); - - // If dropping an image on e.g. TextureInput, create a texture on the same level as - // target, as the target doesn't support Texture children (QTBUG-86219) - if (sibling) - outMoveNodesAfter = !moveNodeToParent(targetProp, newNode); - } - }); - }; - - if (targetNode.metaInfo().isQtQuick3DDefaultMaterial() - || targetNode.metaInfo().isQtQuick3DPrincipledMaterial() - || targetNode.metaInfo().isQtQuick3DSpecularGlossyMaterial()) { - // if dropping an image on a material, create a texture instead of image - // Show texture property selection dialog - auto dialog = ChooseFromPropertyListDialog::createIfNeeded(targetNode, - m_view->model()->metaInfo( - "QtQuick3D.Texture"), - Core::ICore::dialogParent()); - if (!dialog) - return false; - - dialog->exec(); - - if (dialog->result() == QDialog::Accepted) { - m_view->executeInTransaction("NavigatorTreeModel::dropAsImage3dTexture", [&] { - newNode = createTextureNode(targetProp, imagePath); - if (newNode.isValid()) // Automatically set the texture to selected property - targetNode.bindingProperty(dialog->selectedProperty()).setExpression(newNode.validId()); - }); - } - - delete dialog; - return true; - } else if (targetNode.metaInfo().isQtQuick3DTextureInput()) { - bindToProperty("texture", true); - return newNode.isValid(); - } else if (targetNode.metaInfo().isQtQuick3DParticles3DSpriteParticle3D()) { - bindToProperty("sprite", false); - return newNode.isValid(); - } else if (targetNode.metaInfo().isQtQuick3DSceneEnvironment()) { - bindToProperty("lightProbe", false); - return newNode.isValid(); - } else if (targetNode.metaInfo().isQtQuick3DTexture()) { - // if dropping an image on an existing texture, set the source - targetNode.variantProperty("source").setValue(imagePath); - return true; - } else if (targetNode.metaInfo().isQtQuick3DModel()) { - QTimer::singleShot(0, this, [this, targetNode, imagePath]() { - if (m_view && targetNode.isValid()) { - // To MaterialBrowserView. Done async to avoid custom notification in transaction - m_view->emitCustomNotification( - "apply_asset_to_model3D", {targetNode}, - {DocumentManager::currentFilePath().absolutePath().pathAppended(imagePath).cleanPath().toString()}); - } - }); - return true; - } - - return false; -} - ModelNode NavigatorTreeModel::createTextureNode(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 ab45294b75f..25a4d9637e7 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h @@ -93,21 +93,7 @@ private: int targetIndex, bool executeInTransaction = true); void handleInternalDrop(const QMimeData *mimeData, int rowNumber, const QModelIndex &dropModelIndex); void handleItemLibraryItemDrop(const QMimeData *mimeData, int rowNumber, const QModelIndex &dropModelIndex); - void handleTextureDrop(const QMimeData *mimeData, const QModelIndex &dropModelIndex); - void handleMaterialDrop(const QMimeData *mimeData, const QModelIndex &dropModelIndex); - ModelNode handleItemLibraryImageDrop(const QString &imagePath, NodeAbstractProperty targetProperty, - const QModelIndex &rowModelIndex, bool &outMoveNodesAfter); - ModelNode handleItemLibraryFontDrop(const QString &fontFamily, NodeAbstractProperty targetProperty, - const QModelIndex &rowModelIndex); - ModelNode handleItemLibraryShaderDrop(const QString &shaderPath, bool isFragShader, - NodeAbstractProperty targetProperty, - const QModelIndex &rowModelIndex, - bool &outMoveNodesAfter); - ModelNode handleItemLibrarySoundDrop(const QString &soundPath, NodeAbstractProperty targetProperty, - 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/navigator/navigatortreeview.cpp b/src/plugins/qmldesigner/components/navigator/navigatortreeview.cpp index 6e1532155f5..8de0fb1b01c 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreeview.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatortreeview.cpp @@ -12,7 +12,6 @@ #include -#include #include #include diff --git a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp index 456f44e43c6..a4a7ead0426 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp @@ -13,18 +13,18 @@ #include #include #include -#include +#include #include #include #include #include +#include +#include +#include #include #include #include #include -#include -#include -#include #include #include diff --git a/src/plugins/qmldesigner/components/pathtool/pathtoolview.cpp b/src/plugins/qmldesigner/components/pathtool/pathtoolview.cpp index eeb1dbe88d6..6fcedfa7434 100644 --- a/src/plugins/qmldesigner/components/pathtool/pathtoolview.cpp +++ b/src/plugins/qmldesigner/components/pathtool/pathtoolview.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include "pathtool.h" diff --git a/src/plugins/qmldesigner/components/propertyeditor/assetimageprovider.h b/src/plugins/qmldesigner/components/propertyeditor/assetimageprovider.h index 450086d323d..b71543444a6 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/assetimageprovider.h +++ b/src/plugins/qmldesigner/components/propertyeditor/assetimageprovider.h @@ -4,12 +4,13 @@ #pragma once #include "imagecache/midsizeimagecacheprovider.h" +#include "qmldesigner_global.h" #include namespace QmlDesigner { -class AssetImageProvider : public QQuickAsyncImageProvider +class QMLDESIGNER_EXPORT AssetImageProvider : public QQuickAsyncImageProvider { public: AssetImageProvider(AsynchronousImageCache &imageCache, const QImage &defaultImage = {}) diff --git a/src/plugins/qmldesigner/components/propertyeditor/colorpalettebackend.cpp b/src/plugins/qmldesigner/components/propertyeditor/colorpalettebackend.cpp index 8b48ca0ce7f..5f15f351110 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/colorpalettebackend.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/colorpalettebackend.cpp @@ -205,20 +205,17 @@ void ColorPaletteBackend::showDialog(QColor color) void ColorPaletteBackend::eyeDropper() { - QWidget *widget = QApplication::activeWindow(); - if (!widget) + QWindow *window = QGuiApplication::focusWindow(); + if (!window) return; if (!m_colorPickingEventFilter) m_colorPickingEventFilter = new QColorPickingEventFilter(this); - widget->installEventFilter(m_colorPickingEventFilter); + window->installEventFilter(m_colorPickingEventFilter); + window->setMouseGrabEnabled(true); + window->setKeyboardGrabEnabled(true); -#ifndef QT_NO_CURSOR - widget->grabMouse(/*Qt::CrossCursor*/); -#else - w->grabMouse(); -#endif #ifdef Q_OS_WIN32 // excludes WinRT // On Windows mouse tracking doesn't work over other processes's windows updateTimer->start(30); @@ -227,11 +224,6 @@ void ColorPaletteBackend::eyeDropper() // and loose focus. dummyTransparentWindow.show(); #endif - widget->grabKeyboard(); - /* With setMouseTracking(true) the desired color can be more precisely picked up, - * and continuously pushing the mouse button is not necessary. - */ - widget->setMouseTracking(true); updateEyeDropperPosition(QCursor::pos()); } @@ -281,8 +273,8 @@ void ColorPaletteBackend::updateEyeDropperPosition(const QPoint &globalPos) void ColorPaletteBackend::updateCursor(const QImage &image) { - QWidget *widget = QApplication::activeWindow(); - if (!widget) + QWindow *window = QGuiApplication::focusWindow(); + if (!window) return; QPixmap pixmap(QSize(g_cursorWidth, g_cursorHeight)); @@ -316,25 +308,23 @@ void ColorPaletteBackend::updateCursor(const QImage &image) painter.end(); QCursor cursor(pixmap); - widget->setCursor(cursor); + window->setCursor(cursor); } void ColorPaletteBackend::releaseEyeDropper() { - QWidget *widget = QApplication::activeWindow(); - if (!widget) + QWindow *window = QGuiApplication::focusWindow(); + if (!window) return; - widget->removeEventFilter(m_colorPickingEventFilter); - widget->releaseMouse(); + window->removeEventFilter(m_colorPickingEventFilter); + window->setMouseGrabEnabled(false); #ifdef Q_OS_WIN32 updateTimer->stop(); dummyTransparentWindow.setVisible(false); #endif - widget->releaseKeyboard(); - widget->setMouseTracking(false); - - widget->unsetCursor(); + window->setKeyboardGrabEnabled(false); + window->unsetCursor(); } bool ColorPaletteBackend::handleEyeDropperMouseMove(QMouseEvent *e) @@ -370,7 +360,4 @@ bool ColorPaletteBackend::handleEyeDropperKeyPress(QKeyEvent *e) return true; } -/// EYE DROPPER - - } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/gradientmodel.cpp b/src/plugins/qmldesigner/components/propertyeditor/gradientmodel.cpp index eaf907e8f82..cadca2fd604 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/gradientmodel.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/gradientmodel.cpp @@ -22,58 +22,42 @@ #include namespace { -constexpr auto defaultValueLinearX1 = [](const QmlDesigner::QmlItemNode &) -> qreal { return 0.0; }; -constexpr auto defaultValueLinearY1 = [](const QmlDesigner::QmlItemNode &) -> qreal { return 0.0; }; -constexpr auto defaultValueLinearX2 = [](const QmlDesigner::QmlItemNode &node) -> qreal { - return node.instanceValue("width").toReal(); +constexpr auto widthBinding = [](const QStringView nodeName) -> QString { + return QString("%1.width").arg(nodeName); }; -constexpr auto defaultValueLinearY2 = [](const QmlDesigner::QmlItemNode &node) -> qreal { - return node.instanceValue("height").toReal(); +constexpr auto heightBinding = [](const QStringView nodeName) -> QString { + return QString("%1.height").arg(nodeName); }; -constexpr auto defaultValueRadialCenterRadius = [](const QmlDesigner::QmlItemNode &node) -> qreal { - const qreal width = node.instanceValue("width").toReal(); - const qreal height = node.instanceValue("height").toReal(); - return qMin(width, height) / 2.0; +constexpr auto minBinding = [](const QStringView nodeName) -> QString { + return QString("Math.min(%1.width, %1.height)").arg(nodeName); }; -constexpr auto defaultValueRadialCenterX = [](const QmlDesigner::QmlItemNode &node) -> qreal { - return (node.instanceValue("width").toReal() / 2.0); -}; -constexpr auto defaultValueRadialCenterY = [](const QmlDesigner::QmlItemNode &node) -> qreal { - return (node.instanceValue("height").toReal() / 2.0); -}; -constexpr auto defaultValueRadialFocalRadius = [](const QmlDesigner::QmlItemNode &) -> qreal { - return 0.0; -}; -constexpr auto defaultValueRadialFocalX = [](const QmlDesigner::QmlItemNode &node) -> qreal { - return (node.instanceValue("width").toReal() / 2.0); -}; -constexpr auto defaultValueRadialFocalY = [](const QmlDesigner::QmlItemNode &node) -> qreal { - return (node.instanceValue("height").toReal() / 2.0); -}; -constexpr auto defaultValueConicalAngle = [](const QmlDesigner::QmlItemNode &) -> qreal { - return 0.0; -}; -constexpr auto defaultValueConicalCenterX = [](const QmlDesigner::QmlItemNode &node) -> qreal { - return (node.instanceValue("width").toReal() / 2.0); -}; -constexpr auto defaultValueConicalCenterY = [](const QmlDesigner::QmlItemNode &node) -> qreal { - return (node.instanceValue("height").toReal() / 2.0); +constexpr auto emptyBinding = [](const QStringView) -> QString { + return {}; }; -using DefaultValueFunctionVariant = std::variant; +using BindingStringFunctionVariant = std::variant; + +constexpr auto widthBindingValue = [](const QmlDesigner::QmlItemNode &node) -> qreal { + return node.instanceValue("width").toReal(); +}; +constexpr auto heightBindingValue = [](const QmlDesigner::QmlItemNode &node) -> qreal { + return node.instanceValue("height").toReal(); +}; +constexpr auto minBindingValue = [](const QmlDesigner::QmlItemNode &node) -> qreal { + return qMin(node.instanceValue("width").toReal(), node.instanceValue("height").toReal()); +}; +constexpr auto emptyBindingValue = [](const QmlDesigner::QmlItemNode &) -> qreal { return 0.0; }; + +using BindingValueFunctionVariant = std::variant; + } // namespace class ShapeGradientPropertyData @@ -84,34 +68,60 @@ public: constexpr ShapeGradientPropertyData() = default; constexpr ShapeGradientPropertyData(QmlDesigner::PropertyNameView name, - QmlDesigner::PropertyNameView bindingProperty, UsePercents canPercent, - DefaultValueFunctionVariant defVariant) + qreal defPercent, + BindingStringFunctionVariant binVariant, + BindingValueFunctionVariant binValueVariant) : name(name) - , bindingProperty(bindingProperty) , canUsePercentage(canPercent) - , m_defaultValue(defVariant) + , defaultPercent(defPercent) + , m_bindingString(binVariant) + , m_bindingValue(binValueVariant) {} QmlDesigner::PropertyNameView name; - QmlDesigner::PropertyNameView bindingProperty; UsePercents canUsePercentage = UsePercents::No; + qreal defaultPercent = 0.0; private: - DefaultValueFunctionVariant m_defaultValue; + BindingStringFunctionVariant m_bindingString; + BindingValueFunctionVariant m_bindingValue; public: - constexpr qreal getDefaultValue(const QmlDesigner::QmlItemNode &itemNode) const + QString getBindingString(const QStringView nodeId) const { return std::visit( - [&](auto &defValue) -> qreal { - using Type = std::decay_t; + [&](auto &func) -> QString { + using Type = std::decay_t; if constexpr (std::is_same_v) - return 0.0; + return {}; else - return defValue(itemNode); + return func(nodeId); }, - m_defaultValue); + m_bindingString); + } + + qreal getBindingValue(const QmlDesigner::QmlItemNode &node) const + { + return std::visit( + [&](auto &func) -> qreal { + using Type = std::decay_t; + if constexpr (std::is_same_v) + return {}; + else + return func(node); + }, + m_bindingValue); + } + + qreal getDefaultValue(const QmlDesigner::QmlItemNode &itemNode) const + { + return getBindingValue(itemNode) * defaultPercent; + } + + QString getDefaultPercentString(const QStringView nodeId) const + { + return QString("%1 * %2").arg(getBindingString(nodeId), QString::number(defaultPercent)); } }; @@ -132,26 +142,75 @@ constexpr QmlDesigner::PropertyNameView conicalCenterXStr = u8"centerX"; constexpr QmlDesigner::PropertyNameView conicalCenterYStr = u8"centerY"; constexpr ShapeGradientPropertyData defaultLinearShapeGradients[] = { - {linearX1Str, u8"width", ShapeGradientPropertyData::UsePercents::Yes, defaultValueLinearX1}, - {linearX2Str, u8"width", ShapeGradientPropertyData::UsePercents::Yes, defaultValueLinearX2}, - {linearY1Str, u8"height", ShapeGradientPropertyData::UsePercents::Yes, defaultValueLinearY1}, - {linearY2Str, u8"height", ShapeGradientPropertyData::UsePercents::Yes, defaultValueLinearY2}}; + {linearX1Str, + ShapeGradientPropertyData::UsePercents::Yes, + 0.0, + widthBinding, + widthBindingValue}, + {linearX2Str, + ShapeGradientPropertyData::UsePercents::Yes, + 1.0, + widthBinding, + widthBindingValue}, + {linearY1Str, + ShapeGradientPropertyData::UsePercents::Yes, + 0.0, + heightBinding, + heightBindingValue}, + {linearY2Str, + ShapeGradientPropertyData::UsePercents::Yes, + 1.0, + heightBinding, + heightBindingValue}}; constexpr ShapeGradientPropertyData defaultRadialShapeGradients[] = { - {radialCenterRadiusStr, u8"", ShapeGradientPropertyData::UsePercents::No, defaultValueRadialCenterRadius}, - {radialCenterXStr, u8"width", ShapeGradientPropertyData::UsePercents::Yes, defaultValueRadialCenterX}, - {radialCenterYStr, u8"height", ShapeGradientPropertyData::UsePercents::Yes, defaultValueRadialCenterY}, - {radialFocalRadiusStr, u8"", ShapeGradientPropertyData::UsePercents::No, defaultValueRadialFocalRadius}, - {radialFocalXStr, u8"width", ShapeGradientPropertyData::UsePercents::Yes, defaultValueRadialFocalX}, - {radialFocalYStr, u8"height", ShapeGradientPropertyData::UsePercents::Yes, defaultValueRadialFocalY}}; + {radialCenterRadiusStr, + ShapeGradientPropertyData::UsePercents::Yes, + 0.5, + minBinding, + minBindingValue}, + {radialCenterXStr, + ShapeGradientPropertyData::UsePercents::Yes, + 0.5, + widthBinding, + widthBindingValue}, + {radialCenterYStr, + ShapeGradientPropertyData::UsePercents::Yes, + 0.5, + heightBinding, + heightBindingValue}, + {radialFocalRadiusStr, + ShapeGradientPropertyData::UsePercents::Yes, + 0.0, + minBinding, + minBindingValue}, + {radialFocalXStr, + ShapeGradientPropertyData::UsePercents::Yes, + 0.5, + widthBinding, + widthBindingValue}, + {radialFocalYStr, + ShapeGradientPropertyData::UsePercents::Yes, + 0.5, + heightBinding, + heightBindingValue}}; constexpr ShapeGradientPropertyData defaultConicalShapeGradients[] = { - {conicalAngleStr, u8"", ShapeGradientPropertyData::UsePercents::No, defaultValueConicalAngle}, - {conicalCenterXStr, u8"width", ShapeGradientPropertyData::UsePercents::Yes, defaultValueConicalCenterX}, - {conicalCenterYStr, - u8"height", + {conicalAngleStr, + ShapeGradientPropertyData::UsePercents::No, + 0.0, + emptyBinding, + emptyBindingValue}, + {conicalCenterXStr, ShapeGradientPropertyData::UsePercents::Yes, - defaultValueConicalCenterY}}; + 0.5, + widthBinding, + widthBindingValue}, + {conicalCenterYStr, + ShapeGradientPropertyData::UsePercents::Yes, + 0.5, + heightBinding, + heightBindingValue}}; template const ShapeGradientPropertyData *findGradientInArray(const GradientArrayType &array, @@ -183,10 +242,17 @@ const ShapeGradientPropertyData *getDefaultGradientData(const QmlDesigner::Prope template void prepareGradient(const T &array, const QmlDesigner::ModelNode &gradient, - const QmlDesigner::QmlItemNode &node) + const QmlDesigner::QmlItemNode &node, + ShapeGradientPropertyData::UsePercents usePercents) { - std::for_each(std::begin(array), std::end(array), [&](auto &a) { - gradient.variantProperty(a.name.toByteArray()).setValue(a.getDefaultValue(node)); + std::for_each(std::begin(array), std::end(array), [&](const ShapeGradientPropertyData &a) { + if (a.canUsePercentage == ShapeGradientPropertyData::UsePercents::Yes + && usePercents == ShapeGradientPropertyData::UsePercents::Yes) { + gradient.bindingProperty(a.name.toByteArray()) + .setExpression(a.getDefaultPercentString(node.id())); + } else { + gradient.variantProperty(a.name.toByteArray()).setValue(a.getDefaultValue(node)); + } }); } @@ -507,7 +573,7 @@ void GradientModel::setAnchorBackend(const QVariant &anchorBackend) auto anchorBackendObject = anchorBackend.value(); const auto backendCasted = - qobject_cast(anchorBackendObject); + qobject_cast(anchorBackendObject); if (backendCasted) m_itemNode = backendCasted->getItemNode(); @@ -593,11 +659,20 @@ void GradientModel::setupGradientProperties(const QmlDesigner::ModelNode &gradie if (m_gradientTypeName == u"Gradient") { gradient.variantProperty("orientation").setEnumeration("Gradient.Vertical"); } else if (m_gradientTypeName == u"LinearGradient") { - prepareGradient(defaultLinearShapeGradients, gradient, m_itemNode); + prepareGradient(defaultLinearShapeGradients, + gradient, + m_itemNode, + ShapeGradientPropertyData::UsePercents::Yes); } else if (m_gradientTypeName == u"RadialGradient") { - prepareGradient(defaultRadialShapeGradients, gradient, m_itemNode); + prepareGradient(defaultRadialShapeGradients, + gradient, + m_itemNode, + ShapeGradientPropertyData::UsePercents::Yes); } else if (m_gradientTypeName == u"ConicalGradient") { - prepareGradient(defaultConicalShapeGradients, gradient, m_itemNode); + prepareGradient(defaultConicalShapeGradients, + gradient, + m_itemNode, + ShapeGradientPropertyData::UsePercents::Yes); } } @@ -684,7 +759,10 @@ qreal GradientModel::getPercentageGradientProperty(const QmlDesigner::PropertyNa if (ok != nullptr) *ok = false; - if (!m_itemNode.isValid()) + if (!m_itemNode.isValid() || !m_itemNode.hasModelNode()) + return 0.; + + if (!m_itemNode.modelNode().hasId()) return 0.; //valid format is itemName1.property * 0.1 @@ -703,33 +781,20 @@ qreal GradientModel::getPercentageGradientProperty(const QmlDesigner::PropertyNa if (const auto bindingProperty = gradientModel.bindingProperty(propertyName.toByteArray())) { const auto defaultGradient = getDefaultGradientPropertyData(propertyName, m_gradientTypeName); - const auto expectedParentProperty = defaultGradient.bindingProperty; + const auto expectedBindingProperty = defaultGradient.getBindingString(m_itemNode.id()); const QString expression = bindingProperty.expression(); const QStringList splitExpression = expression.split("*", Qt::SkipEmptyParts); - if (splitExpression.length() == 2 && !expectedParentProperty.isEmpty()) { + if (splitExpression.length() == 2 && !expectedBindingProperty.isEmpty()) { const QString parentStr = splitExpression.at(0).trimmed(); const QString percentageStr = splitExpression.at(1).trimmed(); - bool validStatement = false; - - if (!parentStr.isEmpty()) { - const QStringList splitParent = parentStr.split(".", Qt::SkipEmptyParts); - if (splitParent.length() == 2) { - const QString itemId = splitParent.at(0).trimmed(); - const QString itemProp = splitParent.at(1).trimmed(); - const QString realParentId = m_itemNode.modelNode().hasId() ? m_itemNode.id() : ""; - if (!itemId.isEmpty() && !itemProp.isEmpty() && (itemId == realParentId) - && (itemProp.toUtf8() == expectedParentProperty)) { - validStatement = true; - } + if (!parentStr.isEmpty() && !percentageStr.isEmpty()) { + if (parentStr == expectedBindingProperty) { + const qreal percentage = percentageStr.toFloat(ok); + return percentage; } } - - if (!percentageStr.isEmpty() && validStatement) { - const qreal percentage = percentageStr.toFloat(ok); - return percentage; - } } } @@ -793,17 +858,15 @@ void GradientModel::setGradientPropertyPercentage(const QString &propertyName, q QTC_ASSERT(gradientDefaultData.canUsePercentage == ShapeGradientPropertyData::UsePercents::Yes, return); - const QmlDesigner::PropertyNameView parentPropertyStr = gradientDefaultData.bindingProperty; - QTC_ASSERT(!parentPropertyStr.isEmpty(), return); + const QString parentId = m_itemNode.validId(); + const QString bindingPropertyStr = gradientDefaultData.getBindingString(parentId); + QTC_ASSERT(!bindingPropertyStr.isEmpty(), return); - if (parentPropertyStr.isEmpty() + if (bindingPropertyStr.isEmpty() || (gradientDefaultData.canUsePercentage == ShapeGradientPropertyData::UsePercents::No)) { return; } - - const QString parentId = m_itemNode.validId(); - const QString leftBinding = parentId + "." + parentPropertyStr; - const QString expression = leftBinding + " * " + QString::number(value); + const QString expression = bindingPropertyStr + " * " + QString::number(value); try { gradient.setBindingProperty(propertyName.toUtf8(), expression); @@ -840,11 +903,11 @@ void GradientModel::setGradientPropertyUnits(const QString &propertyName, const auto defaultGradientData = getDefaultGradientPropertyData(propertyName.toUtf8(), m_gradientTypeName); - const QmlDesigner::PropertyNameView parentPropertyName = defaultGradientData.bindingProperty; + const QString parentPropertyName = defaultGradientData.getBindingString(m_itemNode.validId()); if (parentPropertyName.isEmpty()) return; - const qreal parentPropertyValue = m_itemNode.instanceValue(parentPropertyName.toByteArray()).toReal(); + const qreal parentPropertyValue = defaultGradientData.getBindingValue(m_itemNode); if (toPixels) { bool ok = false; diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp index 2e49befd9e1..393183f57c4 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp @@ -400,7 +400,7 @@ void PropertyEditorQmlBackend::setSource(const QUrl &url) } } -Internal::QmlAnchorBindingProxy &PropertyEditorQmlBackend::backendAnchorBinding() +QmlAnchorBindingProxy &PropertyEditorQmlBackend::backendAnchorBinding() { return m_backendAnchorBinding; } diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h index b369a417e20..64cca972071 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h @@ -41,7 +41,7 @@ public: PropertyEditorContextObject* contextObject(); QQuickWidget *widget(); void setSource(const QUrl& url); - Internal::QmlAnchorBindingProxy &backendAnchorBinding(); + QmlAnchorBindingProxy &backendAnchorBinding(); DesignerPropertyMap &backendValuesPropertyMap(); PropertyEditorTransaction *propertyEditorTransaction(); @@ -88,7 +88,7 @@ private: private: Quick2PropertyEditorView *m_view; - Internal::QmlAnchorBindingProxy m_backendAnchorBinding; + QmlAnchorBindingProxy m_backendAnchorBinding; QmlModelNodeProxy m_backendModelNode; DesignerPropertyMap m_backendValuesPropertyMap; QScopedPointer m_propertyEditorTransaction; diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp index 1fba74f2178..48be900851e 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp @@ -82,7 +82,7 @@ PropertyEditorView::PropertyEditorView(AsynchronousImageCache &imageCache, m_stackedWidget->setStyleSheet(Theme::replaceCssColors( QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css")))); - m_stackedWidget->setMinimumWidth(340); + m_stackedWidget->setMinimumSize(340, 340); m_stackedWidget->move(0, 0); connect(m_stackedWidget, &PropertyEditorWidget::resized, this, &PropertyEditorView::updateSize); diff --git a/src/plugins/qmldesigner/components/propertyeditor/qmlanchorbindingproxy.cpp b/src/plugins/qmldesigner/components/propertyeditor/qmlanchorbindingproxy.cpp index c28602e7307..140828756aa 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/qmlanchorbindingproxy.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/qmlanchorbindingproxy.cpp @@ -52,8 +52,6 @@ static inline void restoreProperty(const ModelNode &node, const PropertyName &pr node.variantProperty(propertyName).setValue(*value); } -namespace Internal { - QmlAnchorBindingProxy::QmlAnchorBindingProxy(QObject *parent) : QObject(parent), m_relativeTopTarget(SameEdge), m_relativeBottomTarget(SameEdge), @@ -1132,6 +1130,4 @@ void QmlAnchorBindingProxy::setDefaultAnchorTarget(const ModelNode &modelNode) m_rightTarget = modelNode; } -} // namespace Internal } // namespace QmlDesigner - diff --git a/src/plugins/qmldesigner/components/propertyeditor/qmlanchorbindingproxy.h b/src/plugins/qmldesigner/components/propertyeditor/qmlanchorbindingproxy.h index 4f65af289dc..2bb38980790 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/qmlanchorbindingproxy.h +++ b/src/plugins/qmldesigner/components/propertyeditor/qmlanchorbindingproxy.h @@ -13,9 +13,7 @@ namespace QmlDesigner { class NodeInstanceView; -namespace Internal { - -class QmlAnchorBindingProxy : public QObject +class QMLDESIGNERCORE_EXPORT QmlAnchorBindingProxy : public QObject { Q_OBJECT @@ -222,5 +220,4 @@ private: bool m_ignoreQml; }; -} // namespace Internal } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp index 1c0071ee91b..59fbbffc9ca 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp @@ -53,7 +53,7 @@ void Quick2PropertyEditorView::registerQmlTypes() ItemFilterModel::registerDeclarativeType(); ListValidator::registerDeclarativeType(); ColorPaletteBackend::registerDeclarativeType(); - Internal::QmlAnchorBindingProxy::registerDeclarativeType(); + QmlAnchorBindingProxy::registerDeclarativeType(); BindingEditor::registerDeclarativeType(); ActionEditor::registerDeclarativeType(); AnnotationEditor::registerDeclarativeType(); @@ -70,13 +70,6 @@ void Quick2PropertyEditorView::registerQmlTypes() QUrl regExpUrl = QUrl::fromLocalFile(resourcePath + "/RegExpValidator.qml"); qmlRegisterType(regExpUrl, "HelperWidgets", 2, 0, "RegExpValidator"); - - const QString qtPrefix = "/Qt6"; - qmlRegisterType(QUrl::fromLocalFile(resourcePath + qtPrefix + "HelperWindow.qml"), - "HelperWidgets", - 2, - 0, - "HelperWindow"); } } diff --git a/src/plugins/qmldesigner/components/propertyeditor/tooltip.cpp b/src/plugins/qmldesigner/components/propertyeditor/tooltip.cpp index 824e104cdff..dd50e1ff171 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/tooltip.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/tooltip.cpp @@ -34,5 +34,6 @@ void Tooltip::hideText() void Tooltip::registerDeclarativeType() { + qmlRegisterType("StudioControls", 1, 0, "ToolTipExt"); qmlRegisterType("HelperWidgets", 2, 0, "Tooltip"); } diff --git a/src/plugins/qmldesigner/components/stateseditor/stateseditormodel.cpp b/src/plugins/qmldesigner/components/stateseditor/stateseditormodel.cpp index 8c3e79e9764..606e136de01 100644 --- a/src/plugins/qmldesigner/components/stateseditor/stateseditormodel.cpp +++ b/src/plugins/qmldesigner/components/stateseditor/stateseditormodel.cpp @@ -5,7 +5,9 @@ #include "stateseditorview.h" #include +#include #include +#include #include #include #include @@ -73,6 +75,7 @@ void StatesEditorModel::reset() evaluateExtend(); emit baseStateChanged(); + emit isMCUsChanged(); } QVariant StatesEditorModel::data(const QModelIndex &index, int role) const @@ -246,6 +249,11 @@ bool StatesEditorModel::hasDefaultState() const return m_statesEditorView->hasDefaultState(); } +void StatesEditorModel::jumpToCode() +{ + ModelNodeOperations::jumpToCode(m_statesEditorView->currentState().modelNode()); +} + void StatesEditorModel::setAnnotation(int internalNodeId) { m_statesEditorView->setAnnotation(internalNodeId); @@ -450,4 +458,9 @@ void StatesEditorModel::setCanAddNewStates(bool b) emit canAddNewStatesChanged(); } +bool StatesEditorModel::isMCUs() const +{ + return DesignerMcuManager::instance().isMCUProject(); +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/stateseditor/stateseditormodel.h b/src/plugins/qmldesigner/components/stateseditor/stateseditormodel.h index 8eff287c869..12d66e29bf2 100644 --- a/src/plugins/qmldesigner/components/stateseditor/stateseditormodel.h +++ b/src/plugins/qmldesigner/components/stateseditor/stateseditormodel.h @@ -50,6 +50,7 @@ public: Q_INVOKABLE void setStateAsDefault(int internalNodeId); Q_INVOKABLE void resetDefaultState(); Q_INVOKABLE bool hasDefaultState() const; + Q_INVOKABLE void jumpToCode(); Q_INVOKABLE void setAnnotation(int internalNodeId); Q_INVOKABLE void removeAnnotation(int internalNodeId); Q_INVOKABLE bool hasAnnotation(int internalNodeId) const; @@ -81,6 +82,7 @@ public: Q_PROPERTY(int activeStateGroupIndex READ activeStateGroupIndex WRITE setActiveStateGroupIndex NOTIFY activeStateGroupIndexChanged) Q_PROPERTY(QStringList stateGroups READ stateGroups NOTIFY stateGroupsChanged) + Q_PROPERTY(bool isMCUs READ isMCUs NOTIFY isMCUsChanged) Q_INVOKABLE void move(int from, int to); Q_INVOKABLE void drop(int from, int to); @@ -91,6 +93,8 @@ public: bool canAddNewStates() const; void setCanAddNewStates(bool b); + bool isMCUs() const; + signals: void changedToState(int n); void baseStateChanged(); @@ -100,6 +104,7 @@ signals: void activeStateGroupIndexChanged(); void stateGroupsChanged(); void canAddNewStatesChanged(); + void isMCUsChanged(); private: QPointer m_statesEditorView; diff --git a/src/plugins/qmldesigner/components/stateseditor/stateseditorview.cpp b/src/plugins/qmldesigner/components/stateseditor/stateseditorview.cpp index 5df44802e4b..26530647d86 100644 --- a/src/plugins/qmldesigner/components/stateseditor/stateseditorview.cpp +++ b/src/plugins/qmldesigner/components/stateseditor/stateseditorview.cpp @@ -612,6 +612,7 @@ void StatesEditorView::setStateAsDefault(int internalNodeId) e.showException(); } } + resetModel(); } void StatesEditorView::resetDefaultState() @@ -629,6 +630,7 @@ void StatesEditorView::resetDefaultState() } catch (const RewritingException &e) { e.showException(); } + resetModel(); } bool StatesEditorView::hasDefaultState() const @@ -729,8 +731,12 @@ void StatesEditorView::modelAboutToBeDetached(Model *model) void StatesEditorView::propertiesRemoved(const QList &propertyList) { + if (m_block) + return; + for (const AbstractProperty &property : propertyList) { - if (property.name() == "states" && property.parentModelNode() == activeStateGroup().modelNode()) + if ((property.name() == "states" || property.name() == "state") + && property.parentModelNode() == activeStateGroup().modelNode()) resetModel(); if ((property.name() == "when" || property.name() == "name") && QmlModelState::isValidQmlModelState(property.parentModelNode())) diff --git a/src/plugins/qmldesigner/components/stateseditor/stateseditorwidget.cpp b/src/plugins/qmldesigner/components/stateseditor/stateseditorwidget.cpp index d0d5e88f5c4..08c36778691 100644 --- a/src/plugins/qmldesigner/components/stateseditor/stateseditorwidget.cpp +++ b/src/plugins/qmldesigner/components/stateseditor/stateseditorwidget.cpp @@ -91,8 +91,7 @@ StatesEditorWidget::StatesEditorWidget(StatesEditorView *statesEditorView, Theme::setupTheme(engine()); setWindowTitle(tr("States", "Title of Editor widget")); - setMinimumWidth(195); - setMinimumHeight(195); + setMinimumSize(QSize(195, 195)); // init the first load of the QML UI elements reloadQmlSource(); diff --git a/src/plugins/qmldesigner/components/texteditor/texteditorview.cpp b/src/plugins/qmldesigner/components/texteditor/texteditorview.cpp index 0826df54848..cdf09c14933 100644 --- a/src/plugins/qmldesigner/components/texteditor/texteditorview.cpp +++ b/src/plugins/qmldesigner/components/texteditor/texteditorview.cpp @@ -34,11 +34,12 @@ #include #include +#include #include #include +#include #include #include -#include namespace QmlDesigner { @@ -292,6 +293,14 @@ void TextEditorView::reformatFile() } } +void TextEditorView::jumpToModelNode(const ModelNode &modelNode) +{ + m_widget->jumpToModelNode(modelNode); + + m_widget->window()->windowHandle()->requestActivate(); + m_widget->textEditor()->widget()->setFocus(); +} + void TextEditorView::instancePropertyChanged(const QList > &/*propertyList*/) { } diff --git a/src/plugins/qmldesigner/components/texteditor/texteditorview.h b/src/plugins/qmldesigner/components/texteditor/texteditorview.h index e50372279d9..33a5ec8276c 100644 --- a/src/plugins/qmldesigner/components/texteditor/texteditorview.h +++ b/src/plugins/qmldesigner/components/texteditor/texteditorview.h @@ -81,6 +81,8 @@ public: void reformatFile(); + void jumpToModelNode(const ModelNode &modelNode); + private: QPointer m_widget; Internal::TextEditorContext *m_textEditorContext; diff --git a/src/plugins/qmldesigner/components/texteditor/texteditorwidget.cpp b/src/plugins/qmldesigner/components/texteditor/texteditorwidget.cpp index 122228f2086..93976f10d9e 100644 --- a/src/plugins/qmldesigner/components/texteditor/texteditorwidget.cpp +++ b/src/plugins/qmldesigner/components/texteditor/texteditorwidget.cpp @@ -4,28 +4,32 @@ #include "texteditorwidget.h" #include "utils/uniqueobjectptr.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 namespace QmlDesigner { @@ -48,11 +52,15 @@ TextEditorWidget::TextEditorWidget(TextEditorView *textEditorView) m_updateSelectionTimer.setSingleShot(true); m_updateSelectionTimer.setInterval(200); - connect(&m_updateSelectionTimer, &QTimer::timeout, this, &TextEditorWidget::updateSelectionByCursorPosition); + connect(&m_updateSelectionTimer, + &QTimer::timeout, + this, + &TextEditorWidget::updateSelectionByCursorPosition); QmlDesignerPlugin::trackWidgetFocusTime(this, Constants::EVENT_TEXTEDITOR_TIME); } -void TextEditorWidget::setTextEditor(Utils::UniqueObjectLatePtr textEditor) +void TextEditorWidget::setTextEditor( + Utils::UniqueObjectLatePtr textEditor) { std::swap(m_textEditor, textEditor); @@ -98,6 +106,36 @@ void TextEditorWidget::updateSelectionByCursorPosition() m_blockRoundTrip = false; } +void TextEditorWidget::jumpToModelNode(const ModelNode &modelNode) +{ + RewriterView *rewriterView = m_textEditorView->model()->rewriterView(); + + m_blockCursorSelectionSynchronisation = true; + const int nodeOffset = rewriterView->nodeOffset(modelNode); + if (nodeOffset > 0) { + int line, column; + m_textEditor->editorWidget()->convertPosition(nodeOffset, &line, &column); + m_textEditor->editorWidget()->gotoLine(line + 1, column); + + highlightToModelNode(modelNode); + } + m_blockCursorSelectionSynchronisation = false; +} + +void TextEditorWidget::highlightToModelNode(const ModelNode &modelNode) +{ + RewriterView *rewriterView = m_textEditorView->model()->rewriterView(); + const int nodeOffset = rewriterView->nodeOffset(modelNode); + if (nodeOffset > 0) { + int line, column; + m_textEditor->editorWidget()->convertPosition(nodeOffset, &line, &column); + + QTextCursor cursor = m_textEditor->textCursor(); + cursor.setPosition(nodeOffset); + m_textEditor->editorWidget()->updateFoldingHighlight(cursor); + } +} + void TextEditorWidget::jumpTextCursorToSelectedModelNode() { if (m_blockRoundTrip) @@ -115,14 +153,16 @@ void TextEditorWidget::jumpTextCursorToSelectedModelNode() selectedNode = m_textEditorView->selectedModelNodes().constFirst(); if (selectedNode.isValid()) { - RewriterView *rewriterView = m_textEditorView->model()->rewriterView(); - - const int nodeOffset = rewriterView->nodeOffset(selectedNode); - if (nodeOffset > 0) { - int line, column; - m_textEditor->editorWidget()->convertPosition(nodeOffset, &line, &column); - // line has to be 1 based, column 0 based! - m_textEditor->editorWidget()->gotoLine(line, column - 1); + auto currentState = m_textEditorView->currentState(); + if (currentState.isBaseState()) { + jumpToModelNode(selectedNode); + } else { + if (currentState.affectsModelNode(selectedNode)) { + auto propertyChanges = currentState.propertyChanges(selectedNode); + jumpToModelNode(propertyChanges.modelNode()); + } else { + jumpToModelNode(currentState.modelNode()); + } } } m_updateSelectionTimer.stop(); @@ -193,23 +233,131 @@ bool TextEditorWidget::eventFilter(QObject *, QEvent *event) return true; } } + } else if (event->type() == QEvent::FocusIn) { + m_textEditor->editorWidget()->updateFoldingHighlight(QTextCursor()); + } else if (event->type() == QEvent::FocusOut) { + m_textEditor->editorWidget()->updateFoldingHighlight(QTextCursor()); } return false; } void TextEditorWidget::dragEnterEvent(QDragEnterEvent *dragEnterEvent) { - const DesignerActionManager &actionManager = QmlDesignerPlugin::instance() - ->viewManager().designerActionManager(); + const DesignerActionManager &actionManager + = QmlDesignerPlugin::instance()->viewManager().designerActionManager(); if (actionManager.externalDragHasSupportedAssets(dragEnterEvent->mimeData())) dragEnterEvent->acceptProposedAction(); + + if (dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_ITEM_LIBRARY_INFO) + || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_ASSETS)) { + QByteArray data = dragEnterEvent->mimeData()->data(Constants::MIME_TYPE_ITEM_LIBRARY_INFO); + if (!data.isEmpty()) { + QDataStream stream(data); + stream >> m_draggedEntry; + } + dragEnterEvent->acceptProposedAction(); + } +} + +void TextEditorWidget::dragMoveEvent(QDragMoveEvent *dragMoveEvent) +{ + QTextCursor cursor = m_textEditor->editorWidget()->cursorForPosition(dragMoveEvent->pos()); + const int cursorPosition = cursor.position(); + RewriterView *rewriterView = m_textEditorView->model()->rewriterView(); + + QTC_ASSERT(rewriterView, return ); + ModelNode modelNode = rewriterView->nodeAtTextCursorPosition(cursorPosition); + + if (!modelNode.isValid()) + return; + highlightToModelNode(modelNode); } void TextEditorWidget::dropEvent(QDropEvent *dropEvent) { - const DesignerActionManager &actionManager = QmlDesignerPlugin::instance() - ->viewManager().designerActionManager(); - actionManager.handleExternalAssetsDrop(dropEvent->mimeData()); + QTextCursor cursor = m_textEditor->editorWidget()->cursorForPosition(dropEvent->pos()); + const int cursorPosition = cursor.position(); + RewriterView *rewriterView = m_textEditorView->model()->rewriterView(); + + QTC_ASSERT(rewriterView, return); + ModelNode modelNode = rewriterView->nodeAtTextCursorPosition(cursorPosition); + + if (!modelNode.isValid()) + return; + + auto targetProperty = modelNode.defaultNodeAbstractProperty(); + + if (dropEvent->mimeData()->hasFormat(Constants::MIME_TYPE_ITEM_LIBRARY_INFO)) { + if (!m_draggedEntry.name().isEmpty()) { + m_textEditorView->executeInTransaction("TextEditorWidget::dropEventItem", [&] { + auto newQmlObjectNode = QmlItemNode::createQmlObjectNode(m_textEditorView, + m_draggedEntry, + QPointF(), + targetProperty, + false); + }); + } + } else if (dropEvent->mimeData()->hasFormat(Constants::MIME_TYPE_ASSETS)) { + const QStringList assetsPaths + = QString::fromUtf8(dropEvent->mimeData()->data(Constants::MIME_TYPE_ASSETS)).split(','); + + QTC_ASSERT(!assetsPaths.isEmpty(), return); + auto assetTypeAndData = AssetsLibraryWidget::getAssetTypeAndData(assetsPaths.first()); + QString assetType = assetTypeAndData.first; + QString assetData = QString::fromUtf8(assetTypeAndData.second); + + ModelNode newModelNode; + ModelNode targetNode = targetProperty.parentModelNode(); + QList addedNodes; + + for (const QString &assetPath : assetsPaths) { + auto assetTypeAndData = AssetsLibraryWidget::getAssetTypeAndData(assetPath); + QString assetType = assetTypeAndData.first; + QString assetData = QString::fromUtf8(assetTypeAndData.second); + bool moveNodesAfter = true; // Appending to parent is the default in text editor + if (assetType == Constants::MIME_TYPE_ASSET_IMAGE) { + newModelNode = ModelNodeOperations::handleItemLibraryImageDrop(assetPath, + targetProperty, + targetNode, + moveNodesAfter); + } else if (assetType == Constants::MIME_TYPE_ASSET_FONT) { + newModelNode = ModelNodeOperations::handleItemLibraryFontDrop( + assetData, // assetData is fontFamily + targetProperty, + targetNode); + } else if (assetType == Constants::MIME_TYPE_ASSET_SHADER) { + newModelNode = ModelNodeOperations::handleItemLibraryShaderDrop(assetPath, + assetData == "f", + targetProperty, + targetNode, + moveNodesAfter); + } else if (assetType == Constants::MIME_TYPE_ASSET_SOUND) { + newModelNode = ModelNodeOperations::handleItemLibrarySoundDrop(assetPath, + targetProperty, + targetNode); + } else if (assetType == Constants::MIME_TYPE_ASSET_TEXTURE3D) { + newModelNode = ModelNodeOperations::handleItemLibraryTexture3dDrop(assetPath, + targetProperty, + targetNode, + moveNodesAfter); + } else if (assetType == Constants::MIME_TYPE_ASSET_EFFECT) { + newModelNode = ModelNodeOperations::handleItemLibraryEffectDrop(assetPath, + targetNode); + } + + if (newModelNode.isValid()) + addedNodes.append(newModelNode); + } + + if (!addedNodes.isEmpty()) + m_textEditorView->setSelectedModelNodes(addedNodes); + } else { + const DesignerActionManager &actionManager + = QmlDesignerPlugin::instance()->viewManager().designerActionManager(); + actionManager.handleExternalAssetsDrop(dropEvent->mimeData()); + } + m_textEditorView->model()->endDrag(); + m_textEditor->editorWidget()->updateFoldingHighlight(QTextCursor()); } } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/texteditor/texteditorwidget.h b/src/plugins/qmldesigner/components/texteditor/texteditorwidget.h index 947826ab598..c6f23110af0 100644 --- a/src/plugins/qmldesigner/components/texteditor/texteditorwidget.h +++ b/src/plugins/qmldesigner/components/texteditor/texteditorwidget.h @@ -5,6 +5,9 @@ #include #include +#include +#include + #include #include #include @@ -43,10 +46,13 @@ public: int currentLine() const; void setBlockCursorSelectionSynchronisation(bool b); + void jumpToModelNode(const ModelNode &modelNode); + void highlightToModelNode(const ModelNode &modelNode); protected: bool eventFilter(QObject *object, QEvent *event) override; void dragEnterEvent(QDragEnterEvent *dragEnterEvent) override; + void dragMoveEvent(QDragMoveEvent *dragMoveEvent) override; void dropEvent(QDropEvent *dropEvent) override; private: @@ -60,6 +66,7 @@ private: QVBoxLayout *m_layout = nullptr; bool m_blockCursorSelectionSynchronisation = false; bool m_blockRoundTrip = false; + ItemLibraryEntry m_draggedEntry; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp index 1325ade10c7..0cf01cf6209 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp @@ -172,7 +172,7 @@ void TextureEditorQmlBackend::setSource(const QUrl &url) m_view->setSource(url); } -Internal::QmlAnchorBindingProxy &TextureEditorQmlBackend::backendAnchorBinding() +QmlAnchorBindingProxy &TextureEditorQmlBackend::backendAnchorBinding() { return m_backendAnchorBinding; } diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h index 241195bd70d..b6d3fddf22d 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h @@ -39,7 +39,7 @@ public: TextureEditorContextObject *contextObject() const; QQuickWidget *widget() const; void setSource(const QUrl &url); - Internal::QmlAnchorBindingProxy &backendAnchorBinding(); + QmlAnchorBindingProxy &backendAnchorBinding(); DesignerPropertyMap &backendValuesPropertyMap(); TextureEditorTransaction *textureEditorTransaction() const; @@ -59,7 +59,7 @@ private: PropertyName auxNamePostFix(const PropertyName &propertyName); QQuickWidget *m_view = nullptr; - Internal::QmlAnchorBindingProxy m_backendAnchorBinding; + QmlAnchorBindingProxy m_backendAnchorBinding; QmlModelNodeProxy m_backendModelNode; DesignerPropertyMap m_backendValuesPropertyMap; QScopedPointer m_textureEditorTransaction; diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp index d088dd7a26a..47d85c4dbc3 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.h b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.h index 7baa07e9d30..dd5e0b0cc98 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.h +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.h @@ -4,7 +4,6 @@ #pragma once #include -#include #include #include @@ -119,7 +118,6 @@ private: bool m_initializingPreviewData = false; QPointer m_colorDialog; - QPointer m_itemLibraryInfo; DynamicPropertiesModel *m_dynamicPropertiesModel = nullptr; }; diff --git a/src/plugins/qmldesigner/components/toolbar/toolbarbackend.cpp b/src/plugins/qmldesigner/components/toolbar/toolbarbackend.cpp index c28ac7d207b..5a9431677da 100644 --- a/src/plugins/qmldesigner/components/toolbar/toolbarbackend.cpp +++ b/src/plugins/qmldesigner/components/toolbar/toolbarbackend.cpp @@ -308,6 +308,13 @@ ToolBarBackend::ToolBarBackend(QObject *parent) this, &ToolBarBackend::currentWorkspaceChanged); emit currentWorkspaceChanged(); + + connect(dockManager, + &ADS::DockManager::lockWorkspaceChanged, + this, + &ToolBarBackend::lockWorkspaceChanged); + emit lockWorkspaceChanged(); + return true; }; @@ -336,6 +343,7 @@ ToolBarBackend::ToolBarBackend(QObject *parent) connect(Core::ModeManager::instance(), &Core::ModeManager::currentModeChanged, this, [this]() { emit isInDesignModeChanged(); emit isInEditModeChanged(); + emit isInSessionModeChanged(); emit isDesignModeEnabledChanged(); }); @@ -345,6 +353,7 @@ ToolBarBackend::ToolBarBackend(QObject *parent) [this](ProjectExplorer::Project *project) { disconnect(m_kitConnection); emit isQt6Changed(); + emit isMCUsChanged(); emit projectOpenedChanged(); if (project) { m_kitConnection = connect(project, @@ -453,6 +462,11 @@ void ToolBarBackend::setCurrentWorkspace(const QString &workspace) designModeWidget()->dockManager()->openWorkspace(workspace); } +void ToolBarBackend::setLockWorkspace(bool value) +{ + designModeWidget()->dockManager()->lockWorkspace(value); +} + void ToolBarBackend::editGlobalAnnoation() { launchGlobalAnnotations(); @@ -588,6 +602,14 @@ QString ToolBarBackend::currentWorkspace() const return {}; } +bool ToolBarBackend::lockWorkspace() const +{ + if (designModeWidget() && designModeWidget()->dockManager()) + return designModeWidget()->dockManager()->isWorkspaceLocked(); + + return false; +} + QStringList ToolBarBackend::styles() const { const QList items = ChangeStyleWidgetAction::getAllStyleItems(); @@ -614,6 +636,14 @@ bool ToolBarBackend::isInEditMode() const return Core::ModeManager::currentModeId() == Core::Constants::MODE_EDIT; } +bool ToolBarBackend::isInSessionMode() const +{ + if (!Core::ModeManager::instance()) + return false; + + return Core::ModeManager::currentModeId() == ProjectExplorer::Constants::MODE_SESSION; +} + bool ToolBarBackend::isDesignModeEnabled() const { if (Core::DesignMode::instance()) @@ -672,6 +702,20 @@ bool ToolBarBackend::isQt6() const return isQt6Project; } +bool ToolBarBackend::isMCUs() const +{ + if (!ProjectExplorer::ProjectManager::startupTarget()) + return false; + + const QmlProjectManager::QmlBuildSystem *buildSystem = qobject_cast( + ProjectExplorer::ProjectManager::startupTarget()->buildSystem()); + QTC_ASSERT(buildSystem, return false); + + const bool isQtForMCUsProject = buildSystem && buildSystem->qtForMCUs(); + + return isQtForMCUsProject; +} + bool ToolBarBackend::projectOpened() const { return ProjectExplorer::ProjectManager::instance()->startupProject(); diff --git a/src/plugins/qmldesigner/components/toolbar/toolbarbackend.h b/src/plugins/qmldesigner/components/toolbar/toolbarbackend.h index 0091ab8a7f1..ce8dd62b524 100644 --- a/src/plugins/qmldesigner/components/toolbar/toolbarbackend.h +++ b/src/plugins/qmldesigner/components/toolbar/toolbarbackend.h @@ -83,14 +83,17 @@ class ToolBarBackend : public QObject Q_PROPERTY(QStringList documentModel READ documentModel NOTIFY openDocumentsChanged) Q_PROPERTY(int documentIndex READ documentIndex NOTIFY documentIndexChanged) Q_PROPERTY(QString currentWorkspace READ currentWorkspace NOTIFY currentWorkspaceChanged) + Q_PROPERTY(bool lockWorkspace READ lockWorkspace WRITE setLockWorkspace NOTIFY lockWorkspaceChanged) Q_PROPERTY(QStringList styles READ styles CONSTANT) Q_PROPERTY(bool isInDesignMode READ isInDesignMode NOTIFY isInDesignModeChanged) Q_PROPERTY(bool isInEditMode READ isInEditMode NOTIFY isInEditModeChanged) + Q_PROPERTY(bool isInSessionMode READ isInSessionMode NOTIFY isInSessionModeChanged) Q_PROPERTY(bool isDesignModeEnabled READ isDesignModeEnabled NOTIFY isDesignModeEnabledChanged) Q_PROPERTY(int currentStyle READ currentStyle NOTIFY currentStyleChanged) Q_PROPERTY(QStringList kits READ kits NOTIFY kitsChanged) Q_PROPERTY(int currentKit READ currentKit NOTIFY currentKitChanged) Q_PROPERTY(bool isQt6 READ isQt6 NOTIFY isQt6Changed) + Q_PROPERTY(bool isMCUs READ isMCUs NOTIFY isMCUsChanged) Q_PROPERTY(bool projectOpened READ projectOpened NOTIFY projectOpenedChanged) public: @@ -106,6 +109,7 @@ public: Q_INVOKABLE void closeCurrentDocument(); Q_INVOKABLE void shareApplicationOnline(); Q_INVOKABLE void setCurrentWorkspace(const QString &workspace); + Q_INVOKABLE void setLockWorkspace(bool value); Q_INVOKABLE void editGlobalAnnoation(); Q_INVOKABLE void showZoomMenu(int x, int y); Q_INVOKABLE void setCurrentStyle(int index); @@ -120,11 +124,13 @@ public: int documentIndex() const; QString currentWorkspace() const; + bool lockWorkspace() const; QStringList styles() const; bool isInDesignMode() const; bool isInEditMode() const; + bool isInSessionMode() const; bool isDesignModeEnabled() const; int currentStyle() const; @@ -133,6 +139,7 @@ public: int currentKit() const; bool isQt6() const; + bool isMCUs() const; bool projectOpened() const; @@ -143,13 +150,16 @@ signals: void openDocumentsChanged(); void documentIndexChanged(); void currentWorkspaceChanged(); + void lockWorkspaceChanged(); void isInDesignModeChanged(); void isInEditModeChanged(); + void isInSessionModeChanged(); void isDesignModeEnabledChanged(); void currentStyleChanged(); void kitsChanged(); void currentKitChanged(); void isQt6Changed(); + void isMCUsChanged(); void projectOpenedChanged(); private: diff --git a/src/plugins/qmldesigner/designercore/exceptions/invalidmetainfoexception.cpp b/src/plugins/qmldesigner/designercore/exceptions/invalidmetainfoexception.cpp index 3dc8c19d393..fbf32676896 100644 --- a/src/plugins/qmldesigner/designercore/exceptions/invalidmetainfoexception.cpp +++ b/src/plugins/qmldesigner/designercore/exceptions/invalidmetainfoexception.cpp @@ -18,9 +18,9 @@ namespace QmlDesigner { the __FILE__ macro. */ InvalidMetaInfoException::InvalidMetaInfoException(int line, - const QByteArray &function, - const QByteArray &file) - : Exception(line, function, file) + const QByteArray &function, + const QByteArray &file) + : Exception(line, function, file) { createWarning(); } diff --git a/src/plugins/qmldesigner/designercore/imagecache/asynchronousimagecache.cpp b/src/plugins/qmldesigner/designercore/imagecache/asynchronousimagecache.cpp index a18e051c364..fdd06d19a16 100644 --- a/src/plugins/qmldesigner/designercore/imagecache/asynchronousimagecache.cpp +++ b/src/plugins/qmldesigner/designercore/imagecache/asynchronousimagecache.cpp @@ -7,18 +7,37 @@ #include "imagecachestorage.h" #include "timestampprovider.h" +#include + +#include + #include namespace QmlDesigner { +using namespace NanotraceHR::Literals; + +namespace ImageCache { +namespace { + +thread_local Category category_{"image cache"_t, + QmlDesigner::Tracing::eventQueueWithStringArguments(), + category}; +} // namespace + +Category &category() +{ + return category_; +} +} // namespace ImageCache + AsynchronousImageCache::AsynchronousImageCache(ImageCacheStorageInterface &storage, ImageCacheGeneratorInterface &generator, TimeStampProviderInterface &timeStampProvider) : m_storage(storage) , m_generator(generator) , m_timeStampProvider(timeStampProvider) -{ -} +{} AsynchronousImageCache::~AsynchronousImageCache() { @@ -31,6 +50,7 @@ void AsynchronousImageCache::request(Utils::SmallStringView name, ImageCache::CaptureImageCallback captureCallback, ImageCache::AbortCallback abortCallback, ImageCache::AuxiliaryData auxiliaryData, + ImageCache::TraceToken traceToken, ImageCacheStorageInterface &storage, ImageCacheGeneratorInterface &generator, TimeStampProviderInterface &timeStampProvider) @@ -38,7 +58,21 @@ void AsynchronousImageCache::request(Utils::SmallStringView name, const auto id = extraId.empty() ? Utils::PathString{name} : Utils::PathString::join({name, "+", extraId}); + using NanotraceHR::dictonary; + using NanotraceHR::keyValue; + using namespace std::literals::string_view_literals; + + auto [durationToken, flowToken] = traceToken.beginDurationWithFlow( + "AsynchronousImageCache works on the image request"_t, + keyValue("name", name), + keyValue("extra id", extraId)); + + auto timeStrampToken = durationToken.beginDuration("getting timestamp"_t); const auto timeStamp = timeStampProvider.timeStamp(name); + timeStrampToken.end(keyValue("time stamp", timeStamp.value)); + + auto storageTraceToken = durationToken.beginDuration("fetching image from storage"_t, + keyValue("storage id", id)); auto requestImageFromStorage = [&](RequestType requestType) { switch (requestType) { case RequestType::Image: @@ -53,17 +87,26 @@ void AsynchronousImageCache::request(Utils::SmallStringView name, return storage.fetchImage(id, timeStamp); }; + const auto entry = requestImageFromStorage(requestType); if (entry) { - if (entry->isNull()) + if (entry->isNull()) { + storageTraceToken.tick("there was an null image in storage"_t); abortCallback(ImageCache::AbortReason::Failed); - else + } else { captureCallback(*entry); + storageTraceToken.end(keyValue("storage id", id), keyValue("image", *entry)); + } } else { - auto callback = + storageTraceToken.end(); + auto imageGeneratedCallback = [captureCallback = std::move(captureCallback), - requestType](const QImage &image, const QImage &midSizeImage, const QImage &smallImage) { + requestType](const QImage &image, + const QImage &midSizeImage, + const QImage &smallImage, + ImageCache::TraceToken traceToken) { + auto token = traceToken.beginDuration("call capture callback"_t); auto selectImage = [](RequestType requestType, const QImage &image, const QImage &midSizeImage, @@ -83,12 +126,23 @@ void AsynchronousImageCache::request(Utils::SmallStringView name, }; captureCallback(selectImage(requestType, image, midSizeImage, smallImage)); }; + + auto imageGenerationAbortedCallback = + [abortCallback = std::move(abortCallback)](ImageCache::AbortReason reason, + ImageCache::TraceToken traceToken) { + traceToken.tick("image could not be created"_t); + abortCallback(reason); + }; + + traceToken.tick("call the generator"_t); + generator.generateImage(name, extraId, timeStamp, - std::move(callback), - std::move(abortCallback), - std::move(auxiliaryData)); + std::move(imageGeneratedCallback), + std::move(imageGenerationAbortedCallback), + std::move(auxiliaryData), + std::move(flowToken)); } } @@ -98,12 +152,16 @@ void AsynchronousImageCache::requestImage(Utils::SmallStringView name, Utils::SmallStringView extraId, ImageCache::AuxiliaryData auxiliaryData) { - m_taskQueue.addTask(std::move(name), + auto [trace, flowToken] = ImageCache::category().beginDurationWithFlow( + "request image in asynchronous image cache"_t); + m_taskQueue.addTask(trace.createToken(), + std::move(name), std::move(extraId), std::move(captureCallback), std::move(abortCallback), std::move(auxiliaryData), - RequestType::Image); + RequestType::Image, + std ::move(flowToken)); } void AsynchronousImageCache::requestMidSizeImage(Utils::SmallStringView name, @@ -112,12 +170,16 @@ void AsynchronousImageCache::requestMidSizeImage(Utils::SmallStringView name, Utils::SmallStringView extraId, ImageCache::AuxiliaryData auxiliaryData) { - m_taskQueue.addTask(std::move(name), + auto [traceToken, flowToken] = ImageCache::category().beginDurationWithFlow( + "request mid size image in asynchronous image cache"_t); + m_taskQueue.addTask(traceToken.createToken(), + std::move(name), std::move(extraId), std::move(captureCallback), std::move(abortCallback), std::move(auxiliaryData), - RequestType::MidSizeImage); + RequestType::MidSizeImage, + std ::move(flowToken)); } void AsynchronousImageCache::requestSmallImage(Utils::SmallStringView name, @@ -126,12 +188,16 @@ void AsynchronousImageCache::requestSmallImage(Utils::SmallStringView name, Utils::SmallStringView extraId, ImageCache::AuxiliaryData auxiliaryData) { - m_taskQueue.addTask(std::move(name), + auto [traceToken, flowtoken] = ImageCache::category().beginDurationWithFlow( + "request small size image in asynchronous image cache"_t); + m_taskQueue.addTask(traceToken.createToken(), + std::move(name), std::move(extraId), std::move(captureCallback), std::move(abortCallback), std::move(auxiliaryData), - RequestType::SmallImage); + RequestType::SmallImage, + std ::move(flowtoken)); } void AsynchronousImageCache::clean() @@ -140,4 +206,29 @@ void AsynchronousImageCache::clean() m_taskQueue.clean(); } +void AsynchronousImageCache::Dispatch::operator()(Entry &entry) +{ + using namespace NanotraceHR::Literals; + + request(entry.name, + entry.extraId, + entry.requestType, + std::move(entry.captureCallback), + std::move(entry.abortCallback), + std::move(entry.auxiliaryData), + std::move(entry.traceToken), + storage, + generator, + timeStampProvider); +} + +void AsynchronousImageCache::Clean::operator()(Entry &entry) +{ + using namespace NanotraceHR::Literals; + + entry.traceToken.tick("cleaning up in the cache"_t); + + entry.abortCallback(ImageCache::AbortReason::Abort); +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/asynchronousimagefactory.cpp b/src/plugins/qmldesigner/designercore/imagecache/asynchronousimagefactory.cpp index a711f1ad7d8..6feaea8150d 100644 --- a/src/plugins/qmldesigner/designercore/imagecache/asynchronousimagefactory.cpp +++ b/src/plugins/qmldesigner/designercore/imagecache/asynchronousimagefactory.cpp @@ -10,6 +10,8 @@ namespace QmlDesigner { +using namespace NanotraceHR::Literals; + AsynchronousImageFactory::AsynchronousImageFactory(ImageCacheStorageInterface &storage, TimeStampProviderInterface &timeStampProvider, ImageCacheCollectorInterface &collector) @@ -24,7 +26,13 @@ void AsynchronousImageFactory::generate(Utils::SmallStringView name, Utils::SmallStringView extraId, ImageCache::AuxiliaryData auxiliaryData) { - m_taskQueue.addTask(name, extraId, std::move(auxiliaryData)); + auto [trace, flowToken] = ImageCache::category().beginDurationWithFlow( + "request image in asynchronous image factory"_t); + m_taskQueue.addTask(trace.createToken(), + name, + extraId, + std::move(auxiliaryData), + std::move(flowToken)); } AsynchronousImageFactory::~AsynchronousImageFactory() {} @@ -34,8 +42,10 @@ void AsynchronousImageFactory::request(Utils::SmallStringView name, ImageCache::AuxiliaryData auxiliaryData, ImageCacheStorageInterface &storage, TimeStampProviderInterface &timeStampProvider, - ImageCacheCollectorInterface &collector) + ImageCacheCollectorInterface &collector, + ImageCache::TraceToken traceToken) { + auto [storageTracer, flowToken] = traceToken.beginDurationWithFlow("starte image generator"_t); const auto id = extraId.empty() ? Utils::PathString{name} : Utils::PathString::join({name, "+", extraId}); @@ -46,7 +56,10 @@ void AsynchronousImageFactory::request(Utils::SmallStringView name, if (currentModifiedTime < (storageModifiedTime + pause)) return; - auto capture = [&](const QImage &image, const QImage &midSizeImage, const QImage &smallImage) { + auto capture = [&](const QImage &image, + const QImage &midSizeImage, + const QImage &smallImage, + ImageCache::TraceToken) { storage.storeImage(id, currentModifiedTime, image, midSizeImage, smallImage); }; @@ -54,11 +67,23 @@ void AsynchronousImageFactory::request(Utils::SmallStringView name, extraId, std::move(auxiliaryData), std::move(capture), - ImageCache::AbortCallback{}); + ImageCache::InternalAbortCallback{}, + std::move(flowToken)); } void AsynchronousImageFactory::clean() { m_taskQueue.clean(); } + +void AsynchronousImageFactory::Dispatch::operator()(Entry &entry) +{ + request(entry.name, + entry.extraId, + std::move(entry.auxiliaryData), + storage, + timeStampProvider, + collector, + std::move(entry.traceToken)); +} } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/asynchronousimagefactory.h b/src/plugins/qmldesigner/designercore/imagecache/asynchronousimagefactory.h index f53ccc18ed7..bfd76a5ccac 100644 --- a/src/plugins/qmldesigner/designercore/imagecache/asynchronousimagefactory.h +++ b/src/plugins/qmldesigner/designercore/imagecache/asynchronousimagefactory.h @@ -39,17 +39,22 @@ private: struct Entry { Entry() = default; + Entry(Utils::PathString name, Utils::SmallString extraId, - ImageCache::AuxiliaryData &&auxiliaryData) + ImageCache::AuxiliaryData &&auxiliaryData, + ImageCache::TraceToken traceToken) : name{std::move(name)} , extraId{std::move(extraId)} , auxiliaryData{std::move(auxiliaryData)} + , traceToken{std::move(traceToken)} + {} Utils::PathString name; Utils::SmallString extraId; ImageCache::AuxiliaryData auxiliaryData; + NO_UNIQUE_ADDRESS ImageCache::TraceToken traceToken; }; static void request(Utils::SmallStringView name, @@ -57,19 +62,12 @@ private: ImageCache::AuxiliaryData auxiliaryData, ImageCacheStorageInterface &storage, TimeStampProviderInterface &timeStampProvider, - ImageCacheCollectorInterface &collector); + ImageCacheCollectorInterface &collector, + ImageCache::TraceToken traceToken); struct Dispatch { - void operator()(Entry &entry) - { - request(entry.name, - entry.extraId, - std::move(entry.auxiliaryData), - storage, - timeStampProvider, - collector); - } + void operator()(Entry &entry); ImageCacheStorageInterface &storage; TimeStampProviderInterface &timeStampProvider; diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.cpp b/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.cpp index 344bd4a0191..9d32b803cba 100644 --- a/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.cpp +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.cpp @@ -4,9 +4,9 @@ #include "imagecachecollector.h" #include "imagecacheconnectionmanager.h" -#include #include #include +#include #include #include @@ -21,7 +21,7 @@ namespace QmlDesigner { namespace { -QByteArray fileToByteArray(QString const &filename) +QByteArray fileToByteArray(const QString &filename) { QFile file(filename); QFileInfo fleInfo(file); @@ -73,8 +73,13 @@ void ImageCacheCollector::start(Utils::SmallStringView name, Utils::SmallStringView state, const ImageCache::AuxiliaryData &auxiliaryData, CaptureCallback captureCallback, - AbortCallback abortCallback) + AbortCallback abortCallback, + ImageCache::TraceToken traceToken) { + using namespace NanotraceHR::Literals; + auto [collectorTraceToken, flowtoken] = traceToken.beginDurationWithFlow( + "generate image in standard collector"_t); + RewriterView rewriterView{m_externalDependencies, RewriterView::Amend}; NodeInstanceView nodeInstanceView{m_connectionManager, m_externalDependencies}; nodeInstanceView.setCaptureImageMinimumAndMaximumSize(captureImageMinimumSize, @@ -101,7 +106,7 @@ void ImageCacheCollector::start(Utils::SmallStringView name, if (!rewriterView.errors().isEmpty() || (!rewriterView.rootModelNode().metaInfo().isGraphicalItem() && !is3DRoot)) { if (abortCallback) - abortCallback(ImageCache::AbortReason::Failed); + abortCallback(ImageCache::AbortReason::Failed, std::move(flowtoken)); return; } @@ -117,21 +122,17 @@ void ImageCacheCollector::start(Utils::SmallStringView name, if (stateNode.isValid()) rewriterView.setCurrentStateNode(stateNode); - auto callback = [=, captureCallback = std::move(captureCallback)](const QImage &image) { - if (nullImageHandling == ImageCacheCollectorNullImageHandling::CaptureNullImage - || !image.isNull()) { - QImage midSizeImage = scaleImage(image, QSize{300, 300}); - QImage smallImage = scaleImage(midSizeImage, QSize{96, 96}); - captureCallback(image, midSizeImage, smallImage); - } - }; + QImage captureImage; + + auto callback = [&](const QImage &image) { captureImage = image; }; if (!m_target) return; nodeInstanceView.setTarget(m_target.data()); m_connectionManager.setCallback(std::move(callback)); - nodeInstanceView.setCrashCallback([=] { abortCallback(ImageCache::AbortReason::Failed); }); + bool isCrashed = false; + nodeInstanceView.setCrashCallback([&] { isCrashed = true; }); model->setNodeInstanceView(&nodeInstanceView); bool capturedDataArrived = m_connectionManager.waitForCapturedData(); @@ -142,8 +143,18 @@ void ImageCacheCollector::start(Utils::SmallStringView name, model->setNodeInstanceView({}); model->setRewriterView({}); + if (isCrashed) + abortCallback(ImageCache::AbortReason::Failed, std::move(flowtoken)); + if (!capturedDataArrived && abortCallback) - abortCallback(ImageCache::AbortReason::Failed); + abortCallback(ImageCache::AbortReason::Failed, std::move(flowtoken)); + + if (nullImageHandling == ImageCacheCollectorNullImageHandling::CaptureNullImage + || !captureImage.isNull()) { + QImage midSizeImage = scaleImage(captureImage, QSize{300, 300}); + QImage smallImage = scaleImage(midSizeImage, QSize{96, 96}); + captureCallback(captureImage, midSizeImage, smallImage, std::move(flowtoken)); + } } ImageCacheCollectorInterface::ImageTuple ImageCacheCollector::createImage( diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.h b/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.h index 87ebedba048..e5230ea2b23 100644 --- a/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.h +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.h @@ -41,7 +41,8 @@ public: Utils::SmallStringView state, const ImageCache::AuxiliaryData &auxiliaryData, CaptureCallback captureCallback, - AbortCallback abortCallback) override; + AbortCallback abortCallback, + ImageCache::TraceToken traceToken) override; ImageTuple createImage(Utils::SmallStringView filePath, Utils::SmallStringView state, diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachecollectorinterface.h b/src/plugins/qmldesigner/designercore/imagecache/imagecachecollectorinterface.h index 057ab6f03e5..34083f7b293 100644 --- a/src/plugins/qmldesigner/designercore/imagecache/imagecachecollectorinterface.h +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachecollectorinterface.h @@ -15,14 +15,15 @@ class ImageCacheCollectorInterface { public: using CaptureCallback = ImageCache::CaptureImageWithScaledImagesCallback; - using AbortCallback = ImageCache::AbortCallback; + using AbortCallback = ImageCache::InternalAbortCallback; using ImageTuple = std::tuple; virtual void start(Utils::SmallStringView filePath, Utils::SmallStringView extraId, const ImageCache::AuxiliaryData &auxiliaryData, CaptureCallback captureCallback, - AbortCallback abortCallback) + AbortCallback abortCallback, + ImageCache::TraceToken traceToken) = 0; virtual ImageTuple createImage(Utils::SmallStringView filePath, diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachedispatchcollector.h b/src/plugins/qmldesigner/designercore/imagecache/imagecachedispatchcollector.h index 274cf72ad67..59cd77505ad 100644 --- a/src/plugins/qmldesigner/designercore/imagecache/imagecachedispatchcollector.h +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachedispatchcollector.h @@ -12,13 +12,15 @@ class ImageCacheDispatchCollector final : public ImageCacheCollectorInterface { public: ImageCacheDispatchCollector(CollectorEntries collectors) - : m_collectors{std::move(collectors)} {}; + : m_collectors{std::move(collectors)} + {} void start(Utils::SmallStringView filePath, Utils::SmallStringView state, const ImageCache::AuxiliaryData &auxiliaryData, CaptureCallback captureCallback, - AbortCallback abortCallback) override + AbortCallback abortCallback, + ImageCache::TraceToken traceToken) override { std::apply( [&](const auto &...collectors) { @@ -27,6 +29,7 @@ public: auxiliaryData, std::move(captureCallback), std::move(abortCallback), + std::move(traceToken), collectors...); }, m_collectors); @@ -61,6 +64,7 @@ private: const ImageCache::AuxiliaryData &auxiliaryData, CaptureCallback captureCallback, AbortCallback abortCallback, + ImageCache::TraceToken traceToken, const Collector &collector, const Collectors &...collectors) { @@ -69,13 +73,15 @@ private: state, auxiliaryData, std::move(captureCallback), - std::move(abortCallback)); + std::move(abortCallback), + std::move(traceToken)); } else { dispatchStart(filePath, state, auxiliaryData, std::move(captureCallback), std::move(abortCallback), + std::move(traceToken), collectors...); } } @@ -84,10 +90,11 @@ private: Utils::SmallStringView, const ImageCache::AuxiliaryData &, CaptureCallback, - AbortCallback abortCallback) + AbortCallback abortCallback, + ImageCache::TraceToken traceToken) { qWarning() << "ImageCacheDispatchCollector: cannot handle file type."; - abortCallback(ImageCache::AbortReason::Failed); + abortCallback(ImageCache::AbortReason::Failed, std::move(traceToken)); } template diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachefontcollector.cpp b/src/plugins/qmldesigner/designercore/imagecache/imagecachefontcollector.cpp index 2e0c4813ec8..5c10e2e2ce4 100644 --- a/src/plugins/qmldesigner/designercore/imagecache/imagecachefontcollector.cpp +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachefontcollector.cpp @@ -94,7 +94,8 @@ void ImageCacheFontCollector::start(Utils::SmallStringView name, Utils::SmallStringView, const ImageCache::AuxiliaryData &auxiliaryDataValue, CaptureCallback captureCallback, - AbortCallback abortCallback) + AbortCallback abortCallback, + ImageCache::TraceToken traceToken) { QFont font; if (resolveFont(QString(name), font) >= 0) { @@ -107,12 +108,12 @@ void ImageCacheFontCollector::start(Utils::SmallStringView name, QImage image = createFontImage(text, textColor, font, size); if (!image.isNull()) { - captureCallback(std::move(image), {}, {}); + captureCallback(std::move(image), {}, {}, std::move(traceToken)); return; } } } - abortCallback(ImageCache::AbortReason::Failed); + abortCallback(ImageCache::AbortReason::Failed, std::move(traceToken)); } ImageCacheCollectorInterface::ImageTuple ImageCacheFontCollector::createImage( diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachefontcollector.h b/src/plugins/qmldesigner/designercore/imagecache/imagecachefontcollector.h index ff7f624e223..104e48e9b2f 100644 --- a/src/plugins/qmldesigner/designercore/imagecache/imagecachefontcollector.h +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachefontcollector.h @@ -18,7 +18,8 @@ public: Utils::SmallStringView extraId, const ImageCache::AuxiliaryData &auxiliaryData, CaptureCallback captureCallback, - AbortCallback abortCallback) override; + AbortCallback abortCallback, + ImageCache::TraceToken traceToken) override; ImageTuple createImage(Utils::SmallStringView filePath, Utils::SmallStringView extraId, diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachegenerator.cpp b/src/plugins/qmldesigner/designercore/imagecache/imagecachegenerator.cpp index c61327ee087..60b51130312 100644 --- a/src/plugins/qmldesigner/designercore/imagecache/imagecachegenerator.cpp +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachegenerator.cpp @@ -42,8 +42,9 @@ void ImageCacheGenerator::generateImage(Utils::SmallStringView name, Utils::SmallStringView extraId, Sqlite::TimeStamp timeStamp, ImageCache::CaptureImageWithScaledImagesCallback &&captureCallback, - ImageCache::AbortCallback &&abortCallback, - ImageCache::AuxiliaryData &&auxiliaryData) + ImageCache::InternalAbortCallback &&abortCallback, + ImageCache::AuxiliaryData &&auxiliaryData, + ImageCache::TraceToken traceToken) { { std::lock_guard lock{m_mutex}; @@ -64,7 +65,8 @@ void ImageCacheGenerator::generateImage(Utils::SmallStringView name, std::move(auxiliaryData), timeStamp, std::move(captureCallback), - std::move(abortCallback)); + std::move(abortCallback), + std::move(traceToken)); } } @@ -89,9 +91,12 @@ void callCallbacks(const Callbacks &callbacks, Argument &&...arguments) void ImageCacheGenerator::clean() { + using namespace NanotraceHR::Literals; + std::lock_guard lock{m_mutex}; - for (Task &task : m_tasks) - callCallbacks(task.abortCallbacks, ImageCache::AbortReason::Abort); + for (Task &task : m_tasks) { + callCallbacks(task.abortCallbacks, ImageCache::AbortReason::Abort, std::move(task.traceToken)); + } m_tasks.clear(); } @@ -104,47 +109,71 @@ void ImageCacheGenerator::waitForFinished() m_backgroundThread->wait(); } +std::optional ImageCacheGenerator::getTask() +{ + { + auto [lock, abort] = waitForEntries(); + + if (abort) + return {}; + + std::optional task = std::move(m_tasks.front()); + + m_tasks.pop_front(); + + return task; + } +} + void ImageCacheGenerator::startGeneration() { while (true) { - Task task; + auto task = getTask(); - { - auto [lock, abort] = waitForEntries(); - - if (abort) - return; - - task = std::move(m_tasks.front()); - - m_tasks.pop_front(); - } + if (!task) + return; m_collector.start( - task.filePath, - task.extraId, - std::move(task.auxiliaryData), - [this, task](const QImage &image, const QImage &midSizeImage, const QImage &smallImage) { + task->filePath, + task->extraId, + std::move(task->auxiliaryData), + [this, + abortCallbacks = task->abortCallbacks, + captureCallbacks = std::move(task->captureCallbacks), + filePath = task->filePath, + extraId = task->extraId, + timeStamp = task->timeStamp](const QImage &image, + const QImage &midSizeImage, + const QImage &smallImage, + ImageCache::TraceToken traceToken) { if (image.isNull() && midSizeImage.isNull() && smallImage.isNull()) - callCallbacks(task.abortCallbacks, ImageCache::AbortReason::Failed); + callCallbacks(abortCallbacks, + ImageCache::AbortReason::Failed, + std::move(traceToken)); else - callCallbacks(task.captureCallbacks, image, midSizeImage, smallImage); + callCallbacks(captureCallbacks, + image, + midSizeImage, + smallImage, + std::move(traceToken)); - m_storage.storeImage(createId(task.filePath, task.extraId), - task.timeStamp, + m_storage.storeImage(createId(filePath, extraId), + timeStamp, image, midSizeImage, smallImage); }, - [this, task](ImageCache::AbortReason abortReason) { - callCallbacks(task.abortCallbacks, abortReason); + [this, + abortCallbacks = task->abortCallbacks, + filePath = task->filePath, + extraId = task->extraId, + timeStamp = task->timeStamp](ImageCache::AbortReason abortReason, + ImageCache::TraceToken traceToken) { + callCallbacks(abortCallbacks, abortReason, std::move(traceToken)); if (abortReason != ImageCache::AbortReason::Abort) - m_storage.storeImage(createId(task.filePath, task.extraId), - task.timeStamp, - {}, - {}, - {}); - }); + m_storage.storeImage(createId(filePath, extraId), timeStamp, {}, {}, {}); + }, + std::move(task->traceToken)); std::lock_guard lock{m_mutex}; if (m_tasks.empty()) diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachegenerator.h b/src/plugins/qmldesigner/designercore/imagecache/imagecachegenerator.h index cfac1e0ef05..1846eff557c 100644 --- a/src/plugins/qmldesigner/designercore/imagecache/imagecachegenerator.h +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachegenerator.h @@ -34,8 +34,9 @@ public: Utils::SmallStringView extraId, Sqlite::TimeStamp timeStamp, ImageCache::CaptureImageWithScaledImagesCallback &&captureCallback, - ImageCache::AbortCallback &&abortCallback, - ImageCache::AuxiliaryData &&auxiliaryData) override; + ImageCache::InternalAbortCallback &&abortCallback, + ImageCache::AuxiliaryData &&auxiliaryData, + ImageCache::TraceToken traceToken = {}) override; void clean() override; void waitForFinished() override; @@ -44,34 +45,38 @@ private: struct Task { Task() = default; + Task(Utils::SmallStringView filePath, Utils::SmallStringView extraId, ImageCache::AuxiliaryData &&auxiliaryData, Sqlite::TimeStamp timeStamp, ImageCache::CaptureImageWithScaledImagesCallback &&captureCallback, - ImageCache::AbortCallback &&abortCallback) + ImageCache::InternalAbortCallback &&abortCallback, + ImageCache::TraceToken traceToken) : filePath(filePath) , extraId(std::move(extraId)) , auxiliaryData(std::move(auxiliaryData)) , captureCallbacks({std::move(captureCallback)}) , abortCallbacks({std::move(abortCallback)}) , timeStamp(timeStamp) + , traceToken{std::move(traceToken)} {} Utils::PathString filePath; Utils::SmallString extraId; ImageCache::AuxiliaryData auxiliaryData; std::vector captureCallbacks; - std::vector abortCallbacks; + std::vector abortCallbacks; Sqlite::TimeStamp timeStamp; + NO_UNIQUE_ADDRESS ImageCache::TraceToken traceToken; }; void startGeneration(); + std::optional getTask(); void ensureThreadIsRunning(); [[nodiscard]] std::tuple, bool> waitForEntries(); void stopThread(); -private: private: std::unique_ptr m_backgroundThread; mutable std::mutex m_mutex; diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachegeneratorinterface.h b/src/plugins/qmldesigner/designercore/imagecache/imagecachegeneratorinterface.h index d18f0def323..62d1b374914 100644 --- a/src/plugins/qmldesigner/designercore/imagecache/imagecachegeneratorinterface.h +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachegeneratorinterface.h @@ -18,8 +18,9 @@ public: Utils::SmallStringView extraId, Sqlite::TimeStamp timeStamp, ImageCache::CaptureImageWithScaledImagesCallback &&captureCallback, - ImageCache::AbortCallback &&abortCallback, - ImageCache::AuxiliaryData &&auxiliaryData) + ImageCache::InternalAbortCallback &&abortCallback, + ImageCache::AuxiliaryData &&auxiliaryData, + ImageCache::TraceToken) = 0; virtual void clean() = 0; diff --git a/src/plugins/qmldesigner/designercore/imagecache/meshimagecachecollector.cpp b/src/plugins/qmldesigner/designercore/imagecache/meshimagecachecollector.cpp index 31e4a07264d..0e0547c1935 100644 --- a/src/plugins/qmldesigner/designercore/imagecache/meshimagecachecollector.cpp +++ b/src/plugins/qmldesigner/designercore/imagecache/meshimagecachecollector.cpp @@ -30,7 +30,8 @@ void MeshImageCacheCollector::start(Utils::SmallStringView name, Utils::SmallStringView state, const ImageCache::AuxiliaryData &auxiliaryData, CaptureCallback captureCallback, - AbortCallback abortCallback) + AbortCallback abortCallback, + ImageCache::TraceToken traceToken) { QTemporaryFile file(QDir::tempPath() + "/mesh-XXXXXX.qml"); if (file.open()) { @@ -63,7 +64,8 @@ void MeshImageCacheCollector::start(Utils::SmallStringView name, Utils::PathString path{file.fileName()}; - m_imageCacheCollector.start(path, state, auxiliaryData, captureCallback, abortCallback); + m_imageCacheCollector + .start(path, state, auxiliaryData, captureCallback, abortCallback, std::move(traceToken)); } ImageCacheCollectorInterface::ImageTuple MeshImageCacheCollector::createImage( diff --git a/src/plugins/qmldesigner/designercore/imagecache/meshimagecachecollector.h b/src/plugins/qmldesigner/designercore/imagecache/meshimagecachecollector.h index 17b1a412bc1..98c2e84605e 100644 --- a/src/plugins/qmldesigner/designercore/imagecache/meshimagecachecollector.h +++ b/src/plugins/qmldesigner/designercore/imagecache/meshimagecachecollector.h @@ -29,7 +29,8 @@ public: Utils::SmallStringView state, const ImageCache::AuxiliaryData &auxiliaryData, CaptureCallback captureCallback, - AbortCallback abortCallback) override; + AbortCallback abortCallback, + ImageCache::TraceToken traceToken) override; ImageTuple createImage(Utils::SmallStringView filePath, Utils::SmallStringView state, diff --git a/src/plugins/qmldesigner/designercore/imagecache/taskqueue.h b/src/plugins/qmldesigner/designercore/imagecache/taskqueue.h index 67b0904eed0..fac7e7d9bfe 100644 --- a/src/plugins/qmldesigner/designercore/imagecache/taskqueue.h +++ b/src/plugins/qmldesigner/designercore/imagecache/taskqueue.h @@ -3,6 +3,8 @@ #pragma once +#include + #include #include #include @@ -23,19 +25,25 @@ public: ~TaskQueue() { destroy(); } - template - void addTask(Arguments &&...arguments) + template + void addTask(NanotraceHR::Token traceToken, Arguments &&...arguments) { { std::unique_lock lock{m_mutex}; - ensureThreadIsRunning(); + ensureThreadIsRunning(std::move(traceToken)); m_tasks.emplace_back(std::forward(arguments)...); } m_condition.notify_all(); } + template + void addTask(Arguments &&...arguments) + { + addTask(NanotraceHR::DisabledToken{}, std::forward(arguments)...); + } + void clean() { Tasks oldTasks; @@ -83,11 +91,14 @@ private: Task task = std::move(m_tasks.front()); m_tasks.pop_front(); - return {task}; + return {std::move(task)}; } - void ensureThreadIsRunning() + template + void ensureThreadIsRunning(TraceToken traceToken) { + using namespace NanotraceHR::Literals; + if (m_finishing || !m_sleeping) return; @@ -96,15 +107,25 @@ private: m_sleeping = false; - m_backgroundThread = std::thread{[this] { - while (true) { - auto [lock, abort] = waitForTasks(); - if (abort) - return; - if (auto task = getTask(std::move(lock)); task) - m_dispatchCallback(*task); - } - }}; + auto [threadCreateToken, flowToken] = traceToken.beginDurationWithFlow( + "thread is created in the task queue"_t); + m_backgroundThread = std::thread{[this](auto traceToken) { + auto duration = traceToken.beginDuration( + "thread is ready"_t); + while (true) { + auto [lock, abort] = waitForTasks(); + duration.end(); + if (abort) + return; + auto getTaskToken = duration.beginDuration( + "get task from queue"_t); + if (auto task = getTask(std::move(lock)); task) { + getTaskToken.end(); + m_dispatchCallback(*task); + } + } + }, + std::move(flowToken)}; } void clearTasks(Tasks &tasks) diff --git a/src/plugins/qmldesigner/designercore/imagecache/textureimagecachecollector.cpp b/src/plugins/qmldesigner/designercore/imagecache/textureimagecachecollector.cpp index af310ae6e7d..9ec83c0e5b3 100644 --- a/src/plugins/qmldesigner/designercore/imagecache/textureimagecachecollector.cpp +++ b/src/plugins/qmldesigner/designercore/imagecache/textureimagecachecollector.cpp @@ -17,7 +17,8 @@ void TextureImageCacheCollector::start(Utils::SmallStringView name, Utils::SmallStringView, const ImageCache::AuxiliaryData &, CaptureCallback captureCallback, - AbortCallback abortCallback) + AbortCallback abortCallback, + ImageCache::TraceToken traceToken) { Asset asset {QString(name)}; QImage image; @@ -31,11 +32,11 @@ void TextureImageCacheCollector::start(Utils::SmallStringView name, } if (image.isNull()) - abortCallback(ImageCache::AbortReason::Failed); + abortCallback(ImageCache::AbortReason::Failed, std::move(traceToken)); else image = image.scaled(QSize{300, 300}, Qt::KeepAspectRatio); - captureCallback({}, image, {}); + captureCallback({}, image, {}, std::move(traceToken)); } ImageCacheCollectorInterface::ImageTuple TextureImageCacheCollector::createImage( diff --git a/src/plugins/qmldesigner/designercore/imagecache/textureimagecachecollector.h b/src/plugins/qmldesigner/designercore/imagecache/textureimagecachecollector.h index 67876d7641a..fa8885b4d4a 100644 --- a/src/plugins/qmldesigner/designercore/imagecache/textureimagecachecollector.h +++ b/src/plugins/qmldesigner/designercore/imagecache/textureimagecachecollector.h @@ -17,7 +17,8 @@ public: Utils::SmallStringView state, const ImageCache::AuxiliaryData &auxiliaryData, CaptureCallback captureCallback, - AbortCallback abortCallback) override; + AbortCallback abortCallback, + ImageCache::TraceToken traceToken) override; ImageTuple createImage(Utils::SmallStringView filePath, Utils::SmallStringView state, diff --git a/src/plugins/qmldesigner/designercore/include/abstractview.h b/src/plugins/qmldesigner/designercore/include/abstractview.h index 0a3350b693b..71c675fd3cb 100644 --- a/src/plugins/qmldesigner/designercore/include/abstractview.h +++ b/src/plugins/qmldesigner/designercore/include/abstractview.h @@ -79,7 +79,7 @@ public: ~AbstractView() override; - Model *model() const; + Model *model() const { return m_model.data(); } bool isAttached() const; RewriterTransaction beginRewriterTransaction(const QByteArray &identifier); diff --git a/src/plugins/qmldesigner/designercore/include/asynchronousimagecache.h b/src/plugins/qmldesigner/designercore/include/asynchronousimagecache.h index 3eed4a4487c..c3fc7bcd153 100644 --- a/src/plugins/qmldesigner/designercore/include/asynchronousimagecache.h +++ b/src/plugins/qmldesigner/designercore/include/asynchronousimagecache.h @@ -3,6 +3,8 @@ #pragma once +#include + #include "asynchronousimagecacheinterface.h" #include @@ -53,18 +55,21 @@ private: struct Entry { Entry() = default; + Entry(Utils::PathString name, Utils::SmallString extraId, ImageCache::CaptureImageCallback &&captureCallback, ImageCache::AbortCallback &&abortCallback, ImageCache::AuxiliaryData &&auxiliaryData, - RequestType requestType) + RequestType requestType, + ImageCache::TraceToken traceToken) : name{std::move(name)} , extraId{std::move(extraId)} , captureCallback{std::move(captureCallback)} , abortCallback{std::move(abortCallback)} , auxiliaryData{std::move(auxiliaryData)} , requestType{requestType} + , traceToken{std::move(traceToken)} {} Utils::PathString name; @@ -73,6 +78,7 @@ private: ImageCache::AbortCallback abortCallback; ImageCache::AuxiliaryData auxiliaryData; RequestType requestType = RequestType::Image; + NO_UNIQUE_ADDRESS ImageCache::TraceToken traceToken; }; static void request(Utils::SmallStringView name, @@ -81,24 +87,14 @@ private: ImageCache::CaptureImageCallback captureCallback, ImageCache::AbortCallback abortCallback, ImageCache::AuxiliaryData auxiliaryData, + ImageCache::TraceToken traceToken, ImageCacheStorageInterface &storage, ImageCacheGeneratorInterface &generator, TimeStampProviderInterface &timeStampProvider); struct Dispatch { - void operator()(Entry &entry) - { - request(entry.name, - entry.extraId, - entry.requestType, - std::move(entry.captureCallback), - std::move(entry.abortCallback), - std::move(entry.auxiliaryData), - storage, - generator, - timeStampProvider); - } + QMLDESIGNERCORE_EXPORT void operator()(Entry &entry); ImageCacheStorageInterface &storage; ImageCacheGeneratorInterface &generator; @@ -107,7 +103,7 @@ private: struct Clean { - void operator()(Entry &entry) { entry.abortCallback(ImageCache::AbortReason::Abort); } + QMLDESIGNERCORE_EXPORT void operator()(Entry &entry); }; private: diff --git a/src/plugins/qmldesigner/designercore/include/auxiliarydataproperties.h b/src/plugins/qmldesigner/designercore/include/auxiliarydataproperties.h index b93626a331b..fe55402f174 100644 --- a/src/plugins/qmldesigner/designercore/include/auxiliarydataproperties.h +++ b/src/plugins/qmldesigner/designercore/include/auxiliarydataproperties.h @@ -16,6 +16,13 @@ namespace QmlDesigner { inline constexpr AuxiliaryDataKeyDefaultValue customIdProperty{AuxiliaryDataType::Document, "customId", QStringView{}}; + +inline constexpr AuxiliaryDataKeyDefaultValue defaultWidthProperty{ + AuxiliaryDataType::NodeInstancePropertyOverwrite, "defaultWidth", 640}; + +inline constexpr AuxiliaryDataKeyDefaultValue defaultHeightProperty{ + AuxiliaryDataType::NodeInstancePropertyOverwrite, "defaultHeight", 480}; + inline constexpr AuxiliaryDataKeyDefaultValue widthProperty{ AuxiliaryDataType::NodeInstancePropertyOverwrite, "width", 4}; inline constexpr AuxiliaryDataKeyView heightProperty{AuxiliaryDataType::NodeInstancePropertyOverwrite, diff --git a/src/plugins/qmldesigner/designercore/include/customnotifications.h b/src/plugins/qmldesigner/designercore/include/customnotifications.h index d7a15b293fa..217c67730fb 100644 --- a/src/plugins/qmldesigner/designercore/include/customnotifications.h +++ b/src/plugins/qmldesigner/designercore/include/customnotifications.h @@ -11,6 +11,7 @@ const QString StartRewriterAmend = QStringLiteral("__start rewriter amend__"); const QString EndRewriterAmend = QStringLiteral("__end rewriter amend__"); const QString StartRewriterApply = QStringLiteral("start rewriter apply__"); const QString EndRewriterApply = QStringLiteral("__end rewriter apply__"); - - -} +const QString UpdateItemlibrary = QStringLiteral("__update itemlibrary__"); +const QString AddConnectionNotification = QStringLiteral("__add connection__"); +const QString EditConnectionNotification = QStringLiteral("edit connection__"); +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/include/externaldependenciesinterface.h b/src/plugins/qmldesigner/designercore/include/externaldependenciesinterface.h index eecbdd96b8f..71ddeb7dc19 100644 --- a/src/plugins/qmldesigner/designercore/include/externaldependenciesinterface.h +++ b/src/plugins/qmldesigner/designercore/include/externaldependenciesinterface.h @@ -44,6 +44,7 @@ public: virtual QStringList modulePaths() const = 0; virtual QStringList projectModulePaths() const = 0; virtual bool isQt6Project() const = 0; + virtual bool isQtForMcusProject() const = 0; virtual QString qtQuickVersion() const = 0; virtual Utils::FilePath resourcePath(const QString &relativePath) const = 0; }; diff --git a/src/plugins/qmldesigner/designercore/include/imagecacheauxiliarydata.h b/src/plugins/qmldesigner/designercore/include/imagecacheauxiliarydata.h index c147fb0753a..a96082ee0fe 100644 --- a/src/plugins/qmldesigner/designercore/include/imagecacheauxiliarydata.h +++ b/src/plugins/qmldesigner/designercore/include/imagecacheauxiliarydata.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include @@ -16,6 +17,21 @@ namespace QmlDesigner { namespace ImageCache { +constexpr NanotraceHR::Tracing tracingStatus() +{ +#ifdef ENABLE_IMAGE_CACHE_TRACING + return NanotraceHR::tracingStatus(); +#else + return NanotraceHR::Tracing::IsDisabled; +#endif +} + +using Category = NanotraceHR::StringViewWithStringArgumentsCategory; +using TraceToken = Category::FlowTokenType; +using FlowToken = Category::FlowTokenType; +using Token = Category::TokenType; +extern Category &category(); + class FontCollectorSizeAuxiliaryData { public: @@ -46,9 +62,11 @@ using AuxiliaryData = std::variant; -using CaptureImageWithScaledImagesCallback = std::function< - void(const QImage &image, const QImage &midSizeImage, const QImage &smallImage)>; +using CaptureImageWithScaledImagesCallback = std::function; using AbortCallback = std::function; +using InternalAbortCallback = std::function; + } // namespace ImageCache } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/include/import.h b/src/plugins/qmldesigner/designercore/include/import.h index 5dbab393d21..741c5ae54da 100644 --- a/src/plugins/qmldesigner/designercore/include/import.h +++ b/src/plugins/qmldesigner/designercore/include/import.h @@ -59,7 +59,7 @@ public: bool isEmpty() const { return m_type == Type::Empty; } bool isFileImport() const { return m_type == Type::File; } bool isLibraryImport() const { return m_type == Type::Library; } - bool hasVersion() const { return !m_version.isEmpty(); } + bool hasVersion() const; bool hasAlias() const { return !m_alias.isEmpty(); } const QString &url() const { return m_type == Type::Library ? m_url : emptyString; } diff --git a/src/plugins/qmldesigner/designercore/include/itemlibraryentry.h b/src/plugins/qmldesigner/designercore/include/itemlibraryentry.h new file mode 100644 index 00000000000..f88f9e35c6f --- /dev/null +++ b/src/plugins/qmldesigner/designercore/include/itemlibraryentry.h @@ -0,0 +1,93 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "qmldesignercorelib_global.h" + +#include "propertycontainer.h" + +#include + +#include +#include +#include +#include + +namespace QmlDesigner { + +namespace Internal { + +class ItemLibraryEntryData; +class MetaInfoPrivate; +} // namespace Internal + +class ItemLibraryEntry; +class NodeMetaInfo; + +QMLDESIGNERCORE_EXPORT QDataStream &operator<<(QDataStream &stream, + const ItemLibraryEntry &itemLibraryEntry); +QMLDESIGNERCORE_EXPORT QDataStream &operator>>(QDataStream &stream, + ItemLibraryEntry &itemLibraryEntry); +QMLDESIGNERCORE_EXPORT QDebug operator<<(QDebug debug, const ItemLibraryEntry &itemLibraryEntry); + +class QMLDESIGNERCORE_EXPORT ItemLibraryEntry +{ + friend QMLDESIGNERCORE_EXPORT QDataStream &operator<<(QDataStream &stream, + const ItemLibraryEntry &itemLibraryEntry); + friend QMLDESIGNERCORE_EXPORT QDataStream &operator>>(QDataStream &stream, + ItemLibraryEntry &itemLibraryEntry); + friend QMLDESIGNERCORE_EXPORT QDebug operator<<(QDebug debug, + const ItemLibraryEntry &itemLibraryEntry); + +public: + ItemLibraryEntry(); + explicit ItemLibraryEntry(const Storage::Info::ItemLibraryEntry &entry, + const ProjectStorageType &projectStorage); + ~ItemLibraryEntry() = default; + + QString name() const; + TypeName typeName() const; + const NodeMetaInfo &metaInfo() const; + QIcon typeIcon() const; + QString libraryEntryIconPath() const; + int majorVersion() const; + int minorVersion() const; + QString category() const; + QString qmlSource() const; + QString requiredImport() const; + QString customComponentSource() const; + QStringList extraFilePaths() const; + QString toolTip() const; + + using Property = QmlDesigner::PropertyContainer; + + QList properties() const; + QHash hints() const; + + void setType(const TypeName &typeName, int majorVersion = -1, int minorVersion = -1); + void setName(const QString &name); + void setLibraryEntryIconPath(const QString &libraryEntryIconPath); + void addProperty(const Property &p); + void addProperty(PropertyName &name, QString &type, QVariant &value); + void setTypeIcon(const QIcon &typeIcon); + 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); + +private: + std::shared_ptr m_data; +}; + +using ItemLibraryEntries = QList; + +QMLDESIGNERCORE_EXPORT QList toItemLibraryEntries( + const Storage::Info::ItemLibraryEntries &entries, const ProjectStorageType &projectStorage); + +} // namespace QmlDesigner + +Q_DECLARE_METATYPE(QmlDesigner::ItemLibraryEntry) diff --git a/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h b/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h index 2974d58d0b5..99c10f11ed2 100644 --- a/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h +++ b/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h @@ -3,76 +3,12 @@ #pragma once -#include "qmldesignercorelib_global.h" +#ifndef QDS_USE_PROJECTSTORAGE -#include "propertycontainer.h" -#include -#include -#include -#include +# include "itemlibraryentry.h" namespace QmlDesigner { -namespace Internal { - -class ItemLibraryEntryData; -class MetaInfoPrivate; -} - -class ItemLibraryEntry; - -QMLDESIGNERCORE_EXPORT QDataStream& operator<<(QDataStream& stream, const ItemLibraryEntry &itemLibraryEntry); -QMLDESIGNERCORE_EXPORT QDataStream& operator>>(QDataStream& stream, ItemLibraryEntry &itemLibraryEntry); -QMLDESIGNERCORE_EXPORT QDebug operator<<(QDebug debug, const ItemLibraryEntry &itemLibraryEntry); - -class QMLDESIGNERCORE_EXPORT ItemLibraryEntry -{ - friend QMLDESIGNERCORE_EXPORT QDataStream& operator<<(QDataStream& stream, const ItemLibraryEntry &itemLibraryEntry); - friend QMLDESIGNERCORE_EXPORT QDataStream& operator>>(QDataStream& stream, ItemLibraryEntry &itemLibraryEntry); - friend QMLDESIGNERCORE_EXPORT QDebug operator<<(QDebug debug, const ItemLibraryEntry &itemLibraryEntry); - -public: - ItemLibraryEntry(); - ~ItemLibraryEntry() = default; - - QString name() const; - TypeName typeName() const; - QIcon typeIcon() const; - QString libraryEntryIconPath() const; - int majorVersion() const; - int minorVersion() const; - QString category() const; - QIcon dragIcon() const; - QString qmlPath() const; - QString qmlSource() const; - QString requiredImport() const; - QString customComponentSource() const; - QStringList extraFilePaths() const; - QString toolTip() const; - - using Property = QmlDesigner::PropertyContainer; - - QList properties() const; - QHash hints() const; - - void setType(const TypeName &typeName, int majorVersion = -1, int minorVersion = -1); - void setName(const QString &name); - void setLibraryEntryIconPath(const QString &libraryEntryIconPath); - void addProperty(const Property &p); - void addProperty(PropertyName &name, QString &type, QVariant &value); - void setTypeIcon(const QIcon &typeIcon); - 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); - -private: - std::shared_ptr m_data; -}; - class QMLDESIGNERCORE_EXPORT ItemLibraryInfo : public QObject { Q_OBJECT @@ -101,5 +37,4 @@ private: // variables }; } // namespace QmlDesigner - -Q_DECLARE_METATYPE(QmlDesigner::ItemLibraryEntry) +#endif diff --git a/src/plugins/qmldesigner/designercore/include/metainfo.h b/src/plugins/qmldesigner/designercore/include/metainfo.h index 914be96db7b..b176831eb6d 100644 --- a/src/plugins/qmldesigner/designercore/include/metainfo.h +++ b/src/plugins/qmldesigner/designercore/include/metainfo.h @@ -3,13 +3,15 @@ #pragma once -#include "qmldesignercorelib_global.h" +#ifndef QDS_USE_PROJECTSTORAGE -#include -#include +# include "qmldesignercorelib_global.h" -#include -#include "itemlibraryinfo.h" +# include +# include + +# include +# include "itemlibraryinfo.h" namespace QmlDesigner { @@ -63,3 +65,4 @@ private: }; } //namespace QmlDesigner +#endif diff --git a/src/plugins/qmldesigner/designercore/include/metainforeader.h b/src/plugins/qmldesigner/designercore/include/metainforeader.h index b10c7413c59..c12d507ed9c 100644 --- a/src/plugins/qmldesigner/designercore/include/metainforeader.h +++ b/src/plugins/qmldesigner/designercore/include/metainforeader.h @@ -3,13 +3,15 @@ #pragma once -#include "qmldesignercorelib_global.h" -#include +#ifndef QDS_USE_PROJECTSTORAGE -#include +# include "qmldesignercorelib_global.h" +# include -#include -#include +# include + +# include +# include namespace QmlDesigner { @@ -106,3 +108,5 @@ private: } } + +#endif diff --git a/src/plugins/qmldesigner/designercore/include/model.h b/src/plugins/qmldesigner/designercore/include/model.h index cc3b4aadf9c..9816857db65 100644 --- a/src/plugins/qmldesigner/designercore/include/model.h +++ b/src/plugins/qmldesigner/designercore/include/model.h @@ -10,11 +10,13 @@ #include #include #include +#include #include #include #include #include +#include #include @@ -44,6 +46,7 @@ class AbstractProperty; class RewriterView; class NodeInstanceView; class TextModifier; +class ItemLibraryEntry; using PropertyListType = QList>; @@ -125,15 +128,18 @@ public: SourceId fileUrlSourceId() const; void setFileUrl(const QUrl &url); +#ifndef QDS_USE_PROJECTSTORAGE const MetaInfo metaInfo() const; MetaInfo metaInfo(); + void setMetaInfo(const MetaInfo &metaInfo); +#endif + Module module(Utils::SmallStringView moduleName); NodeMetaInfo metaInfo(const TypeName &typeName, int majorVersion = -1, int minorVersion = -1) const; NodeMetaInfo metaInfo(Module module, Utils::SmallStringView typeName, Storage::Version version = Storage::Version{}) const; bool hasNodeMetaInfo(const TypeName &typeName, int majorVersion = -1, int minorVersion = -1) const; - void setMetaInfo(const MetaInfo &metaInfo); NodeMetaInfo boolMetaInfo() const; NodeMetaInfo doubleMetaInfo() const; @@ -148,10 +154,15 @@ public: NodeMetaInfo qtQmlModelsListElementMetaInfo() const; NodeMetaInfo qtQuick3DBakedLightmapMetaInfo() const; NodeMetaInfo qtQuick3DDefaultMaterialMetaInfo() const; + NodeMetaInfo qtQuick3DDirectionalLightMetaInfo() const; NodeMetaInfo qtQuick3DMaterialMetaInfo() const; NodeMetaInfo qtQuick3DModelMetaInfo() const; NodeMetaInfo qtQuick3DNodeMetaInfo() const; + NodeMetaInfo qtQuick3DOrthographicCameraMetaInfo() const; + NodeMetaInfo qtQuick3DPerspectiveCameraMetaInfo() const; + NodeMetaInfo qtQuick3DPointLightMetaInfo() const; NodeMetaInfo qtQuick3DPrincipledMaterialMetaInfo() const; + NodeMetaInfo qtQuick3DSpotLightMetaInfo() const; NodeMetaInfo qtQuick3DTextureMetaInfo() const; NodeMetaInfo qtQuickConnectionsMetaInfo() const; NodeMetaInfo qtQuickControlsTextAreaMetaInfo() const; @@ -169,6 +180,9 @@ public: NodeMetaInfo vector2dMetaInfo() const; NodeMetaInfo vector3dMetaInfo() const; NodeMetaInfo vector4dMetaInfo() const; + QVarLengthArray metaInfosForModule(Module module) const; + + QList itemLibraryEntries() const; void attachView(AbstractView *view); void detachView(AbstractView *view, ViewNotification emitDetachNotify = NotifyView); diff --git a/src/plugins/qmldesigner/designercore/include/modelfwd.h b/src/plugins/qmldesigner/designercore/include/modelfwd.h index eedc46e3b56..0a062289fd0 100644 --- a/src/plugins/qmldesigner/designercore/include/modelfwd.h +++ b/src/plugins/qmldesigner/designercore/include/modelfwd.h @@ -48,6 +48,7 @@ public: /* */ using TypeName = QByteArray; +using TypeNameView = QByteArrayView; using PropertyTypeList = QList; using IdName = QByteArray; class Model; diff --git a/src/plugins/qmldesigner/designercore/include/nodehints.h b/src/plugins/qmldesigner/designercore/include/nodehints.h index 8bdb30f5a38..9e67c2d99b3 100644 --- a/src/plugins/qmldesigner/designercore/include/nodehints.h +++ b/src/plugins/qmldesigner/designercore/include/nodehints.h @@ -58,8 +58,9 @@ public: private: explicit NodeHints(const ModelNode &modelNode); + explicit NodeHints(const NodeMetaInfo &metaInfo); explicit NodeHints(const ItemLibraryEntry &entry); - ModelNode modelNode() const; + const ModelNode &modelNode() const; bool isValid() const; Model *model() const; bool evaluateBooleanExpression(const QString &hintName, bool defaultValue, const ModelNode potentialParent = ModelNode()) const; diff --git a/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h b/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h index fd7f21d090b..80727bf5a9e 100644 --- a/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h +++ b/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h @@ -132,7 +132,7 @@ public: void selectedNodesChanged(const QList &selectedNodeList, const QList &lastSelectedNodeList) override; - void sendInputEvent(QInputEvent *e) const; + void sendInputEvent(QEvent *e) const; void view3DAction(View3DActionType type, const QVariant &value) override; void requestModelNodePreviewImage(const ModelNode &node, const ModelNode &renderNode) const; void edit3DViewResized(const QSize &size) const; diff --git a/src/plugins/qmldesigner/designercore/include/nodemetainfo.h b/src/plugins/qmldesigner/designercore/include/nodemetainfo.h index 02a0c81ca4a..58cdeba228e 100644 --- a/src/plugins/qmldesigner/designercore/include/nodemetainfo.h +++ b/src/plugins/qmldesigner/designercore/include/nodemetainfo.h @@ -33,6 +33,8 @@ enum class MetaInfoType { None, Reference, Value, Sequence }; class QMLDESIGNERCORE_EXPORT NodeMetaInfo { + using NodeMetaInfos = std::vector; + public: NodeMetaInfo(); NodeMetaInfo(Model *model, const TypeName &typeName, int majorVersion, int minorVersion); @@ -59,6 +61,20 @@ public: bool isFileComponent() const; bool isProjectComponent() const; bool isInProjectModule() const; + FlagIs canBeContainer() const; + FlagIs forceClip() const; + FlagIs doesLayoutChildren() const; + FlagIs canBeDroppedInFormEditor() const; + FlagIs canBeDroppedInNavigator() const; + FlagIs canBeDroppedInView3D() const; + FlagIs isMovable() const; + FlagIs isResizable() const; + FlagIs hasFormEditorItem() const; + FlagIs isStackedContainer() const; + FlagIs takesOverRenderingOfChildren() const; + FlagIs visibleInNavigator() const; + FlagIs visibleInLibrary() const; + bool hasProperty(::Utils::SmallStringView propertyName) const; PropertyMetaInfos properties() const; PropertyMetaInfos localProperties() const; @@ -69,8 +85,9 @@ public: PropertyMetaInfo defaultProperty() const; bool hasDefaultProperty() const; - std::vector selfAndPrototypes() const; - std::vector prototypes() const; + NodeMetaInfos selfAndPrototypes() const; + NodeMetaInfos prototypes() const; + NodeMetaInfos heirs() const; NodeMetaInfo commonBase(const NodeMetaInfo &metaInfo) const; bool defaultPropertyIsComponent() const; @@ -83,6 +100,10 @@ public: Storage::Info::ExportedTypeNames allExportedTypeNames() const; Storage::Info::ExportedTypeNames exportedTypeNamesForSourceId(SourceId sourceId) const; + Storage::Info::TypeHints typeHints() const; + Utils::PathString iconPath() const; + Storage::Info::ItemLibraryEntries itemLibrariesEntries() const; + SourceId sourceId() const; QString componentFileName() const; diff --git a/src/plugins/qmldesigner/designercore/include/stylesheetmerger.h b/src/plugins/qmldesigner/designercore/include/stylesheetmerger.h index 288dbc1082e..d4f387ad356 100644 --- a/src/plugins/qmldesigner/designercore/include/stylesheetmerger.h +++ b/src/plugins/qmldesigner/designercore/include/stylesheetmerger.h @@ -50,7 +50,9 @@ private: void syncBindingProperties(ModelNode &outputNode, const ModelNode &inputNode); void syncAuxiliaryProperties(ModelNode &outputNode, const ModelNode &inputNode); void syncVariantProperties(ModelNode &outputNode, const ModelNode &inputNode); + void syncStateNode(ModelNode &outputState, const ModelNode &inputState) const; void parseTemplateOptions(); + void mergeStates(ModelNode &outputNode, const ModelNode &inputNode) const; AbstractView *m_templateView; AbstractView *m_styleView; diff --git a/src/plugins/qmldesigner/designercore/include/subcomponentmanager.h b/src/plugins/qmldesigner/designercore/include/subcomponentmanager.h index dbc89c1cccd..7fa23488543 100644 --- a/src/plugins/qmldesigner/designercore/include/subcomponentmanager.h +++ b/src/plugins/qmldesigner/designercore/include/subcomponentmanager.h @@ -3,18 +3,20 @@ #pragma once -#include "qmldesignercorelib_global.h" +#ifndef QDS_USE_PROJECTSTORAGE -#include +# include "qmldesignercorelib_global.h" -#include -#include -#include -#include -#include -#include -#include -#include +# include + +# include +# include +# include +# include +# include +# include +# include +# include namespace QmlDesigner { @@ -63,3 +65,4 @@ private: // variables }; } // namespace QmlDesigner +#endif diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp index 99d34cd1353..cd56af86643 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp @@ -57,7 +57,6 @@ #include #include #include -#include #include #include #include @@ -1720,6 +1719,11 @@ void NodeInstanceView::handlePuppetToCreatorCommand(const PuppetToCreatorCommand } else if (command.type() == PuppetToCreatorCommand::ActiveSceneChanged) { const auto sceneState = qvariant_cast(command.data()); emitUpdateActiveScene3D(sceneState); + } else if (command.type() == PuppetToCreatorCommand::ActiveSplitChanged) { + // Active split change is a special case of active scene change + QVariantMap splitState; + splitState.insert("activeSplit", command.data()); + emitUpdateActiveScene3D(splitState); } else if (command.type() == PuppetToCreatorCommand::RenderModelNodePreviewImage) { ImageContainer container = qvariant_cast(command.data()); QImage image = container.image(); @@ -1764,7 +1768,7 @@ void NodeInstanceView::selectedNodesChanged(const QList &selectedNode m_rotBlockTimer.start(); } -void NodeInstanceView::sendInputEvent(QInputEvent *e) const +void NodeInstanceView::sendInputEvent(QEvent *e) const { m_nodeInstanceServer->inputEvent(InputEventCommand(e)); } @@ -1777,7 +1781,7 @@ void NodeInstanceView::view3DAction(View3DActionType type, const QVariant &value void NodeInstanceView::requestModelNodePreviewImage(const ModelNode &node, const ModelNode &renderNode) const { - if (m_nodeInstanceServer && node.isValid()) { + if (m_nodeInstanceServer && node.isValid() && hasInstanceForModelNode(node)) { auto instance = instanceForModelNode(node); if (instance.isValid()) { qint32 renderItemId = -1; diff --git a/src/plugins/qmldesigner/designercore/metainfo/itemlibraryentry.cpp b/src/plugins/qmldesigner/designercore/metainfo/itemlibraryentry.cpp new file mode 100644 index 00000000000..806da7e7c4d --- /dev/null +++ b/src/plugins/qmldesigner/designercore/metainfo/itemlibraryentry.cpp @@ -0,0 +1,306 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "../include/itemlibraryentry.h" +#include "nodemetainfo.h" +#include "qregularexpression.h" + +#include +#include + +#include + +#include +#include + +namespace QmlDesigner { + +namespace Internal { + +class ItemLibraryEntryData +{ +public: + QString name; + TypeName typeName; + NodeMetaInfo metaInfo; + QString category; + int majorVersion{-1}; + int minorVersion{-1}; + QString libraryEntryIconPath; + QIcon typeIcon = QIcon(":/ItemLibrary/images/item-default-icon.png"); + QList properties; + QString qml; + QString qmlSource; + QString requiredImport; + QHash hints; + QString customComponentSource; + QStringList extraFilePaths; + QString toolTip; +}; + +} // namespace Internal + +void ItemLibraryEntry::setTypeIcon(const QIcon &icon) +{ + m_data->typeIcon = icon; +} + +void ItemLibraryEntry::addProperty(const Property &property) +{ + m_data->properties.append(property); +} + +QList ItemLibraryEntry::properties() const +{ + return m_data->properties; +} + +QHash ItemLibraryEntry::hints() const +{ + return m_data->hints; +} + +ItemLibraryEntry::ItemLibraryEntry() + : m_data(std::make_shared()) +{} + +ItemLibraryEntry::ItemLibraryEntry(const Storage::Info::ItemLibraryEntry &entry, + const ProjectStorageType &projectStorage) + : ItemLibraryEntry{} +{ + m_data->name = entry.name.toQString(); + m_data->metaInfo = {entry.typeId, &projectStorage}; + m_data->category = entry.category.toQString(); + if (entry.iconPath.size()) + m_data->libraryEntryIconPath = entry.iconPath.toQString(); + m_data->requiredImport = entry.import.toQString(); + m_data->toolTip = entry.toolTip.toQString(); + m_data->qmlSource = entry.templatePath.toQString(); + m_data->properties.reserve(Utils::ssize(entry.properties)); + for (const auto &property : entry.properties) { + m_data->properties.emplace_back(property.name.toQByteArray(), + property.type.toQString(), + QVariant{property.value}); + } + m_data->extraFilePaths.reserve(Utils::ssize(entry.extraFilePaths)); + for (const auto &extraFilePath : entry.extraFilePaths) + m_data->extraFilePaths.emplace_back(extraFilePath.toQString()); +} + +QString ItemLibraryEntry::name() const +{ + return m_data->name; +} + +TypeName ItemLibraryEntry::typeName() const +{ + return m_data->typeName; +} + +const NodeMetaInfo &ItemLibraryEntry::metaInfo() const +{ + return m_data->metaInfo; +} + +QString ItemLibraryEntry::qmlSource() const +{ + return m_data->qmlSource; +} + +QString ItemLibraryEntry::requiredImport() const +{ + return m_data->requiredImport; +} + +QString ItemLibraryEntry::customComponentSource() const +{ + return m_data->customComponentSource; +} + +QStringList ItemLibraryEntry::extraFilePaths() const +{ + return m_data->extraFilePaths; +} + +QString ItemLibraryEntry::toolTip() const +{ + return m_data->toolTip; +} + +int ItemLibraryEntry::majorVersion() const +{ + return m_data->majorVersion; +} + +int ItemLibraryEntry::minorVersion() const +{ + return m_data->minorVersion; +} + +QString ItemLibraryEntry::category() const +{ + return m_data->category; +} + +void ItemLibraryEntry::setCategory(const QString &category) +{ + m_data->category = category; +} + +QIcon ItemLibraryEntry::typeIcon() const +{ + return m_data->typeIcon; +} + +QString ItemLibraryEntry::libraryEntryIconPath() const +{ + return m_data->libraryEntryIconPath; +} + +void ItemLibraryEntry::setName(const QString &name) +{ + m_data->name = name; +} + +void ItemLibraryEntry::setType(const TypeName &typeName, int majorVersion, int minorVersion) +{ + m_data->typeName = typeName; + m_data->majorVersion = majorVersion; + m_data->minorVersion = minorVersion; +} + +void ItemLibraryEntry::setLibraryEntryIconPath(const QString &iconPath) +{ + m_data->libraryEntryIconPath = iconPath; +} + +static QByteArray getSourceForUrl(const QString &fileURl) +{ + Utils::FileReader fileReader; + + if (fileReader.fetch(Utils::FilePath::fromString(fileURl))) + return fileReader.data(); + else + return Utils::FileReader::fetchQrc(fileURl); +} + +void ItemLibraryEntry::setQmlPath(const QString &qml) +{ + m_data->qml = qml; + + m_data->qmlSource = QString::fromUtf8(getSourceForUrl(qml)); +} + +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); +} + +void ItemLibraryEntry::setCustomComponentSource(const QString &source) +{ + m_data->customComponentSource = source; +} + +void ItemLibraryEntry::addExtraFilePath(const QString &extraFile) +{ + m_data->extraFilePaths.append(extraFile); +} + +void ItemLibraryEntry::addProperty(PropertyName &name, QString &type, QVariant &value) +{ + Property property; + property.set(name, type, value); + addProperty(property); +} + +QDataStream &operator<<(QDataStream &stream, const ItemLibraryEntry &itemLibraryEntry) +{ + stream << itemLibraryEntry.name(); + stream << itemLibraryEntry.typeName(); + stream << itemLibraryEntry.majorVersion(); + stream << itemLibraryEntry.minorVersion(); + stream << itemLibraryEntry.typeIcon(); + stream << itemLibraryEntry.libraryEntryIconPath(); + stream << itemLibraryEntry.category(); + stream << itemLibraryEntry.requiredImport(); + stream << itemLibraryEntry.hints(); + + stream << itemLibraryEntry.m_data->properties; + stream << itemLibraryEntry.m_data->qml; + stream << itemLibraryEntry.m_data->qmlSource; + stream << itemLibraryEntry.m_data->customComponentSource; + stream << itemLibraryEntry.m_data->extraFilePaths; + + return stream; +} + +QDataStream &operator>>(QDataStream &stream, ItemLibraryEntry &itemLibraryEntry) +{ + // Clear containers so that we don't simply append to them in case the object is reused + itemLibraryEntry.m_data->hints.clear(); + itemLibraryEntry.m_data->properties.clear(); + + stream >> itemLibraryEntry.m_data->name; + stream >> itemLibraryEntry.m_data->typeName; + stream >> itemLibraryEntry.m_data->majorVersion; + stream >> itemLibraryEntry.m_data->minorVersion; + stream >> itemLibraryEntry.m_data->typeIcon; + stream >> itemLibraryEntry.m_data->libraryEntryIconPath; + stream >> itemLibraryEntry.m_data->category; + stream >> itemLibraryEntry.m_data->requiredImport; + stream >> itemLibraryEntry.m_data->hints; + + stream >> itemLibraryEntry.m_data->properties; + stream >> itemLibraryEntry.m_data->qml; + stream >> itemLibraryEntry.m_data->qmlSource; + stream >> itemLibraryEntry.m_data->customComponentSource; + stream >> itemLibraryEntry.m_data->extraFilePaths; + + return stream; +} + +QDebug operator<<(QDebug debug, const ItemLibraryEntry &itemLibraryEntry) +{ + debug << itemLibraryEntry.m_data->name; + debug << itemLibraryEntry.m_data->typeName; + debug << itemLibraryEntry.m_data->majorVersion; + debug << itemLibraryEntry.m_data->minorVersion; + debug << itemLibraryEntry.m_data->typeIcon; + debug << itemLibraryEntry.m_data->libraryEntryIconPath; + debug << itemLibraryEntry.m_data->category; + debug << itemLibraryEntry.m_data->requiredImport; + debug << itemLibraryEntry.m_data->hints; + + debug << itemLibraryEntry.m_data->properties; + debug << itemLibraryEntry.m_data->qml; + debug << itemLibraryEntry.m_data->qmlSource; + debug << itemLibraryEntry.m_data->customComponentSource; + debug << itemLibraryEntry.m_data->extraFilePaths; + + return debug.space(); +} + +QList toItemLibraryEntries(const Storage::Info::ItemLibraryEntries &entries, + const ProjectStorageType &projectStorage) +{ + return Utils::transform>(entries, [&](const auto &entry) { + return ItemLibraryEntry{entry, projectStorage}; + }); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/metainfo/itemlibraryinfo.cpp b/src/plugins/qmldesigner/designercore/metainfo/itemlibraryinfo.cpp index 9f542826156..9f8b43f9209 100644 --- a/src/plugins/qmldesigner/designercore/metainfo/itemlibraryinfo.cpp +++ b/src/plugins/qmldesigner/designercore/metainfo/itemlibraryinfo.cpp @@ -2,280 +2,12 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "../include/itemlibraryinfo.h" -#include "nodemetainfo.h" -#include "qregularexpression.h" #include #include -#include - -#include -#include - namespace QmlDesigner { -namespace Internal { - -class ItemLibraryEntryData -{ -public: - QString name; - TypeName typeName; - QString category; - int majorVersion{-1}; - int minorVersion{-1}; - QString libraryEntryIconPath; - QIcon typeIcon = QIcon(":/ItemLibrary/images/item-default-icon.png"); - QList properties; - QString qml; - QString qmlSource; - QString requiredImport; - QHash hints; - QString customComponentSource; - QStringList extraFilePaths; - QString toolTip; -}; - -} // namespace Internal - -void ItemLibraryEntry::setTypeIcon(const QIcon &icon) -{ - m_data->typeIcon = icon; -} - -void ItemLibraryEntry::addProperty(const Property &property) -{ - m_data->properties.append(property); -} - -QList ItemLibraryEntry::properties() const -{ - return m_data->properties; -} - -QHash ItemLibraryEntry::hints() const -{ - return m_data->hints; -} - -ItemLibraryEntry::ItemLibraryEntry() : m_data(new Internal::ItemLibraryEntryData) -{ - m_data->name.clear(); -} - -QString ItemLibraryEntry::name() const -{ - return m_data->name; -} - -TypeName ItemLibraryEntry::typeName() const -{ - return m_data->typeName; -} - -QString ItemLibraryEntry::qmlPath() const -{ - return m_data->qml; -} - -QString ItemLibraryEntry::qmlSource() const -{ - return m_data->qmlSource; -} - -QString ItemLibraryEntry::requiredImport() const -{ - return m_data->requiredImport; -} - -QString ItemLibraryEntry::customComponentSource() const -{ - return m_data->customComponentSource; -} - -QStringList ItemLibraryEntry::extraFilePaths() const -{ - return m_data->extraFilePaths; -} - -QString ItemLibraryEntry::toolTip() const -{ - return m_data->toolTip; -} - -int ItemLibraryEntry::majorVersion() const -{ - return m_data->majorVersion; -} - -int ItemLibraryEntry::minorVersion() const -{ - return m_data->minorVersion; -} - -QString ItemLibraryEntry::category() const -{ - return m_data->category; -} - -void ItemLibraryEntry::setCategory(const QString &category) -{ - m_data->category = category; -} - -QIcon ItemLibraryEntry::typeIcon() const -{ - return m_data->typeIcon; -} - -QString ItemLibraryEntry::libraryEntryIconPath() const -{ - return m_data->libraryEntryIconPath; -} - -void ItemLibraryEntry::setName(const QString &name) -{ - m_data->name = name; -} - -void ItemLibraryEntry::setType(const TypeName &typeName, int majorVersion, int minorVersion) -{ - m_data->typeName = typeName; - m_data->majorVersion = majorVersion; - m_data->minorVersion = minorVersion; -} - -void ItemLibraryEntry::setLibraryEntryIconPath(const QString &iconPath) -{ - m_data->libraryEntryIconPath = iconPath; -} - -static QByteArray getSourceForUrl(const QString &fileURl) -{ - Utils::FileReader fileReader; - - if (fileReader.fetch(Utils::FilePath::fromString(fileURl))) - return fileReader.data(); - else - return Utils::FileReader::fetchQrc(fileURl); -} - -void ItemLibraryEntry::setQmlPath(const QString &qml) -{ - m_data->qml = qml; - - m_data->qmlSource = QString::fromUtf8(getSourceForUrl(qml)); -} - -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); -} - -void ItemLibraryEntry::setCustomComponentSource(const QString &source) -{ - m_data->customComponentSource = source; -} - -void ItemLibraryEntry::addExtraFilePath(const QString &extraFile) -{ - m_data->extraFilePaths.append(extraFile); -} - -void ItemLibraryEntry::addProperty(PropertyName &name, QString &type, QVariant &value) -{ - Property property; - property.set(name, type, value); - addProperty(property); -} - -QDataStream& operator<<(QDataStream& stream, const ItemLibraryEntry &itemLibraryEntry) -{ - stream << itemLibraryEntry.name(); - stream << itemLibraryEntry.typeName(); - stream << itemLibraryEntry.majorVersion(); - stream << itemLibraryEntry.minorVersion(); - stream << itemLibraryEntry.typeIcon(); - stream << itemLibraryEntry.libraryEntryIconPath(); - stream << itemLibraryEntry.category(); - stream << itemLibraryEntry.requiredImport(); - stream << itemLibraryEntry.hints(); - - stream << itemLibraryEntry.m_data->properties; - stream << itemLibraryEntry.m_data->qml; - stream << itemLibraryEntry.m_data->qmlSource; - stream << itemLibraryEntry.m_data->customComponentSource; - stream << itemLibraryEntry.m_data->extraFilePaths; - - return stream; -} - -QDataStream& operator>>(QDataStream& stream, ItemLibraryEntry &itemLibraryEntry) -{ - // Clear containers so that we don't simply append to them in case the object is reused - itemLibraryEntry.m_data->hints.clear(); - itemLibraryEntry.m_data->properties.clear(); - - stream >> itemLibraryEntry.m_data->name; - stream >> itemLibraryEntry.m_data->typeName; - stream >> itemLibraryEntry.m_data->majorVersion; - stream >> itemLibraryEntry.m_data->minorVersion; - stream >> itemLibraryEntry.m_data->typeIcon; - stream >> itemLibraryEntry.m_data->libraryEntryIconPath; - stream >> itemLibraryEntry.m_data->category; - stream >> itemLibraryEntry.m_data->requiredImport; - stream >> itemLibraryEntry.m_data->hints; - - stream >> itemLibraryEntry.m_data->properties; - stream >> itemLibraryEntry.m_data->qml; - stream >> itemLibraryEntry.m_data->qmlSource; - stream >> itemLibraryEntry.m_data->customComponentSource; - stream >> itemLibraryEntry.m_data->extraFilePaths; - - return stream; -} - -QDebug operator<<(QDebug debug, const ItemLibraryEntry &itemLibraryEntry) -{ - debug << itemLibraryEntry.m_data->name; - debug << itemLibraryEntry.m_data->typeName; - debug << itemLibraryEntry.m_data->majorVersion; - debug << itemLibraryEntry.m_data->minorVersion; - debug << itemLibraryEntry.m_data->typeIcon; - debug << itemLibraryEntry.m_data->libraryEntryIconPath; - debug << itemLibraryEntry.m_data->category; - debug << itemLibraryEntry.m_data->requiredImport; - debug << itemLibraryEntry.m_data->hints; - - debug << itemLibraryEntry.m_data->properties; - debug << itemLibraryEntry.m_data->qml; - debug << itemLibraryEntry.m_data->qmlSource; - debug << itemLibraryEntry.m_data->customComponentSource; - debug << itemLibraryEntry.m_data->extraFilePaths; - - return debug.space(); -} - -// -// ItemLibraryInfo -// - ItemLibraryInfo::ItemLibraryInfo(QObject *parent) : QObject(parent) { @@ -306,7 +38,7 @@ QList ItemLibraryInfo::entries() const return list; } -static inline QString keyForEntry(const ItemLibraryEntry &entry) +inline static QString keyForEntry(const ItemLibraryEntry &entry) { return entry.name() + entry.category() + QString::number(entry.majorVersion()); } diff --git a/src/plugins/qmldesigner/designercore/metainfo/nodehints.cpp b/src/plugins/qmldesigner/designercore/metainfo/nodehints.cpp index 8cdc590135f..32e68a3cdc6 100644 --- a/src/plugins/qmldesigner/designercore/metainfo/nodehints.cpp +++ b/src/plugins/qmldesigner/designercore/metainfo/nodehints.cpp @@ -6,10 +6,11 @@ #include "metainfo.h" #include -#include -#include +#include #include #include +#include +#include #include @@ -21,7 +22,9 @@ #include -#include +#ifndef QDS_USE_PROJECTSTORAGE +# include +#endif #include @@ -65,8 +68,15 @@ static QVariant evaluateExpression(const QString &expression, const ModelNode &m } //Internal -QmlDesigner::NodeHints::NodeHints(const ModelNode &node) : m_modelNode(node) +QmlDesigner::NodeHints::NodeHints(const ModelNode &node) +#ifdef QDS_USE_PROJECTSTORAGE + : NodeHints{node.metaInfo()} +#else + : m_modelNode(node) +#endif { +#ifndef QDS_USE_PROJECTSTORAGE + if (!isValid()) return; @@ -92,13 +102,31 @@ QmlDesigner::NodeHints::NodeHints(const ModelNode &node) : m_modelNode(node) } } +#endif +} + +NodeHints::NodeHints(const NodeMetaInfo &metaInfo) +{ + for (const auto &[name, expression] : metaInfo.typeHints()) + m_hints.insert(name.toQString(), expression.toQString()); } NodeHints::NodeHints(const ItemLibraryEntry &entry) +#ifdef QDS_USE_PROJECTSTORAGE + : NodeHints{entry.metaInfo()} +#endif { - m_hints = entry.hints(); + if constexpr (!useProjectStorage()) + m_hints = entry.hints(); } +namespace { +bool convert(FlagIs flagIs) +{ + return flagIs == FlagIs::True ? true : false; +} +} // namespace + bool NodeHints::canBeContainerFor(const ModelNode &potenialChild) const { /* The default is true for now to avoid confusion. Once our .metaInfo files in Qt @@ -107,6 +135,11 @@ bool NodeHints::canBeContainerFor(const ModelNode &potenialChild) const if (!isValid()) return true; + auto flagIs = m_modelNode.metaInfo().canBeContainer(); + + if (flagIs != FlagIs::Set) + return convert(flagIs); + return evaluateBooleanExpression("canBeContainer", true, potenialChild); } @@ -118,6 +151,11 @@ bool NodeHints::forceClip() const if (isSwipeView(modelNode())) return true; + auto flagIs = m_modelNode.metaInfo().forceClip(); + + if (flagIs != FlagIs::Set) + return convert(flagIs); + return evaluateBooleanExpression("forceClip", false); } @@ -129,21 +167,41 @@ bool NodeHints::doesLayoutChildren() const if (isSwipeView(modelNode())) return true; + auto flagIs = m_modelNode.metaInfo().doesLayoutChildren(); + + if (flagIs != FlagIs::Set) + return convert(flagIs); + return evaluateBooleanExpression("doesLayoutChildren", false); } bool NodeHints::canBeDroppedInFormEditor() const { + auto flagIs = m_modelNode.metaInfo().canBeDroppedInFormEditor(); + + if (flagIs != FlagIs::Set) + return convert(flagIs); + return evaluateBooleanExpression("canBeDroppedInFormEditor", true); } bool NodeHints::canBeDroppedInNavigator() const { + auto flagIs = m_modelNode.metaInfo().canBeDroppedInNavigator(); + + if (flagIs != FlagIs::Set) + return convert(flagIs); + return evaluateBooleanExpression("canBeDroppedInNavigator", true); } bool NodeHints::canBeDroppedInView3D() const { + auto flagIs = m_modelNode.metaInfo().canBeDroppedInView3D(); + + if (flagIs != FlagIs::Set) + return convert(flagIs); + return evaluateBooleanExpression("canBeDroppedInView3D", false); } @@ -152,16 +210,37 @@ bool NodeHints::isMovable() const if (!isValid()) return true; + auto flagIs = m_modelNode.metaInfo().isMovable(); + + if (flagIs != FlagIs::Set) + return convert(flagIs); + return evaluateBooleanExpression("isMovable", true); } bool NodeHints::isResizable() const { + if (!isValid()) + return true; + + auto flagIs = m_modelNode.metaInfo().isResizable(); + + if (flagIs != FlagIs::Set) + return convert(flagIs); + return evaluateBooleanExpression("isResizable", true); } bool NodeHints::hasFormEditorItem() const { + if (!isValid()) + return true; + + auto flagIs = m_modelNode.metaInfo().hasFormEditorItem(); + + if (flagIs != FlagIs::Set) + return convert(flagIs); + return evaluateBooleanExpression("hasFormEditorItem", true); } @@ -173,6 +252,11 @@ bool NodeHints::isStackedContainer() const if (isSwipeView(modelNode())) return true; + auto flagIs = m_modelNode.metaInfo().isStackedContainer(); + + if (flagIs != FlagIs::Set) + return convert(flagIs); + return evaluateBooleanExpression("isStackedContainer", false); } @@ -215,6 +299,11 @@ bool NodeHints::takesOverRenderingOfChildren() const if (!isValid()) return false; + auto flagIs = m_modelNode.metaInfo().takesOverRenderingOfChildren(); + + if (flagIs != FlagIs::Set) + return convert(flagIs); + return evaluateBooleanExpression("takesOverRenderingOfChildren", false); } @@ -223,11 +312,21 @@ bool NodeHints::visibleInNavigator() const if (!isValid()) return false; + auto flagIs = m_modelNode.metaInfo().visibleInNavigator(); + + if (flagIs != FlagIs::Set) + return convert(flagIs); + return evaluateBooleanExpression("visibleInNavigator", false); } bool NodeHints::visibleInLibrary() const { + auto flagIs = m_modelNode.metaInfo().visibleInLibrary(); + + if (flagIs != FlagIs::Set) + return convert(flagIs); + return evaluateBooleanExpression("visibleInLibrary", true); } @@ -297,7 +396,7 @@ NodeHints NodeHints::fromItemLibraryEntry(const ItemLibraryEntry &entry) return NodeHints(entry); } -ModelNode NodeHints::modelNode() const +const ModelNode &NodeHints::modelNode() const { return m_modelNode; } diff --git a/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp b/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp index e78a6d1a37e..3fb5de210cc 100644 --- a/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp +++ b/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp @@ -1118,9 +1118,15 @@ bool NodeMetaInfoPrivate::cleverCheckType(const TypeName &otherType) const return typeName == convertedName.toUtf8(); } +static TypeName toSimplifiedTypeName(const TypeName &typeName) +{ + return typeName.split('.').constLast(); +} + QVariant::Type NodeMetaInfoPrivate::variantTypeId(const PropertyName &propertyName) const { - TypeName typeName = propertyType(propertyName); + TypeName typeName = toSimplifiedTypeName(propertyType(propertyName)); + if (typeName == "string") return QVariant::String; @@ -1478,12 +1484,12 @@ MetaInfoType NodeMetaInfo::type() const { if constexpr (useProjectStorage()) { if (isValid()) { - switch (typeData().traits) { - case Storage::TypeTraits::Reference: + switch (typeData().traits.kind) { + case Storage::TypeTraitsKind::Reference: return MetaInfoType::Reference; - case Storage::TypeTraits::Value: + case Storage::TypeTraitsKind::Value: return MetaInfoType::Value; - case Storage::TypeTraits::Sequence: + case Storage::TypeTraitsKind::Sequence: return MetaInfoType::Sequence; default: break; @@ -1497,7 +1503,7 @@ MetaInfoType NodeMetaInfo::type() const bool NodeMetaInfo::isFileComponent() const { if constexpr (useProjectStorage()) - return isValid() && bool(typeData().traits & Storage::TypeTraits::IsFileComponent); + return isValid() && typeData().traits.isFileComponent; else return isValid() && m_privateData->isFileComponent(); } @@ -1505,7 +1511,7 @@ bool NodeMetaInfo::isFileComponent() const bool NodeMetaInfo::isProjectComponent() const { if constexpr (useProjectStorage()) { - return isValid() && bool(typeData().traits & Storage::TypeTraits::IsProjectComponent); + return isValid() && typeData().traits.isProjectComponent; } return false; @@ -1514,12 +1520,168 @@ bool NodeMetaInfo::isProjectComponent() const bool NodeMetaInfo::isInProjectModule() const { if constexpr (useProjectStorage()) { - return isValid() && bool(typeData().traits & Storage::TypeTraits::IsInProjectModule); + return isValid() && typeData().traits.isInProjectModule; } return false; } +FlagIs NodeMetaInfo::canBeContainer() const +{ + if constexpr (useProjectStorage()) { + if (isValid()) + return typeData().traits.canBeContainer; + + return FlagIs::False; + } + + return FlagIs::Set; +} + +FlagIs NodeMetaInfo::forceClip() const +{ + if constexpr (useProjectStorage()) { + if (isValid()) + return typeData().traits.forceClip; + + return FlagIs::False; + } + + return FlagIs::Set; +} + +FlagIs NodeMetaInfo::doesLayoutChildren() const +{ + if constexpr (useProjectStorage()) { + if (isValid()) + return typeData().traits.doesLayoutChildren; + + return FlagIs::False; + } + + return FlagIs::Set; +} + +FlagIs NodeMetaInfo::canBeDroppedInFormEditor() const +{ + if constexpr (useProjectStorage()) { + if (isValid()) + return typeData().traits.canBeDroppedInFormEditor; + + return FlagIs::False; + } + + return FlagIs::Set; +} + +FlagIs NodeMetaInfo::canBeDroppedInNavigator() const +{ + if constexpr (useProjectStorage()) { + if (isValid()) + return typeData().traits.canBeDroppedInNavigator; + + return FlagIs::False; + } + + return FlagIs::Set; +} + +FlagIs NodeMetaInfo::canBeDroppedInView3D() const +{ + if constexpr (useProjectStorage()) { + if (isValid()) + return typeData().traits.canBeDroppedInView3D; + + return FlagIs::False; + } + + return FlagIs::Set; +} + +FlagIs NodeMetaInfo::isMovable() const +{ + if constexpr (useProjectStorage()) { + if (isValid()) + return typeData().traits.isMovable; + + return FlagIs::False; + } + + return FlagIs::Set; +} + +FlagIs NodeMetaInfo::isResizable() const +{ + if constexpr (useProjectStorage()) { + if (isValid()) + return typeData().traits.isResizable; + + return FlagIs::False; + } + + return FlagIs::Set; +} + +FlagIs NodeMetaInfo::hasFormEditorItem() const +{ + if constexpr (useProjectStorage()) { + if (isValid()) + return typeData().traits.hasFormEditorItem; + + return FlagIs::False; + } + + return FlagIs::Set; +} + +FlagIs NodeMetaInfo::isStackedContainer() const +{ + if constexpr (useProjectStorage()) { + if (isValid()) + return typeData().traits.isStackedContainer; + + return FlagIs::False; + } + + return FlagIs::Set; +} + +FlagIs NodeMetaInfo::takesOverRenderingOfChildren() const +{ + if constexpr (useProjectStorage()) { + if (isValid()) + return typeData().traits.takesOverRenderingOfChildren; + + return FlagIs::False; + } + + return FlagIs::Set; +} + +FlagIs NodeMetaInfo::visibleInNavigator() const +{ + if constexpr (useProjectStorage()) { + if (isValid()) + return typeData().traits.visibleInNavigator; + + return FlagIs::False; + } + + return FlagIs::Set; +} + +FlagIs NodeMetaInfo::visibleInLibrary() const +{ + if constexpr (useProjectStorage()) { + if (isValid()) + return typeData().traits.visibleInLibrary; + + return FlagIs::False; + } + + return FlagIs::Set; +} + namespace { [[maybe_unused]] auto propertyId(const ProjectStorageType &projectStorage, @@ -1773,7 +1935,7 @@ TypeName NodeMetaInfo::typeName() const TypeName NodeMetaInfo::simplifiedTypeName() const { if (isValid()) - return typeName().split('.').constLast(); + return toSimplifiedTypeName(typeName()); return {}; } @@ -1820,6 +1982,36 @@ Storage::Info::ExportedTypeNames NodeMetaInfo::exportedTypeNamesForSourceId(Sour return {}; } +Storage::Info::TypeHints NodeMetaInfo::typeHints() const +{ + if constexpr (useProjectStorage()) { + if (isValid()) + return m_projectStorage->typeHints(m_typeId); + } + + return {}; +} + +Utils::PathString NodeMetaInfo::iconPath() const +{ + if constexpr (useProjectStorage()) { + if (isValid()) + return m_projectStorage->typeIconPath(m_typeId); + } + + return {}; +} + +Storage::Info::ItemLibraryEntries NodeMetaInfo::itemLibrariesEntries() const +{ + if constexpr (useProjectStorage()) { + if (isValid()) + return m_projectStorage->itemLibraryEntries(m_typeId); + } + + return {}; +} + SourceId NodeMetaInfo::sourceId() const { if constexpr (useProjectStorage()) { @@ -2230,7 +2422,7 @@ bool NodeMetaInfo::isView() const bool NodeMetaInfo::usesCustomParser() const { if constexpr (useProjectStorage()) { - return isValid() && bool(typeData().traits & Storage::TypeTraits::UsesCustomParser); + return isValid() && typeData().traits.usesCustomParser; } else { if (!isValid()) return false; @@ -2408,9 +2600,7 @@ bool NodeMetaInfo::isNumber() const return false; } - auto type = simplifiedTypeName(); - - return type == "int" || type == "uint" || type == "float" || type == "double"; + return isFloat() || isInteger(); } } @@ -2886,7 +3076,7 @@ bool NodeMetaInfo::isInteger() const auto type = simplifiedTypeName(); - return type == "int" || type == "integer"; + return type == "int" || type == "integer" || type == "uint"; } } @@ -2908,7 +3098,7 @@ bool NodeMetaInfo::isFloat() const auto type = simplifiedTypeName(); - return type == "qreal" || type == "double" || type == "float"; + return type == "qreal" || type == "double" || type == "float" || type == "real"; } } @@ -3129,7 +3319,7 @@ bool NodeMetaInfo::isQtQuick3DEffect() const bool NodeMetaInfo::isEnumeration() const { if constexpr (useProjectStorage()) - return isValid() && bool(typeData().traits & Storage::TypeTraits::IsEnum); + return isValid() && typeData().traits.isEnum; return false; } @@ -3402,6 +3592,19 @@ NodeMetaInfo NodeMetaInfo::commonBase(const NodeMetaInfo &metaInfo) const return {}; } +NodeMetaInfo::NodeMetaInfos NodeMetaInfo::heirs() const +{ + if constexpr (useProjectStorage()) { + if (isValid()) { + return Utils::transform(m_projectStorage->heirIds(m_typeId), [&](TypeId typeId) { + return NodeMetaInfo{typeId, m_projectStorage}; + }); + } + } + + return {}; +} + namespace { void addCompoundProperties(CompoundPropertyMetaInfos &inflatedProperties, diff --git a/src/plugins/qmldesigner/designercore/model/abstractview.cpp b/src/plugins/qmldesigner/designercore/model/abstractview.cpp index a982d8dc5e5..ff332ee3b2d 100644 --- a/src/plugins/qmldesigner/designercore/model/abstractview.cpp +++ b/src/plugins/qmldesigner/designercore/model/abstractview.cpp @@ -129,12 +129,6 @@ WidgetInfo AbstractView::createWidgetInfo(QWidget *widget, return widgetInfo; } -// Returns the model of the view. -Model* AbstractView::model() const -{ - return m_model.data(); -} - bool AbstractView::isAttached() const { return model(); diff --git a/src/plugins/qmldesigner/designercore/model/import.cpp b/src/plugins/qmldesigner/designercore/model/import.cpp index 6d4cd19d8b8..f23331edebf 100644 --- a/src/plugins/qmldesigner/designercore/model/import.cpp +++ b/src/plugins/qmldesigner/designercore/model/import.cpp @@ -24,6 +24,11 @@ Import Import::empty() return Import(QString(), QString(), QString(), QStringList(), Type::Empty); } +bool Import::hasVersion() const +{ + return !m_version.isEmpty() && m_version != "-1.-1"; +} + QString Import::toImportString() const { QString result = QStringLiteral("import "); diff --git a/src/plugins/qmldesigner/designercore/model/internalnode_p.h b/src/plugins/qmldesigner/designercore/model/internalnode_p.h index af5b43eec28..7d32b498a09 100644 --- a/src/plugins/qmldesigner/designercore/model/internalnode_p.h +++ b/src/plugins/qmldesigner/designercore/model/internalnode_p.h @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -30,6 +31,7 @@ namespace QmlDesigner { namespace Internal { +using namespace NanotraceHR::Literals; class InternalProperty; class InternalNode; @@ -221,6 +223,8 @@ public: ModuleId moduleId; ImportedTypeNameId importedTypeNameId; TypeId typeId; + NO_UNIQUE_ADDRESS ModelTracing::ObjectTraceToken traceToken = ModelTracing::category().beginObject( + "InternalNode"_t); private: AuxiliaryDatas m_auxiliaryDatas; diff --git a/src/plugins/qmldesigner/designercore/model/internalproperty.h b/src/plugins/qmldesigner/designercore/model/internalproperty.h index 66d0bcaa881..f93b5e85dde 100644 --- a/src/plugins/qmldesigner/designercore/model/internalproperty.h +++ b/src/plugins/qmldesigner/designercore/model/internalproperty.h @@ -5,6 +5,8 @@ #include "qmldesignercorelib_global.h" +#include + #include #include @@ -14,6 +16,8 @@ namespace QmlDesigner { namespace Internal { +using namespace NanotraceHR::Literals; + class InternalBindingProperty; class InternalSignalHandlerProperty; class InternalSignalDeclarationProperty; @@ -84,6 +88,10 @@ struct TypeLookup template using type_lookup_t = typename TypeLookup::Type; +using NanotraceHR::array; +using NanotraceHR::dictonary; +using NanotraceHR::keyValue; + class QMLDESIGNERCORE_EXPORT InternalProperty : public std::enable_shared_from_this { public: @@ -185,6 +193,8 @@ private: TypeName m_dynamicType; std::weak_ptr m_propertyOwner; PropertyType m_propertyType = PropertyType::None; + NO_UNIQUE_ADDRESS ModelTracing::ObjectTraceToken traceToken = ModelTracing::category().beginObject( + "InternalProperty"_t, keyValue("name", m_name)); }; } // namespace Internal diff --git a/src/plugins/qmldesigner/designercore/model/model.cpp b/src/plugins/qmldesigner/designercore/model/model.cpp index 7b5868d6cf5..67001855757 100644 --- a/src/plugins/qmldesigner/designercore/model/model.cpp +++ b/src/plugins/qmldesigner/designercore/model/model.cpp @@ -17,7 +17,10 @@ #include "internalproperty.h" #include "internalsignalhandlerproperty.h" #include "internalvariantproperty.h" -#include "metainfo.h" +#include "itemlibraryentry.h" +#ifndef QDS_USE_PROJECTSTORAGE +# include "metainfo.h" +#endif #include "nodeinstanceview.h" #include "nodemetainfo.h" @@ -43,6 +46,8 @@ #include #include +#include + /*! \defgroup CoreModel */ @@ -84,7 +89,7 @@ ModelPrivate::ModelPrivate(Model *model, m_currentTimelineNode = m_rootInternalNode; if constexpr (useProjectStorage()) - projectStorage->addRefreshCallback(&m_metaInfoRefreshCallback); + projectStorage->addObserver(this); } ModelPrivate::ModelPrivate(Model *model, @@ -108,7 +113,7 @@ ModelPrivate::ModelPrivate(Model *model, m_currentTimelineNode = m_rootInternalNode; if constexpr (useProjectStorage()) - projectStorage->addRefreshCallback(&m_metaInfoRefreshCallback); + projectStorage->addObserver(this); } ModelPrivate::ModelPrivate(Model *model, @@ -132,14 +137,11 @@ ModelPrivate::ModelPrivate(Model *model, ModelPrivate::~ModelPrivate() { if constexpr (useProjectStorage()) - projectStorage->removeRefreshCallback(&m_metaInfoRefreshCallback); + projectStorage->removeObserver(this); }; void ModelPrivate::detachAllViews() { - if constexpr (useProjectStorage()) - projectStorage->removeRefreshCallback(&m_metaInfoRefreshCallback); - for (const QPointer &view : std::as_const(m_viewList)) detachView(view.data(), true); @@ -394,11 +396,6 @@ void ModelPrivate::setTypeId(InternalNode *node, Utils::SmallStringView typeName } } -void ModelPrivate::emitRefreshMetaInfos(const TypeIds &deletedTypeIds) -{ - notifyNodeInstanceViewLast([&](AbstractView *view) { view->refreshMetaInfos(deletedTypeIds); }); -} - void ModelPrivate::handleResourceSet(const ModelResourceSet &resourceSet) { for (const ModelNode &node : resourceSet.removeModelNodes) { @@ -411,6 +408,11 @@ void ModelPrivate::handleResourceSet(const ModelResourceSet &resourceSet) setBindingProperties(resourceSet.setExpressions); } +void ModelPrivate::removedTypeIds(const TypeIds &removedTypeIds) +{ + notifyNodeInstanceViewLast([&](AbstractView *view) { view->refreshMetaInfos(removedTypeIds); }); +} + void ModelPrivate::removeAllSubNodes(const InternalNodePointer &node) { for (const InternalNodePointer &subNode : node->allSubNodes()) @@ -459,6 +461,7 @@ InternalNodePointer ModelPrivate::rootNode() const return m_rootInternalNode; } +#ifndef QDS_USE_PROJECTSTORAGE MetaInfo ModelPrivate::metaInfo() const { return m_metaInfo; @@ -468,12 +471,16 @@ void ModelPrivate::setMetaInfo(const MetaInfo &metaInfo) { m_metaInfo = metaInfo; } +#endif void ModelPrivate::changeNodeId(const InternalNodePointer &node, const QString &id) { + using namespace NanotraceHR::Literals; + const QString oldId = node->id; node->id = id; + node->traceToken.change("id"_t, std::forward_as_tuple("id", id)); if (!oldId.isEmpty()) m_idNodeHash.remove(oldId); if (!id.isEmpty()) @@ -2061,23 +2068,21 @@ void Model::setFileUrl(const QUrl &url) d->setFileUrl(url); } -/*! - \brief Returns list of QML types available within the model. - */ -const MetaInfo Model::metaInfo() const -{ - return d->metaInfo(); -} - bool Model::hasNodeMetaInfo(const TypeName &typeName, int majorVersion, int minorVersion) const { return metaInfo(typeName, majorVersion, minorVersion).isValid(); } +#ifndef QDS_USE_PROJECTSTORAGE +const MetaInfo Model::metaInfo() const +{ + return d->metaInfo(); +} void Model::setMetaInfo(const MetaInfo &metaInfo) { d->setMetaInfo(metaInfo); } +#endif NodeMetaInfo Model::boolMetaInfo() const { @@ -2287,6 +2292,45 @@ NodeMetaInfo Model::qtQuick3DNodeMetaInfo() const } } +NodeMetaInfo Model::qtQuick3DPointLightMetaInfo() const +{ + if constexpr (useProjectStorage()) { + using namespace Storage::Info; + return createNodeMetaInfo(); + } else { + return metaInfo("QtQuick3D.PointLight"); + } +} + +NodeMetaInfo Model::qtQuick3DSpotLightMetaInfo() const +{ + if constexpr (useProjectStorage()) { + using namespace Storage::Info; + return createNodeMetaInfo(); + } else { + return metaInfo("QtQuick3D.SpotLight"); + } +} + +NodeMetaInfo Model::qtQuick3DOrthographicCameraMetaInfo() const +{ + if constexpr (useProjectStorage()) { + using namespace Storage::Info; + return createNodeMetaInfo(); + } else { + return metaInfo("QtQuick3D.OrthographicCamera"); + } +} + +NodeMetaInfo Model::qtQuick3DPerspectiveCameraMetaInfo() const +{ + if constexpr (useProjectStorage()) { + using namespace Storage::Info; + return createNodeMetaInfo(); + } else { + return metaInfo("QtQuick3D.PerspectiveCamera"); + } +} NodeMetaInfo Model::qtQuick3DTextureMetaInfo() const { if constexpr (useProjectStorage()) { @@ -2327,6 +2371,16 @@ NodeMetaInfo Model::qtQuick3DDefaultMaterialMetaInfo() const } } +NodeMetaInfo Model::qtQuick3DDirectionalLightMetaInfo() const +{ + if constexpr (useProjectStorage()) { + using namespace Storage::Info; + return createNodeMetaInfo(); + } else { + return metaInfo("QtQuick3D.DirectionalLight"); + } +} + NodeMetaInfo Model::qtQuick3DPrincipledMaterialMetaInfo() const { if constexpr (useProjectStorage()) { @@ -2407,6 +2461,30 @@ NodeMetaInfo Model::vector4dMetaInfo() const } } +QVarLengthArray Model::metaInfosForModule(Module module) const +{ + if constexpr (useProjectStorage()) { + using namespace Storage::Info; + return Utils::transform>( + d->projectStorage->typeIds(module.id()), [&](TypeId id) { + return NodeMetaInfo{id, d->projectStorage}; + }); + } else { + return {}; + } +} + +QList Model::itemLibraryEntries() const +{ +#ifdef QDS_USE_PROJECTSTORAGE + using namespace Storage::Info; + return toItemLibraryEntries(d->projectStorage->itemLibraryEntries(d->m_sourceId), + *d->projectStorage); +#else + return d->metaInfo().itemLibraryInfo()->entries(); +#endif +} + NodeMetaInfo Model::qtQuickTimelineKeyframeGroupMetaInfo() const { if constexpr (useProjectStorage()) { @@ -2460,13 +2538,12 @@ NodeMetaInfo Model::metaInfo(Module module, Utils::SmallStringView typeName, Sto } } -/*! - \brief Returns list of QML types available within the model. - */ +#ifndef QDS_USE_PROJECTSTORAGE MetaInfo Model::metaInfo() { return d->metaInfo(); } +#endif Module Model::module(Utils::SmallStringView moduleName) { diff --git a/src/plugins/qmldesigner/designercore/model/model_p.h b/src/plugins/qmldesigner/designercore/model/model_p.h index d09e4054ca2..d7743b028e2 100644 --- a/src/plugins/qmldesigner/designercore/model/model_p.h +++ b/src/plugins/qmldesigner/designercore/model/model_p.h @@ -6,7 +6,9 @@ #include "qmldesignercorelib_global.h" #include "abstractview.h" -#include "metainfo.h" +#ifndef QDS_USE_PROJECTSTORAGE +# include "metainfo.h" +#endif #include "modelnode.h" #include "skipiterator.h" @@ -82,7 +84,8 @@ public: {} }; -class ModelPrivate : public QObject +class ModelPrivate : public QObject, + protected ProjectStorageObserver { Q_OBJECT @@ -137,8 +140,10 @@ public: InternalNodePointer rootNode() const; InternalNodePointer findNode(const QString &id) const; +#ifndef QDS_USE_PROJECTSTORAGE MetaInfo metaInfo() const; void setMetaInfo(const MetaInfo &metaInfo); +#endif void attachView(AbstractView *view); void detachView(AbstractView *view, bool notifyView); @@ -303,6 +308,9 @@ public: return m_nodeMetaInfoCache; } +protected: + void removedTypeIds(const TypeIds &removedTypeIds) override; + private: void removePropertyWithoutNotification(InternalProperty *property); void removeAllSubNodes(const InternalNodePointer &node); @@ -317,7 +325,6 @@ private: EnabledViewRange enabledViews() const; ImportedTypeNameId importedTypeNameId(Utils::SmallStringView typeName); void setTypeId(InternalNode *node, Utils::SmallStringView typeName); - void emitRefreshMetaInfos(const TypeIds &deletedTypeIds); public: NotNullPointer projectStorage = nullptr; @@ -325,9 +332,9 @@ public: private: Model *m_model = nullptr; +#ifndef QDS_USE_PROJECTSTORAGE MetaInfo m_metaInfo; - std::function m_metaInfoRefreshCallback{ - [&](const TypeIds &deletedTypeIds) { emitRefreshMetaInfos(deletedTypeIds); }}; +#endif Imports m_imports; Imports m_possibleImportList; Imports m_usedImportList; diff --git a/src/plugins/qmldesigner/designercore/model/modelnode.cpp b/src/plugins/qmldesigner/designercore/model/modelnode.cpp index fe973f04abd..94d431dda6f 100644 --- a/src/plugins/qmldesigner/designercore/model/modelnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/modelnode.cpp @@ -7,6 +7,7 @@ #include "bindingproperty.h" #include "internalnode_p.h" #include "model_p.h" +#include "modelutils.h" #include "nodeabstractproperty.h" #include "nodelistproperty.h" #include "nodeproperty.h" @@ -19,12 +20,15 @@ #include #include +#include #include #include #include #include +#include + namespace QmlDesigner { using namespace QmlDesigner::Internal; @@ -84,44 +88,42 @@ QString ModelNode::validId() return id(); } -static bool idIsQmlKeyWord(const QString &id) +namespace { +bool isQmlKeyWord(QStringView id) { - static const QSet keywords = {"as", "break", "case", "catch", - "continue", "debugger", "default", "delete", - "do", "else", "finally", "for", - "function", "if", "import", "in", - "instanceof", "new", "print", "return", - "switch", "this", "throw", "try", - "typeof", "var", "void", "while", - "with"}; + static constexpr auto keywords = Utils::to_array( + {u"as", u"break", u"case", u"catch", u"continue", u"debugger", + u"default", u"delete", u"do", u"else", u"finally", u"for", + u"function", u"if", u"import", u"in", u"instanceof", u"new", + u"print", u"return", u"switch", u"this", u"throw", u"try", + u"typeof", u"var", u"void", u"while", u"with"}); - return keywords.contains(id); + return std::binary_search(keywords.begin(), keywords.end(), ModelUtils::toStdStringView(id)); } -static bool isIdToAvoid(const QString &id) +bool isIdToAvoid(QStringView id) { - static const QSet ids = {"top", "bottom", "left", "right", - "width", "height", "x", "y", - "opacity", "parent", "item", "flow", - "color", "margin", "padding", "border", - "font", "text", "source", "state", - "visible", "focus", "data", "clip", - "layer", "scale", "enabled", "anchors", - "texture", "shaderInfo", "sprite", "spriteSequence", - "baseState", "rect"}; + static constexpr auto token = Utils::to_array( + {u"anchors", u"baseState", u"border", u"bottom", u"clip", u"color", + u"data", u"enabled", u"flow", u"focus", u"font", u"height", + u"item", u"layer", u"left", u"margin", u"opacity", u"padding", + u"parent", u"rect", u"right", u"scale", u"shaderInfo", u"source", + u"sprite", u"spriteSequence", u"state", u"text", u"texture", u"top", + u"visible", u"width", u"x", u"y"}); - return ids.contains(id); + return std::binary_search(token.begin(), token.end(), ModelUtils::toStdStringView(id)); } -static bool idContainsWrongLetter(const QString &id) +bool idContainsWrongLetter(const QString &id) { static QRegularExpression idExpr(QStringLiteral("^[a-z_][a-zA-Z0-9_]*$")); return !id.contains(idExpr); } +} // namespace bool ModelNode::isValidId(const QString &id) { - return id.isEmpty() || (!idContainsWrongLetter(id) && !idIsQmlKeyWord(id) && !isIdToAvoid(id)); + return id.isEmpty() || (!idContainsWrongLetter(id) && !isQmlKeyWord(id) && !isIdToAvoid(id)); } QString ModelNode::getIdValidityErrorMessage(const QString &id) @@ -138,7 +140,7 @@ QString ModelNode::getIdValidityErrorMessage(const QString &id) if (id.contains(' ')) return QObject::tr("ID cannot include whitespace (%1).").arg(id); - if (idIsQmlKeyWord(id)) + if (isQmlKeyWord(id)) return QObject::tr("%1 is a reserved QML keyword.").arg(id); if (isIdToAvoid(id)) @@ -1366,6 +1368,15 @@ bool ModelNode::isComponent() const QIcon ModelNode::typeIcon() const { +#ifdef QDS_USE_PROJECTSTORAGE + if (!isValid()) + return QIcon(QStringLiteral(":/ItemLibrary/images/item-invalid-icon.png")); + + if (auto iconPath = metaInfo().iconPath(); iconPath.size()) + return QIcon(iconPath.toQString()); + else + return QIcon(QStringLiteral(":/ItemLibrary/images/item-default-icon.png")); +#else if (isValid()) { // if node has no own icon, search for it in the itemlibrary const ItemLibraryInfo *libraryInfo = model()->metaInfo().itemLibraryInfo(); @@ -1379,6 +1390,7 @@ QIcon ModelNode::typeIcon() const } return QIcon(QStringLiteral(":/ItemLibrary/images/item-invalid-icon.png")); +#endif } QString ModelNode::behaviorPropertyName() const diff --git a/src/plugins/qmldesigner/designercore/model/modeltotextmerger.cpp b/src/plugins/qmldesigner/designercore/model/modeltotextmerger.cpp index c57d947f69c..7b2c61f5767 100644 --- a/src/plugins/qmldesigner/designercore/model/modeltotextmerger.cpp +++ b/src/plugins/qmldesigner/designercore/model/modeltotextmerger.cpp @@ -349,45 +349,48 @@ QmlRefactoring::PropertyType ModelToTextMerger::propertyType(const AbstractPrope PropertyNameList ModelToTextMerger::propertyOrder() { - static const PropertyNameList properties = { - PropertyName("id"), - PropertyName("name"), - PropertyName("target"), - PropertyName("property"), - PropertyName("x"), - PropertyName("y"), - PropertyName("width"), - PropertyName("height"), - PropertyName("opacity"), - PropertyName("visible"), - PropertyName("position"), - PropertyName("color"), - PropertyName("radius"), - PropertyName("text"), - PropertyName("elide"), - PropertyName("border.color"), - PropertyName("border.width"), - PropertyName("anchors.verticalCenter"), - PropertyName("anchors.left"), - PropertyName("anchors.right"), - PropertyName("anchors.top"), - PropertyName("anchors.bottom"), - PropertyName("anchors.fill"), - PropertyName("anchors.margins"), - PropertyName("font.letterSpacing"), - PropertyName("font.pixelSize"), - PropertyName("horizontalAlignment"), - PropertyName("verticalAlignment"), - PropertyName("source"), - PropertyName("lineHeight"), - PropertyName("lineHeightMode"), - PropertyName("wrapMode"), - PropertyName(), - PropertyName("states"), - PropertyName("to"), - PropertyName("from"), - PropertyName("transitions") - }; + static const PropertyNameList properties = {PropertyName("id"), + PropertyName("name"), + PropertyName("target"), + PropertyName("property"), + PropertyName("x"), + PropertyName("y"), + PropertyName("width"), + PropertyName("height"), + PropertyName("opacity"), + PropertyName("visible"), + PropertyName("position"), + PropertyName("color"), + PropertyName("radius"), + PropertyName("text"), + PropertyName("elide"), + PropertyName("value"), + PropertyName("border.color"), + PropertyName("border.width"), + PropertyName("anchors.verticalCenter"), + PropertyName("anchors.left"), + PropertyName("anchors.right"), + PropertyName("anchors.top"), + PropertyName("anchors.bottom"), + PropertyName("anchors.fill"), + PropertyName("anchors.margins"), + PropertyName("anchors.leftMargin"), + PropertyName("anchors.rightMargin"), + PropertyName("anchors.topMargin"), + PropertyName("anchors.bottomMargin"), + PropertyName("font.letterSpacing"), + PropertyName("font.pixelSize"), + PropertyName("horizontalAlignment"), + PropertyName("verticalAlignment"), + PropertyName("source"), + PropertyName("lineHeight"), + PropertyName("lineHeightMode"), + PropertyName("wrapMode"), + PropertyName(), + PropertyName("states"), + PropertyName("to"), + PropertyName("from"), + PropertyName("transitions")}; return properties; } diff --git a/src/plugins/qmldesigner/designercore/model/modelutils.h b/src/plugins/qmldesigner/designercore/model/modelutils.h index 3f8ddeab56b..2cef2e1fb26 100644 --- a/src/plugins/qmldesigner/designercore/model/modelutils.h +++ b/src/plugins/qmldesigner/designercore/model/modelutils.h @@ -5,10 +5,15 @@ #include "qmldesignercorelib_global.h" +#include + #include #include +#include + #include +#include namespace QmlDesigner { class PropertyMetaInfo; @@ -41,4 +46,9 @@ QMLDESIGNERCORE_EXPORT QList allModelNodesWithId(AbstractView *view); QMLDESIGNERCORE_EXPORT bool isThisOrAncestorLocked(const ModelNode &node); QMLDESIGNERCORE_EXPORT ModelNode lowestCommonAncestor(Utils::span nodes); +constexpr std::u16string_view toStdStringView(QStringView view) +{ + return {view.utf16(), Utils::usize(view)}; +} + } // namespace QmlDesigner::ModelUtils diff --git a/src/plugins/qmldesigner/designercore/model/propertyparser.cpp b/src/plugins/qmldesigner/designercore/model/propertyparser.cpp index 107cc41b07a..788530c291a 100644 --- a/src/plugins/qmldesigner/designercore/model/propertyparser.cpp +++ b/src/plugins/qmldesigner/designercore/model/propertyparser.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include diff --git a/src/plugins/qmldesigner/designercore/model/qml3dnode.cpp b/src/plugins/qmldesigner/designercore/model/qml3dnode.cpp index 4db2567e36d..12befa942f1 100644 --- a/src/plugins/qmldesigner/designercore/model/qml3dnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/qml3dnode.cpp @@ -5,13 +5,11 @@ #include "auxiliarydataproperties.h" #include "bindingproperty.h" #include "invalidmodelnodeexception.h" -#include "itemlibraryinfo.h" #include "nodehints.h" #include "nodelistproperty.h" #include "qmlanchors.h" #include "qmlchangeset.h" #include "variantproperty.h" -#include #include "plaintexteditmodifier.h" #include "rewriterview.h" diff --git a/src/plugins/qmldesigner/designercore/model/qmlchangeset.cpp b/src/plugins/qmldesigner/designercore/model/qmlchangeset.cpp index 889a5a38650..134a9a91375 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlchangeset.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlchangeset.cpp @@ -6,7 +6,6 @@ #include "rewritertransaction.h" #include "variantproperty.h" #include "abstractview.h" -#include #include diff --git a/src/plugins/qmldesigner/designercore/model/qmlconnections.cpp b/src/plugins/qmldesigner/designercore/model/qmlconnections.cpp index 9c98399cb30..7a63e73d629 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlconnections.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlconnections.cpp @@ -3,7 +3,6 @@ #include "qmlconnections.h" -#include #include #include #include diff --git a/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp b/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp index 175d3291183..0ce155ca6b9 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp @@ -2,14 +2,12 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qmlitemnode.h" -#include #include "nodelistproperty.h" #include "nodehints.h" #include "nodeproperty.h" #include "variantproperty.h" #include "bindingproperty.h" #include "qmlanchors.h" -#include "itemlibraryinfo.h" #include #include diff --git a/src/plugins/qmldesigner/designercore/model/qmlstate.cpp b/src/plugins/qmldesigner/designercore/model/qmlstate.cpp index 7d681071088..55864745a96 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlstate.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlstate.cpp @@ -5,7 +5,6 @@ #include "abstractview.h" #include #include -#include #include #include "bindingproperty.h" #include "qmlchangeset.h" diff --git a/src/plugins/qmldesigner/designercore/model/qmltimeline.cpp b/src/plugins/qmldesigner/designercore/model/qmltimeline.cpp index fc0e2d4f988..ae0f675d793 100644 --- a/src/plugins/qmldesigner/designercore/model/qmltimeline.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmltimeline.cpp @@ -9,7 +9,6 @@ #include #include -#include #include #include diff --git a/src/plugins/qmldesigner/designercore/model/qmltimelinekeyframegroup.cpp b/src/plugins/qmldesigner/designercore/model/qmltimelinekeyframegroup.cpp index d40616a0226..7cee6095bb4 100644 --- a/src/plugins/qmldesigner/designercore/model/qmltimelinekeyframegroup.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmltimelinekeyframegroup.cpp @@ -8,7 +8,6 @@ #include #include -#include #include #include diff --git a/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp b/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp index 6ccab365bd1..75db18b5a22 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp @@ -2,14 +2,13 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qmlvisualnode.h" -#include -#include "qmlchangeset.h" -#include "nodelistproperty.h" -#include "nodehints.h" -#include "variantproperty.h" #include "bindingproperty.h" +#include "itemlibraryentry.h" +#include "nodehints.h" +#include "nodelistproperty.h" #include "qmlanchors.h" -#include "itemlibraryinfo.h" +#include "qmlchangeset.h" +#include "variantproperty.h" #include diff --git a/src/plugins/qmldesigner/designercore/model/rewriterview.cpp b/src/plugins/qmldesigner/designercore/model/rewriterview.cpp index 68b6426b8fa..4ae2261e604 100644 --- a/src/plugins/qmldesigner/designercore/model/rewriterview.cpp +++ b/src/plugins/qmldesigner/designercore/model/rewriterview.cpp @@ -892,6 +892,8 @@ void RewriterView::handleLibraryInfoUpdate() if (isAttached() && !m_modelAttachPending && !debugQmlPuppet(externalDependencies().designerSettings())) m_amendTimer.start(); + + emitCustomNotification(UpdateItemlibrary); } void RewriterView::handleProjectUpdate() diff --git a/src/plugins/qmldesigner/designercore/model/stylesheetmerger.cpp b/src/plugins/qmldesigner/designercore/model/stylesheetmerger.cpp index 90891cbc8c1..c4b4eb8c09c 100644 --- a/src/plugins/qmldesigner/designercore/model/stylesheetmerger.cpp +++ b/src/plugins/qmldesigner/designercore/model/stylesheetmerger.cpp @@ -182,6 +182,7 @@ ModelNode StylesheetMerger::createReplacementNode(const ModelNode& styleNode, Mo syncId(newNode, modelNode); syncNodeProperties(newNode, modelNode); syncNodeListProperties(newNode, modelNode); + mergeStates(newNode, modelNode); return newNode; } @@ -402,6 +403,80 @@ void StylesheetMerger::parseTemplateOptions() } } +void StylesheetMerger::syncStateNode(ModelNode &outputState, const ModelNode &inputState) const +{ + auto addProperty = [](ModelNode &n, const AbstractProperty &p) { + if (n.hasProperty(p.name())) + return; // Do not ovewrite. Only merge when property not defined in template. + if (p.isBindingProperty()) + n.bindingProperty(p.name()).setExpression(p.toBindingProperty().expression()); + else + n.variantProperty(p.name()).setValue(p.toVariantProperty().value()); + }; + + addProperty(outputState, inputState.property("when")); + addProperty(outputState, inputState.property("extend")); + + auto changeSetKey = [](const ModelNode &n) { + return QString("%1::%2").arg(QString::fromUtf8(n.type()), + n.bindingProperty("target").expression()); + }; + + // Collect change sets already defined in the output state. + std::map outputChangeSets; + for (ModelNode propChange : outputState.directSubModelNodes()) + outputChangeSets.insert({changeSetKey(propChange), propChange}); + + // Merge the child nodes of the states i.e. AnchorChanges, PropertyChanges, etc. + for (ModelNode inputChangeset : inputState.directSubModelNodes()) { + const QString key = changeSetKey(inputChangeset); + const auto itr = outputChangeSets.find(key); + ModelNode changeSet; + if (itr != outputChangeSets.end()) { + changeSet = itr->second; + } else { + const QByteArray typeName = inputChangeset.type(); + NodeMetaInfo metaInfo = m_templateView->model()->metaInfo(typeName); + int major = metaInfo.majorVersion(); + int minor = metaInfo.minorVersion(); + changeSet = m_templateView->createModelNode(typeName, major, minor); + outputState.nodeListProperty("changes").reparentHere(changeSet); + outputChangeSets.insert({key, changeSet}); + } + + for (const auto &p : inputChangeset.properties()) + addProperty(changeSet, p); + } +} + +void StylesheetMerger::mergeStates(ModelNode &outputNode, const ModelNode &inputNode) const +{ + QMap outputStates; + for (auto stateNode : outputNode.nodeListProperty("states").toModelNodeList()) { + const QString name = stateNode.variantProperty("name").value().toString(); + if (name.isEmpty()) + continue; + outputStates[name] = stateNode; + } + + for (auto inputStateNode : inputNode.nodeListProperty("states").toModelNodeList()) { + const QString name = inputStateNode.variantProperty("name").value().toString(); + try { + if (outputStates.contains(name)) { + syncStateNode(outputStates[name], inputStateNode); + continue; + } + ModelMerger merger(m_templateView); + ModelNode stateClone = merger.insertModel(inputStateNode); + if (stateClone.isValid()) + outputNode.nodeListProperty("states").reparentHere(stateClone); + } catch (Exception &e) { + qDebug().noquote() << "Exception while merging states."; + e.createWarning(); + } + } +} + void StylesheetMerger::merge() { ModelNode templateRootNode = m_templateView->rootModelNode(); @@ -422,6 +497,8 @@ void StylesheetMerger::merge() return; } + mergeStates(templateRootNode, styleRootNode); + QQueue replacementNodes; QList directRootSubNodes = styleRootNode.directSubModelNodes(); diff --git a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp index def00f395d3..676011bdc57 100644 --- a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp +++ b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -37,6 +38,7 @@ #include #include +#include #include #include @@ -71,50 +73,53 @@ bool isSupportedVersion(QmlDesigner::Version version) return version.minor <= 15; if (version.major == 6) - return version.minor <= 5; + return version.minor <= 6; return false; } bool isGlobalQtEnums(QStringView value) { - static constexpr QLatin1StringView list[] = { - "Horizontal"_L1, "Vertical"_L1, "AlignVCenter"_L1, "AlignLeft"_L1, - "LeftToRight"_L1, "RightToLeft"_L1, "AlignHCenter"_L1, "AlignRight"_L1, - "AlignBottom"_L1, "AlignBaseline"_L1, "AlignTop"_L1, "BottomLeft"_L1, - "LeftEdge"_L1, "RightEdge"_L1, "BottomEdge"_L1, "TopEdge"_L1, - "TabFocus"_L1, "ClickFocus"_L1, "StrongFocus"_L1, "WheelFocus"_L1, - "NoFocus"_L1, "ArrowCursor"_L1, "UpArrowCursor"_L1, "CrossCursor"_L1, - "WaitCursor"_L1, "IBeamCursor"_L1, "SizeVerCursor"_L1, "SizeHorCursor"_L1, - "SizeBDiagCursor"_L1, "SizeFDiagCursor"_L1, "SizeAllCursor"_L1, "BlankCursor"_L1, - "SplitVCursor"_L1, "SplitHCursor"_L1, "PointingHandCursor"_L1, "ForbiddenCursor"_L1, - "WhatsThisCursor"_L1, "BusyCursor"_L1, "OpenHandCursor"_L1, "ClosedHandCursor"_L1, - "DragCopyCursor"_L1, "DragMoveCursor"_L1, "DragLinkCursor"_L1, "TopToBottom"_L1, - "LeftButton"_L1, "RightButton"_L1, "MiddleButton"_L1, "BackButton"_L1, - "ForwardButton"_L1, "AllButtons"_L1}; + static constexpr auto list = Utils::to_array( + {u"AlignBaseline", u"AlignBottom", u"AlignHCenter", u"AlignLeft", + u"AlignRight", u"AlignTop", u"AlignVCenter", u"AllButtons", + u"ArrowCursor", u"BackButton", u"BlankCursor", u"BottomEdge", + u"BottomLeft", u"BusyCursor", u"ClickFocus", u"ClosedHandCursor", + u"CrossCursor", u"DragCopyCursor", u"DragLinkCursor", u"DragMoveCursor", + u"ForbiddenCursor", u"ForwardButton", u"Horizontal", u"IBeamCursor", + u"LeftButton", u"LeftEdge", u"LeftToRight", u"MiddleButton", + u"NoFocus", u"OpenHandCursor", u"PointingHandCursor", u"RightButton", + u"RightEdge", u"RightToLeft", u"SizeAllCursor", u"SizeBDiagCursor", + u"SizeFDiagCursor", u"SizeHorCursor", u"SizeVerCursor", u"SplitHCursor", + u"SplitVCursor", u"StrongFocus", u"TabFocus", u"TopEdge", + u"TopToBottom", u"UpArrowCursor", u"Vertical", u"WaitCursor", + u"WhatsThisCursor", u"WheelFocus"}); - return std::find(std::begin(list), std::end(list), value) != std::end(list); + return std::binary_search(std::begin(list), + std::end(list), + QmlDesigner::ModelUtils::toStdStringView(value)); } bool isKnownEnumScopes(QStringView value) { - static constexpr QLatin1StringView list[] = {"TextInput"_L1, - "TextEdit"_L1, - "Material"_L1, - "Universal"_L1, - "Font"_L1, - "Shape"_L1, - "ShapePath"_L1, - "AbstractButton"_L1, - "Text"_L1, - "ShaderEffectSource"_L1, - "Grid"_L1, - "ItemLayer"_L1, - "ImageLayer"_L1, - "SpriteLayer"_L1, - "Light"_L1}; + static constexpr auto list = Utils::to_array({u"TextInput", + u"TextEdit", + u"Material", + u"Universal", + u"Font", + u"Shape", + u"ShapePath", + u"AbstractButton", + u"Text", + u"ShaderEffectSource", + u"Grid", + u"ItemLayer", + u"ImageLayer", + u"SpriteLayer", + u"Light"}); - return std::find(std::begin(list), std::end(list), value) != std::end(list); + return std::find(std::begin(list), std::end(list), QmlDesigner::ModelUtils::toStdStringView(value)) + != std::end(list); } bool supportedQtQuickVersion(const QmlDesigner::Import &import) @@ -1235,8 +1240,14 @@ void TextToModelMerger::syncNode(ModelNode &modelNode, return; } - int majorVersion = info.majorVersion(); - int minorVersion = info.minorVersion(); + int majorVersion = -1; + int minorVersion = -1; + + if constexpr (!useProjectStorage()) { + typeName = info.typeName(); + majorVersion = info.majorVersion(); + minorVersion = info.minorVersion(); + } if (modelNode.isRootNode() && !m_rewriterView->allowComponentRoot() && info.isQmlComponent()) { for (AST::UiObjectMemberList *iter = astInitializer->members; iter; iter = iter->next) { diff --git a/src/plugins/qmldesigner/designercore/projectstorage/commontypecache.h b/src/plugins/qmldesigner/designercore/projectstorage/commontypecache.h index e7b41c559e3..41f894356ab 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/commontypecache.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/commontypecache.h @@ -38,6 +38,7 @@ inline constexpr char Control[] = "Control"; inline constexpr char CubeMapTexture[] = "CubeMapTexture"; inline constexpr char DefaultMaterial[] = "DefaultMaterial"; inline constexpr char Dialog[] = "Dialog"; +inline constexpr char DirectionalLight[] = "DirectionalLight"; inline constexpr char DoubleType[] = "double"; inline constexpr char Effect[] = "Effect"; inline constexpr char FloatType[] = "float"; @@ -52,12 +53,12 @@ inline constexpr char GroupItem[] = "GroupItem"; inline constexpr char Image[] = "Image"; inline constexpr char InstanceListEntry[] = "InstanceListEntry"; inline constexpr char InstanceList[] = "InstanceList"; -inline constexpr char Light[] = "Light"; inline constexpr char IntType[] = "int"; inline constexpr char Item[] = "Item"; inline constexpr char KeyframeGroup[] = "KeyframeGroup"; inline constexpr char Keyframe[] = "Keyframe"; inline constexpr char Layout[] = "Layout"; +inline constexpr char Light[] = "Light"; inline constexpr char ListElement[] = "ListElement"; inline constexpr char ListModel[] = "ListModel"; inline constexpr char ListView[] = "ListView"; @@ -66,17 +67,19 @@ inline constexpr char Material[] = "Material"; inline constexpr char Model[] = "Model"; inline constexpr char MouseArea[] = "MouseArea"; inline constexpr char Node[] = "Node"; +inline constexpr char OrthographicCamera[] = "OrthographicCamera"; inline constexpr char Particle3D[] = "Particle3D"; inline constexpr char ParticleEmitter3D[] = "ParticleEmitter3D"; inline constexpr char Pass[] = "Pass"; inline constexpr char PathView[] = "PathView"; inline constexpr char Path[] = "Path"; inline constexpr char PauseAnimation[] = "PauseAnimation"; +inline constexpr char PerspectiveCamera[] = "PerspectiveCamera"; inline constexpr char Picture[] = "Picture"; +inline constexpr char PointLight[] = "PointLight"; inline constexpr char Popup[] = "Popup"; inline constexpr char Positioner[] = "Positioner"; inline constexpr char PrincipledMaterial[] = "PrincipledMaterial"; -inline constexpr char SpecularGlossyMaterial[] = "SpecularGlossyMaterial"; inline constexpr char PropertyAnimation[] = "PropertyAnimation"; inline constexpr char PropertyChanges[] = "PropertyChanges"; inline constexpr char QML[] = "QML"; @@ -91,7 +94,6 @@ inline constexpr char QtQuick3D[] = "QtQuick3D"; inline constexpr char QtQuick3D_Particles3D[] = "QtQuick3D.Particles3D"; inline constexpr char QtQuick3D_Particles3D_cppnative[] = "QtQuick3D.Particles3D-cppnative"; inline constexpr char QtQuick[] = "QtQuick"; -inline constexpr char QtQuick_cppnative[] = "QtQuick-cppnative"; inline constexpr char QtQuick_Controls[] = "QtQuick.Controls"; inline constexpr char QtQuick_Dialogs[] = "QtQuick.Dialogs"; inline constexpr char QtQuick_Extras[] = "QtQuick.Extras"; @@ -100,6 +102,7 @@ inline constexpr char QtQuick_Studio_Components[] = "QtQuick.Studio.Components"; inline constexpr char QtQuick_Templates[] = "QtQuick.Templates"; inline constexpr char QtQuick_Timeline[] = "QtQuick.Timeline"; inline constexpr char QtQuick_Window[] = "QtQuick.Window"; +inline constexpr char QtQuick_cppnative[] = "QtQuick-cppnative"; inline constexpr char Qt_SafeRenderer[] = "Qt.SafeRenderer"; inline constexpr char Rectangle[] = "Rectangle"; inline constexpr char Repeater[] = "Repeater"; @@ -108,7 +111,9 @@ inline constexpr char SafeRendererPicture[] = "SafeRendererPicture"; inline constexpr char SceneEnvironment[] = "SceneEnvironment"; inline constexpr char Shader[] = "Shader"; inline constexpr char SoundEffect[] = "SoundEffect"; +inline constexpr char SpecularGlossyMaterial[] = "SpecularGlossyMaterial"; inline constexpr char SplitView[] = "SplitView"; +inline constexpr char SpotLight[] = "SpotLight"; inline constexpr char SpriteParticle3D[] = "SpriteParticle3D"; inline constexpr char StateGroup[] = "StateGroup"; inline constexpr char State[] = "State"; @@ -201,6 +206,7 @@ class CommonTypeCache CacheType, CacheType, CacheType, + CacheType, CacheType, CacheType, CacheType, @@ -208,11 +214,15 @@ class CommonTypeCache CacheType, CacheType, CacheType, + CacheType, CacheType, + CacheType, + CacheType, CacheType, CacheType, - CacheType, CacheType, + CacheType, + CacheType, CacheType, CacheType, CacheType, @@ -262,6 +272,12 @@ public: updateTypeIdsWithoutProperties(); } + void clearForTestsOnly() + { + std::apply([](auto &...type) { ((type.typeId = QmlDesigner::TypeId{}), ...); }, m_types); + std::fill(std::begin(m_typesWithoutProperties), std ::end(m_typesWithoutProperties), TypeId{}); + } + template TypeId typeId() const { diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.cpp b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.cpp index 2bef2244d70..700a85bc112 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.cpp +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.cpp @@ -3,6 +3,23 @@ #include "projectstorage.h" +#include + #include +namespace QmlDesigner { + +namespace { + +thread_local NanotraceHR::StringViewCategory projectStorageCategory_{ + "project storage"_t, Tracing::eventQueue(), projectStorageCategory}; +} + +NanotraceHR::StringViewCategory &projectStorageCategory() +{ + return projectStorageCategory_; +} + +} // namespace QmlDesigner + template class QmlDesigner::ProjectStorage; diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h index cc4de52663e..6244977ec79 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h @@ -26,6 +26,19 @@ namespace QmlDesigner { +using namespace NanotraceHR::Literals; + +constexpr NanotraceHR::Tracing projectStorageTracingStatus() +{ +#ifdef ENABLE_PROJECT_STORAGE_TRACING + return NanotraceHR::tracingStatus(); +#else + return NanotraceHR::Tracing::IsDisabled; +#endif +} + +NanotraceHR::StringViewCategory &projectStorageCategory(); + template class ProjectStorage final : public ProjectStorageInterface { @@ -44,6 +57,8 @@ public: , exclusiveTransaction{database} , initializer{database, isInitialized} { + NanotraceHR::Tracer tracer{"initialize"_t, projectStorageCategory()}; + exclusiveTransaction.commit(); database.walCheckpointFull(); @@ -53,6 +68,8 @@ public: void synchronize(Storage::Synchronization::SynchronizationPackage package) override { + NanotraceHR::Tracer tracer{"synchronize"_t, projectStorageCategory()}; + TypeIds deletedTypeIds; Sqlite::withImmediateTransaction(database, [&] { AliasPropertyDeclarations insertedAliasPropertyDeclarations; @@ -86,6 +103,8 @@ public: relinkablePrototypes, relinkableExtensions, package.updatedSourceIds); + synchronizeTypeAnnotations(package.typeAnnotations, + package.updatedTypeAnnotationSourceIds); synchronizePropertyEditorQmlPaths(package.propertyEditorQmlPaths, package.updatedPropertyEditorQmlPathSourceIds); @@ -116,6 +135,8 @@ public: void synchronizeDocumentImports(Storage::Imports imports, SourceId sourceId) override { + NanotraceHR::Tracer tracer{"synchronize document imports"_t, projectStorageCategory()}; + Sqlite::withImmediateTransaction(database, [&] { synchronizeDocumentImports(imports, {sourceId}, @@ -123,24 +144,24 @@ public: }); } - void addRefreshCallback(std::function *callback) override - { - m_refreshCallbacks.push_back(callback); - } + void addObserver(ProjectStorageObserver *observer) override { observers.push_back(observer); } - void removeRefreshCallback(std::function *callback) override + void removeObserver(ProjectStorageObserver *observer) override { - m_refreshCallbacks.erase( - std::find(m_refreshCallbacks.begin(), m_refreshCallbacks.end(), callback)); + observers.removeOne(observer); } ModuleId moduleId(Utils::SmallStringView moduleName) const override { + NanotraceHR::Tracer tracer{"get module id"_t, projectStorageCategory()}; + return moduleCache.id(moduleName); } Utils::SmallString moduleName(ModuleId moduleId) const { + NanotraceHR::Tracer tracer{"get module name"_t, projectStorageCategory()}; + if (!moduleId) throw ModuleDoesNotExists{}; @@ -151,6 +172,8 @@ public: Utils::SmallStringView exportedTypeName, Storage::Version version) const override { + NanotraceHR::Tracer tracer{"get type id by exported name"_t, projectStorageCategory()}; + if (version.minor) return selectTypeIdByModuleIdAndExportedNameAndVersionStatement .template valueWithTransaction(moduleId, @@ -168,11 +191,23 @@ public: TypeId typeId(ImportedTypeNameId typeNameId) const override { + NanotraceHR::Tracer tracer{"get type id by imported type name"_t, projectStorageCategory()}; + return Sqlite::withDeferredTransaction(database, [&] { return fetchTypeId(typeNameId); }); } + QVarLengthArray typeIds(ModuleId moduleId) const override + { + NanotraceHR::Tracer tracer{"get type ids by module id"_t, projectStorageCategory()}; + + return selectTypeIdsByModuleIdStatement + .template valuesWithTransaction>(moduleId); + } + Storage::Info::ExportedTypeNames exportedTypeNames(TypeId typeId) const override { + NanotraceHR::Tracer tracer{"get exported type names by type id"_t, projectStorageCategory()}; + return selectExportedTypesByTypeIdStatement .template valuesWithTransaction(typeId); } @@ -180,12 +215,16 @@ public: Storage::Info::ExportedTypeNames exportedTypeNames(TypeId typeId, SourceId sourceId) const override { + NanotraceHR::Tracer tracer{"get exported type names by source id"_t, projectStorageCategory()}; + return selectExportedTypesByTypeIdAndSourceIdStatement .template valuesWithTransaction(typeId, sourceId); } ImportId importId(const Storage::Import &import) const override { + NanotraceHR::Tracer tracer{"get import id by import"_t, projectStorageCategory()}; + return Sqlite::withDeferredTransaction(database, [&] { return fetchImportId(import.sourceId, import); }); @@ -194,6 +233,9 @@ public: ImportedTypeNameId importedTypeNameId(ImportId importId, Utils::SmallStringView typeName) override { + NanotraceHR::Tracer tracer{"get imported type name id by import id"_t, + projectStorageCategory()}; + return Sqlite::withDeferredTransaction(database, [&] { return fetchImportedTypeNameId(Storage::Synchronization::TypeNameKind::QualifiedExported, importId, @@ -204,6 +246,9 @@ public: ImportedTypeNameId importedTypeNameId(SourceId sourceId, Utils::SmallStringView typeName) override { + NanotraceHR::Tracer tracer{"get imported type name id by source id"_t, + projectStorageCategory()}; + return Sqlite::withDeferredTransaction(database, [&] { return fetchImportedTypeNameId(Storage::Synchronization::TypeNameKind::Exported, sourceId, @@ -213,12 +258,16 @@ public: QVarLengthArray propertyDeclarationIds(TypeId typeId) const override { + NanotraceHR::Tracer tracer{"get property declaration ids"_t, projectStorageCategory()}; + return selectPropertyDeclarationIdsForTypeStatement .template valuesWithTransaction>(typeId); } QVarLengthArray localPropertyDeclarationIds(TypeId typeId) const override { + NanotraceHR::Tracer tracer{"get local property declaration ids"_t, projectStorageCategory()}; + return selectLocalPropertyDeclarationIdsForTypeStatement .template valuesWithTransaction>(typeId); } @@ -226,6 +275,8 @@ public: PropertyDeclarationId propertyDeclarationId(TypeId typeId, Utils::SmallStringView propertyName) const override { + NanotraceHR::Tracer tracer{"get property declaration id"_t, projectStorageCategory()}; + return selectPropertyDeclarationIdForTypeAndPropertyNameStatement .template valueWithTransaction(typeId, propertyName); } @@ -233,6 +284,8 @@ public: PropertyDeclarationId localPropertyDeclarationId(TypeId typeId, Utils::SmallStringView propertyName) const { + NanotraceHR::Tracer tracer{"get local property declaration id"_t, projectStorageCategory()}; + return selectLocalPropertyDeclarationIdForTypeAndPropertyNameStatement .template valueWithTransaction(typeId, propertyName); } @@ -240,6 +293,8 @@ public: std::optional propertyDeclaration( PropertyDeclarationId propertyDeclarationId) const override { + NanotraceHR::Tracer tracer{"get property declaration"_t, projectStorageCategory()}; + return selectPropertyDeclarationForPropertyDeclarationIdStatement .template optionalValueWithTransaction( propertyDeclarationId); @@ -247,24 +302,135 @@ public: std::optional type(TypeId typeId) const override { + NanotraceHR::Tracer tracer{"get type"_t, projectStorageCategory()}; + return selectInfoTypeByTypeIdStatement.template optionalValueWithTransaction( typeId); } + Utils::PathString typeIconPath(TypeId typeId) const override + { + NanotraceHR::Tracer tracer{"get type icon path"_t, projectStorageCategory()}; + + return selectTypeIconPathStatement.template valueWithTransaction(typeId); + } + + Storage::Info::TypeHints typeHints(TypeId typeId) const override + { + NanotraceHR::Tracer tracer{"get type hints"_t, projectStorageCategory()}; + + return selectTypeHintsStatement.template valuesWithTransaction( + typeId); + } + + Storage::Info::ItemLibraryEntries itemLibraryEntries(TypeId typeId) const override + { + NanotraceHR::Tracer tracer{"get item library entries by type id"_t, projectStorageCategory()}; + + using Storage::Info::ItemLibraryProperties; + Storage::Info::ItemLibraryEntries entries; + + auto callback = [&](TypeId typeId_, + Utils::SmallStringView name, + Utils::SmallStringView iconPath, + Utils::SmallStringView category, + Utils::SmallStringView import, + Utils::SmallStringView toolTip, + Utils::SmallStringView properties, + Utils::SmallStringView extraFilePaths, + Utils::SmallStringView templatePath) { + auto &last = entries.emplace_back( + typeId_, name, iconPath, category, import, toolTip, templatePath); + if (properties.size()) + selectItemLibraryPropertiesStatement.readTo(last.properties, properties); + if (extraFilePaths.size()) + selectItemLibraryExtraFilePathsStatement.readTo(last.extraFilePaths, extraFilePaths); + }; + + selectItemLibraryEntriesByTypeIdStatement.readCallbackWithTransaction(callback, typeId); + + return entries; + } + + Storage::Info::ItemLibraryEntries itemLibraryEntries(SourceId sourceId) const override + { + NanotraceHR::Tracer tracer{"get item library entries by source id"_t, + projectStorageCategory()}; + + using Storage::Info::ItemLibraryProperties; + Storage::Info::ItemLibraryEntries entries; + + auto callback = [&](TypeId typeId, + Utils::SmallStringView name, + Utils::SmallStringView iconPath, + Utils::SmallStringView category, + Utils::SmallStringView import, + Utils::SmallStringView toolTip, + Utils::SmallStringView properties, + Utils::SmallStringView extraFilePaths, + Utils::SmallStringView templatePath) { + auto &last = entries.emplace_back( + typeId, name, iconPath, category, import, toolTip, templatePath); + if (properties.size()) + selectItemLibraryPropertiesStatement.readTo(last.properties, properties); + if (extraFilePaths.size()) + selectItemLibraryExtraFilePathsStatement.readTo(last.extraFilePaths, extraFilePaths); + }; + + selectItemLibraryEntriesBySourceIdStatement.readCallbackWithTransaction(callback, sourceId); + + return entries; + } + + Storage::Info::ItemLibraryEntries allItemLibraryEntries() const override + { + NanotraceHR::Tracer tracer{"get all item library entries"_t, projectStorageCategory()}; + + using Storage::Info::ItemLibraryProperties; + Storage::Info::ItemLibraryEntries entries; + + auto callback = [&](TypeId typeId, + Utils::SmallStringView name, + Utils::SmallStringView iconPath, + Utils::SmallStringView category, + Utils::SmallStringView import, + Utils::SmallStringView toolTip, + Utils::SmallStringView properties, + Utils::SmallStringView extraFilePaths, + Utils::SmallStringView templatePath) { + auto &last = entries.emplace_back( + typeId, name, iconPath, category, import, toolTip, templatePath); + if (properties.size()) + selectItemLibraryPropertiesStatement.readTo(last.properties, properties); + if (extraFilePaths.size()) + selectItemLibraryExtraFilePathsStatement.readTo(last.extraFilePaths, extraFilePaths); + }; + + selectItemLibraryEntriesStatement.readCallbackWithTransaction(callback); + + return entries; + } + std::vector signalDeclarationNames(TypeId typeId) const override { + NanotraceHR::Tracer tracer{"get signal names"_t, projectStorageCategory()}; + return selectSignalDeclarationNamesForTypeStatement .template valuesWithTransaction(typeId); } std::vector functionDeclarationNames(TypeId typeId) const override { + NanotraceHR::Tracer tracer{"get function names"_t, projectStorageCategory()}; + return selectFuncionDeclarationNamesForTypeStatement .template valuesWithTransaction(typeId); } std::optional propertyName(PropertyDeclarationId propertyDeclarationId) const override { + NanotraceHR::Tracer tracer{"get property name"_t, projectStorageCategory()}; + return selectPropertyNameStatement.template optionalValueWithTransaction( propertyDeclarationId); } @@ -277,36 +443,57 @@ public: template TypeId commonTypeId() const { + NanotraceHR::Tracer tracer{"get type id from common type cache"_t, projectStorageCategory()}; + return commonTypeCache_.template typeId(); } template TypeId builtinTypeId() const { + NanotraceHR::Tracer tracer{"get builtin type id from common type cache"_t, + projectStorageCategory()}; + return commonTypeCache_.template builtinTypeId(); } template TypeId builtinTypeId() const { + NanotraceHR::Tracer tracer{"get builtin type id from common type cache"_t, + projectStorageCategory()}; + return commonTypeCache_.template builtinTypeId(); } TypeIds prototypeIds(TypeId type) const override { + NanotraceHR::Tracer tracer{"get prototypes"_t, projectStorageCategory()}; + return selectPrototypeIdsForTypeIdInOrderStatement.template valuesWithTransaction( type); } TypeIds prototypeAndSelfIds(TypeId type) const override { + NanotraceHR::Tracer tracer{"get prototypes and self"_t, projectStorageCategory()}; + return selectPrototypeAndSelfIdsForTypeIdInOrderStatement .template valuesWithTransaction(type); } + TypeIds heirIds(TypeId typeId) const override + { + NanotraceHR::Tracer tracer{"get heirs"_t, projectStorageCategory()}; + + return selectHeirTypeIdsStatement.template valuesWithTransaction(typeId); + } + template bool isBasedOn_(TypeId typeId, TypeIds... baseTypeIds) const { + NanotraceHR::Tracer tracer{"is based on"_t, projectStorageCategory()}; + static_assert(((std::is_same_v) &&...), "Parameter must be a TypeId!"); if (((typeId == baseTypeIds) || ...)) @@ -366,6 +553,8 @@ public: TypeId fetchTypeIdByExportedName(Utils::SmallStringView name) const { + NanotraceHR::Tracer tracer{"is based on"_t, projectStorageCategory()}; + return selectTypeIdByExportedNameStatement.template valueWithTransaction(name); } @@ -426,6 +615,8 @@ public: SourceContextId fetchSourceContextIdUnguarded(Utils::SmallStringView sourceContextPath) { + NanotraceHR::Tracer tracer{"fetch source context id unguarded"_t, projectStorageCategory()}; + auto sourceContextId = readSourceContextId(sourceContextPath); return sourceContextId ? sourceContextId : writeSourceContextId(sourceContextPath); @@ -433,6 +624,8 @@ public: SourceContextId fetchSourceContextId(Utils::SmallStringView sourceContextPath) { + NanotraceHR::Tracer tracer{"fetch source context id"_t, projectStorageCategory()}; + try { return Sqlite::withDeferredTransaction(database, [&] { return fetchSourceContextIdUnguarded(sourceContextPath); @@ -444,6 +637,8 @@ public: Utils::PathString fetchSourceContextPath(SourceContextId sourceContextId) const { + NanotraceHR::Tracer tracer{"fetch source context path"_t, projectStorageCategory()}; + return Sqlite::withDeferredTransaction(database, [&] { auto optionalSourceContextPath = selectSourceContextPathFromSourceContextsBySourceContextIdStatement .template optionalValue( @@ -458,12 +653,16 @@ public: auto fetchAllSourceContexts() const { + NanotraceHR::Tracer tracer{"fetch all source contexts"_t, projectStorageCategory()}; + return selectAllSourceContextsStatement .template valuesWithTransaction(); } SourceId fetchSourceId(SourceContextId sourceContextId, Utils::SmallStringView sourceName) { + NanotraceHR::Tracer tracer{"fetch source id"_t, projectStorageCategory()}; + return Sqlite::withDeferredTransaction(database, [&] { return fetchSourceIdUnguarded(sourceContextId, sourceName); }); @@ -471,6 +670,9 @@ public: auto fetchSourceNameAndSourceContextId(SourceId sourceId) const { + NanotraceHR::Tracer tracer{"fetch source name and source context id"_t, + projectStorageCategory()}; + auto value = selectSourceNameAndSourceContextIdFromSourcesBySourceIdStatement .template valueWithTransaction(sourceId); @@ -490,6 +692,8 @@ public: SourceContextId fetchSourceContextId(SourceId sourceId) const { + NanotraceHR::Tracer tracer{"fetch source context id"_t, projectStorageCategory()}; + auto sourceContextId = selectSourceContextIdFromSourcesBySourceIdStatement .template valueWithTransaction(sourceId); @@ -501,11 +705,15 @@ public: auto fetchAllSources() const { + NanotraceHR::Tracer tracer{"fetch all sources"_t, projectStorageCategory()}; + return selectAllSourcesStatement.template valuesWithTransaction(); } SourceId fetchSourceIdUnguarded(SourceContextId sourceContextId, Utils::SmallStringView sourceName) { + NanotraceHR::Tracer tracer{"fetch source id unguarded"_t, projectStorageCategory()}; + auto sourceId = readSourceId(sourceContextId, sourceName); if (sourceId) @@ -516,23 +724,31 @@ public: auto fetchAllFileStatuses() const { + NanotraceHR::Tracer tracer{"fetch all file statuses"_t, projectStorageCategory()}; + return selectAllFileStatusesStatement.template rangeWithTransaction(); } FileStatus fetchFileStatus(SourceId sourceId) const override { + NanotraceHR::Tracer tracer{"fetch file status"_t, projectStorageCategory()}; + return selectFileStatusesForSourceIdStatement.template valueWithTransaction( sourceId); } std::optional fetchProjectData(SourceId sourceId) const override { + NanotraceHR::Tracer tracer{"fetch project data"_t, projectStorageCategory()}; + return selectProjectDataForSourceIdStatement .template optionalValueWithTransaction(sourceId); } Storage::Synchronization::ProjectDatas fetchProjectDatas(SourceId projectSourceId) const override { + NanotraceHR::Tracer tracer{"fetch project datas by source id"_t, projectStorageCategory()}; + return selectProjectDatasForSourceIdStatement .template valuesWithTransaction( projectSourceId); @@ -540,6 +756,8 @@ public: Storage::Synchronization::ProjectDatas fetchProjectDatas(const SourceIds &projectSourceIds) const { + NanotraceHR::Tracer tracer{"fetch project datas by source ids"_t, projectStorageCategory()}; + return selectProjectDatasForSourceIdsStatement .template valuesWithTransaction( toIntegers(projectSourceIds)); @@ -561,10 +779,19 @@ public: Storage::Imports fetchDocumentImports() const { + NanotraceHR::Tracer tracer{"fetch document imports"_t, projectStorageCategory()}; + return selectAllDocumentImportForSourceIdStatement .template valuesWithTransaction(); } + void resetForTestsOnly() + { + database.clearAllTablesForTestsOnly(); + commonTypeCache_.clearForTestsOnly(); + moduleCache.clearForTestOnly(); + } + private: class ModuleStorageAdapter { @@ -624,8 +851,10 @@ private: void callRefreshMetaInfoCallback(const TypeIds &deletedTypeIds) { - for (auto *callback : m_refreshCallbacks) - (*callback)(deletedTypeIds); + if (deletedTypeIds.size()) { + for (ProjectStorageObserver *observer : observers) + observer->removedTypeIds(deletedTypeIds); + } } class AliasPropertyDeclaration @@ -768,6 +997,101 @@ private: sourceIds.erase(newEnd, sourceIds.end()); } + void synchronizeTypeTraits(TypeId typeId, Storage::TypeTraits traits) + { + updateTypeAnnotationTraitStatement.write(typeId, traits.annotation); + } + + class TypeAnnotationView + { + public: + TypeAnnotationView(TypeId typeId, + Utils::SmallStringView iconPath, + Utils::SmallStringView itemLibraryJson, + Utils::SmallStringView hintsJson) + : typeId{typeId} + , iconPath{iconPath} + , itemLibraryJson{itemLibraryJson} + , hintsJson{hintsJson} + {} + + public: + TypeId typeId; + Utils::SmallStringView iconPath; + Utils::SmallStringView itemLibraryJson; + Utils::PathString hintsJson; + }; + + void updateTypeIdInTypeAnnotations(Storage::Synchronization::TypeAnnotations &typeAnnotations) + { + for (auto &annotation : typeAnnotations) { + annotation.typeId = fetchTypeIdByModuleIdAndExportedName(annotation.moduleId, + annotation.typeName); + } + } + + void synchronizeTypeAnnotations(Storage::Synchronization::TypeAnnotations &typeAnnotations, + const SourceIds &updatedTypeAnnotationSourceIds) + { + NanotraceHR::Tracer tracer{"synchronize type annotations"_t, projectStorageCategory()}; + + using Storage::Synchronization::TypeAnnotation; + + updateTypeIdInTypeAnnotations(typeAnnotations); + + auto compareKey = [](auto &&first, auto &&second) { return first.typeId - second.typeId; }; + + std::sort(typeAnnotations.begin(), typeAnnotations.end(), [&](auto &&first, auto &&second) { + return first.typeId < second.typeId; + }); + + auto range = selectTypeAnnotationsForSourceIdsStatement.template range( + toIntegers(updatedTypeAnnotationSourceIds)); + + auto insert = [&](const TypeAnnotation &annotation) { + if (!annotation.sourceId) + throw TypeAnnotationHasInvalidSourceId{}; + + synchronizeTypeTraits(annotation.typeId, annotation.traits); + + insertTypeAnnotationStatement.write(annotation.typeId, + annotation.sourceId, + annotation.iconPath, + annotation.itemLibraryJson, + annotation.hintsJson); + }; + + auto update = [&](const TypeAnnotationView &annotationFromDatabase, + const TypeAnnotation &annotation) { + synchronizeTypeTraits(annotation.typeId, annotation.traits); + + if (annotationFromDatabase.iconPath != annotation.iconPath + || annotationFromDatabase.itemLibraryJson != annotation.itemLibraryJson + || annotationFromDatabase.hintsJson != annotation.hintsJson) { + updateTypeAnnotationStatement.write(annotation.typeId, + annotation.iconPath, + annotation.itemLibraryJson, + annotation.hintsJson); + return Sqlite::UpdateChange::Update; + } + + return Sqlite::UpdateChange::No; + }; + + auto remove = [&](const TypeAnnotationView &annotationFromDatabase) { + synchronizeTypeTraits(annotationFromDatabase.typeId, Storage::TypeTraits{}); + + deleteTypeAnnotationStatement.write(annotationFromDatabase.typeId); + }; + + Sqlite::insertUpdateDelete(range, typeAnnotations, compareKey, insert, update, remove); + } + + void synchronizeTypeTrait(const Storage::Synchronization::Type &type) + { + updateTypeTraitStatement.write(type.typeId, type.traits.type); + } + void synchronizeTypes(Storage::Synchronization::Types &types, TypeIds &updatedTypeIds, AliasPropertyDeclarations &insertedAliasPropertyDeclarations, @@ -778,6 +1102,8 @@ private: Prototypes &relinkableExtensions, const SourceIds &updatedSourceIds) { + NanotraceHR::Tracer tracer{"synchronize types"_t, projectStorageCategory()}; + Storage::Synchronization::ExportedTypes exportedTypes; exportedTypes.reserve(types.size() * 3); SourceIds sourceIdsOfTypes; @@ -792,6 +1118,7 @@ private: throw TypeHasInvalidSourceId{}; TypeId typeId = declareType(type); + synchronizeTypeTrait(type); sourceIdsOfTypes.push_back(type.sourceId); updatedTypeIds.push_back(typeId); if (type.changeLevel != Storage::Synchronization::ChangeLevel::ExcludeExportedTypes) { @@ -832,6 +1159,8 @@ private: void synchronizeProjectDatas(Storage::Synchronization::ProjectDatas &projectDatas, const SourceIds &updatedProjectSourceIds) { + NanotraceHR::Tracer tracer{"synchronize project datas"_t, projectStorageCategory()}; + auto compareKey = [](auto &&first, auto &&second) { auto projectSourceIdDifference = first.projectSourceId - second.projectSourceId; if (projectSourceIdDifference != 0) @@ -884,6 +1213,8 @@ private: void synchronizeFileStatuses(FileStatuses &fileStatuses, const SourceIds &updatedSourceIds) { + NanotraceHR::Tracer tracer{"synchronize file statuses"_t, projectStorageCategory()}; + auto compareKey = [](auto &&first, auto &&second) { return first.sourceId - second.sourceId; }; @@ -929,6 +1260,8 @@ private: Storage::Synchronization::ModuleExportedImports &moduleExportedImports, const ModuleIds &updatedModuleIds) { + NanotraceHR::Tracer tracer{"synchronize imports"_t, projectStorageCategory()}; + synchromizeModuleExportedImports(moduleExportedImports, updatedModuleIds); synchronizeDocumentImports(imports, updatedSourceIds, @@ -1016,7 +1349,7 @@ private: void handleAliasPropertyDeclarationsWithPropertyType( TypeId typeId, AliasPropertyDeclarations &relinkableAliasPropertyDeclarations) { - auto callback = [&](TypeId typeId, + auto callback = [&](TypeId typeId_, PropertyDeclarationId propertyDeclarationId, ImportedTypeNameId propertyImportedTypeNameId, PropertyDeclarationId aliasPropertyDeclarationId, @@ -1029,7 +1362,7 @@ private: aliasPropertyDeclarationTailId); relinkableAliasPropertyDeclarations - .emplace_back(TypeId{typeId}, + .emplace_back(TypeId{typeId_}, PropertyDeclarationId{propertyDeclarationId}, ImportedTypeNameId{propertyImportedTypeNameId}, std::move(aliasPropertyName), @@ -1088,6 +1421,8 @@ private: void relinkAliasPropertyDeclarations(AliasPropertyDeclarations &aliasPropertyDeclarations, const TypeIds &deletedTypeIds) { + NanotraceHR::Tracer tracer{"relink alias properties"_t, projectStorageCategory()}; + std::sort(aliasPropertyDeclarations.begin(), aliasPropertyDeclarations.end()); Utils::set_greedy_difference( @@ -1116,6 +1451,8 @@ private: void relinkPropertyDeclarations(PropertyDeclarations &relinkablePropertyDeclaration, const TypeIds &deletedTypeIds) { + NanotraceHR::Tracer tracer{"relink properties"_t, projectStorageCategory()}; + std::sort(relinkablePropertyDeclaration.begin(), relinkablePropertyDeclaration.end()); Utils::set_greedy_difference( @@ -1140,6 +1477,8 @@ private: const TypeIds &deletedTypeIds, Callable updateStatement) { + NanotraceHR::Tracer tracer{"relink prototypes"_t, projectStorageCategory()}; + std::sort(relinkablePrototypes.begin(), relinkablePrototypes.end()); Utils::set_greedy_difference( @@ -1168,6 +1507,8 @@ private: Prototypes &relinkableExtensions, TypeIds &deletedTypeIds) { + NanotraceHR::Tracer tracer{"delete not updated types"_t, projectStorageCategory()}; + auto callback = [&](TypeId typeId) { deletedTypeIds.push_back(typeId); deleteType(typeId, @@ -1190,6 +1531,8 @@ private: Prototypes &relinkableExtensions, TypeIds &deletedTypeIds) { + NanotraceHR::Tracer tracer{"relink"_t, projectStorageCategory()}; + std::sort(deletedTypeIds.begin(), deletedTypeIds.end()); relinkPrototypes(relinkablePrototypes, deletedTypeIds, [&](TypeId typeId, TypeId prototypeId) { @@ -1256,6 +1599,8 @@ private: void linkAliases(const AliasPropertyDeclarations &insertedAliasPropertyDeclarations, const AliasPropertyDeclarations &updatedAliasPropertyDeclarations) { + NanotraceHR::Tracer tracer{"link aliases"_t, projectStorageCategory()}; + linkAliasPropertyDeclarationAliasIds(insertedAliasPropertyDeclarations); linkAliasPropertyDeclarationAliasIds(updatedAliasPropertyDeclarations); @@ -1273,6 +1618,8 @@ private: Prototypes &relinkablePrototypes, Prototypes &relinkableExtensions) { + NanotraceHR::Tracer tracer{"synchronize exported types"_t, projectStorageCategory()}; + std::sort(exportedTypes.begin(), exportedTypes.end(), [](auto &&first, auto &&second) { if (first.moduleId < second.moduleId) return true; @@ -1456,6 +1803,8 @@ private: AliasPropertyDeclarations &updatedAliasPropertyDeclarations, PropertyDeclarationIds &propertyDeclarationIds) { + NanotraceHR::Tracer tracer{"synchronize property declaration"_t, projectStorageCategory()}; + std::sort(propertyDeclarations.begin(), propertyDeclarations.end(), [](auto &&first, auto &&second) { @@ -1572,6 +1921,9 @@ private: Storage::Synchronization::Types &types, AliasPropertyDeclarations &relinkableAliasPropertyDeclarations) { + NanotraceHR::Tracer tracer{"reset removed alias properties to null"_t, + projectStorageCategory()}; + PropertyDeclarationIds propertyDeclarationIds; propertyDeclarationIds.reserve(types.size()); @@ -1684,14 +2036,14 @@ private: for (const auto ¶meter : parameters) { json.append(comma); comma = ","; - json.append("{\"n\":\""); + json.append(R"({"n":")"); json.append(parameter.name); - json.append("\",\"tn\":\""); + json.append(R"(","tn":")"); json.append(parameter.typeName); if (parameter.traits == Storage::PropertyDeclarationTraits::None) { json.append("\"}"); } else { - json.append("\",\"tr\":"); + json.append(R"(","tr":)"); json.append(Utils::SmallString::number(to_underlying(parameter.traits))); json.append("}"); } @@ -1769,6 +2121,9 @@ private: void synchronizePropertyEditorQmlPaths(Storage::Synchronization::PropertyEditorQmlPaths &paths, SourceIds updatedPropertyEditorQmlPathsSourceIds) { + NanotraceHR::Tracer tracer{"synchronize property editor qml paths"_t, + projectStorageCategory()}; + addTypeIdToPropertyEditorQmlPaths(paths); synchronizePropertyEditorPaths(paths, updatedPropertyEditorQmlPathsSourceIds); } @@ -1776,6 +2131,8 @@ private: void synchronizeFunctionDeclarations( TypeId typeId, Storage::Synchronization::FunctionDeclarations &functionsDeclarations) { + NanotraceHR::Tracer tracer{"synchronize function declaration"_t, projectStorageCategory()}; + std::sort(functionsDeclarations.begin(), functionsDeclarations.end(), [](auto &&first, auto &&second) { @@ -1833,6 +2190,8 @@ private: void synchronizeSignalDeclarations(TypeId typeId, Storage::Synchronization::SignalDeclarations &signalDeclarations) { + NanotraceHR::Tracer tracer{"synchronize signal declaration"_t, projectStorageCategory()}; + std::sort(signalDeclarations.begin(), signalDeclarations.end(), [](auto &&first, auto &&second) { auto compare = Sqlite::compare(first.name, second.name); @@ -1907,6 +2266,8 @@ private: void synchronizeEnumerationDeclarations( TypeId typeId, Storage::Synchronization::EnumerationDeclarations &enumerationDeclarations) { + NanotraceHR::Tracer tracer{"synchronize enumeation declaration"_t, projectStorageCategory()}; + std::sort(enumerationDeclarations.begin(), enumerationDeclarations.end(), [](auto &&first, auto &&second) { @@ -1965,9 +2326,7 @@ private: return type.typeId; } - type.typeId = upsertTypeStatement.template value(type.sourceId, - type.typeName, - type.traits); + type.typeId = insertTypeStatement.template value(type.sourceId, type.typeName); if (!type.typeId) type.typeId = selectTypeIdBySourceIdAndNameStatement.template value(type.sourceId, @@ -1981,6 +2340,8 @@ private: AliasPropertyDeclarations &updatedAliasPropertyDeclarations, PropertyDeclarationIds &propertyDeclarationIds) { + NanotraceHR::Tracer tracer{"synchronize declaration per type"_t, projectStorageCategory()}; + if (type.changeLevel == Storage::Synchronization::ChangeLevel::Minimal) return; @@ -1998,6 +2359,8 @@ private: template void removeRelinkableEntries(std::vector &relinkables, Ids &ids, Compare compare) { + NanotraceHR::Tracer tracer{"remove relinkable entries"_t, projectStorageCategory()}; + std::vector newRelinkables; newRelinkables.reserve(relinkables.size()); @@ -2020,6 +2383,8 @@ private: AliasPropertyDeclarations &updatedAliasPropertyDeclarations, PropertyDeclarations &relinkablePropertyDeclarations) { + NanotraceHR::Tracer tracer{"synchronize declaration"_t, projectStorageCategory()}; + PropertyDeclarationIds propertyDeclarationIds; propertyDeclarationIds.reserve(types.size() * 10); @@ -2048,6 +2413,8 @@ private: void syncDefaultProperties(Storage::Synchronization::Types &types) { + NanotraceHR::Tracer tracer{"synchronize default properties"_t, projectStorageCategory()}; + auto range = selectTypesWithDefaultPropertyStatement.template range(); auto compareKey = [](const TypeWithDefaultPropertyView &view, @@ -2082,6 +2449,8 @@ private: void resetDefaultPropertiesIfChanged(Storage::Synchronization::Types &types) { + NanotraceHR::Tracer tracer{"reset changed default properties"_t, projectStorageCategory()}; + auto range = selectTypesWithDefaultPropertyStatement.template range(); auto compareKey = [](const TypeWithDefaultPropertyView &view, @@ -2142,7 +2511,7 @@ private: { TypeId typeId; ImportedTypeNameId typeNameId; - if (!std::visit([](auto &&typeName) -> bool { return typeName.name.isEmpty(); }, typeName)) { + if (!std::visit([](auto &&typeName_) -> bool { return typeName_.name.isEmpty(); }, typeName)) { typeNameId = fetchImportedTypeNameId(typeName, sourceId); typeId = fetchTypeId(typeNameId); @@ -2180,6 +2549,8 @@ private: Prototypes &relinkablePrototypes, Prototypes &relinkableExtensions) { + NanotraceHR::Tracer tracer{"synchronize prototypes"_t, projectStorageCategory()}; + TypeIds typeIds; typeIds.reserve(types.size()); @@ -2430,6 +2801,7 @@ private: createFileStatusesTable(database); createProjectDatasTable(database); createPropertyEditorPathsTable(database); + createTypeAnnotionsTable(database); } database.setIsInitialized(true); } @@ -2478,21 +2850,23 @@ private: auto &sourceIdColumn = typesTable.addColumn("sourceId", Sqlite::StrictColumnType::Integer); auto &typesNameColumn = typesTable.addColumn("name", Sqlite::StrictColumnType::Text); typesTable.addColumn("traits", Sqlite::StrictColumnType::Integer); - typesTable.addForeignKeyColumn("prototypeId", - typesTable, - Sqlite::ForeignKeyAction::NoAction, - Sqlite::ForeignKeyAction::Restrict); + auto &prototypeIdColumn = typesTable.addForeignKeyColumn("prototypeId", + typesTable, + Sqlite::ForeignKeyAction::NoAction, + Sqlite::ForeignKeyAction::Restrict); typesTable.addColumn("prototypeNameId", Sqlite::StrictColumnType::Integer); - typesTable.addForeignKeyColumn("extensionId", - typesTable, - Sqlite::ForeignKeyAction::NoAction, - Sqlite::ForeignKeyAction::Restrict); + auto &extensionIdColumn = typesTable.addForeignKeyColumn("extensionId", + typesTable, + Sqlite::ForeignKeyAction::NoAction, + Sqlite::ForeignKeyAction::Restrict); typesTable.addColumn("extensionNameId", Sqlite::StrictColumnType::Integer); auto &defaultPropertyIdColumn = typesTable.addColumn("defaultPropertyId", Sqlite::StrictColumnType::Integer); - + typesTable.addColumn("annotationTraits", Sqlite::StrictColumnType::Integer); typesTable.addUniqueIndex({sourceIdColumn, typesNameColumn}); typesTable.addIndex({defaultPropertyIdColumn}); + typesTable.addIndex({prototypeIdColumn}); + typesTable.addIndex({extensionIdColumn}); typesTable.initialize(database); @@ -2779,6 +3153,25 @@ private: table.initialize(database); } + + void createTypeAnnotionsTable(Database &database) + { + Sqlite::StrictTable table; + table.setUseIfNotExists(true); + table.setUseWithoutRowId(true); + table.setName("typeAnnotations"); + auto &typeIdColumn = table.addColumn("typeId", + Sqlite::StrictColumnType::Integer, + {Sqlite::PrimaryKey{}}); + auto &sourceIdColumn = table.addColumn("sourceId", Sqlite::StrictColumnType::Integer); + table.addColumn("iconPath", Sqlite::StrictColumnType::Text); + table.addColumn("itemLibrary", Sqlite::StrictColumnType::Text); + table.addColumn("hints", Sqlite::StrictColumnType::Text); + + table.addUniqueIndex({sourceIdColumn, typeIdColumn}); + + table.initialize(database); + } }; public: @@ -2787,12 +3180,9 @@ public: Initializer initializer; mutable ModuleCache moduleCache{ModuleStorageAdapter{*this}}; Storage::Info::CommonTypeCache commonTypeCache_{*this}; - std::vector *> m_refreshCallbacks; - ReadWriteStatement<1, 3> upsertTypeStatement{ - "INSERT INTO types(sourceId, name, traits) VALUES(?1, ?2, ?3) ON CONFLICT DO " - "UPDATE SET traits=excluded.traits WHERE traits IS NOT " - "excluded.traits RETURNING typeId", - database}; + QVarLengthArray observers; + ReadWriteStatement<1, 2> insertTypeStatement{ + "INSERT OR IGNORE INTO types(sourceId, name) VALUES(?1, ?2) RETURNING typeId", database}; WriteStatement<5> updatePrototypeAndExtensionStatement{ "UPDATE types SET prototypeId=?2, prototypeNameId=?3, extensionId=?4, extensionNameId=?5 " "WHERE typeId=?1 AND (prototypeId IS NOT ?2 OR extensionId IS NOT ?3 AND prototypeId " @@ -2891,10 +3281,12 @@ public: "INSERT INTO sources(sourceContextId, sourceName) VALUES (?,?)", database}; mutable ReadStatement<3> selectAllSourcesStatement{ "SELECT sourceName, sourceContextId, sourceId FROM sources", database}; - mutable ReadStatement<7, 1> selectTypeByTypeIdStatement{ - "SELECT sourceId, t.name, t.typeId, prototypeId, extensionId, traits, pd.name " - "FROM types AS t LEFT JOIN propertyDeclarations AS pd " - " ON defaultPropertyId=propertyDeclarationId WHERE t.typeId=?", + mutable ReadStatement<8, 1> selectTypeByTypeIdStatement{ + "SELECT sourceId, t.name, t.typeId, prototypeId, extensionId, traits, annotationTraits, " + "pd.name " + "FROM types AS t LEFT JOIN propertyDeclarations AS pd ON " + "defaultPropertyId=propertyDeclarationId " + "WHERE t.typeId=?", database}; mutable ReadStatement<4, 1> selectExportedTypesByTypeIdStatement{ "SELECT moduleId, name, ifnull(majorVersion, -1), ifnull(minorVersion, -1) FROM " @@ -2905,11 +3297,16 @@ public: "FROM exportedTypeNames AS etn JOIN documentImports USING(moduleId) WHERE typeId=?1 AND " "sourceId=?2", database}; - mutable ReadStatement<7> selectTypesStatement{ - "SELECT sourceId, t.name, t.typeId, prototypeId, extensionId, traits, pd.name " - "FROM types AS t LEFT JOIN propertyDeclarations AS pd " - " ON defaultPropertyId=propertyDeclarationId", + mutable ReadStatement<8> selectTypesStatement{ + "SELECT sourceId, t.name, t.typeId, prototypeId, extensionId, traits, annotationTraits, " + "pd.name " + "FROM types AS t LEFT JOIN propertyDeclarations AS pd ON " + "defaultPropertyId=propertyDeclarationId", database}; + WriteStatement<2> updateTypeTraitStatement{"UPDATE types SET traits = ?2 WHERE typeId=?1", + database}; + WriteStatement<2> updateTypeAnnotationTraitStatement{ + "UPDATE types SET annotationTraits = ?2 WHERE typeId=?1", database}; ReadStatement<1, 2> selectNotUpdatedTypesInSourcesStatement{ "SELECT DISTINCT typeId FROM types WHERE (sourceId IN carray(?1) AND typeId NOT IN " "carray(?2))", @@ -3430,8 +3827,9 @@ public: "UPDATE types SET defaultPropertyId=?2 WHERE typeId=?1", database}; WriteStatement<1> updateDefaultPropertyIdToNullStatement{ "UPDATE types SET defaultPropertyId=NULL WHERE defaultPropertyId=?1", database}; - mutable ReadStatement<3, 1> selectInfoTypeByTypeIdStatement{ - "SELECT defaultPropertyId, sourceId, traits FROM types WHERE typeId=?", database}; + mutable ReadStatement<4, 1> selectInfoTypeByTypeIdStatement{ + "SELECT defaultPropertyId, sourceId, traits, annotationTraits FROM types WHERE typeId=?", + database}; mutable ReadStatement<1, 1> selectPrototypeIdsForTypeIdInOrderStatement{ "WITH RECURSIVE " " all_prototype_and_extension(typeId, prototypeId) AS (" @@ -3493,6 +3891,65 @@ public: database}; WriteStatement<1> deletePropertyEditorPathStatement{ "DELETE FROM propertyEditorPaths WHERE typeId=?1", database}; + mutable ReadStatement<4, 1> selectTypeAnnotationsForSourceIdsStatement{ + "SELECT typeId, iconPath, itemLibrary, hints FROM typeAnnotations WHERE " + "sourceId IN carray(?1) ORDER BY typeId", + database}; + WriteStatement<5> insertTypeAnnotationStatement{ + "INSERT INTO typeAnnotations(typeId, sourceId, iconPath, itemLibrary, hints) VALUES(?1, " + "?2, ?3, ?4, ?5)", + database}; + WriteStatement<4> updateTypeAnnotationStatement{ + "UPDATE typeAnnotations SET iconPath=?2, itemLibrary=?3, hints=?4 WHERE typeId=?1", database}; + WriteStatement<1> deleteTypeAnnotationStatement{"DELETE FROM typeAnnotations WHERE typeId=?1", + database}; + mutable ReadStatement<1, 1> selectTypeIconPathStatement{ + "SELECT iconPath FROM typeAnnotations WHERE typeId=?1", database}; + mutable ReadStatement<2, 1> selectTypeHintsStatement{ + "SELECT hints.key, hints.value " + "FROM typeAnnotations, json_each(typeAnnotations.hints) AS hints " + "WHERE typeId=?1", + database}; + mutable ReadStatement<9> selectItemLibraryEntriesStatement{ + "SELECT typeId, i.value->>'$.name', i.value->>'$.iconPath', i.value->>'$.category', " + " i.value->>'$.import', i.value->>'$.toolTip', i.value->>'$.properties', " + " i.value->>'$.extraFilePaths', i.value->>'$.templatePath' " + "FROM typeAnnotations, json_each(typeAnnotations.itemLibrary) AS i", + database}; + mutable ReadStatement<9, 1> selectItemLibraryEntriesByTypeIdStatement{ + "SELECT typeId, i.value->>'$.name', i.value->>'$.iconPath', i.value->>'$.category', " + " i.value->>'$.import', i.value->>'$.toolTip', i.value->>'$.properties', " + " i.value->>'$.extraFilePaths', i.value->>'$.templatePath' " + "FROM typeAnnotations, json_each(typeAnnotations.itemLibrary) AS i " + "WHERE typeId=?1", + database}; + mutable ReadStatement<9, 1> selectItemLibraryEntriesBySourceIdStatement{ + "SELECT typeId, i.value->>'$.name', i.value->>'$.iconPath', i.value->>'$.category', " + " i.value->>'$.import', i.value->>'$.toolTip', i.value->>'$.properties', " + " i.value->>'$.extraFilePaths', i.value->>'$.templatePath' " + "FROM typeAnnotations, json_each(typeAnnotations.itemLibrary) AS i " + "WHERE typeId IN (SELECT DISTINCT typeId " + " FROM documentImports AS di JOIN exportedTypeNames USING(moduleId) " + " WHERE di.sourceId=?)", + database}; + mutable ReadStatement<3, 1> selectItemLibraryPropertiesStatement{ + "SELECT p.value->>0, p.value->>1, p.value->>2 FROM json_each(?1) AS p", database}; + mutable ReadStatement<1, 1> selectItemLibraryExtraFilePathsStatement{ + "SELECT p.value FROM json_each(?1) AS p", database}; + mutable ReadStatement<1, 1> selectTypeIdsByModuleIdStatement{ + "SELECT DISTINCT typeId FROM exportedTypeNames WHERE moduleId=?", database}; + mutable ReadStatement<1, 1> selectHeirTypeIdsStatement{ + "WITH RECURSIVE " + " typeSelection(typeId) AS (" + " SELECT typeId FROM types WHERE prototypeId=?1 OR extensionId=?1" + " UNION ALL " + " SELECT t.typeId " + " FROM types AS t JOIN typeSelection AS ts " + " WHERE prototypeId=ts.typeId OR extensionId=ts.typeId)" + "SELECT typeId FROM typeSelection", + database}; }; + extern template class ProjectStorage; + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.cpp b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.cpp index 9f7e72784b8..efe9bc58f5d 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.cpp +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.cpp @@ -112,4 +112,9 @@ ExportedTypeCannotBeInserted::ExportedTypeCannotBeInserted(std::string_view erro : ProjectStorageErrorWithMessage{"ExportedTypeCannotBeInserted"sv, errorMessage} {} +const char *TypeAnnotationHasInvalidSourceId::what() const noexcept +{ + return "The source id in a type annotation is invalid!"; +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.h index a9a9a5c8856..412dd4a9ff9 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.h @@ -136,4 +136,10 @@ public: const char *what() const noexcept override; }; +class QMLDESIGNERCORE_EXPORT TypeAnnotationHasInvalidSourceId : public ProjectStorageError +{ +public: + const char *what() const noexcept override; +}; + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinfotypes.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinfotypes.h index bf73a3c724c..427c0ff8d61 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinfotypes.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinfotypes.h @@ -5,6 +5,7 @@ #include "projectstorageids.h" +#include #include #include @@ -21,15 +22,19 @@ constexpr std::underlying_type_t to_underlying(Enumeration enumerat return static_cast>(enumeration); } +enum class FlagIs : unsigned int { False, Set, True }; + } // namespace QmlDesigner namespace QmlDesigner::Storage { + enum class PropertyDeclarationTraits : int { None = 0, IsReadOnly = 1 << 0, IsPointer = 1 << 1, IsList = 1 << 2 }; + constexpr PropertyDeclarationTraits operator|(PropertyDeclarationTraits first, PropertyDeclarationTraits second) { @@ -41,27 +46,100 @@ constexpr bool operator&(PropertyDeclarationTraits first, PropertyDeclarationTra return static_cast(first) & static_cast(second); } -enum class TypeTraits : int { +enum class TypeTraitsKind : unsigned int { None, Reference, Value, Sequence, - IsEnum = 1 << 8, - IsFileComponent = 1 << 9, - IsProjectComponent = 1 << 10, - IsInProjectModule = 1 << 11, - UsesCustomParser = 1 << 12 }; -constexpr TypeTraits operator|(TypeTraits first, TypeTraits second) +struct TypeTraits { - return static_cast(static_cast(first) | static_cast(second)); -} + constexpr TypeTraits() + : kind{TypeTraitsKind::None} + , isEnum{false} + , isFileComponent{false} + , isProjectComponent{false} + , isInProjectModule{false} + , usesCustomParser{false} + , dummy{0U} + , canBeContainer{FlagIs::False} + , forceClip{FlagIs::False} + , doesLayoutChildren{FlagIs::False} + , canBeDroppedInFormEditor{FlagIs::False} + , canBeDroppedInNavigator{FlagIs::False} + , canBeDroppedInView3D{FlagIs::False} + , isMovable{FlagIs::False} + , isResizable{FlagIs::False} + , hasFormEditorItem{FlagIs::False} + , isStackedContainer{FlagIs::False} + , takesOverRenderingOfChildren{FlagIs::False} + , visibleInNavigator{FlagIs::False} + , visibleInLibrary{FlagIs::False} + , dummy2{0U} + {} -constexpr TypeTraits operator&(TypeTraits first, TypeTraits second) -{ - return static_cast(static_cast(first) & static_cast(second)); -} + constexpr TypeTraits(TypeTraitsKind aKind) + : TypeTraits{} + { + kind = aKind; + } + + explicit constexpr TypeTraits(long long type, long long int annotation) + : type{static_cast(type)} + , annotation{static_cast(annotation)} + {} + + explicit constexpr TypeTraits(unsigned int type, unsigned int annotation) + : type{type} + , annotation{annotation} + {} + + friend bool operator==(TypeTraits first, TypeTraits second) + { + return first.type == second.type && first.annotation == second.annotation; + } + + union { + struct + { + TypeTraitsKind kind : 4; + unsigned int isEnum : 1; + unsigned int isFileComponent : 1; + unsigned int isProjectComponent : 1; + unsigned int isInProjectModule : 1; + unsigned int usesCustomParser : 1; + unsigned int dummy : 23; + }; + + unsigned int type; + }; + + union { + struct + { + FlagIs canBeContainer : 2; + FlagIs forceClip : 2; + FlagIs doesLayoutChildren : 2; + FlagIs canBeDroppedInFormEditor : 2; + FlagIs canBeDroppedInNavigator : 2; + FlagIs canBeDroppedInView3D : 2; + FlagIs isMovable : 2; + FlagIs isResizable : 2; + FlagIs hasFormEditorItem : 2; + FlagIs isStackedContainer : 2; + FlagIs takesOverRenderingOfChildren : 2; + FlagIs visibleInNavigator : 2; + FlagIs visibleInLibrary : 2; + unsigned int dummy2 : 6; + }; + + unsigned int annotation; + }; +}; + +static_assert(sizeof(TypeTraits) == sizeof(unsigned int) * 2, + "TypeTraits must be of size unsigned long long!"); using TypeNameString = ::Utils::BasicSmallString<63>; @@ -132,6 +210,83 @@ public: namespace QmlDesigner::Storage::Info { +struct TypeHint +{ + TypeHint(Utils::SmallStringView name, Utils::SmallStringView expression) + : name{name} + , expression{expression} + {} + + Utils::SmallString name; + Utils::PathString expression; +}; + +using TypeHints = QVarLengthArray; + +struct ItemLibraryProperty +{ + ItemLibraryProperty(Utils::SmallStringView name, Utils::SmallStringView type, Sqlite::ValueView value) + : name{name} + , type{type} + , value{value} + {} + + Utils::SmallString name; + Utils::SmallString type; + Sqlite::Value value; +}; + +using ItemLibraryProperties = QVarLengthArray; + +using ToolTipString = Utils::BasicSmallString<94>; + +struct ItemLibraryEntry +{ + ItemLibraryEntry(TypeId typeId, + Utils::SmallStringView name, + Utils::SmallStringView iconPath, + Utils::SmallStringView category, + Utils::SmallStringView import, + Utils::SmallStringView toolTip, + Utils::SmallStringView templatePath) + : typeId{typeId} + , name{name} + , iconPath{iconPath} + , category{category} + , import{import} + , toolTip{toolTip} + , templatePath{templatePath} + {} + + ItemLibraryEntry(TypeId typeId, + Utils::SmallStringView name, + Utils::SmallStringView iconPath, + Utils::SmallStringView category, + Utils::SmallStringView import, + Utils::SmallStringView toolTip, + ItemLibraryProperties properties) + : typeId{typeId} + , name{name} + , iconPath{iconPath} + , category{category} + , import{import} + , toolTip{toolTip} + , properties{std::move(properties)} + {} + + TypeId typeId; + Utils::SmallString name; + Utils::PathString iconPath; + Utils::SmallString category; + Utils::SmallString import; + ToolTipString toolTip; + Utils::PathString templatePath; + ItemLibraryProperties properties; + std::vector extraFilePaths; +}; + +using ItemLibraryEntries = QVarLengthArray; + class ExportedTypeName { public: @@ -196,6 +351,15 @@ public: class Type { public: + Type(PropertyDeclarationId defaultPropertyId, + SourceId sourceId, + long long typeTraits, + long long typeAnnotationTraits) + : defaultPropertyId{defaultPropertyId} + , sourceId{sourceId} + , traits{typeTraits, typeAnnotationTraits} + {} + Type(PropertyDeclarationId defaultPropertyId, SourceId sourceId, TypeTraits traits) : defaultPropertyId{defaultPropertyId} , sourceId{sourceId} diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinterface.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinterface.h index e6962602b38..266c6ee7caa 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinterface.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinterface.h @@ -6,6 +6,7 @@ #include "commontypecache.h" #include "filestatus.h" #include "projectstorageids.h" +#include "projectstorageobserver.h" #include "projectstoragetypes.h" #include @@ -27,8 +28,8 @@ public: virtual void synchronize(Storage::Synchronization::SynchronizationPackage package) = 0; virtual void synchronizeDocumentImports(const Storage::Imports imports, SourceId sourceId) = 0; - virtual void addRefreshCallback(std::function *callback) = 0; - virtual void removeRefreshCallback(std::function *callback) = 0; + virtual void addObserver(ProjectStorageObserver *observer) = 0; + virtual void removeObserver(ProjectStorageObserver *observer) = 0; virtual ModuleId moduleId(::Utils::SmallStringView name) const = 0; virtual std::optional @@ -38,6 +39,7 @@ public: Storage::Version version) const = 0; virtual TypeId typeId(ImportedTypeNameId typeNameId) const = 0; + virtual QVarLengthArray typeIds(ModuleId moduleId) const = 0; virtual Storage::Info::ExportedTypeNames exportedTypeNames(TypeId typeId) const = 0; virtual Storage::Info::ExportedTypeNames exportedTypeNames(TypeId typeId, SourceId sourceId) const @@ -53,12 +55,18 @@ public: ::Utils::SmallStringView propertyName) const = 0; virtual std::optional type(TypeId typeId) const = 0; + virtual Utils::PathString typeIconPath(TypeId typeId) const = 0; + virtual Storage::Info::TypeHints typeHints(TypeId typeId) const = 0; + virtual Storage::Info::ItemLibraryEntries itemLibraryEntries(TypeId typeId) const = 0; + virtual Storage::Info::ItemLibraryEntries itemLibraryEntries(SourceId sourceId) const = 0; + virtual Storage::Info::ItemLibraryEntries allItemLibraryEntries() const = 0; virtual std::vector<::Utils::SmallString> signalDeclarationNames(TypeId typeId) const = 0; virtual std::vector<::Utils::SmallString> functionDeclarationNames(TypeId typeId) const = 0; virtual std::optional<::Utils::SmallString> propertyName(PropertyDeclarationId propertyDeclarationId) const = 0; virtual TypeIds prototypeAndSelfIds(TypeId type) const = 0; virtual TypeIds prototypeIds(TypeId type) const = 0; + virtual TypeIds heirIds(TypeId typeId) const = 0; virtual bool isBasedOn(TypeId, TypeId) const = 0; virtual bool isBasedOn(TypeId, TypeId, TypeId) const = 0; virtual bool isBasedOn(TypeId, TypeId, TypeId, TypeId) const = 0; diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageobserver.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageobserver.h new file mode 100644 index 00000000000..c3393c91d4c --- /dev/null +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageobserver.h @@ -0,0 +1,15 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "projectstorageids.h" + +namespace QmlDesigner { + +class ProjectStorageObserver +{ +public: + virtual void removedTypeIds(const TypeIds &removedTypeIds) = 0; +}; +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h index 7c7cab80e27..18c39312496 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h @@ -7,6 +7,7 @@ #include "projectstorageids.h" #include "projectstorageinfotypes.h" +#include #include #include @@ -637,6 +638,19 @@ public: , changeLevel{changeLevel} {} + explicit Type(::Utils::SmallStringView typeName, + TypeId prototypeId, + TypeId extensionId, + long long typeTraits, + long long typeAnnotationTraits, + SourceId sourceId) + : typeName{typeName} + , traits{typeTraits, typeAnnotationTraits} + , sourceId{sourceId} + , prototypeId{prototypeId} + , extensionId{extensionId} + {} + explicit Type(::Utils::SmallStringView typeName, TypeId prototypeId, TypeId extensionId, @@ -679,11 +693,12 @@ public: TypeId typeId, TypeId prototypeId, TypeId extensionId, - TypeTraits traits, + long long typeTraits, + long long typeAnnotationTraits, ::Utils::SmallStringView defaultPropertyName) : typeName{typeName} , defaultPropertyName{defaultPropertyName} - , traits{traits} + , traits{typeTraits, typeAnnotationTraits} , sourceId{sourceId} , typeId{typeId} , prototypeId{prototypeId} @@ -712,7 +727,7 @@ public: FunctionDeclarations functionDeclarations; SignalDeclarations signalDeclarations; EnumerationDeclarations enumerationDeclarations; - TypeTraits traits = TypeTraits::None; + TypeTraits traits; SourceId sourceId; TypeId typeId; TypeId prototypeId; @@ -768,6 +783,41 @@ public: using ProjectDatas = std::vector; +class TypeAnnotation +{ +public: + TypeAnnotation(SourceId sourceId) + : sourceId{sourceId} + {} + TypeAnnotation(SourceId sourceId, + Utils::SmallStringView typeName, + ModuleId moduleId, + Utils::SmallStringView iconPath, + TypeTraits traits, + Utils::SmallStringView hintsJson, + Utils::SmallStringView itemLibraryJson) + : typeName{typeName} + , iconPath{iconPath} + , itemLibraryJson{itemLibraryJson} + , hintsJson{hintsJson} + , sourceId{sourceId} + , moduleId{moduleId} + , traits{traits} + {} + +public: + TypeNameString typeName; + Utils::PathString iconPath; + Utils::PathString itemLibraryJson; + Utils::PathString hintsJson; + TypeId typeId; + SourceId sourceId; + ModuleId moduleId; + TypeTraits traits; +}; + +using TypeAnnotations = std::vector; + class SynchronizationPackage { public: @@ -822,6 +872,8 @@ public: ModuleIds updatedModuleIds; PropertyEditorQmlPaths propertyEditorQmlPaths; SourceIds updatedPropertyEditorQmlPathSourceIds; + TypeAnnotations typeAnnotations; + SourceIds updatedTypeAnnotationSourceIds; }; } // namespace Synchronization diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.cpp b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.cpp index 3759736cfaf..62fcf310f65 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.cpp +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.cpp @@ -373,6 +373,14 @@ void ProjectStorageUpdater::updatePropertyEditorPaths( } } +void ProjectStorageUpdater::updateTypeAnnotations( + const QString & /*propertyEditorResourcesPath*/, + Storage::Synchronization::SynchronizationPackage & /*package*/, + NotUpdatedSourceIds & /*notUpdatedSourceIds*/) +{ + // const auto typeAnnotations = dir.entryInfoList({"*.metainfo"}, QDir::Files); +} + void ProjectStorageUpdater::updatePropertyEditorPath( const QString &directoryPath, Storage::Synchronization::SynchronizationPackage &package, @@ -649,7 +657,7 @@ void ProjectStorageUpdater::parseQmlComponent(Utils::SmallStringView relativeFil package.updatedSourceIds.push_back(sourceId); type.typeName = SourcePath{qmlFilePath}.name(); - type.traits = Storage::TypeTraits::Reference; + type.traits = Storage::TypeTraitsKind::Reference; type.sourceId = sourceId; type.exportedTypes = std::move(exportedTypes); @@ -675,7 +683,7 @@ void ProjectStorageUpdater::parseQmlComponent(SourceId sourceId, auto type = m_qmlDocumentParser.parse(content, package.imports, sourceId, sourcePath.directory()); type.typeName = sourcePath.name(); - type.traits = Storage::TypeTraits::Reference; + type.traits = Storage::TypeTraitsKind::Reference; type.sourceId = sourceId; type.changeLevel = Storage::Synchronization::ChangeLevel::ExcludeExportedTypes; diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.h index 6a77f353e20..187a2219d09 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.h @@ -145,6 +145,9 @@ private: void updatePropertyEditorPaths(const QString &propertyEditorResourcesPath, Storage::Synchronization::SynchronizationPackage &package, NotUpdatedSourceIds ¬UpdatedSourceIds); + void updateTypeAnnotations(const QString &propertyEditorResourcesPath, + Storage::Synchronization::SynchronizationPackage &package, + NotUpdatedSourceIds ¬UpdatedSourceIds); void updatePropertyEditorPath(const QString &path, Storage::Synchronization::SynchronizationPackage &package, SourceId directorySourceId); diff --git a/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.cpp b/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.cpp index 41f568e66d2..3768535299a 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.cpp +++ b/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.cpp @@ -84,16 +84,16 @@ Storage::TypeTraits createAccessTypeTraits(QQmlJSScope::AccessSemantics accessSe { switch (accessSematics) { case QQmlJSScope::AccessSemantics::Reference: - return Storage::TypeTraits::Reference; + return Storage::TypeTraitsKind::Reference; case QQmlJSScope::AccessSemantics::Value: - return Storage::TypeTraits::Value; + return Storage::TypeTraitsKind::Value; case QQmlJSScope::AccessSemantics::None: - return Storage::TypeTraits::None; + return Storage::TypeTraitsKind::None; case QQmlJSScope::AccessSemantics::Sequence: - return Storage::TypeTraits::Sequence; + return Storage::TypeTraitsKind::Sequence; } - return Storage::TypeTraits::None; + return Storage::TypeTraitsKind::None; } Storage::TypeTraits createTypeTraits(QQmlJSScope::AccessSemantics accessSematics, bool hasCustomParser) @@ -101,7 +101,7 @@ Storage::TypeTraits createTypeTraits(QQmlJSScope::AccessSemantics accessSematics auto typeTrait = createAccessTypeTraits(accessSematics); if (hasCustomParser) - typeTrait = typeTrait | Storage::TypeTraits::UsesCustomParser; + typeTrait.usesCustomParser = true; return typeTrait; } @@ -348,10 +348,12 @@ void addEnumerationType(EnumerationTypes &enumerationTypes, Utils::SmallStringView enumerationAlias) { auto fullTypeName = addEnumerationType(enumerationTypes, typeName, enumerationName); + Storage::TypeTraits typeTraits{Storage::TypeTraitsKind::Value}; + typeTraits.isEnum = true; types.emplace_back(fullTypeName, Storage::Synchronization::ImportedType{TypeNameString{}}, Storage::Synchronization::ImportedType{}, - Storage::TypeTraits::Value | Storage::TypeTraits::IsEnum, + typeTraits, sourceId, createCppEnumerationExports(typeName, cppModuleId, diff --git a/src/plugins/qmldesigner/designercore/projectstorage/storagecache.h b/src/plugins/qmldesigner/designercore/projectstorage/storagecache.h index f2d6841052d..85c6147d2c7 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/storagecache.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/storagecache.h @@ -62,17 +62,17 @@ class StorageCache return StorageCacheIndex{id + amount}; } - constexpr friend bool operator==(StorageCacheIndex first, StorageCacheIndex second) noexcept + friend constexpr bool operator==(StorageCacheIndex first, StorageCacheIndex second) noexcept { return first.id == second.id && first.isValid() && second.isValid(); } - constexpr friend bool operator<(StorageCacheIndex first, StorageCacheIndex second) noexcept + friend constexpr bool operator<(StorageCacheIndex first, StorageCacheIndex second) noexcept { return first.id < second.id; } - constexpr friend bool operator>=(StorageCacheIndex first, StorageCacheIndex second) noexcept + friend constexpr bool operator>=(StorageCacheIndex first, StorageCacheIndex second) noexcept { return first.id >= second.id; } @@ -286,6 +286,12 @@ public: Mutex &mutex() const { return m_mutex; } + void clearForTestOnly() + { + m_entries.clear(); + m_indices.clear(); + } + private: void updateIndices() { diff --git a/src/plugins/qmldesigner/designercore/projectstorage/typeannotationreader.cpp b/src/plugins/qmldesigner/designercore/projectstorage/typeannotationreader.cpp new file mode 100644 index 00000000000..b829e9db36d --- /dev/null +++ b/src/plugins/qmldesigner/designercore/projectstorage/typeannotationreader.cpp @@ -0,0 +1,490 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "typeannotationreader.h" + +#include "projectstorage.h" + +#include + +#include + +#include +#include +#include +#include + +namespace QmlDesigner::Storage { +using namespace Qt::StringLiterals; + +namespace { +constexpr auto rootElementName = "MetaInfo"_L1; +constexpr auto typeElementName = "Type"_L1; +constexpr auto itemLibraryEntryElementName = "ItemLibraryEntry"_L1; +constexpr auto hintsElementName = "Hints"_L1; +constexpr auto qmlSourceElementNamentName = "QmlSource"_L1; +constexpr auto propertyElementName = "Property"_L1; +constexpr auto extraFileElementName = "ExtraFile"_L1; +} // namespace + +Synchronization::TypeAnnotations TypeAnnotationReader::parseTypeAnnotation(const QString &content, + const QString &directoryPath, + SourceId sourceId) +{ + m_sourceId = sourceId; + m_directoryPath = directoryPath; + m_parserState = ParsingDocument; + if (!SimpleAbstractStreamReader::readFromSource(content)) { + m_parserState = Error; + throw TypeAnnoationParsingError(errors()); + } + + if (!errors().isEmpty()) { + m_parserState = Error; + throw TypeAnnoationParsingError(errors()); + } + + return takeTypeAnnotations(); +} + +QStringList TypeAnnotationReader::errors() +{ + return QmlJS::SimpleAbstractStreamReader::errors(); +} + +void TypeAnnotationReader::elementStart(const QString &name, + [[maybe_unused]] const QmlJS::SourceLocation &nameLocation) +{ + switch (parserState()) { + case ParsingDocument: + setParserState(readDocument(name)); + break; + case ParsingMetaInfo: + setParserState(readMetaInfoRootElement(name)); + break; + case ParsingType: + setParserState(readTypeElement(name)); + break; + case ParsingItemLibrary: + setParserState(readItemLibraryEntryElement(name)); + break; + case ParsingProperty: + setParserState(readPropertyElement(name)); + break; + case ParsingQmlSource: + setParserState(readQmlSourceElement(name)); + break; + case ParsingExtraFile: + setParserState(readExtraFileElement(name)); + break; + case ParsingHints: + case Finished: + case Undefined: + setParserState(Error); + addError(TypeAnnotationReader::tr("Illegal state while parsing."), currentSourceLocation()); + [[fallthrough]]; + case Error: + break; + } +} + +void TypeAnnotationReader::elementEnd() +{ + switch (parserState()) { + case ParsingMetaInfo: + setParserState(Finished); + break; + case ParsingType: + if (m_itemLibraryEntries.size()) + m_typeAnnotations.back().itemLibraryJson = to_string(m_itemLibraryEntries); + setParserState(ParsingMetaInfo); + break; + case ParsingItemLibrary: + setParserState((ParsingType)); + break; + case ParsingHints: + addHints(); + setParserState(ParsingType); + break; + case ParsingProperty: + insertProperty(); + setParserState(ParsingItemLibrary); + break; + case ParsingQmlSource: + setParserState(ParsingItemLibrary); + break; + case ParsingExtraFile: + setParserState(ParsingItemLibrary); + break; + case ParsingDocument: + case Finished: + case Undefined: + setParserState(Error); + addError(TypeAnnotationReader::tr("Illegal state while parsing."), currentSourceLocation()); + [[fallthrough]]; + case Error: + break; + } +} + +void TypeAnnotationReader::propertyDefinition(const QString &name, + [[maybe_unused]] const QmlJS::SourceLocation &nameLocation, + const QVariant &value, + [[maybe_unused]] const QmlJS::SourceLocation &valueLocation) +{ + switch (parserState()) { + case ParsingType: + readTypeProperty(name, value); + break; + case ParsingItemLibrary: + readItemLibraryEntryProperty(name, value); + break; + case ParsingProperty: + readPropertyProperty(name, value); + break; + case ParsingQmlSource: + readQmlSourceProperty(name, value); + break; + case ParsingExtraFile: + readExtraFileProperty(name, value); + break; + case ParsingMetaInfo: + addError(TypeAnnotationReader::tr("No property definition allowed."), currentSourceLocation()); + break; + case ParsingDocument: + case ParsingHints: + readHint(name, value); + break; + case Finished: + case Undefined: + setParserState(Error); + addError(TypeAnnotationReader::tr("Illegal state while parsing."), currentSourceLocation()); + [[fallthrough]]; + case Error: + break; + } +} + +TypeAnnotationReader::ParserSate TypeAnnotationReader::readDocument(const QString &name) +{ + if (name == rootElementName) { + return ParsingMetaInfo; + } else { + addErrorInvalidType(name); + return Error; + } +} + +TypeAnnotationReader::ParserSate TypeAnnotationReader::readMetaInfoRootElement(const QString &name) +{ + if (name == typeElementName) { + m_typeAnnotations.emplace_back(m_sourceId); + m_itemLibraryEntries = json::array(); + return ParsingType; + } else { + addErrorInvalidType(name); + return Error; + } +} + +TypeAnnotationReader::ParserSate TypeAnnotationReader::readTypeElement(const QString &name) +{ + if (name == itemLibraryEntryElementName) { + m_itemLibraryEntries.push_back({}); + + return ParsingItemLibrary; + } else if (name == hintsElementName) { + return ParsingHints; + } else { + addErrorInvalidType(name); + return Error; + } +} + +TypeAnnotationReader::ParserSate TypeAnnotationReader::readItemLibraryEntryElement(const QString &name) +{ + if (name == qmlSourceElementNamentName) { + return ParsingQmlSource; + } else if (name == propertyElementName) { + m_currentProperty = {}; + return ParsingProperty; + } else if (name == extraFileElementName) { + return ParsingExtraFile; + } else { + addError(TypeAnnotationReader::tr("Invalid type %1").arg(name), currentSourceLocation()); + return Error; + } +} + +TypeAnnotationReader::ParserSate TypeAnnotationReader::readPropertyElement(const QString &name) +{ + addError(TypeAnnotationReader::tr("Invalid type %1").arg(name), currentSourceLocation()); + return Error; +} + +TypeAnnotationReader::ParserSate TypeAnnotationReader::readQmlSourceElement(const QString &name) +{ + addError(TypeAnnotationReader::tr("Invalid type %1").arg(name), currentSourceLocation()); + return Error; +} + +TypeAnnotationReader::ParserSate TypeAnnotationReader::readExtraFileElement(const QString &name) +{ + addError(TypeAnnotationReader::tr("Invalid type %1").arg(name), currentSourceLocation()); + return Error; +} + +namespace { +QT_WARNING_PUSH +QT_WARNING_DISABLE_CLANG("-Wunneeded-internal-declaration") + +std::pair decomposeTypePath(Utils::SmallStringView typeName) +{ + auto found = std::find(typeName.rbegin(), typeName.rend(), '.'); + + if (found == typeName.rend()) + return {{}, typeName}; + + return {{typeName.begin(), std::prev(found.base())}, {found.base(), typeName.end()}}; +} + +QT_WARNING_POP +} // namespace + +void TypeAnnotationReader::readTypeProperty(QStringView name, const QVariant &value) +{ + if (name == "name"_L1) { + Utils::PathString fullTypeName = value.toString(); + auto [moduleName, typeName] = decomposeTypePath(fullTypeName); + + m_typeAnnotations.back().typeName = typeName; + m_typeAnnotations.back().moduleId = m_projectStorage.moduleId(moduleName); + + } else if (name == "icon"_L1) { + m_typeAnnotations.back().iconPath = absoluteFilePathForDocument(value.toString()); + } else { + addError(TypeAnnotationReader::tr("Unknown property for Type %1").arg(name), + currentSourceLocation()); + setParserState(Error); + } +} + +void TypeAnnotationReader::readItemLibraryEntryProperty(QStringView name, const QVariant &variant) +{ + auto value = variant.toString().toStdString(); + if (name == "name"_L1) { + m_itemLibraryEntries.back()["name"] = value; + } else if (name == "category"_L1) { + m_itemLibraryEntries.back()["category"] = value; + } else if (name == "libraryIcon"_L1) { + m_itemLibraryEntries.back()["iconPath"] = value; + } else if (name == "version"_L1) { + // setVersion(value.toString()); + } else if (name == "requiredImport"_L1) { + m_itemLibraryEntries.back()["import"] = value; + } else if (name == "toolTip"_L1) { + m_itemLibraryEntries.back()["toolTip"] = value; + } else { + addError(TypeAnnotationReader::tr("Unknown property for ItemLibraryEntry %1").arg(name), + currentSourceLocation()); + setParserState(Error); + } +} + +namespace { +QString deEscape(const QString &value) +{ + QString result = value; + + result.replace(R"(\")"_L1, R"(")"_L1); + result.replace(R"(\\")"_L1, R"(\)"_L1); + + return result; +} + +QVariant deEscapeVariant(const QVariant &value) +{ + if (value.typeId() == QVariant::String) + return deEscape(value.toString()); + return value; +} +} // namespace + +void TypeAnnotationReader::readPropertyProperty(QStringView name, const QVariant &value) +{ + if (name == "name"_L1) { + m_currentProperty.name = value.toByteArray(); + } else if (name == "type"_L1) { + m_currentProperty.type = value.toString(); + } else if (name == "value"_L1) { + m_currentProperty.value = deEscapeVariant(value); + } else { + addError(TypeAnnotationReader::tr("Unknown property for Property %1").arg(name), + currentSourceLocation()); + setParserState(Error); + } +} + +void TypeAnnotationReader::readQmlSourceProperty(QStringView name, const QVariant &value) +{ + if (name == "source"_L1) { + m_itemLibraryEntries.back()["templatePath"] = absoluteFilePathForDocument(value.toString()); + } else { + addError(TypeAnnotationReader::tr("Unknown property for QmlSource %1").arg(name), + currentSourceLocation()); + setParserState(Error); + } +} + +void TypeAnnotationReader::readExtraFileProperty(QStringView name, const QVariant &value) +{ + if (name == "source"_L1) { + m_itemLibraryEntries.back()["extraFilePaths"].push_back( + absoluteFilePathForDocument(value.toString())); + } else { + addError(TypeAnnotationReader::tr("Unknown property for ExtraFile %1").arg(name), + currentSourceLocation()); + setParserState(Error); + } +} + +namespace { +FlagIs createFlag(QStringView expression) +{ + using namespace Qt::StringLiterals; + if (expression == "true"_L1) + return FlagIs::True; + + if (expression == "false"_L1) + return FlagIs::False; + + return FlagIs::Set; +} + +template +void setTrait(QStringView name, FlagIs flag, Storage::TypeTraits &traits) +{ + using namespace Qt::StringLiterals; + + if (name == "canBeContainer"_L1) { + traits.canBeContainer = flag; + } else if (name == "forceClip"_L1) { + traits.forceClip = flag; + } else if (name == "doesLayoutChildren"_L1) { + traits.doesLayoutChildren = flag; + } else if (name == "canBeDroppedInFormEditor"_L1) { + traits.canBeDroppedInFormEditor = flag; + } else if (name == "canBeDroppedInNavigator"_L1) { + traits.canBeDroppedInNavigator = flag; + } else if (name == "canBeDroppedInView3D"_L1) { + traits.canBeDroppedInView3D = flag; + } else if (name == "isMovable"_L1) { + traits.isMovable = flag; + } else if (name == "isResizable"_L1) { + traits.isResizable = flag; + } else if (name == "hasFormEditorItem"_L1) { + traits.hasFormEditorItem = flag; + } else if (name == "isStackedContainer"_L1) { + traits.isStackedContainer = flag; + } else if (name == "takesOverRenderingOfChildren"_L1) { + traits.takesOverRenderingOfChildren = flag; + } else if (name == "visibleInNavigator"_L1) { + traits.visibleInNavigator = flag; + } else if (name == "visibleInLibrary"_L1) { + traits.visibleInLibrary = flag; + } +} + +void setComplexHint(nlohmann::json &hints, const QString &name, const QString &expression) +{ + hints[name.toStdString()] = expression.toStdString(); +} + +} // namespace + +void TypeAnnotationReader::readHint(const QString &name, const QVariant &value) +{ + auto expression = value.toString(); + + auto flag = createFlag(expression); + setTrait(name, flag, m_typeAnnotations.back().traits); + if (flag == FlagIs::Set) + setComplexHint(m_hints, name, expression); +} + +void TypeAnnotationReader::addHints() +{ + if (m_hints.size()) { + m_typeAnnotations.back().hintsJson = to_string(m_hints); + m_hints.clear(); + } +} + +void TypeAnnotationReader::setVersion(const QString &versionNumber) +{ + // const TypeName typeName = m_currentEntry.typeName(); + int major = 1; + int minor = 0; + + if (!versionNumber.isEmpty()) { + int val; + bool ok; + if (versionNumber.contains('.'_L1)) { + val = versionNumber.split('.'_L1).constFirst().toInt(&ok); + major = ok ? val : major; + val = versionNumber.split('.'_L1).constLast().toInt(&ok); + minor = ok ? val : minor; + } else { + val = versionNumber.toInt(&ok); + major = ok ? val : major; + } + } + // m_currentEntry.setType(typeName, major, minor); +} + +TypeAnnotationReader::ParserSate TypeAnnotationReader::parserState() const +{ + return m_parserState; +} + +void TypeAnnotationReader::setParserState(ParserSate newParserState) +{ + m_parserState = newParserState; +} + +using json = nlohmann::json; + +[[maybe_unused]] static void to_json(json &out, const TypeAnnotationReader::Property &property) +{ + out = json::array({}); + out.push_back(property.name); + out.push_back(property.type); + if (property.value.type() == QVariant::String) + out.push_back(Utils::PathString{property.value.toString()}); + else if (property.value.type() == QVariant::Int || property.value.type() == QVariant::LongLong) + out.push_back(property.value.toLongLong()); + else + out.push_back(property.value.toDouble()); +} + +void TypeAnnotationReader::insertProperty() +{ + m_itemLibraryEntries.back()["properties"].push_back(m_currentProperty); +} + +void TypeAnnotationReader::addErrorInvalidType(const QString &typeName) +{ + addError(TypeAnnotationReader::tr("Invalid type %1").arg(typeName), currentSourceLocation()); +} + +Utils::PathString TypeAnnotationReader::absoluteFilePathForDocument(Utils::PathString relativeFilePath) +{ + return Utils::PathString::join({m_directoryPath, "/", relativeFilePath}); +} + +const char *TypeAnnoationParsingError::what() const noexcept +{ + return "TypeAnnoationParsingError"; +} + +} // namespace QmlDesigner::Storage diff --git a/src/plugins/qmldesigner/designercore/projectstorage/typeannotationreader.h b/src/plugins/qmldesigner/designercore/projectstorage/typeannotationreader.h new file mode 100644 index 00000000000..9332d5bed94 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/projectstorage/typeannotationreader.h @@ -0,0 +1,129 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "qmldesignercorelib_global.h" + +#include "projectstoragetypes.h" + +#include + +#include <3rdparty/json/json.hpp> +#include +#include + +#include +#include +#include + +#include +#include + +namespace QmlDesigner::Storage { + +class ItemLibraryEntry; + +class TypeAnnoationParsingError : public std::exception +{ +public: + TypeAnnoationParsingError(QStringList errors) + : errors{std::move(errors)} + {} + + const char *what() const noexcept override; + + QStringList errors; +}; + +class TypeAnnotationReader : protected QmlJS::SimpleAbstractStreamReader +{ + Q_DECLARE_TR_FUNCTIONS(QmlDesigner::Internal::TypeAnnotationReader) + + using json = nlohmann::json; + +public: + TypeAnnotationReader(ProjectStorageType &projectStorage) + : m_projectStorage{projectStorage} + {} + + Synchronization::TypeAnnotations parseTypeAnnotation(const QString &content, + const QString &directoryPath, + SourceId sourceId); + + QStringList errors(); + + void setQualifcation(const TypeName &qualification); + + struct Property + { + Utils::SmallString name; + Utils::SmallString type; + QVariant value; + }; + +protected: + Synchronization::TypeAnnotations takeTypeAnnotations() { return std::move(m_typeAnnotations); } + + void elementStart(const QString &name, const QmlJS::SourceLocation &nameLocation) override; + void elementEnd() override; + void propertyDefinition(const QString &name, + const QmlJS::SourceLocation &nameLocation, + const QVariant &value, + const QmlJS::SourceLocation &valueLocation) override; + +private: + enum ParserSate { + Error, + Finished, + Undefined, + ParsingDocument, + ParsingMetaInfo, + ParsingType, + ParsingItemLibrary, + ParsingHints, + ParsingProperty, + ParsingQmlSource, + ParsingExtraFile + }; + + ParserSate readDocument(const QString &name); + + ParserSate readMetaInfoRootElement(const QString &name); + ParserSate readTypeElement(const QString &name); + ParserSate readItemLibraryEntryElement(const QString &name); + ParserSate readPropertyElement(const QString &name); + ParserSate readQmlSourceElement(const QString &name); + ParserSate readExtraFileElement(const QString &name); + + void readTypeProperty(QStringView name, const QVariant &value); + void readItemLibraryEntryProperty(QStringView name, const QVariant &value); + void readPropertyProperty(QStringView name, const QVariant &value); + void readQmlSourceProperty(QStringView name, const QVariant &value); + void readExtraFileProperty(QStringView name, const QVariant &value); + void readHint(const QString &name, const QVariant &value); + void addHints(); + + void setVersion(const QString &versionNumber); + + ParserSate parserState() const; + void setParserState(ParserSate newParserState); + + void insertProperty(); + + void addErrorInvalidType(const QString &typeName); + + Utils::PathString absoluteFilePathForDocument(Utils::PathString relativeFilePath); + +private: + ProjectStorageType &m_projectStorage; + Utils::PathString m_directoryPath; + ParserSate m_parserState = Undefined; + Synchronization::TypeAnnotations m_typeAnnotations; + json m_hints; + json m_itemLibraryEntries; + Property m_currentProperty; + SourceId m_sourceId; +}; + +} // namespace QmlDesigner::Storage diff --git a/src/plugins/qmldesigner/designercore/tracing/qmldesignertracing.cpp b/src/plugins/qmldesigner/designercore/tracing/qmldesignertracing.cpp new file mode 100644 index 00000000000..be723a3dccc --- /dev/null +++ b/src/plugins/qmldesigner/designercore/tracing/qmldesignertracing.cpp @@ -0,0 +1,50 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qmldesignertracing.h" + +namespace QmlDesigner { +namespace Tracing { + +namespace { +using TraceFile = NanotraceHR::TraceFile; + +TraceFile traceFile{"qml_designer.json"}; + +thread_local NanotraceHR::EventQueueData + strinViewEventQueueData(traceFile); +thread_local NanotraceHR::EventQueue stringViewEventQueue_ = strinViewEventQueueData.createEventQueue(); + +thread_local NanotraceHR::EventQueueData + stringViewWithStringArgumentsEventQueueData(traceFile); +thread_local NanotraceHR::EventQueue stringViewEventWithStringArgumentsQueue_ = stringViewWithStringArgumentsEventQueueData + .createEventQueue(); +} // namespace + +EventQueue &eventQueue() +{ + return stringViewEventQueue_; +} + +EventQueueWithStringArguments &eventQueueWithStringArguments() +{ + return stringViewEventWithStringArgumentsQueue_; +} + +} // namespace Tracing + +namespace ModelTracing { +namespace { +using namespace NanotraceHR::Literals; + +thread_local Category category_{"model"_t, Tracing::stringViewEventWithStringArgumentsQueue_, category}; + +} // namespace + +Category &category() +{ + return category_; +} + +} // namespace ModelTracing +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/tracing/qmldesignertracing.h b/src/plugins/qmldesigner/designercore/tracing/qmldesignertracing.h new file mode 100644 index 00000000000..fb3f23d2f14 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/tracing/qmldesignertracing.h @@ -0,0 +1,45 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include + +#include + +#pragma once + +namespace QmlDesigner { +namespace Tracing { + +constexpr NanotraceHR::Tracing tracingStatus() +{ +#ifdef ENABLE_QMLDESIGNER_TRACING + return NanotraceHR::tracingStatus(); +#else + return NanotraceHR::Tracing::IsDisabled; +#endif +} + +using EventQueue = NanotraceHR::StringViewEventQueue; +using EventQueueWithStringArguments = NanotraceHR::StringViewWithStringArgumentsEventQueue; + +QMLDESIGNERCORE_EXPORT EventQueue &eventQueue(); +QMLDESIGNERCORE_EXPORT EventQueueWithStringArguments &eventQueueWithStringArguments(); + +} // namespace Tracing + +namespace ModelTracing { +constexpr NanotraceHR::Tracing tracingStatus() +{ +#ifdef ENABLE_MODEL_TRACING + return NanotraceHR::tracingStatus(); +#else + return NanotraceHR::Tracing::IsDisabled; +#endif +} + +using Category = NanotraceHR::StringViewWithStringArgumentsCategory; +using ObjectTraceToken = Category::ObjectTokenType; +QMLDESIGNERCORE_EXPORT Category &category(); + +} // namespace ModelTracing +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designmodewidget.cpp b/src/plugins/qmldesigner/designmodewidget.cpp index fe9fafde5c4..1387f42f9c9 100644 --- a/src/plugins/qmldesigner/designmodewidget.cpp +++ b/src/plugins/qmldesigner/designmodewidget.cpp @@ -186,6 +186,10 @@ void DesignModeWidget::setup() //ADS::DockManager::setAutoHideConfigFlags(ADS::DockManager::DefaultAutoHideConfig); + auto designerSettings = DesignerSettings(settings); + if (designerSettings.value(DesignerSettingsKey::ENABLE_DOCKWIDGET_CONTENT_MIN_SIZE).toBool()) + m_minimumSizeHintMode = ADS::DockWidget::MinimumSizeHintFromContentMinimumSize; + m_dockManager = new ADS::DockManager(this); m_dockManager->setSettings(settings); m_dockManager->setWorkspacePresetsPath( @@ -306,8 +310,9 @@ void DesignModeWidget::setup() // Create DockWidget ADS::DockWidget *dockWidget = new ADS::DockWidget(uniqueId); - dockWidget->setWidget(navigationView.widget); + dockWidget->setWidget(navigationView.widget, ADS::DockWidget::ForceNoScrollArea); dockWidget->setWindowTitle(title); + dockWidget->setMinimumSizeHintMode(m_minimumSizeHintMode); m_dockManager->addDockWidget(ADS::NoDockWidgetArea, dockWidget); // Set unique id as object name @@ -325,8 +330,9 @@ void DesignModeWidget::setup() for (const WidgetInfo &widgetInfo : viewManager().widgetInfos()) { // Create DockWidget ADS::DockWidget *dockWidget = new ADS::DockWidget(widgetInfo.uniqueId); - dockWidget->setWidget(widgetInfo.widget); + dockWidget->setWidget(widgetInfo.widget, ADS::DockWidget::ForceNoScrollArea); dockWidget->setWindowTitle(widgetInfo.tabName); + dockWidget->setMinimumSizeHintMode(m_minimumSizeHintMode); m_dockManager->addDockWidget(ADS::NoDockWidgetArea, dockWidget); // Add to view widgets @@ -349,8 +355,9 @@ void DesignModeWidget::setup() const QString uniqueId = "OutputPane"; auto outputPanePlaceholder = new Core::OutputPanePlaceHolder(Core::Constants::MODE_DESIGN); m_outputPaneDockWidget = new ADS::DockWidget(uniqueId); - m_outputPaneDockWidget->setWidget(outputPanePlaceholder); + m_outputPaneDockWidget->setWidget(outputPanePlaceholder, ADS::DockWidget::ForceNoScrollArea); m_outputPaneDockWidget->setWindowTitle(tr("Output")); + m_outputPaneDockWidget->setMinimumSizeHintMode(m_minimumSizeHintMode); m_dockManager->addDockWidget(ADS::NoDockWidgetArea, m_outputPaneDockWidget); // Set unique id as object name @@ -476,6 +483,13 @@ void DesignModeWidget::aboutToShowWorkspaces() QAction *action = menu->addAction(tr("Manage...")); connect(action, &QAction::triggered, m_dockManager, &ADS::DockManager::showWorkspaceMananger); + QAction *lockWorkspace = menu->addAction(tr("Lock Workspaces")); + lockWorkspace->setCheckable(true); + lockWorkspace->setChecked(m_dockManager->isWorkspaceLocked()); + connect(lockWorkspace, &QAction::triggered, this, [this](bool checked) { + m_dockManager->lockWorkspace(checked); + }); + QAction *resetWorkspace = menu->addAction(tr("Reset Active")); connect(resetWorkspace, &QAction::triggered, this, [this]() { if (m_dockManager->resetWorkspacePreset(m_dockManager->activeWorkspace()->fileName())) @@ -541,6 +555,29 @@ GlobalAnnotationEditor &DesignModeWidget::globalAnnotationEditor() return m_globalAnnotationEditor; } +void DesignModeWidget::setMinimumSizeHintFromContentMinimumSize(bool value) +{ + // This is the default mode + ADS::DockWidget::eMinimumSizeHintMode newMode = ADS::DockWidget::MinimumSizeHintFromDockWidget; + + if (value) + newMode = ADS::DockWidget::MinimumSizeHintFromContentMinimumSize; + + if (newMode == m_minimumSizeHintMode) + return; + + m_minimumSizeHintMode = newMode; + + const auto &dockWidgets = m_dockManager->dockWidgetsMap(); + for (auto dockWidget : dockWidgets) + dockWidget->setMinimumSizeHintMode(m_minimumSizeHintMode); + + const auto &dockContainers = m_dockManager->dockContainers(); + // This still needs to be updated manually to show changes immediately after option was toggled + for (auto dockContainer : dockContainers) + dockContainer->layout()->update(); +} + void DesignModeWidget::dragEnterEvent(QDragEnterEvent *event) { event->accept(); diff --git a/src/plugins/qmldesigner/designmodewidget.h b/src/plugins/qmldesigner/designmodewidget.h index 1dfa79a0b3d..432549e694d 100644 --- a/src/plugins/qmldesigner/designmodewidget.h +++ b/src/plugins/qmldesigner/designmodewidget.h @@ -74,6 +74,8 @@ public: GlobalAnnotationEditor &globalAnnotationEditor(); + void setMinimumSizeHintFromContentMinimumSize(bool value); + signals: void navigationHistoryChanged(); void initialized(); @@ -113,6 +115,8 @@ private: bool m_canGoForward = false; bool m_canGoBack = false; + + ADS::DockWidget::eMinimumSizeHintMode m_minimumSizeHintMode = ADS::DockWidget::MinimumSizeHintFromDockWidget; }; } // namespace Internal diff --git a/src/plugins/qmldesigner/documentmanager.cpp b/src/plugins/qmldesigner/documentmanager.cpp index 0ce1f5e07fd..3d173842b45 100644 --- a/src/plugins/qmldesigner/documentmanager.cpp +++ b/src/plugins/qmldesigner/documentmanager.cpp @@ -75,19 +75,21 @@ inline static QHash getProperties(const ModelNode &node) inline static void applyProperties(ModelNode &node, const QHash &propertyHash) { const auto auxiliaryData = node.auxiliaryData(AuxiliaryDataType::NodeInstancePropertyOverwrite); - for (const auto &element : auxiliaryData) node.removeAuxiliaryData(AuxiliaryDataType::NodeInstancePropertyOverwrite, element.first); - for (auto propertyIterator = propertyHash.cbegin(), end = propertyHash.cend(); - propertyIterator != end; - ++propertyIterator) { - const PropertyName propertyName = propertyIterator.key(); - if (propertyName == "width" || propertyName == "height") { - node.setAuxiliaryData(AuxiliaryDataType::NodeInstancePropertyOverwrite, - propertyIterator.key(), - propertyIterator.value()); + auto needsOverwrite = [&node](const auto& check, const auto& name, const auto& instanceValue) { + if (check == name) { + VariantProperty property = node.variantProperty(name); + if (property.isValid() && property.value() != instanceValue) + return true; } + return false; + }; + + for (const auto& [name, value] : propertyHash.asKeyValueRange()) { + if (needsOverwrite("width", name, value) || needsOverwrite("height", name, value)) + node.setAuxiliaryData(AuxiliaryDataType::NodeInstancePropertyOverwrite, name, value); } } diff --git a/src/plugins/qmldesigner/qmldesignerconstants.h b/src/plugins/qmldesigner/qmldesignerconstants.h index 4ca8b1ea923..dc545e06a8c 100644 --- a/src/plugins/qmldesigner/qmldesignerconstants.h +++ b/src/plugins/qmldesigner/qmldesignerconstants.h @@ -62,6 +62,7 @@ const char EDIT3D_PARTICLE_MODE[] = "QmlDesigner.Editor3D.ParticleViewModeTo const char EDIT3D_PARTICLES_PLAY[] = "QmlDesigner.Editor3D.ParticlesPlay"; const char EDIT3D_PARTICLES_SEEKER[] = "QmlDesigner.Editor3D.ParticlesSeeker"; const char EDIT3D_PARTICLES_RESTART[] = "QmlDesigner.Editor3D.ParticlesRestart"; +const char EDIT3D_SPLIT_VIEW[] = "QmlDesigner.Editor3D.SplitViewToggle"; const char EDIT3D_VISIBILITY_TOGGLES[] = "QmlDesigner.Editor3D.VisibilityToggles"; const char EDIT3D_BACKGROUND_COLOR_ACTIONS[] = "QmlDesigner.Editor3D.BackgroundColorActions"; const char EDIT3D_BAKE_LIGHTS[] = "QmlDesigner.Editor3D.BakeLights"; @@ -134,6 +135,8 @@ const char EVENT_MATERIALEDITOR_TIME[] = "materialEditor"; const char EVENT_MATERIALBROWSER_TIME[] = "materialBrowser"; const char EVENT_CONTENTLIBRARY_TIME[] = "contentLibrary"; const char EVENT_INSIGHT_TIME[] = "insight"; +const char EVENT_MODELEDITOR_TIME[] = "modelEditor"; +const char EVENT_NEWEFFECTMAKER_TIME[] = "newEffectMaker"; const char EVENT_TOOLBAR_MODE_CHANGE[] = "ToolBarTriggerModeChange"; const char EVENT_TOOLBAR_PROJECT_SETTINGS[] = "ToolBarTriggerProjectSettings"; const char EVENT_TOOLBAR_RUN_PROJECT[] = "ToolBarRunProject"; diff --git a/src/plugins/qmldesigner/qmldesignerexternaldependencies.cpp b/src/plugins/qmldesigner/qmldesignerexternaldependencies.cpp index f2c96de6736..321d95197fc 100644 --- a/src/plugins/qmldesigner/qmldesignerexternaldependencies.cpp +++ b/src/plugins/qmldesigner/qmldesignerexternaldependencies.cpp @@ -246,6 +246,18 @@ bool ExternalDependencies::isQt6Project() const return qmlBuildSystem && qmlBuildSystem->qt6Project(); } +bool ExternalDependencies::isQtForMcusProject() const +{ + // QmlBuildSystem + auto [project, target, qmlBuildSystem] = activeProjectEntries(); + if (qmlBuildSystem) + return qmlBuildSystem->qtForMCUs(); + + // CMakeBuildSystem + ProjectExplorer::Target *activeTarget = ProjectExplorer::ProjectManager::startupTarget(); + return activeTarget && activeTarget->kit() && activeTarget->kit()->hasValue("McuSupport.McuTargetKitVersion"); +} + QString ExternalDependencies::qtQuickVersion() const { auto [project, target, qmlBuildSystem] = activeProjectEntries(); diff --git a/src/plugins/qmldesigner/qmldesignerexternaldependencies.h b/src/plugins/qmldesigner/qmldesignerexternaldependencies.h index 13317075027..b4908c23833 100644 --- a/src/plugins/qmldesigner/qmldesignerexternaldependencies.h +++ b/src/plugins/qmldesigner/qmldesignerexternaldependencies.h @@ -37,6 +37,7 @@ public: QStringList modulePaths() const override; QStringList projectModulePaths() const override; bool isQt6Project() const override; + bool isQtForMcusProject() const override; QString qtQuickVersion() const override; Utils::FilePath resourcePath(const QString &relativePath) const override; diff --git a/src/plugins/qmldesigner/qmldesignerplugin.cpp b/src/plugins/qmldesigner/qmldesignerplugin.cpp index c7ed127b6ef..32abfe73853 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.cpp +++ b/src/plugins/qmldesigner/qmldesignerplugin.cpp @@ -4,6 +4,7 @@ #include "qmldesignerplugin.h" #include "qmldesignertr.h" +#include "collectioneditor/collectionview.h" #include "coreplugin/iwizardfactory.h" #include "designmodecontext.h" #include "designmodewidget.h" @@ -25,7 +26,11 @@ #include #include #include -#include +#include +#include +#ifndef QDS_USE_PROJECTSTORAGE +# include +#endif #include #include #include @@ -70,12 +75,12 @@ #include #include #include +#include #include #include #include #include #include -#include #include "nanotrace/nanotrace.h" #include @@ -280,6 +285,9 @@ bool QmlDesignerPlugin::initialize(const QStringList & /*arguments*/, QString *e //TODO Move registering those types out of the property editor, since they are used also in the states editor Quick2PropertyEditorView::registerQmlTypes(); + CollectionView::registerDeclarativeType(); + StudioQuickWidget::registerDeclarativeType(); + QmlDesignerBase::WindowManager::registerDeclarativeType(); if (checkEnterpriseLicense()) Core::IWizardFactory::registerFeatureProvider(new EnterpriseFeatureProvider); @@ -545,7 +553,9 @@ void QmlDesignerPlugin::activateAutoSynchronization() d->mainWidget.setupNavigatorHistory(currentDesignDocument()->textEditor()); +#ifndef QDS_USE_PROJECTSTORAGE currentDesignDocument()->updateSubcomponentManager(); +#endif } void QmlDesignerPlugin::deactivateAutoSynchronization() @@ -607,7 +617,9 @@ void QmlDesignerPlugin::enforceDelayedInitialize() return QString(p + postfix); }); +#ifndef QDS_USE_PROJECTSTORAGE MetaInfo::initializeGlobal(pluginPaths, d->externalDependencies); +#endif d->viewManager.registerView(std::make_unique(d->externalDependencies)); diff --git a/src/plugins/qmldesigner/qtquickplugin/images/animatedsprite-loading.png b/src/plugins/qmldesigner/qtquickplugin/images/animatedsprite-loading.png new file mode 100644 index 00000000000..ff2bbbd140f Binary files /dev/null and b/src/plugins/qmldesigner/qtquickplugin/images/animatedsprite-loading.png differ diff --git a/src/plugins/qmldesigner/qtquickplugin/images/drop-area-16px.png b/src/plugins/qmldesigner/qtquickplugin/images/drop-area-16px.png new file mode 100644 index 00000000000..278690f07f2 Binary files /dev/null and b/src/plugins/qmldesigner/qtquickplugin/images/drop-area-16px.png differ diff --git a/src/plugins/qmldesigner/qtquickplugin/images/drop-area-24px.png b/src/plugins/qmldesigner/qtquickplugin/images/drop-area-24px.png new file mode 100644 index 00000000000..a286efb0324 Binary files /dev/null and b/src/plugins/qmldesigner/qtquickplugin/images/drop-area-24px.png differ diff --git a/src/plugins/qmldesigner/qtquickplugin/images/drop-area-24px@2x.png b/src/plugins/qmldesigner/qtquickplugin/images/drop-area-24px@2x.png new file mode 100644 index 00000000000..47abb7f9e3a Binary files /dev/null and b/src/plugins/qmldesigner/qtquickplugin/images/drop-area-24px@2x.png differ diff --git a/src/plugins/qmldesigner/qtquickplugin/qtquickplugin.qrc b/src/plugins/qmldesigner/qtquickplugin/qtquickplugin.qrc index 30d705ad980..cb5346c0795 100644 --- a/src/plugins/qmldesigner/qtquickplugin/qtquickplugin.qrc +++ b/src/plugins/qmldesigner/qtquickplugin/qtquickplugin.qrc @@ -119,5 +119,9 @@ images/spatial-audio-16.png images/spatial-audio-24.png images/spatial-audio-24@2x.png + images/drop-area-16px.png + images/drop-area-24px.png + images/drop-area-24px@2x.png + images/animatedsprite-loading.png diff --git a/src/plugins/qmldesigner/qtquickplugin/quick.metainfo b/src/plugins/qmldesigner/qtquickplugin/quick.metainfo index f1aeaa9ebbd..d3fdd6f55f0 100644 --- a/src/plugins/qmldesigner/qtquickplugin/quick.metainfo +++ b/src/plugins/qmldesigner/qtquickplugin/quick.metainfo @@ -105,6 +105,22 @@ MetaInfo { } } + Type { + name: "QtQuick.DropArea" + icon: ":/qtquickplugin/images/drop-area-16px.png" + + ItemLibraryEntry { + name: "Drop Area" + category: "a.Qt Quick - Basic" + libraryIcon: ":/qtquickplugin/images/drop-area-24px.png" + version: "2.0" + + Property { name: "width"; type: "int"; value: 100; } + Property { name: "height"; type: "int"; value: 100; } + toolTip: qsTr("Sets an area that serves as a drag target.") + } +} + Type { name: "QtQuick.Image" icon: ":/qtquickplugin/images/image-icon16.png" @@ -140,6 +156,26 @@ MetaInfo { } } + Type { + name: "QtQuick.AnimatedSprite" + icon: ":/qtquickplugin/images/animated-image-icon16.png" + + ItemLibraryEntry { + name: "Animated Sprite" + category: "a.Qt Quick - Basic" + libraryIcon: ":/qtquickplugin/images/animated-image-icon.png" + version: "2.0" + + Property { name: "frameWidth"; type: "int"; value: 64; } + Property { name: "frameHeight"; type: "int"; value: 64; } + Property { name: "frameCount"; type: "int"; value: 4; } + Property { name: "frameDuration"; type: "int"; value: 500; } + Property { name: "source"; type: "QUrl"; value:"animatedsprite-loading.png"; } + ExtraFile { source: ":/qtquickplugin/images/animatedsprite-loading.png" } + toolTip: qsTr("Draws a sprite animation.") + } + } + Type { name: "QtQuick.BorderImage" icon: ":/qtquickplugin/images/border-image-icon16.png" diff --git a/src/plugins/qmldesigner/settingspage.cpp b/src/plugins/qmldesigner/settingspage.cpp index 557cf96c8e5..19b329ad283 100644 --- a/src/plugins/qmldesigner/settingspage.cpp +++ b/src/plugins/qmldesigner/settingspage.cpp @@ -4,6 +4,7 @@ #include "settingspage.h" #include "designersettings.h" +#include "designmodewidget.h" #include "qmldesignerexternaldependencies.h" #include "qmldesignerplugin.h" @@ -81,6 +82,7 @@ private: QCheckBox *m_askBeforeDeletingAssetCheckBox; QCheckBox *m_alwaysAutoFormatUICheckBox; QCheckBox *m_featureTimelineEditorCheckBox; + QCheckBox *m_featureDockWidgetContentMinSize; QGroupBox *m_debugGroupBox; QCheckBox *m_designerShowDebuggerCheckBox; QCheckBox *m_showPropertyEditorWarningsCheckBox; @@ -182,6 +184,7 @@ SettingsPageWidget::SettingsPageWidget(ExternalDependencies &externalDependencie m_askBeforeDeletingAssetCheckBox = new QCheckBox(tr("Ask for confirmation before deleting asset")); m_alwaysAutoFormatUICheckBox = new QCheckBox(tr("Always auto-format ui.qml files in Design mode")); m_featureTimelineEditorCheckBox = new QCheckBox(tr("Enable Timeline editor")); + m_featureDockWidgetContentMinSize = new QCheckBox(tr("Enable DockWidget content minimum size")); m_debugGroupBox = new QGroupBox(tr("Debugging")); m_designerShowDebuggerCheckBox = new QCheckBox(tr("Show the debugging view")); @@ -219,71 +222,51 @@ SettingsPageWidget::SettingsPageWidget(ExternalDependencies &externalDependencie Form { tr("Debug QML emulation layer:"), m_debugPuppetComboBox } }.attachTo(m_debugGroupBox); - Column { - Row { - Group { - title(tr("Snapping")), - Form { - tr("Parent component padding:"), m_spinSnapMargin, br, - tr("Sibling component spacing:"), m_spinItemSpacing - } - }, - Group { - title(tr("Canvas")), - Form { - tr("Width:"), m_spinCanvasWidth, br, - tr("Height:"), m_spinCanvasHeight, br, - tr("Smooth rendering:"), m_smoothRendering - } - }, - Group { - title(tr("Root Component Init Size")), - Form { - tr("Width:"), m_spinRootItemInitWidth, br, - tr("Height:"), m_spinRootItemInitHeight - } - }, - Group { - title(tr("Styling")), - Form { - tr("Controls style:"), m_styleLineEdit, resetStyle, br, - tr("Controls 2 style:"), m_controls2StyleComboBox - } - } - }, - m_emulationGroupBox, - Group { - title(tr("Subcomponents")), - Column { m_alwaysSaveSubcomponentsCheckBox } - }, - Row { - Group { - title(tr("Warnings")), - Column { - m_designerWarningsCheckBox, - m_designerWarningsInEditorCheckBox, - m_designerWarningsUiQmlfiles - } - }, - Group { - title(tr("Internationalization")), - Column { - m_useQsTrFunctionRadioButton, - m_useQsTrIdFunctionRadioButton, - m_useQsTranslateFunctionRadioButton - } - } - }, - Group { - title(tr("Features")), - Grid { - m_designerAlwaysDesignModeCheckBox, m_alwaysAutoFormatUICheckBox, br, - m_askBeforeDeletingAssetCheckBox, m_featureTimelineEditorCheckBox - } - }, - m_debugGroupBox, - st - }.attachTo(this); + Column{Row{Group{title(tr("Snapping")), + Form{tr("Parent component padding:"), + m_spinSnapMargin, + br, + tr("Sibling component spacing:"), + m_spinItemSpacing}}, + Group{title(tr("Canvas")), + Form{tr("Width:"), + m_spinCanvasWidth, + br, + tr("Height:"), + m_spinCanvasHeight, + br, + tr("Smooth rendering:"), + m_smoothRendering}}, + Group{title(tr("Root Component Init Size")), + Form{tr("Width:"), m_spinRootItemInitWidth, br, tr("Height:"), m_spinRootItemInitHeight}}, + Group{title(tr("Styling")), + Form{tr("Controls style:"), + m_styleLineEdit, + resetStyle, + br, + tr("Controls 2 style:"), + m_controls2StyleComboBox}}}, + m_emulationGroupBox, + Group{title(tr("Subcomponents")), Column{m_alwaysSaveSubcomponentsCheckBox}}, + Row{Group{title(tr("Warnings")), + Column{m_designerWarningsCheckBox, + m_designerWarningsInEditorCheckBox, + m_designerWarningsUiQmlfiles}}, + Group{title(tr("Internationalization")), + Column{m_useQsTrFunctionRadioButton, + m_useQsTrIdFunctionRadioButton, + m_useQsTranslateFunctionRadioButton}}}, + Group{title(tr("Features")), + Grid{m_designerAlwaysDesignModeCheckBox, + m_alwaysAutoFormatUICheckBox, + br, + m_askBeforeDeletingAssetCheckBox, + m_featureTimelineEditorCheckBox, + br, + m_featureDockWidgetContentMinSize}}, + m_debugGroupBox, + st} + .attachTo(this); connect(m_designerEnableDebuggerCheckBox, &QCheckBox::toggled, [=](bool checked) { if (checked && ! m_designerShowDebuggerCheckBox->isChecked()) @@ -307,9 +290,15 @@ SettingsPageWidget::SettingsPageWidget(ExternalDependencies &externalDependencie connect(resetStyle, &QPushButton::clicked, m_styleLineEdit, &QLineEdit::clear); connect(m_controls2StyleComboBox, &QComboBox::currentTextChanged, [=]() { - m_styleLineEdit->setText(m_controls2StyleComboBox->currentText()); - } - ); + m_styleLineEdit->setText(m_controls2StyleComboBox->currentText()); + }); + + connect(m_featureDockWidgetContentMinSize, &QCheckBox::toggled, this, [=](bool checked) { + if (checked && !m_featureDockWidgetContentMinSize->isChecked()) + m_featureDockWidgetContentMinSize->setChecked(true); + + QmlDesignerPlugin::instance()->mainWidget()->setMinimumSizeHintFromContentMinimumSize(checked); + }); m_forwardPuppetOutputComboBox->addItems(puppetModes()); m_debugPuppetComboBox->addItems(puppetModes()); @@ -389,6 +378,8 @@ QHash SettingsPageWidget::newSettings() const m_showWarnExceptionsCheckBox->isChecked()); settings.insert(DesignerSettingsKey::ENABLE_TIMELINEVIEW, m_featureTimelineEditorCheckBox->isChecked()); + settings.insert(DesignerSettingsKey::ENABLE_DOCKWIDGET_CONTENT_MIN_SIZE, + m_featureDockWidgetContentMinSize->isChecked()); settings.insert(DesignerSettingsKey::ALWAYS_DESIGN_MODE, m_designerAlwaysDesignModeCheckBox->isChecked()); settings.insert(DesignerSettingsKey::ASK_BEFORE_DELETING_ASSET, @@ -468,8 +459,11 @@ void SettingsPageWidget::setSettings(const DesignerSettings &settings) DesignerSettingsKey::ALWAYS_DESIGN_MODE).toBool()); m_featureTimelineEditorCheckBox->setChecked(settings.value( DesignerSettingsKey::ENABLE_TIMELINEVIEW).toBool()); - m_askBeforeDeletingAssetCheckBox->setChecked(settings.value( - DesignerSettingsKey::ASK_BEFORE_DELETING_ASSET).toBool()); + m_featureDockWidgetContentMinSize->setChecked( + settings.value(DesignerSettingsKey::ENABLE_DOCKWIDGET_CONTENT_MIN_SIZE).toBool()); + + m_askBeforeDeletingAssetCheckBox->setChecked( + settings.value(DesignerSettingsKey::ASK_BEFORE_DELETING_ASSET).toBool()); #ifdef QT_DEBUG const auto showDebugSettings = true; @@ -480,6 +474,7 @@ void SettingsPageWidget::setSettings(const DesignerSettings &settings) m_emulationGroupBox->setVisible(showAdvancedFeatures); m_debugGroupBox->setVisible(showAdvancedFeatures); m_featureTimelineEditorCheckBox->setVisible(Core::ICore::isQtDesignStudio()); + m_featureDockWidgetContentMinSize->setVisible(Core::ICore::isQtDesignStudio()); m_smoothRendering->setChecked(settings.value(DesignerSettingsKey::SMOOTH_RENDERING).toBool()); m_alwaysAutoFormatUICheckBox->setChecked( @@ -490,16 +485,13 @@ void SettingsPageWidget::apply() { auto settings = newSettings(); - const auto restartNecessaryKeys = { - DesignerSettingsKey::PUPPET_DEFAULT_DIRECTORY, - DesignerSettingsKey::PUPPET_TOPLEVEL_BUILD_DIRECTORY, - DesignerSettingsKey::ENABLE_MODEL_EXCEPTION_OUTPUT, - DesignerSettingsKey::PUPPET_KILL_TIMEOUT, - DesignerSettingsKey::FORWARD_PUPPET_OUTPUT, - DesignerSettingsKey::DEBUG_PUPPET, - DesignerSettingsKey::ENABLE_MODEL_EXCEPTION_OUTPUT, - DesignerSettingsKey::ENABLE_TIMELINEVIEW - }; + const auto restartNecessaryKeys = {DesignerSettingsKey::PUPPET_DEFAULT_DIRECTORY, + DesignerSettingsKey::PUPPET_TOPLEVEL_BUILD_DIRECTORY, + DesignerSettingsKey::ENABLE_MODEL_EXCEPTION_OUTPUT, + DesignerSettingsKey::FORWARD_PUPPET_OUTPUT, + DesignerSettingsKey::DEBUG_PUPPET, + DesignerSettingsKey::ENABLE_MODEL_EXCEPTION_OUTPUT, + DesignerSettingsKey::ENABLE_TIMELINEVIEW}; for (const char * const key : restartNecessaryKeys) { if (QmlDesignerPlugin::settings().value(key) != settings.value(key)) { diff --git a/src/plugins/qmldesigner/utils/fileextractor.cpp b/src/plugins/qmldesigner/utils/fileextractor.cpp index 11827fca021..40381c5a27a 100644 --- a/src/plugins/qmldesigner/utils/fileextractor.cpp +++ b/src/plugins/qmldesigner/utils/fileextractor.cpp @@ -70,10 +70,14 @@ FileExtractor::FileExtractor(QObject *parent) }); } -FileExtractor::~FileExtractor() {} +FileExtractor::~FileExtractor() +{ + removeTempTargetPath(); +} void FileExtractor::changeTargetPath(const QString &path) { + removeTempTargetPath(); m_targetPath = FilePath::fromString(path); emit targetPathChanged(); emit targetFolderExistsChanged(); @@ -86,6 +90,7 @@ QString FileExtractor::targetPath() const void FileExtractor::setTargetPath(const QString &path) { + removeTempTargetPath(); m_targetPath = FilePath::fromString(path); QDir dir(m_targetPath.toString()); @@ -101,8 +106,10 @@ void FileExtractor::browse() { const FilePath path = FileUtils::getExistingDirectory(nullptr, tr("Choose Directory"), m_targetPath); - if (!path.isEmpty()) + if (!path.isEmpty()) { + removeTempTargetPath(); m_targetPath = path; + } emit targetPathChanged(); emit targetFolderExistsChanged(); @@ -203,6 +210,7 @@ void FileExtractor::extract() QString tempFileName = QDir::tempPath() + "/.qds_" + uniqueText + "_extract_" + m_archiveName + "_dir"; m_targetPath = FilePath::fromString(tempFileName); + m_isTempTargetPath = true; } m_targetFolder = m_targetPath.toString() + "/" + m_archiveName; @@ -250,4 +258,12 @@ void FileExtractor::extract() m_unarchiver->start(); } +void QmlDesigner::FileExtractor::removeTempTargetPath() +{ + if (m_isTempTargetPath && m_targetPath.exists()) { + m_targetPath.removeRecursively(); + m_isTempTargetPath = false; + } +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/utils/fileextractor.h b/src/plugins/qmldesigner/utils/fileextractor.h index c02237fab4f..8cee7b34c42 100644 --- a/src/plugins/qmldesigner/utils/fileextractor.h +++ b/src/plugins/qmldesigner/utils/fileextractor.h @@ -72,6 +72,8 @@ signals: void alwaysCreateDirChanged(); private: + void removeTempTargetPath(); + Utils::FilePath m_targetPath; QString m_targetFolder; // The same as m_targetPath, but with the archive name also. Utils::FilePath m_sourceFile; @@ -86,6 +88,7 @@ private: QDateTime m_birthTime; bool m_clearTargetPathContents = false; bool m_alwaysCreateDir = false; + bool m_isTempTargetPath = false; qint64 m_bytesBefore = 0; qint64 m_compressedSize = 0; diff --git a/src/plugins/qmldesignerbase/CMakeLists.txt b/src/plugins/qmldesignerbase/CMakeLists.txt index 5f5f1ef258b..40fe8bbb134 100644 --- a/src/plugins/qmldesignerbase/CMakeLists.txt +++ b/src/plugins/qmldesignerbase/CMakeLists.txt @@ -29,6 +29,7 @@ extend_qtc_plugin(QmlDesignerBase designerpaths.cpp designerpaths.h designersettings.cpp designersettings.h qmlpuppetpaths.cpp qmlpuppetpaths.h + windowmanager.cpp windowmanager.h ) extend_qtc_plugin(QmlDesignerBase diff --git a/src/plugins/qmldesignerbase/studio/studioquickwidget.cpp b/src/plugins/qmldesignerbase/studio/studioquickwidget.cpp index b2c55c5b61d..15baebecfaf 100644 --- a/src/plugins/qmldesignerbase/studio/studioquickwidget.cpp +++ b/src/plugins/qmldesignerbase/studio/studioquickwidget.cpp @@ -24,6 +24,8 @@ StudioQuickWidget::StudioQuickWidget(QWidget *parent) setLayout(layout); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(m_quickWidget); + + setMinimumSize(QSize(100, 100)); // sensible default } QQmlEngine *StudioQuickWidget::engine() const @@ -50,12 +52,17 @@ void StudioQuickWidget::setSource(const QUrl &url) { m_quickWidget->setSource(url); - if (rootObject()) { - const auto windows = rootObject()->findChildren(); + if (!rootObject()) + return; - for (auto window : windows) { + if (!Core::ICore::dialogParent()->windowHandle()) + return; + + const auto windows = rootObject()->findChildren(); + + for (auto window : windows) { + if (!window->transientParent()) window->setTransientParent(Core::ICore::dialogParent()->windowHandle()); - } } } @@ -84,6 +91,11 @@ QQuickWidget *StudioQuickWidget::quickWidget() const return m_quickWidget; } +void StudioQuickWidget::registerDeclarativeType() +{ + qmlRegisterType("StudioHelpers", 1, 0, "ColorBackend"); +} + StudioPropertyMap::StudioPropertyMap(QObject *parent) : QQmlPropertyMap(parent) {} diff --git a/src/plugins/qmldesignerbase/studio/studioquickwidget.h b/src/plugins/qmldesignerbase/studio/studioquickwidget.h index 165db562e21..5c4bad8dae3 100644 --- a/src/plugins/qmldesignerbase/studio/studioquickwidget.h +++ b/src/plugins/qmldesignerbase/studio/studioquickwidget.h @@ -9,6 +9,45 @@ #include #include +class QMLDESIGNERBASE_EXPORT StudioQmlColorBackend : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) + +public: + explicit StudioQmlColorBackend(QObject *parent = nullptr) + : QObject(parent) + {} + + void setColor(const QColor &value) + { + if (m_color == value) + return; + + m_color = value; + emit colorChanged(); + } + + QColor color() const { return m_color; } + + Q_INVOKABLE void activateColor(const QColor &value) + { + if (m_color == value) + return; + + setColor(value); + emit activated(value); + } + +signals: + void colorChanged(); + void activated(const QColor &value); + +private: + QColor m_color = Qt::red; +}; + class QMLDESIGNERBASE_EXPORT StudioQmlTextBackend : public QObject { Q_OBJECT @@ -169,6 +208,8 @@ public: StudioPropertyMap *registerPropertyMap(const QByteArray &name); QQuickWidget *quickWidget() const; + static void registerDeclarativeType(); + signals: void adsFocusChanged(); diff --git a/src/plugins/qmldesignerbase/utils/designersettings.cpp b/src/plugins/qmldesignerbase/utils/designersettings.cpp index 87248bbc207..af5bd36fc56 100644 --- a/src/plugins/qmldesignerbase/utils/designersettings.cpp +++ b/src/plugins/qmldesignerbase/utils/designersettings.cpp @@ -77,6 +77,7 @@ void DesignerSettings::fromSettings(QtcSettings *settings) restoreValue(settings, DesignerSettingsKey::NAVIGATOR_SHOW_ONLY_VISIBLE_ITEMS, true); restoreValue(settings, DesignerSettingsKey::NAVIGATOR_REVERSE_ITEM_ORDER, false); restoreValue(settings, DesignerSettingsKey::ENABLE_TIMELINEVIEW, true); + restoreValue(settings, DesignerSettingsKey::ENABLE_DOCKWIDGET_CONTENT_MIN_SIZE, true); restoreValue(settings, DesignerSettingsKey::COLOR_PALETTE_RECENT, QStringList()); restoreValue(settings, DesignerSettingsKey::COLOR_PALETTE_FAVORITE, QStringList()); restoreValue(settings, DesignerSettingsKey::ALWAYS_DESIGN_MODE, true); diff --git a/src/plugins/qmldesignerbase/utils/designersettings.h b/src/plugins/qmldesignerbase/utils/designersettings.h index aaeec55ec37..7bb82555fc5 100644 --- a/src/plugins/qmldesignerbase/utils/designersettings.h +++ b/src/plugins/qmldesignerbase/utils/designersettings.h @@ -57,6 +57,7 @@ inline constexpr char IGNORE_DEVICE_PIXEL_RATIO[] = "IgnoreDevicePixelRaio"; /* The settings can be used to turn off the feature, if there are serious issues */ inline constexpr char SHOW_DEBUG_SETTINGS[] = "ShowDebugSettings"; inline constexpr char ENABLE_TIMELINEVIEW[] = "EnableTimelineView"; +inline constexpr char ENABLE_DOCKWIDGET_CONTENT_MIN_SIZE[] = "EnableDockWidgetContentMinSize"; inline constexpr char COLOR_PALETTE_RECENT[] = "ColorPaletteRecent"; inline constexpr char COLOR_PALETTE_FAVORITE[] = "ColorPaletteFavorite"; inline constexpr char ALWAYS_DESIGN_MODE[] = "AlwaysDesignMode"; diff --git a/src/plugins/qmldesignerbase/utils/windowmanager.cpp b/src/plugins/qmldesignerbase/utils/windowmanager.cpp new file mode 100644 index 00000000000..c52d5d469a6 --- /dev/null +++ b/src/plugins/qmldesignerbase/utils/windowmanager.cpp @@ -0,0 +1,46 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "windowmanager.h" + +#include + +#include +#include +#include +#include + +namespace QmlDesignerBase { + +QPointer WindowManager::m_instance = nullptr; + +WindowManager::WindowManager() +{ + connect(qGuiApp, &QGuiApplication::focusWindowChanged, this, &WindowManager::focusWindowChanged); + connect(qGuiApp, &QGuiApplication::aboutToQuit, this, &WindowManager::aboutToQuit); + connect(Core::ICore::instance()->mainWindow()->windowHandle(), + &QWindow::visibleChanged, + this, + &WindowManager::mainWindowVisibleChanged); +} + +void WindowManager::registerDeclarativeType() +{ + [[maybe_unused]] static const int typeIndex + = qmlRegisterSingletonType("StudioWindowManager", + 1, + 0, + "WindowManager", + [](QQmlEngine *, QJSEngine *) { + return new WindowManager(); + }); +} + +WindowManager::~WindowManager() {} + +QPoint WindowManager::globalCursorPosition() +{ + return QCursor::pos(); +} + +} // namespace QmlDesignerBase diff --git a/src/plugins/qmldesignerbase/utils/windowmanager.h b/src/plugins/qmldesignerbase/utils/windowmanager.h new file mode 100644 index 00000000000..3d8e692c1aa --- /dev/null +++ b/src/plugins/qmldesignerbase/utils/windowmanager.h @@ -0,0 +1,40 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "../qmldesignerbase_global.h" + +#include +#include + +QT_FORWARD_DECLARE_CLASS(QWindow) + +namespace QmlDesignerBase { + +class QMLDESIGNERBASE_EXPORT WindowManager : public QObject +{ + Q_OBJECT + +public: + ~WindowManager(); + + WindowManager(const WindowManager &) = delete; + void operator=(const WindowManager &) = delete; + + static void registerDeclarativeType(); + + Q_INVOKABLE QPoint globalCursorPosition(); + +signals: + void focusWindowChanged(QWindow *window); + void aboutToQuit(); + void mainWindowVisibleChanged(bool value); + +private: + WindowManager(); + + static QPointer m_instance; +}; + +} // namespace QmlDesignerBase diff --git a/src/plugins/qmlprojectmanager/buildsystem/projectitem/converters.cpp b/src/plugins/qmlprojectmanager/buildsystem/projectitem/converters.cpp index c77189a8b6c..5cafae5660f 100644 --- a/src/plugins/qmlprojectmanager/buildsystem/projectitem/converters.cpp +++ b/src/plugins/qmlprojectmanager/buildsystem/projectitem/converters.cpp @@ -3,28 +3,20 @@ #include "converters.h" -#include +#include namespace QmlProjectManager::Converters { -using PropsPair = QPair; -struct FileProps -{ - const PropsPair image{"image", - QStringList{"*.jpeg", "*.jpg", "*.png", "*.svg", "*.hdr", ".ktx"}}; - const PropsPair qml{"qml", QStringList{"*.qml"}}; - const PropsPair qmlDir{"qmldir", QStringList{"qmldir"}}; - const PropsPair javaScr{"javaScript", QStringList{"*.js", "*.ts"}}; - const PropsPair video{"video", QStringList{"*.mp4"}}; - const PropsPair sound{"sound", QStringList{"*.mp3", "*.wav"}}; - const PropsPair font{"font", QStringList{"*.ttf", "*.otf"}}; - const PropsPair config{"config", QStringList{"*.conf"}}; - const PropsPair styling{"styling", QStringList{"*.css"}}; - const PropsPair mesh{"meshes", QStringList{"*.mesh"}}; - const PropsPair - shader{"shader", - QStringList{"*.glsl", "*.glslv", "*.glslf", "*.vsh", "*.fsh", "*.vert", "*.frag"}}; -}; +const static QStringList qmlFilesFilter{QStringLiteral("*.qml")}; +const static QStringList javaScriptFilesFilter{QStringLiteral("*.js"), QStringLiteral("*.ts")}; +const static QStringList imageFilesFilter{QStringLiteral("*.jpeg"), + QStringLiteral("*.jpg"), + QStringLiteral("*.png"), + QStringLiteral("*.svg"), + QStringLiteral("*.hdr"), + QStringLiteral("*.ktx")}; + +QString jsonValueToString(const QJsonValue &val, int indentationLevel, bool indented); QString jsonToQmlProject(const QJsonObject &rootObject) { @@ -37,7 +29,12 @@ QString jsonToQmlProject(const QJsonObject &rootObject) QJsonObject versionConfig = rootObject["versions"].toObject(); QJsonObject environmentConfig = rootObject["environment"].toObject(); QJsonObject deploymentConfig = rootObject["deployment"].toObject(); - QJsonObject filesConfig = rootObject["fileGroups"].toObject(); + QJsonArray filesConfig = rootObject["fileGroups"].toArray(); + QJsonObject otherProperties = rootObject["otherProperties"].toObject(); + + QJsonObject mcuObject = rootObject["mcu"].toObject(); + QJsonObject mcuConfig = mcuObject["config"].toObject(); + QJsonObject mcuModule = mcuObject["module"].toObject(); int indentationLevel = 0; @@ -54,6 +51,8 @@ QString jsonToQmlProject(const QJsonObject &rootObject) }; auto appendString = [&appendItem](const QString &key, const QString &val) { + if (val.isEmpty()) + return; appendItem(key, val, true); }; @@ -61,15 +60,39 @@ QString jsonToQmlProject(const QJsonObject &rootObject) appendItem(key, QString::fromStdString(val ? "true" : "false"), false); }; - auto appendArray = [&appendItem](const QString &key, const QStringList &vals) { + auto appendStringArray = [&appendItem](const QString &key, const QStringList &vals) { + if (vals.isEmpty()) + return; QString finalString; for (const QString &value : vals) finalString.append("\"").append(value).append("\"").append(","); + finalString.remove(finalString.length() - 1, 1); finalString.prepend("[ ").append(" ]"); appendItem(key, finalString, false); }; + auto appendJsonArray = [&appendItem, &indentationLevel](const QString &key, + const QJsonArray &vals) { + if (vals.isEmpty()) + return; + appendItem(key, jsonValueToString(vals, indentationLevel, /*indented*/ true), false); + }; + + auto appendProperties = [&appendItem, &indentationLevel](const QJsonObject &props, + const QString &prefix) { + for (const auto &key : props.keys()) { + QJsonValue val = props[key]; + QString keyWithPrefix = key; + if (!prefix.isEmpty()) { + keyWithPrefix.prepend(prefix + "."); + } + appendItem(keyWithPrefix, + jsonValueToString(val, indentationLevel, /*indented*/ false), + false); + } + }; + auto startObject = [&ts, &indentationLevel](const QString &objectName) { ts << Qt::endl << QString(" ").repeated(indentationLevel * 4) << objectName << " {" << Qt::endl; @@ -81,42 +104,31 @@ QString jsonToQmlProject(const QJsonObject &rootObject) ts << QString(" ").repeated(indentationLevel * 4) << "}" << Qt::endl; }; - auto appendDirectories = - [&startObject, &endObject, &appendString, &filesConfig](const QString &jsonKey, - const QString &qmlKey) { - QJsonValue dirsObj = filesConfig[jsonKey].toObject()["directories"]; - const QStringList dirs = dirsObj.toVariant().toStringList(); - for (const QString &directory : dirs) { - startObject(qmlKey); - appendString("directory", directory); - endObject(); - } - }; - - auto appendFiles = [&startObject, - &endObject, - &appendString, - &appendArray, - &filesConfig](const QString &jsonKey, const QString &qmlKey) { - QJsonValue dirsObj = filesConfig[jsonKey].toObject()["directories"]; - QJsonValue filesObj = filesConfig[jsonKey].toObject()["files"]; - QJsonValue filtersObj = filesConfig[jsonKey].toObject()["filters"]; - - const QStringList directories = dirsObj.toVariant().toStringList(); - for (const QString &directory : directories) { - startObject(qmlKey); - appendString("directory", directory); - appendString("filters", filtersObj.toVariant().toStringList().join(";")); - - if (!filesObj.toArray().isEmpty()) { - QStringList fileList; - const QJsonArray files = filesObj.toArray(); - for (const QJsonValue &file : files) - fileList.append(file.toObject()["name"].toString()); - appendArray("files", fileList); - } - endObject(); + auto appendFileGroup = [&startObject, + &endObject, + &appendString, + &appendProperties, + &appendJsonArray](const QJsonObject &fileGroup, + const QString &nodeName) { + startObject(nodeName); + appendString("directory", fileGroup["directory"].toString()); + QStringList filters = fileGroup["filters"].toVariant().toStringList(); + QStringList filter = {}; + if (nodeName.toLower() == "qmlfiles") { + filter = qmlFilesFilter; + } else if (nodeName.toLower() == "imagefiles") { + filter = imageFilesFilter; + } else if (nodeName.toLower() == "javascriptfiles") { + filter = javaScriptFilesFilter; } + for (const QString &entry : filter) { + filters.removeOne(entry); + } + appendString("filter", filters.join(";")); + appendJsonArray("files", fileGroup["files"].toArray()); + appendProperties(fileGroup["mcuProperties"].toObject(), "MCU"); + appendProperties(fileGroup["otherProperties"].toObject(), ""); + endObject(); }; // start creating the file content @@ -127,54 +139,78 @@ QString jsonToQmlProject(const QJsonObject &rootObject) { startObject("Project"); - { // append non-object props - appendString("mainFile", runConfig["mainFile"].toString()); - appendString("mainUiFile", runConfig["mainUiFile"].toString()); - appendString("targetDirectory", deploymentConfig["targetDirectory"].toString()); - appendBool("widgetApp", runConfig["widgetApp"].toBool()); - appendArray("importPaths", rootObject["importPaths"].toVariant().toStringList()); - appendBreak(); - appendString("qdsVersion", versionConfig["designStudio"].toString()); - appendString("quickVersion", versionConfig["qtQuick"].toString()); - appendBool("qt6Project", versionConfig["qt"].toString() == "6"); - appendBool("qtForMCUs", !(rootObject["mcuConfig"].toObject().isEmpty())); + // append non-object props + appendString("mainFile", runConfig["mainFile"].toString()); + appendString("mainUiFile", runConfig["mainUiFile"].toString()); + appendString("targetDirectory", deploymentConfig["targetDirectory"].toString()); + appendBool("widgetApp", runConfig["widgetApp"].toBool()); + appendStringArray("importPaths", rootObject["importPaths"].toVariant().toStringList()); + appendBreak(); + appendString("qdsVersion", versionConfig["designStudio"].toString()); + appendString("quickVersion", versionConfig["qtQuick"].toString()); + appendBool("qt6Project", versionConfig["qt"].toString() == "6"); + appendBool("qtForMCUs", + mcuObject["enabled"].toBool() || !mcuConfig.isEmpty() || !mcuModule.isEmpty()); + if (!languageConfig.isEmpty()) { appendBreak(); appendBool("multilanguageSupport", languageConfig["multiLanguageSupport"].toBool()); appendString("primaryLanguage", languageConfig["primaryLanguage"].toString()); - appendArray("supportedLanguages", - languageConfig["supportedLanguages"].toVariant().toStringList()); + appendStringArray("supportedLanguages", + languageConfig["supportedLanguages"].toVariant().toStringList()); } - { // append Environment object + // Since different versions of Qt for MCUs may introduce new properties, we collect all + // unknown properties in a separate object. + // We need to avoid losing content regardless of which QDS/QUL version combo is used. + if (!otherProperties.isEmpty()) { + appendBreak(); + appendProperties(otherProperties, ""); + } + + // append Environment object + if (!environmentConfig.isEmpty()) { startObject("Environment"); - const QStringList keys = environmentConfig.keys(); - for (const QString &key : keys) + for (const QString &key : environmentConfig.keys()) { appendItem(key, environmentConfig[key].toString(), true); + } endObject(); } - { // append ShaderTool object - if (!shaderConfig["args"].toVariant().toStringList().isEmpty()) { - startObject("ShaderTool"); - appendString("args", - shaderConfig["args"].toVariant().toStringList().join(" ").replace( - "\"", "\\\"")); - appendArray("files", shaderConfig["files"].toVariant().toStringList()); - endObject(); - } + // append ShaderTool object + if (!shaderConfig["args"].toVariant().toStringList().isEmpty()) { + startObject("ShaderTool"); + appendString("args", + shaderConfig["args"].toVariant().toStringList().join(" ").replace("\"", + "\\\"")); + appendStringArray("files", shaderConfig["files"].toVariant().toStringList()); + endObject(); } - { // append files objects - appendDirectories("qml", "QmlFiles"); - appendDirectories("javaScript", "JavaScriptFiles"); - appendDirectories("image", "ImageFiles"); - appendFiles("config", "Files"); - appendFiles("font", "Files"); - appendFiles("meshes", "Files"); - appendFiles("qmldir", "Files"); - appendFiles("shader", "Files"); - appendFiles("sound", "Files"); - appendFiles("video", "Files"); + // append the MCU.Config object + if (!mcuConfig.isEmpty()) { + // Append MCU.Config + startObject("MCU.Config"); + appendProperties(mcuConfig, ""); + endObject(); + } + + // Append the MCU.Module object + if (!mcuModule.isEmpty()) { + // Append MCU.Module + startObject("MCU.Module"); + appendProperties(mcuModule, ""); + endObject(); + } + + // append files objects + for (const QJsonValue &fileGroup : filesConfig) { + QString nodeType = QString("%1Files").arg(fileGroup["type"].toString()); + if (fileGroup["type"].toString().isEmpty() + && fileGroup["filters"].toArray().contains("*.qml")) { + // TODO: IS this important? It turns Files node with *.qml in the filters into QmlFiles nodes + nodeType = "QmlFiles"; + } + appendFileGroup(fileGroup.toObject(), nodeType); } endObject(); // Closing 'Project' @@ -201,22 +237,27 @@ QJsonObject qmlProjectTojson(const Utils::FilePath &projectFile) auto nodeToJsonObject = [](const QmlJS::SimpleReaderNode::Ptr &node) { QJsonObject tObj; - const QStringList childPropNames = node->propertyNames(); - for (const QString &childPropName : childPropNames) + for (const QString &childPropName : node->propertyNames()) tObj.insert(childPropName, node->property(childPropName).value.toJsonValue()); + return tObj; }; auto toCamelCase = [](const QString &s) { return QString(s).replace(0, 1, s[0].toLower()); }; QJsonObject rootObject; // root object - QJsonObject fileGroupsObject; + QJsonArray fileGroupsObject; QJsonObject languageObject; QJsonObject versionObject; QJsonObject runConfigObject; QJsonObject deploymentObject; QJsonObject mcuObject; + QJsonObject mcuConfigObject; + QJsonObject mcuModuleObject; QJsonObject shaderToolObject; + QJsonObject otherProperties; + + bool qtForMCUs = false; // convert the non-object props for (const QString &propName : rootNode->propertyNames()) { @@ -224,10 +265,7 @@ QJsonObject qmlProjectTojson(const Utils::FilePath &projectFile) QString objKey = QString(propName).remove("QDS.", Qt::CaseInsensitive); QJsonValue value = rootNode->property(propName).value.toJsonValue(); - if (propName.startsWith("mcu.", Qt::CaseInsensitive)) { - currentObj = &mcuObject; - objKey = QString(propName).remove("MCU."); - } else if (propName.contains("language", Qt::CaseInsensitive)) { + if (propName.contains("language", Qt::CaseInsensitive)) { currentObj = &languageObject; if (propName.contains("multilanguagesupport", Qt::CaseInsensitive)) // fixing the camelcase @@ -247,12 +285,17 @@ QJsonObject qmlProjectTojson(const Utils::FilePath &projectFile) } else if (propName.contains("targetdirectory", Qt::CaseInsensitive)) { currentObj = &deploymentObject; } else if (propName.contains("qtformcus", Qt::CaseInsensitive)) { - currentObj = &mcuObject; - objKey = "mcuEnabled"; + qtForMCUs = value.toBool(); + continue; } else if (propName.contains("qt6project", Qt::CaseInsensitive)) { currentObj = &versionObject; objKey = "qt"; value = rootNode->property(propName).value.toBool() ? "6" : "5"; + } else if (propName.contains("importpaths", Qt::CaseInsensitive)) { + objKey = "importPaths"; + } else { + currentObj = &otherProperties; + objKey = propName; // With prefix } currentObj->insert(objKey, value); @@ -267,85 +310,77 @@ QJsonObject qmlProjectTojson(const Utils::FilePath &projectFile) versionObject.insert("qt", "5"); } + rootObject.insert("otherProperties", otherProperties); + // convert the object props for (const QmlJS::SimpleReaderNode::Ptr &childNode : rootNode->children()) { if (childNode->name().contains("files", Qt::CaseInsensitive)) { - PropsPair propsPair; - FileProps fileProps; - const QString childNodeName = childNode->name().toLower().remove("qds."); - const QmlJS::SimpleReaderNode::Property childNodeFilter = childNode->property("filter"); - const QmlJS::SimpleReaderNode::Property childNodeDirectory = childNode->property( - "directory"); - const QmlJS::SimpleReaderNode::Property childNodeFiles = childNode->property("files"); - const QString childNodeFilterValue = childNodeFilter.value.toString(); + QString childNodeName = childNode->name().remove("qds.", Qt::CaseInsensitive); + qsizetype filesPos = childNodeName.indexOf("files", 0, Qt::CaseInsensitive); + const QString childNodeType = childNodeName.first(filesPos); + childNodeName = childNodeName.toLower(); - if (childNodeName == "qmlfiles" || childNodeFilterValue.contains("*.qml")) { - propsPair = fileProps.qml; - } else if (childNodeName == "javascriptfiles") { - propsPair = fileProps.javaScr; - } else if (childNodeName == "imagefiles") { - propsPair = fileProps.image; - } else { - if (childNodeFilter.isValid()) { - if (childNodeFilterValue.contains(".conf")) - propsPair = fileProps.config; - else if (childNodeFilterValue.contains(".ttf")) - propsPair = fileProps.font; - else if (childNodeFilterValue.contains("qmldir")) - propsPair = fileProps.qmlDir; - else if (childNodeFilterValue.contains(".wav")) - propsPair = fileProps.sound; - else if (childNodeFilterValue.contains(".mp4")) - propsPair = fileProps.video; - else if (childNodeFilterValue.contains(".mesh")) - propsPair = fileProps.mesh; - else if (childNodeFilterValue.contains(".glsl")) - propsPair = fileProps.shader; - else if (childNodeFilterValue.contains(".css")) - propsPair = fileProps.styling; + QJsonArray childNodeFiles = childNode->property("files").value.toJsonArray(); + QString childNodeDirectory = childNode->property("directory").value.toString(); + QStringList filters + = childNode->property("filter").value.toString().split(";", Qt::SkipEmptyParts); + QJsonArray childNodeFilters = QJsonArray::fromStringList(filters); + + // files have priority over filters + // if explicit files are given, then filters will be ignored + // and all files are prefixed such as "directory/". + // if directory is empty, then the files are prefixed with the project directory + if (childNodeFiles.empty()) { + auto inserter = [&childNodeFilters](const QStringList &filterSource) { + std::for_each(filterSource.begin(), + filterSource.end(), + [&childNodeFilters](const auto &value) { + if (!childNodeFilters.contains(value)) { + childNodeFilters << value; + } + }); + }; + + // Those 3 file groups are the special ones + // that have a default set of filters. + // The default filters are written to the + // qmlproject file after conversion + if (childNodeName == "qmlfiles") { + inserter(qmlFilesFilter); + } else if (childNodeName == "javascriptfiles") { + inserter(javaScriptFilesFilter); + } else if (childNodeName == "imagefiles") { + inserter(imageFilesFilter); } } - // get all objects we'll work on - QJsonObject targetObject = fileGroupsObject[propsPair.first].toObject(); - QJsonArray directories = targetObject["directories"].toArray(); - QJsonArray filters = targetObject["filters"].toArray(); - QJsonArray files = targetObject["files"].toArray(); + // create the file group object + QJsonObject targetObject; + targetObject.insert("directory", childNodeDirectory); + targetObject.insert("filters", childNodeFilters); + targetObject.insert("files", childNodeFiles); + targetObject.insert("type", childNodeType); - // populate & update filters - if (filters.isEmpty()) { - filters = QJsonArray::fromStringList( - propsPair.second); // populate the filters with the predefined ones - } + QJsonObject mcuPropertiesObject; + QJsonObject otherPropertiesObject; + for (const auto &propName : childNode->propertyNames()) { + if (propName == "directory" || propName == "filter" || propName == "files") { + continue; + } - if (childNodeFilter.isValid()) { // append filters from qmlproject (merge) - const QStringList filtersFromProjectFile = childNodeFilterValue.split(";"); - for (const QString &filter : filtersFromProjectFile) { - if (!filters.contains(QJsonValue(filter))) { - filters.append(QJsonValue(filter)); - } + auto val = QJsonValue::fromVariant(childNode->property(propName).value); + + if (propName.startsWith("MCU.", Qt::CaseInsensitive)) { + mcuPropertiesObject.insert(propName.mid(4), val); + } else { + otherPropertiesObject.insert(propName, val); } } - // populate & update directories - if (childNodeDirectory.isValid()) { - directories.append(childNodeDirectory.value.toJsonValue()); - } - if (directories.isEmpty()) - directories.append("."); + targetObject.insert("mcuProperties", mcuPropertiesObject); + targetObject.insert("otherProperties", otherPropertiesObject); - // populate & update files - if (childNodeFiles.isValid()) { - const QJsonArray fileList = childNodeFiles.value.toJsonArray(); - for (const QJsonValue &file : fileList) - files.append(QJsonObject{{"name", file.toString()}}); - } - - // put everything back into the root object - targetObject.insert("directories", directories); - targetObject.insert("filters", filters); - targetObject.insert("files", files); - fileGroupsObject.insert(propsPair.first, targetObject); + fileGroupsObject.append(targetObject); } else if (childNode->name().contains("shadertool", Qt::CaseInsensitive)) { QStringList quotedArgs = childNode->property("args").value.toString().split('\"', Qt::SkipEmptyParts); @@ -361,21 +396,51 @@ QJsonObject qmlProjectTojson(const Utils::FilePath &projectFile) shaderToolObject.insert("args", QJsonArray::fromStringList(args)); shaderToolObject.insert("files", childNode->property("files").value.toJsonValue()); + } else if (childNode->name().contains("config", Qt::CaseInsensitive)) { + mcuConfigObject = nodeToJsonObject(childNode); + } else if (childNode->name().contains("module", Qt::CaseInsensitive)) { + mcuModuleObject = nodeToJsonObject(childNode); } else { rootObject.insert(toCamelCase(childNode->name().remove("qds.", Qt::CaseInsensitive)), nodeToJsonObject(childNode)); } } + mcuObject.insert("config", mcuConfigObject); + mcuObject.insert("module", mcuModuleObject); + qtForMCUs |= !(mcuModuleObject.isEmpty() && mcuConfigObject.isEmpty()); + mcuObject.insert("enabled", qtForMCUs); + rootObject.insert("fileGroups", fileGroupsObject); rootObject.insert("language", languageObject); rootObject.insert("versions", versionObject); rootObject.insert("runConfig", runConfigObject); rootObject.insert("deployment", deploymentObject); - rootObject.insert("mcuConfig", mcuObject); + rootObject.insert("mcu", mcuObject); if (!shaderToolObject.isEmpty()) rootObject.insert("shaderTool", shaderToolObject); rootObject.insert("fileVersion", 1); return rootObject; } + +QString jsonValueToString(const QJsonValue &val, int indentationLevel, bool indented) +{ + if (val.isArray()) { + auto jsonFormat = indented ? QJsonDocument::JsonFormat::Indented + : QJsonDocument::JsonFormat::Compact; + QString str = QString::fromUtf8((QJsonDocument(val.toArray()).toJson(jsonFormat))); + if (indented) { + // Strip trailing newline + str.chop(1); + } + return str.replace("\n", QString(" ").repeated(indentationLevel * 4).prepend("\n")); + } else if (val.isBool()) { + return val.toBool() ? QString("true") : QString("false"); + } else if (val.isDouble()) { + return QString("%1").arg(val.toDouble()); + } else { + return val.toString().prepend("\"").append("\""); + } +} + } // namespace QmlProjectManager::Converters diff --git a/src/plugins/qmlprojectmanager/buildsystem/projectitem/qmlprojectitem.cpp b/src/plugins/qmlprojectmanager/buildsystem/projectitem/qmlprojectitem.cpp index 2761caff089..38f27fbc101 100644 --- a/src/plugins/qmlprojectmanager/buildsystem/projectitem/qmlprojectitem.cpp +++ b/src/plugins/qmlprojectmanager/buildsystem/projectitem/qmlprojectitem.cpp @@ -65,19 +65,15 @@ bool QmlProjectItem::initProjectObject() void QmlProjectItem::setupFileFilters() { auto setupFileFilterItem = [this](const QJsonObject &fileGroup) { - for (const QString &directory : fileGroup["directories"].toVariant().toStringList()) { + // first we need to add all directories as a 'resource' path for the project and set them as + // recursive. + // if there're any files with the explicit absolute paths, we need to add them afterwards. + for (const QString &directory : fileGroup["directory"].toVariant().toStringList()) { std::unique_ptr fileFilterItem{new FileFilterItem}; - - QStringList filesArr; - for (const QJsonValue &file : fileGroup["files"].toArray()) { - filesArr.append(file["name"].toString()); - } - fileFilterItem->setDirectory(directory); fileFilterItem->setFilters(fileGroup["filters"].toVariant().toStringList()); - fileFilterItem->setRecursive(fileGroup["recursive"].toBool(true)); - fileFilterItem->setPathsProperty(fileGroup["directories"].toVariant().toStringList()); - fileFilterItem->setPathsProperty(filesArr); + fileFilterItem->setRecursive(true); + fileFilterItem->setDefaultDirectory(m_projectFile.parentDir().toFSPathString()); #ifndef TESTS_ENABLED_QMLPROJECTITEM connect(fileFilterItem.get(), @@ -87,11 +83,38 @@ void QmlProjectItem::setupFileFilters() #endif m_content.push_back(std::move(fileFilterItem)); }; + + // here we begin to add files with the explicit absolute paths + QJsonArray files = fileGroup["files"].toArray(); + if (files.isEmpty()) + return; + + QStringList filesArr; + std::transform(files.begin(), + files.end(), + std::back_inserter(filesArr), + [](const QJsonValue &value) { return value.toString(); }); + + const QString directory = fileGroup["directory"].toString() == "" + ? m_projectFile.parentDir().toString() + : fileGroup["directory"].toString(); + Utils::FilePath groupDir = Utils::FilePath::fromString(directory); + std::unique_ptr fileFilterItem{new FileFilterItem}; + fileFilterItem->setRecursive(false); + fileFilterItem->setPathsProperty(filesArr); + fileFilterItem->setDefaultDirectory(m_projectFile.parentDir().toString()); + fileFilterItem->setDirectory(groupDir.toString()); +#ifndef TESTS_ENABLED_QMLPROJECTITEM + connect(fileFilterItem.get(), + &FileFilterItem::filesChanged, + this, + &QmlProjectItem::qmlFilesChanged); +#endif + m_content.push_back(std::move(fileFilterItem)); }; - QJsonObject fileGroups = m_project["fileGroups"].toObject(); - for (const QString &groupName : fileGroups.keys()) { - setupFileFilterItem(fileGroups[groupName].toObject()); + for (const QJsonValue &fileGroup : m_project["fileGroups"].toArray()) { + setupFileFilterItem(fileGroup.toObject()); } } @@ -107,7 +130,7 @@ QString QmlProjectItem::targetDirectory() const bool QmlProjectItem::isQt4McuProject() const { - return m_project["mcuConfig"].toObject()["mcuEnabled"].toBool(); + return m_project["mcu"].toObject()["enabled"].toBool(); } Utils::EnvironmentItems QmlProjectItem::environment() const diff --git a/src/plugins/qmlprojectmanager/cmakegen/qmlprojectmaincmakelists.tpl b/src/plugins/qmlprojectmanager/cmakegen/qmlprojectmaincmakelists.tpl index 4cd900a31ff..9ca0ace63ba 100644 --- a/src/plugins/qmlprojectmanager/cmakegen/qmlprojectmaincmakelists.tpl +++ b/src/plugins/qmlprojectmanager/cmakegen/qmlprojectmaincmakelists.tpl @@ -5,8 +5,13 @@ option(BUILD_QDS_COMPONENTS "Build design studio components" ON) project(%1 LANGUAGES CXX) -set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/qml) +set(QML_IMPORT_PATH ${QT_QML_OUTPUT_DIRECTORY} + CACHE STRING "Import paths for Qt Creator's code model" + FORCE +) find_package(Qt6 6.2 REQUIRED COMPONENTS Core Gui Qml Quick) @@ -39,7 +44,7 @@ if (LINK_INSIGHT) endif () include(GNUInstallDirs) -install(TARGETS CppExampleApp +install(TARGETS %1 BUNDLE DESTINATION . LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} diff --git a/src/plugins/studiowelcome/CMakeLists.txt b/src/plugins/studiowelcome/CMakeLists.txt index 74c8315507a..c4db40db5e9 100644 --- a/src/plugins/studiowelcome/CMakeLists.txt +++ b/src/plugins/studiowelcome/CMakeLists.txt @@ -1,5 +1,5 @@ add_qtc_plugin(StudioWelcome - CONDITION TARGET Qt::QuickWidgets AND TARGET QmlDesigner + CONDITION TARGET Qt::QuickWidgets AND TARGET QtCreator::QmlDesigner DEPENDS Qt::QuickWidgets Qt::QmlPrivate PLUGIN_DEPENDS Core ProjectExplorer QtSupport QmlDesigner DEFINES STUDIO_QML_PATH="${CMAKE_CURRENT_SOURCE_DIR}/qml/" diff --git a/src/plugins/studiowelcome/studiowelcomeplugin.cpp b/src/plugins/studiowelcome/studiowelcomeplugin.cpp index e01215e3d5c..d9b29ffb951 100644 --- a/src/plugins/studiowelcome/studiowelcomeplugin.cpp +++ b/src/plugins/studiowelcome/studiowelcomeplugin.cpp @@ -221,6 +221,7 @@ public: Q_PROPERTY(bool communityVersion MEMBER m_communityVersion NOTIFY communityVersionChanged) Q_PROPERTY(bool enterpriseVersion MEMBER m_enterpriseVersion NOTIFY enterpriseVersionChanged) + Q_PROPERTY(int count READ count NOTIFY countChanged) explicit ProjectModel(QObject *parent = nullptr); @@ -228,6 +229,8 @@ public: QVariant data(const QModelIndex &index, int role) const override; QHash roleNames() const override; + int count() { return ProjectExplorer::ProjectExplorerPlugin::recentProjects().count(); } + Q_INVOKABLE void createProject() { QTimer::singleShot(0, this, []() { @@ -259,6 +262,33 @@ public: }; } + delayedResetProjects(); + } + + Q_INVOKABLE void removeFromRecentProjects(int row) + { + if (m_blockOpenRecent) + return; + + m_blockOpenRecent = true; + const FilePath projectFile = FilePath::fromVariant( + data(index(row, 0), ProjectModel::FilePathRole)); + + if (projectFile.exists()) + ProjectExplorer::ProjectExplorerPlugin::removeFromRecentProjects(projectFile); + + resetProjects(); + } + + Q_INVOKABLE void clearRecentProjects() + { + if (m_blockOpenRecent) + return; + + m_blockOpenRecent = true; + + ProjectExplorer::ProjectExplorerPlugin::clearRecentProjects(); + resetProjects(); } @@ -317,10 +347,12 @@ public: public slots: void resetProjects(); + void delayedResetProjects(); signals: void communityVersionChanged(); void enterpriseVersionChanged(); + void countChanged(); private: void setupVersion(); @@ -343,7 +375,9 @@ ProjectModel::ProjectModel(QObject *parent) connect(ProjectExplorer::ProjectExplorerPlugin::instance(), &ProjectExplorer::ProjectExplorerPlugin::recentProjectsChanged, this, - &ProjectModel::resetProjects); + &ProjectModel::delayedResetProjects); + + connect(this, &QAbstractListModel::modelReset, this, &ProjectModel::countChanged); setupVersion(); } @@ -410,13 +444,13 @@ static QString tags(const FilePath &projectFilePath) const bool isQt6 = data.contains("qt6Project: true"); const bool isMcu = data.contains("qtForMCUs: true"); - if (isQt6) + if (isMcu) + ret.append("Qt For MCU"); + else if (isQt6) ret.append("Qt 6"); else ret.append("Qt 5"); - if (isMcu) - ret.append("Qt For MCU"); return ret.join(","); } @@ -467,6 +501,13 @@ QHash ProjectModel::roleNames() const } void ProjectModel::resetProjects() +{ + beginResetModel(); + endResetModel(); + m_blockOpenRecent = false; +} + +void ProjectModel::delayedResetProjects() { QTimer::singleShot(2000, this, [this]() { beginResetModel(); diff --git a/src/plugins/texteditor/basefilefind.cpp b/src/plugins/texteditor/basefilefind.cpp index 4bda6757745..8ddadeb7c7e 100644 --- a/src/plugins/texteditor/basefilefind.cpp +++ b/src/plugins/texteditor/basefilefind.cpp @@ -388,7 +388,6 @@ void BaseFileFind::doReplace(const QString &text, const SearchResultItems &items if (!files.isEmpty()) { FadingIndicator::showText(ICore::dialogParent(), Tr::tr("%n occurrences replaced.", nullptr, items.size()), FadingIndicator::SmallText); - DocumentManager::notifyFilesChangedInternally(files); SearchResultWindow::instance()->hide(); } } diff --git a/src/plugins/texteditor/refactoringchanges.cpp b/src/plugins/texteditor/refactoringchanges.cpp index 8a14376f87c..e9016d3cb60 100644 --- a/src/plugins/texteditor/refactoringchanges.cpp +++ b/src/plugins/texteditor/refactoringchanges.cpp @@ -340,9 +340,9 @@ bool RefactoringFile::apply() QString error; // suppress "file has changed" warnings if the file is open in a read-only editor Core::FileChangeBlocker block(m_filePath); - if (!m_textFileFormat.writeFile(m_filePath, - doc->toPlainText(), - &error)) { + if (m_textFileFormat.writeFile(m_filePath, doc->toPlainText(), &error)) { + Core::DocumentManager::notifyFilesChangedInternally({m_filePath}); + } else { qWarning() << "Could not apply changes to" << m_filePath << ". Error: " << error; result = false; diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index 5b48392676f..bb71933e0e7 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -6429,9 +6429,6 @@ void TextEditorWidget::updateFoldingHighlight(const QPoint &pos) QTextCursor cursor = cursorForPosition(QPoint(0, pos.y())); // Update which folder marker is highlighted - const int highlightBlockNumber = d->extraAreaHighlightFoldedBlockNumber; - d->extraAreaHighlightFoldedBlockNumber = -1; - int boxWidth = 0; if (TextEditorSettings::fontSettings().relativeLineSpacing() == 100) boxWidth = foldBoxWidth(fontMetrics()); @@ -6439,13 +6436,25 @@ void TextEditorWidget::updateFoldingHighlight(const QPoint &pos) boxWidth = foldBoxWidth(); if (pos.x() > extraArea()->width() - boxWidth) { - d->extraAreaHighlightFoldedBlockNumber = cursor.blockNumber(); + updateFoldingHighlight(cursor); } else if (d->m_displaySettings.m_highlightBlocks) { QTextCursor cursor = textCursor(); - d->extraAreaHighlightFoldedBlockNumber = cursor.blockNumber(); + updateFoldingHighlight(cursor); + } else { + updateFoldingHighlight(QTextCursor()); } +} - if (highlightBlockNumber != d->extraAreaHighlightFoldedBlockNumber) +void TextEditorWidget::updateFoldingHighlight(const QTextCursor &cursor) +{ + const int highlightBlockNumber = d->extraAreaHighlightFoldedBlockNumber; + const bool curserIsNull = !cursor.isNull(); + if (curserIsNull) + d->extraAreaHighlightFoldedBlockNumber = cursor.blockNumber(); + else + d->extraAreaHighlightFoldedBlockNumber = -1; + + if (curserIsNull || (highlightBlockNumber != d->extraAreaHighlightFoldedBlockNumber)) d->m_highlightBlocksTimer.start(d->m_highlightBlocksInfo.isEmpty() ? 120 : 0); } diff --git a/src/plugins/texteditor/texteditor.h b/src/plugins/texteditor/texteditor.h index 4807d4cdfbf..2674c1811c7 100644 --- a/src/plugins/texteditor/texteditor.h +++ b/src/plugins/texteditor/texteditor.h @@ -277,6 +277,7 @@ public: virtual void extraAreaContextMenuEvent(QContextMenuEvent *); virtual void extraAreaMouseEvent(QMouseEvent *); void updateFoldingHighlight(const QPoint &pos); + void updateFoldingHighlight(const QTextCursor &cursor); void setLanguageSettingsId(Utils::Id settingsId); Utils::Id languageSettingsId() const; diff --git a/src/tools/qml2puppet/editor3d_qt6.qrc b/src/tools/qml2puppet/editor3d_qt6.qrc index df498e643cf..59423beb91d 100644 --- a/src/tools/qml2puppet/editor3d_qt6.qrc +++ b/src/tools/qml2puppet/editor3d_qt6.qrc @@ -4,7 +4,6 @@ mockfiles/meshes/scalerod.mesh mockfiles/meshes/ring.mesh mockfiles/meshes/ringselect.mesh - mockfiles/meshes/axishelper.mesh mockfiles/images/editor_camera.png mockfiles/images/editor_camera@2x.png mockfiles/images/editor_particlesystem.png @@ -21,14 +20,13 @@ mockfiles/qt6/AdjustableArrow.qml mockfiles/qt6/Arrow.qml mockfiles/qt6/AutoScaleHelper.qml - mockfiles/qt6/AxisHelper.qml - mockfiles/qt6/AxisHelperArm.qml mockfiles/qt6/CameraFrustum.qml mockfiles/qt6/CameraGizmo.qml mockfiles/qt6/DirectionalDraggable.qml mockfiles/qt6/EditCameraController.qml mockfiles/qt6/EditView3D.qml mockfiles/qt6/FadeHandle.qml + mockfiles/qt6/GridMaterial.qml mockfiles/qt6/HelperGrid.qml mockfiles/qt6/IconGizmo.qml mockfiles/qt6/LightGizmo.qml @@ -41,7 +39,9 @@ mockfiles/qt6/ModelNodeView.qml mockfiles/qt6/MoveGizmo.qml mockfiles/qt6/NodeNodeView.qml + mockfiles/qt6/OriginGizmo.qml mockfiles/qt6/Overlay2D.qml + mockfiles/qt6/OverlayView3D.qml mockfiles/qt6/ParticleSystemGizmo.qml mockfiles/qt6/ParticleEmitterGizmo.qml mockfiles/qt6/PlanarDraggable.qml @@ -54,7 +54,6 @@ mockfiles/qt6/SceneView3D.qml mockfiles/qt6/SelectionBox.qml mockfiles/qt6/SpotLightHandle.qml - mockfiles/qt6/GridMaterial.qml mockfiles/shaders/gridmaterial.frag mockfiles/shaders/gridmaterial.vert diff --git a/src/tools/qml2puppet/mockfiles/meshes/axishelper.mesh b/src/tools/qml2puppet/mockfiles/meshes/axishelper.mesh deleted file mode 100644 index 3e9e4958e4b..00000000000 Binary files a/src/tools/qml2puppet/mockfiles/meshes/axishelper.mesh and /dev/null differ diff --git a/src/tools/qml2puppet/mockfiles/qt6/AutoScaleHelper.qml b/src/tools/qml2puppet/mockfiles/qt6/AutoScaleHelper.qml index 5c73716426a..c0e9b21f52b 100644 --- a/src/tools/qml2puppet/mockfiles/qt6/AutoScaleHelper.qml +++ b/src/tools/qml2puppet/mockfiles/qt6/AutoScaleHelper.qml @@ -30,6 +30,12 @@ Node { function onOverlayUpdateNeeded() { updateScale() } } + Connections { + target: view3D + function onWidthChanged() { updateScale() } + function onHeightChanged() { updateScale() } + } + function getScale(baseScale) { return Qt.vector3d(baseScale.x * relativeScale, baseScale.y * relativeScale, diff --git a/src/tools/qml2puppet/mockfiles/qt6/AxisHelper.qml b/src/tools/qml2puppet/mockfiles/qt6/AxisHelper.qml deleted file mode 100644 index ea44acc1ec3..00000000000 --- a/src/tools/qml2puppet/mockfiles/qt6/AxisHelper.qml +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (C) 2019 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -import QtQuick 6.0 -import QtQuick3D 6.0 - -View3D { - id: axisHelperView - - property var editCameraCtrl - property Node selectedNode - - camera: axisHelperCamera - - Node { - OrthographicCamera { - id: axisHelperCamera - rotation: editCameraCtrl.camera ? editCameraCtrl.camera.rotation : Qt.quaternion(1, 0, 0, 0) - position: editCameraCtrl.camera ? editCameraCtrl.camera.position.minus(editCameraCtrl._lookAtPoint) - .normalized().times(600) : Qt.vector3d(0, 0, 0) - } - - AutoScaleHelper { - id: autoScale - view3D: axisHelperView - position: axisHelperGizmo.scenePosition - } - - Node { - id: axisHelperGizmo - scale: autoScale.getScale(Qt.vector3d(4, 4, 4)) - - AxisHelperArm { - id: armX - eulerRotation: Qt.vector3d(0, 0, -90) - color: Qt.rgba(1, 0, 0, 1) - hoverColor: Qt.lighter(Qt.rgba(1, 0, 0, 1)) - view3D: axisHelperView - camRotPos: Qt.vector3d(0, 90, 0) - camRotNeg: Qt.vector3d(0, -90, 0) - } - - AxisHelperArm { - id: armY - eulerRotation: Qt.vector3d(0, 0, 0) - color: Qt.rgba(0, 0.6, 0, 1) - hoverColor: Qt.lighter(Qt.rgba(0, 0.6, 0, 1)) - view3D: axisHelperView - camRotPos: Qt.vector3d(-90, 0, 0) - camRotNeg: Qt.vector3d(90, 0, 0) - } - - AxisHelperArm { - id: armZ - eulerRotation: Qt.vector3d(90, 0, 0) - color: Qt.rgba(0, 0, 1, 1) - hoverColor: Qt.lighter(Qt.rgba(0, 0, 1, 1)) - view3D: axisHelperView - camRotPos: Qt.vector3d(0, 0, 0) - camRotNeg: Qt.vector3d(0, 180, 0) - } - } - } - - MouseArea { - anchors.fill: parent - hoverEnabled: true - acceptedButtons: Qt.LeftButton - - property var pickObj: null - - function cancelHover() - { - if (pickObj) { - pickObj.hovering = false; - pickObj = null; - } - } - - function pick(mouse) - { - var result = axisHelperView.pick(mouse.x, mouse.y); - if (result.objectHit) { - if (result.objectHit !== pickObj) { - cancelHover(); - pickObj = result.objectHit; - pickObj.hovering = true; - } - } else { - cancelHover(); - } - } - - onPositionChanged: (mouse)=> { - pick(mouse); - } - - onPressed: (mouse)=> { - pick(mouse); - if (pickObj) { - axisHelperView.editCameraCtrl.focusObject(axisHelperView.selectedNode, - pickObj.cameraRotation, false, false); - } else { - mouse.accepted = false; - } - } - - onExited: cancelHover() - onCanceled: cancelHover() - } -} diff --git a/src/tools/qml2puppet/mockfiles/qt6/AxisHelperArm.qml b/src/tools/qml2puppet/mockfiles/qt6/AxisHelperArm.qml deleted file mode 100644 index 683075c4c7f..00000000000 --- a/src/tools/qml2puppet/mockfiles/qt6/AxisHelperArm.qml +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (C) 2019 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -import QtQuick 6.0 -import QtQuick3D 6.0 - -Node { - id: armRoot - property alias posModel: posModel - property alias negModel: negModel - property View3D view3D - property color hoverColor - property color color - property vector3d camRotPos - property vector3d camRotNeg - - Model { - id: posModel - - property bool hovering: false - property vector3d cameraRotation: armRoot.camRotPos - - source: "../meshes/axishelper.mesh" - materials: DefaultMaterial { - id: posMat - diffuseColor: posModel.hovering ? armRoot.hoverColor : armRoot.color - lighting: DefaultMaterial.NoLighting - } - pickable: true - } - - Model { - id: negModel - - property bool hovering: false - property vector3d cameraRotation: armRoot.camRotNeg - - source: "#Sphere" - y: -6 - scale: Qt.vector3d(0.025, 0.025, 0.025) - materials: DefaultMaterial { - id: negMat - diffuseColor: negModel.hovering ? armRoot.hoverColor : armRoot.color - lighting: DefaultMaterial.NoLighting - } - pickable: true - } -} diff --git a/src/tools/qml2puppet/mockfiles/qt6/EditCameraController.qml b/src/tools/qml2puppet/mockfiles/qt6/EditCameraController.qml index 42712d56a5d..cb9276305a3 100644 --- a/src/tools/qml2puppet/mockfiles/qt6/EditCameraController.qml +++ b/src/tools/qml2puppet/mockfiles/qt6/EditCameraController.qml @@ -7,9 +7,11 @@ import QtQuick3D 6.0 Item { id: cameraCtrl - property Camera camera: null - property View3D view3d: null - property string sceneId + property var viewRoot: null + property int splitId: -1 + property Camera camera: view3d ? view3d.camera : null + property View3D view3d: viewRoot.editViews[splitId] + property string sceneId: viewRoot.sceneId property vector3d _lookAtPoint property vector3d _pressPoint property vector3d _prevPoint @@ -27,6 +29,9 @@ Item { readonly property real _keyPanAmount: 5 property bool ignoreToolState: false + z: 10 + anchors.fill: parent + function restoreCameraState(cameraState) { if (!camera || ignoreToolState) @@ -47,10 +52,19 @@ Item { _lookAtPoint = Qt.vector3d(0, 0, 0); _zoomFactor = 1; - camera.position = _defaultCameraPosition; - camera.eulerRotation = _defaultCameraRotation; - _generalHelper.zoomCamera(view3d, camera, 0, _defaultCameraLookAtDistance, _lookAtPoint, - _zoomFactor, false); + + if (splitId === 1) { + jumpToRotation(originGizmo.quaternionForAxis(OriginGizmo.PositiveZ)); + } else if (splitId === 2) { + jumpToRotation(originGizmo.quaternionForAxis(OriginGizmo.NegativeY)); + } else if (splitId === 3) { + jumpToRotation(originGizmo.quaternionForAxis(OriginGizmo.NegativeX)); + } else { + camera.position = _defaultCameraPosition; + camera.eulerRotation = _defaultCameraRotation; + _generalHelper.zoomCamera(view3d, camera, 0, _defaultCameraLookAtDistance, _lookAtPoint, + _zoomFactor, false); + } } function storeCameraState(delay) @@ -63,10 +77,9 @@ Item { cameraState[1] = _zoomFactor; cameraState[2] = camera.position; cameraState[3] = camera.rotation; - _generalHelper.storeToolState(sceneId, "editCamState", cameraState, delay); + _generalHelper.storeToolState(sceneId, "editCamState" + splitId, cameraState, delay); } - function focusObject(targetNodes, rotation, updateZoom, closeUp) { if (!camera) @@ -88,6 +101,15 @@ Item { storeCameraState(0); } + function jumpToRotation(rotation) + { + let distance = camera.scenePosition.minus(_lookAtPoint).length() + let direction = _generalHelper.dirForRotation(rotation) + + camera.rotation = rotation + camera.position = _lookAtPoint.plus(direction.times(distance)) + } + function alignCameras(targetNodes) { if (!camera) @@ -149,7 +171,7 @@ Item { acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton hoverEnabled: false anchors.fill: parent - onPositionChanged: (mouse)=> { + onPositionChanged: (mouse) => { if (cameraCtrl.camera && mouse.modifiers === Qt.AltModifier && cameraCtrl._dragging) { var currentPoint = Qt.vector3d(mouse.x, mouse.y, 0); if (cameraCtrl._button == Qt.LeftButton) { @@ -167,7 +189,8 @@ Item { } } } - onPressed: (mouse)=> { + onPressed: (mouse) => { + viewRoot.activeSplit = cameraCtrl.splitId if (cameraCtrl.camera && mouse.modifiers === Qt.AltModifier) { cameraCtrl._dragging = true; cameraCtrl._startRotation = cameraCtrl.camera.eulerRotation; @@ -182,7 +205,8 @@ Item { } } - function handleRelease() { + function handleRelease() + { cameraCtrl._dragging = false; cameraCtrl.storeCameraState(0); } @@ -190,7 +214,8 @@ Item { onReleased: handleRelease() onCanceled: handleRelease() - onWheel: (wheel)=> { + onWheel: (wheel) => { + viewRoot.activeSplit = cameraCtrl.splitId if (cameraCtrl.camera) { // Empirically determined divisor for nice zoom cameraCtrl.zoomRelative(wheel.angleDelta.y / -40); @@ -199,7 +224,7 @@ Item { } } - Keys.onPressed: (event)=> { + Keys.onPressed: (event) => { var pressPoint = Qt.vector3d(view3d.width / 2, view3d.height / 2, 0); var currentPoint; @@ -228,4 +253,19 @@ Item { event.accepted = true; } } + + OriginGizmo { + id: originGizmo + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: 4 + width: 70 + height: 70 + targetNode: cameraCtrl.camera + + onAxisClicked: (axis) => { + viewRoot.activeSplit = cameraCtrl.splitId + cameraCtrl.jumpToRotation(quaternionForAxis(axis)); + } + } } diff --git a/src/tools/qml2puppet/mockfiles/qt6/EditView3D.qml b/src/tools/qml2puppet/mockfiles/qt6/EditView3D.qml index a44a2a75d2d..f526f01e2db 100644 --- a/src/tools/qml2puppet/mockfiles/qt6/EditView3D.qml +++ b/src/tools/qml2puppet/mockfiles/qt6/EditView3D.qml @@ -1,9 +1,9 @@ // Copyright (C) 2019 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -import QtQuick 6.0 -import QtQuick3D 6.0 -import MouseArea3D 1.0 +import QtQuick +import QtQuick3D +import MouseArea3D Item { id: viewRoot @@ -12,7 +12,15 @@ Item { visible: true property Node activeScene: null - property View3D editView: null + property int activeSplit: 0 + property var editViews: [null, null, null, null] + property var overlayViews: [overlayView0, overlayView1, overlayView2, overlayView3] + property var cameraControls: [cameraControl0, cameraControl1, cameraControl2, cameraControl3] + property var viewRects: [viewRect0, viewRect1, viewRect2, viewRect3] + property var materialOverrides: [DebugSettings.None, DebugSettings.None, DebugSettings.None, DebugSettings.None] + property var showWireframes: [false, false, false, false] + property var activeEditView: editViews[activeSplit] + property var activeOverlayView: overlayViews[activeSplit] property string sceneId property bool showEditLight: false @@ -28,6 +36,7 @@ Item { property color backgroundGradientColorEnd: "#999999" property color gridColor: "#cccccc" property bool syncEnvBackground: false + property bool splitView: false enum SelectionMode { Item, Group } enum TransformMode { Move, Rotate, Scale } @@ -37,12 +46,8 @@ Item { property Node selectedNode: null // This is multiSelectionNode in multi-selection case property var selectedNodes: [] // All selected nodes + property int selectionBoxCount: 0 - property var lightIconGizmos: [] - property var cameraGizmos: [] - property var particleSystemIconGizmos: [] - property var particleEmitterGizmos: [] - property var selectionBoxes: [] property rect viewPortRect: Qt.rect(0, 0, 1000, 1000) property Node activeParticleSystem: null property bool shuttingDown: false @@ -53,6 +58,7 @@ Item { signal commitObjectProperty(var objects, var propNames) signal changeObjectProperty(var objects, var propNames) signal notifyActiveSceneChange() + signal notifyActiveSplitChange(int index) onUsePerspectiveChanged: _generalHelper.storeToolState(sceneId, "usePerspective", usePerspective) onShowEditLightChanged: _generalHelper.storeToolState(sceneId, "showEditLight", showEditLight) @@ -65,6 +71,17 @@ Item { onShowParticleEmitterChanged: _generalHelper.storeToolState(sceneId, "showParticleEmitter", showParticleEmitter); onSelectionModeChanged: _generalHelper.storeToolState(sceneId, "selectionMode", selectionMode); onTransformModeChanged: _generalHelper.storeToolState(sceneId, "transformMode", transformMode); + onMaterialOverridesChanged: _generalHelper.storeToolState(sceneId, "matOverride", materialOverrides); + onShowWireframesChanged: _generalHelper.storeToolState(sceneId, "showWireframe", showWireframes); + onSplitViewChanged: { + _generalHelper.storeToolState(sceneId, "splitView", splitView); + _generalHelper.requestOverlayUpdate(); + } + onActiveSplitChanged: { + _generalHelper.storeToolState(sceneId, "activeSplit", activeSplit); + cameraControls[activeSplit].forceActiveFocus(); + notifyActiveSplitChange(activeSplit); + } onActiveSceneChanged: updateActiveScene() @@ -73,26 +90,43 @@ Item { shuttingDown = true; } - function createEditView() + function createEditViews() { var component = Qt.createComponent("SceneView3D.qml"); if (component.status === Component.Ready) { - editView = component.createObject(viewRect, - {"usePerspective": usePerspective, - "showSceneLight": showEditLight, - "showGrid": showGrid, - "gridColor": gridColor, - "importScene": activeScene, - "cameraLookAt": cameraControl._lookAtPoint, - "z": 1}); - editView.usePerspective = Qt.binding(function() {return usePerspective;}); - editView.showSceneLight = Qt.binding(function() {return showEditLight;}); - editView.showGrid = Qt.binding(function() {return showGrid;}); - editView.gridColor = Qt.binding(function() {return gridColor;}); - editView.cameraLookAt = Qt.binding(function() {return cameraControl._lookAtPoint;}); + for (var i = 0; i < 4; ++i) { + editViews[i] = component.createObject(viewRects[i], + {"usePerspective": usePerspective, + "showSceneLight": showEditLight, + "showGrid": showGrid, + "gridColor": gridColor, + "importScene": activeScene, + "cameraLookAt": cameraControls[i]._lookAtPoint, + "z": 1, + "sceneEnv.debugSettings.materialOverride": materialOverrides[i], + "sceneEnv.debugSettings.wireframeEnabled": showWireframes[i]}); + editViews[i].usePerspective = Qt.binding(function() {return usePerspective;}); + editViews[i].showSceneLight = Qt.binding(function() {return showEditLight;}); + editViews[i].showGrid = Qt.binding(function() {return showGrid;}); + editViews[i].gridColor = Qt.binding(function() {return gridColor;}); + } + editViews[0].cameraLookAt = Qt.binding(function() {return cameraControl0._lookAtPoint;}); + editViews[1].cameraLookAt = Qt.binding(function() {return cameraControl1._lookAtPoint;}); + editViews[2].cameraLookAt = Qt.binding(function() {return cameraControl2._lookAtPoint;}); + editViews[3].cameraLookAt = Qt.binding(function() {return cameraControl3._lookAtPoint;}); - selectionBoxes.length = 0; - cameraControl.forceActiveFocus(); + editViews[0].sceneEnv.debugSettings.materialOverride = Qt.binding(function() {return materialOverrides[0];}); + editViews[1].sceneEnv.debugSettings.materialOverride = Qt.binding(function() {return materialOverrides[1];}); + editViews[2].sceneEnv.debugSettings.materialOverride = Qt.binding(function() {return materialOverrides[2];}); + editViews[3].sceneEnv.debugSettings.materialOverride = Qt.binding(function() {return materialOverrides[3];}); + + editViews[0].sceneEnv.debugSettings.wireframeEnabled = Qt.binding(function() {return showWireframes[0];}); + editViews[1].sceneEnv.debugSettings.wireframeEnabled = Qt.binding(function() {return showWireframes[1];}); + editViews[2].sceneEnv.debugSettings.wireframeEnabled = Qt.binding(function() {return showWireframes[2];}); + editViews[3].sceneEnv.debugSettings.wireframeEnabled = Qt.binding(function() {return showWireframes[3];}); + + selectionBoxCount = 0; + editViewsChanged(); return true; } return false; @@ -100,13 +134,18 @@ Item { function updateActiveScene() { - if (editView) { - editView.visible = false; - editView.destroy(); + let viewsDeleted = false; + for (var i = 0; i < editViews.length; ++i) { + if (editViews[i]) { + editViews[i].visible = false; + editViews[i].destroy(); + editViews[i] = null; + viewsDeleted = true; + } } // importScene cannot be updated after initial set, so we need to reconstruct entire View3D - if (createEditView()) { + if (createEditViews()) { if (activeScene) { var toolStates = _generalHelper.getToolStates(sceneId); if (Object.keys(toolStates).length > 0) { @@ -116,13 +155,18 @@ Item { // turn the edit light on for scenes that do not have any scene // lights, and turn it off for scenes that have. var hasSceneLight = false; - for (var i = 0; i < lightIconGizmos.length; ++i) { - if (lightIconGizmos[i].scene === activeScene) { + for (var j = 0; j < overlayView0.lightIconGizmos.length; ++j) { + if (overlayView0.lightIconGizmos[j].scene === activeScene) { hasSceneLight = true; break; } } showEditLight = !hasSceneLight; + + // Don't inherit camera angles from the previous scene + for (let i = 0; i < 4; ++i) + cameraControls[i].restoreDefaultState(); + storeCurrentToolStates(); } } else { @@ -139,6 +183,8 @@ Item { updateEnvBackground(); notifyActiveSceneChange(); + } else if (viewsDeleted) { + editViewsChanged(); } } @@ -163,24 +209,25 @@ Item { function fitToView() { - if (editView) { + if (activeEditView) { var boxModels = []; if (selectedNodes.length > 1) { for (var i = 0; i < selectedNodes.length; ++i) { - if (selectionBoxes.length > i) - boxModels.push(selectionBoxes[i].model) + if (selectionBoxCount > i) + boxModels.push(activeEditView.selectionBoxes[i].model) } - } else if (selectedNodes.length > 0 && selectionBoxes.length > 0) { - boxModels.push(selectionBoxes[0].model); + } else if (selectedNodes.length > 0 && selectionBoxCount > 0) { + boxModels.push(activeEditView.selectionBoxes[0].model); } - cameraControl.focusObject(boxModels, editView.camera.eulerRotation, true, false); + cameraControls[activeSplit].focusObject( + boxModels, activeEditView.camera.eulerRotation, true, false); } } function alignCamerasToView(cameraNodes) { - if (editView) { - cameraControl.alignCameras(cameraNodes); + if (activeEditView) { + cameraControls[activeSplit].alignCameras(cameraNodes); var propertyNames = ["position", "eulerRotation"]; viewRoot.changeObjectProperty(cameraNodes, propertyNames); viewRoot.commitObjectProperty(cameraNodes, propertyNames); @@ -189,11 +236,12 @@ Item { function alignViewToCamera(cameraNodes) { - if (editView) - cameraControl.alignView(cameraNodes); + if (activeEditView) + cameraControls[activeSplit].alignView(cameraNodes); } - function updateBackgroundColors(colors) { + function updateBackgroundColors(colors) + { if (colors.length === 1) { backgroundGradientColorStart = colors[0]; backgroundGradientColorEnd = colors[0]; @@ -203,28 +251,31 @@ Item { } } - function updateEnvBackground() { + function updateEnvBackground() + { updateBackgroundColors(_generalHelper.bgColor); - if (!editView) + if (!editViews[0]) return; - if (syncEnvBackground) { - let bgMode = _generalHelper.sceneEnvironmentBgMode(sceneId); - if ((!_generalHelper.sceneEnvironmentLightProbe(sceneId) && bgMode === SceneEnvironment.SkyBox) - || (!_generalHelper.sceneEnvironmentSkyBoxCubeMap(sceneId) && bgMode === SceneEnvironment.SkyBoxCubeMap)) { - editView.sceneEnv.backgroundMode = SceneEnvironment.Color; + for (var i = 0; i < 4; ++i) { + if (syncEnvBackground) { + let bgMode = _generalHelper.sceneEnvironmentBgMode(sceneId); + if ((!_generalHelper.sceneEnvironmentLightProbe(sceneId) && bgMode === SceneEnvironment.SkyBox) + || (!_generalHelper.sceneEnvironmentSkyBoxCubeMap(sceneId) && bgMode === SceneEnvironment.SkyBoxCubeMap)) { + editViews[i].sceneEnv.backgroundMode = SceneEnvironment.Color; + } else { + editViews[i].sceneEnv.backgroundMode = bgMode; + } + editViews[i].sceneEnv.lightProbe = _generalHelper.sceneEnvironmentLightProbe(sceneId); + editViews[i].sceneEnv.skyBoxCubeMap = _generalHelper.sceneEnvironmentSkyBoxCubeMap(sceneId); + editViews[i].sceneEnv.clearColor = _generalHelper.sceneEnvironmentColor(sceneId); } else { - editView.sceneEnv.backgroundMode = bgMode; + editViews[i].sceneEnv.backgroundMode = SceneEnvironment.Transparent; + editViews[i].sceneEnv.lightProbe = null; + editViews[i].sceneEnv.skyBoxCubeMap = null; + editViews[i].sceneEnv.clearColor = "transparent"; } - editView.sceneEnv.lightProbe = _generalHelper.sceneEnvironmentLightProbe(sceneId); - editView.sceneEnv.skyBoxCubeMap = _generalHelper.sceneEnvironmentSkyBoxCubeMap(sceneId); - editView.sceneEnv.clearColor = _generalHelper.sceneEnvironmentColor(sceneId); - } else { - editView.sceneEnv.backgroundMode = SceneEnvironment.Transparent; - editView.sceneEnv.lightProbe = null; - editView.sceneEnv.skyBoxCubeMap = null; - editView.sceneEnv.clearColor = "transparent"; } } @@ -290,10 +341,33 @@ Item { else if (resetToDefault) transformMode = EditView3D.TransformMode.Move; - if ("editCamState" in toolStates) - cameraControl.restoreCameraState(toolStates.editCamState); + for (var i = 0; i < 4; ++i) { + let propId = "editCamState" + i; + if (propId in toolStates) + cameraControls[i].restoreCameraState(toolStates[propId]); + else if (resetToDefault) + cameraControls[i].restoreDefaultState(); + } + + if ("splitView" in toolStates) + splitView = toolStates.splitView; else if (resetToDefault) - cameraControl.restoreDefaultState(); + splitView = false; + + if ("activeSplit" in toolStates) + activeSplit = toolStates.activeSplit; + else if (resetToDefault) + activeSplit = 0; + + if ("showWireframe" in toolStates) + showWireframes = toolStates.showWireframe; + else if (resetToDefault) + showWireframes = [false, false, false, false]; + + if ("matOverride" in toolStates) + materialOverrides = toolStates.matOverride; + else if (resetToDefault) + materialOverrides = [DebugSettings.None, DebugSettings.None, DebugSettings.None, DebugSettings.None]; } function storeCurrentToolStates() @@ -309,29 +383,23 @@ Item { _generalHelper.storeToolState(sceneId, "globalOrientation", globalOrientation) _generalHelper.storeToolState(sceneId, "selectionMode", selectionMode); _generalHelper.storeToolState(sceneId, "transformMode", transformMode); + _generalHelper.storeToolState(sceneId, "splitView", splitView) + _generalHelper.storeToolState(sceneId, "activeSplit", activeSplit) + _generalHelper.storeToolState(sceneId, "showWireframe", showWireframes) + _generalHelper.storeToolState(sceneId, "matOverride", materialOverrides) - cameraControl.storeCameraState(0); + for (var i = 0; i < 4; ++i) + cameraControls[i].storeCameraState(0); } function ensureSelectionBoxes(count) { - var needMore = count - selectionBoxes.length - if (needMore > 0) { - var component = Qt.createComponent("SelectionBox.qml"); - if (component.status === Component.Ready) { - for (var i = 0; i < needMore; ++i) { - var geometryName = _generalHelper.generateUniqueName("SelectionBoxGeometry"); - var boxParent = null; - if (editView) - boxParent = editView.sceneHelpers; - var box = component.createObject(boxParent, {"view3D": editView, - "geometryName": geometryName}); - selectionBoxes[selectionBoxes.length] = box; - box.view3D = Qt.binding(function() {return editView;}); - box.showBox = Qt.binding(function() {return showSelectionBox;}); - } - } + for (var i = 0; i < 4; ++i) { + if (editViews[i]) + editViews[i].ensureSelectionBoxes(count); } + + selectionBoxCount = count; } function selectObjects(objects) @@ -341,11 +409,15 @@ Item { // This fixes an occasional visual glitch when creating a new box. ensureSelectionBoxes(objects.length + 1) - var i; - for (i = 0; i < objects.length; ++i) - selectionBoxes[i].targetNode = objects[i]; - for (i = objects.length; i < selectionBoxes.length; ++i) - selectionBoxes[i].targetNode = null; + for (var idx = 0; idx < 4; ++idx) { + if (editViews[idx]) { + var i; + for (i = 0; i < objects.length; ++i) + editViews[idx].selectionBoxes[i].targetNode = objects[i]; + for (i = objects.length; i < editViews[idx].selectionBoxes.length; ++i) + editViews[idx].selectionBoxes[i].targetNode = null; + } + } selectedNodes = objects; if (objects.length === 0) { @@ -416,274 +488,116 @@ Item { function addLightGizmo(scene, obj) { - // Insert into first available gizmo if we don't already have gizmo for this object - var slotFound = -1; - for (var i = 0; i < lightIconGizmos.length; ++i) { - if (!lightIconGizmos[i].targetNode) { - slotFound = i; - } else if (lightIconGizmos[i].targetNode === obj) { - lightIconGizmos[i].scene = scene; - return; - } - } - - if (slotFound !== -1) { - lightIconGizmos[slotFound].scene = scene; - lightIconGizmos[slotFound].targetNode = obj; - lightIconGizmos[slotFound].locked = _generalHelper.isLocked(obj); - lightIconGizmos[slotFound].hidden = _generalHelper.isHidden(obj); - return; - } - - // No free gizmos available, create a new one - var gizmoComponent = Qt.createComponent("LightIconGizmo.qml"); - if (gizmoComponent.status === Component.Ready) { - var gizmo = gizmoComponent.createObject(overlayView, - {"view3D": overlayView, "targetNode": obj, - "selectedNodes": selectedNodes, "scene": scene, - "activeScene": activeScene, - "locked": _generalHelper.isLocked(obj), - "hidden": _generalHelper.isHidden(obj), - "globalShow": showIconGizmo}); - lightIconGizmos[lightIconGizmos.length] = gizmo; - gizmo.clicked.connect(handleObjectClicked); - gizmo.selectedNodes = Qt.binding(function() {return selectedNodes;}); - gizmo.activeScene = Qt.binding(function() {return activeScene;}); - gizmo.globalShow = Qt.binding(function() {return showIconGizmo;}); - } + for (var i = 0; i < 4; ++i) + overlayViews[i].addLightGizmo(scene, obj); } function addCameraGizmo(scene, obj) { - // Insert into first available gizmo if we don't already have gizmo for this object - var slotFound = -1; - for (var i = 0; i < cameraGizmos.length; ++i) { - if (!cameraGizmos[i].targetNode) { - slotFound = i; - } else if (cameraGizmos[i].targetNode === obj) { - cameraGizmos[i].scene = scene; - return; - } - } - - if (slotFound !== -1) { - cameraGizmos[slotFound].scene = scene; - cameraGizmos[slotFound].targetNode = obj; - cameraGizmos[slotFound].locked = _generalHelper.isLocked(obj); - cameraGizmos[slotFound].hidden = _generalHelper.isHidden(obj); - return; - } - - // No free gizmos available, create a new one - var gizmoComponent = Qt.createComponent("CameraGizmo.qml"); - var frustumComponent = Qt.createComponent("CameraFrustum.qml"); - if (gizmoComponent.status === Component.Ready && frustumComponent.status === Component.Ready) { - var geometryName = _generalHelper.generateUniqueName("CameraGeometry"); - var frustum = frustumComponent.createObject( - overlayScene, - {"geometryName": geometryName, "viewPortRect": viewPortRect}); - var gizmo = gizmoComponent.createObject( - overlayView, - {"view3D": overlayView, "targetNode": obj, - "selectedNodes": selectedNodes, "scene": scene, "activeScene": activeScene, - "locked": _generalHelper.isLocked(obj), "hidden": _generalHelper.isHidden(obj), - "globalShow": showIconGizmo, "globalShowFrustum": showCameraFrustum}); - - cameraGizmos[cameraGizmos.length] = gizmo; - gizmo.clicked.connect(handleObjectClicked); - gizmo.selectedNodes = Qt.binding(function() {return selectedNodes;}); - gizmo.activeScene = Qt.binding(function() {return activeScene;}); - gizmo.globalShow = Qt.binding(function() {return showIconGizmo;}); - gizmo.globalShowFrustum = Qt.binding(function() {return showCameraFrustum;}); - frustum.viewPortRect = Qt.binding(function() {return viewPortRect;}); - gizmo.connectFrustum(frustum); - } + for (var i = 0; i < 4; ++i) + overlayViews[i].addCameraGizmo(scene, obj); } function addParticleSystemGizmo(scene, obj) { - // Insert into first available gizmo if we don't already have gizmo for this object - var slotFound = -1; - for (var i = 0; i < particleSystemIconGizmos.length; ++i) { - if (!particleSystemIconGizmos[i].targetNode) { - slotFound = i; - } else if (particleSystemIconGizmos[i].targetNode === obj) { - particleSystemIconGizmos[i].scene = scene; - return; - } - } - - if (slotFound !== -1) { - particleSystemIconGizmos[slotFound].scene = scene; - particleSystemIconGizmos[slotFound].targetNode = obj; - particleSystemIconGizmos[slotFound].locked = _generalHelper.isLocked(obj); - particleSystemIconGizmos[slotFound].hidden = _generalHelper.isHidden(obj); - return; - } - - // No free gizmos available, create a new one - var gizmoComponent = Qt.createComponent("ParticleSystemGizmo.qml"); - if (gizmoComponent.status === Component.Ready) { - var gizmo = gizmoComponent.createObject(overlayView, - {"view3D": overlayView, "targetNode": obj, - "selectedNodes": selectedNodes, "scene": scene, - "activeScene": activeScene, - "locked": _generalHelper.isLocked(obj), - "hidden": _generalHelper.isHidden(obj), - "globalShow": showIconGizmo, - "activeParticleSystem": activeParticleSystem}); - particleSystemIconGizmos[particleSystemIconGizmos.length] = gizmo; - gizmo.clicked.connect(handleObjectClicked); - gizmo.selectedNodes = Qt.binding(function() {return selectedNodes;}); - gizmo.activeScene = Qt.binding(function() {return activeScene;}); - gizmo.globalShow = Qt.binding(function() {return showIconGizmo;}); - gizmo.activeParticleSystem = Qt.binding(function() {return activeParticleSystem;}); - } + for (var i = 0; i < 4; ++i) + overlayViews[i].addParticleSystemGizmo(scene, obj); } function addParticleEmitterGizmo(scene, obj) { - // Insert into first available gizmo if we don't already have gizmo for this object - var slotFound = -1; - for (var i = 0; i < particleEmitterGizmos.length; ++i) { - if (!particleEmitterGizmos[i].targetNode) { - slotFound = i; - } else if (particleEmitterGizmos[i].targetNode === obj) { - particleEmitterGizmos[i].scene = scene; - return; - } - } - - if (slotFound !== -1) { - particleEmitterGizmos[slotFound].scene = scene; - particleEmitterGizmos[slotFound].targetNode = obj; - particleEmitterGizmos[slotFound].hidden = _generalHelper.isHidden(obj); - particleEmitterGizmos[slotFound].systemHidden = _generalHelper.isHidden(obj.system); - return; - } - - // No free gizmos available, create a new one - var gizmoComponent = Qt.createComponent("ParticleEmitterGizmo.qml"); - if (gizmoComponent.status === Component.Ready) { - var gizmo = gizmoComponent.createObject( - overlayScene, - {"targetNode": obj, "selectedNodes": selectedNodes, - "activeParticleSystem": activeParticleSystem, "scene": scene, - "activeScene": activeScene, "hidden": _generalHelper.isHidden(obj), - "systemHidden": _generalHelper.isHidden(obj.system), - "globalShow": showParticleEmitter}); - - particleEmitterGizmos[particleEmitterGizmos.length] = gizmo; - gizmo.selectedNodes = Qt.binding(function() {return selectedNodes;}); - gizmo.activeParticleSystem = Qt.binding(function() {return activeParticleSystem;}); - gizmo.globalShow = Qt.binding(function() {return showParticleEmitter;}); - gizmo.activeScene = Qt.binding(function() {return activeScene;}); - } + for (var i = 0; i < 4; ++i) + overlayViews[i].addParticleEmitterGizmo(scene, obj); } function releaseLightGizmo(obj) { - for (var i = 0; i < lightIconGizmos.length; ++i) { - if (lightIconGizmos[i].targetNode === obj) { - lightIconGizmos[i].scene = null; - lightIconGizmos[i].targetNode = null; - return; - } - } + for (var i = 0; i < 4; ++i) + overlayViews[i].releaseLightGizmo(obj); } function releaseCameraGizmo(obj) { - for (var i = 0; i < cameraGizmos.length; ++i) { - if (cameraGizmos[i].targetNode === obj) { - cameraGizmos[i].scene = null; - cameraGizmos[i].targetNode = null; - return; - } - } + for (var i = 0; i < 4; ++i) + overlayViews[i].releaseCameraGizmo(obj); } function releaseParticleSystemGizmo(obj) { - for (var i = 0; i < particleSystemIconGizmos.length; ++i) { - if (particleSystemIconGizmos[i].targetNode === obj) { - particleSystemIconGizmos[i].scene = null; - particleSystemIconGizmos[i].targetNode = null; - return; - } - } + for (var i = 0; i < 4; ++i) + overlayViews[i].releaseParticleSystemGizmo(obj); } function releaseParticleEmitterGizmo(obj) { - for (var i = 0; i < particleEmitterGizmos.length; ++i) { - if (particleEmitterGizmos[i].targetNode === obj) { - particleEmitterGizmos[i].scene = null; - particleEmitterGizmos[i].targetNode = null; - return; - } - } + for (var i = 0; i < 4; ++i) + overlayViews[i].releaseParticleEmitterGizmo(obj); } function updateLightGizmoScene(scene, obj) { - for (var i = 0; i < lightIconGizmos.length; ++i) { - if (lightIconGizmos[i].targetNode === obj) { - lightIconGizmos[i].scene = scene; - return; - } - } + for (var i = 0; i < 4; ++i) + overlayViews[i].updateLightGizmoScene(scene, obj); } function updateCameraGizmoScene(scene, obj) { - for (var i = 0; i < cameraGizmos.length; ++i) { - if (cameraGizmos[i].targetNode === obj) { - cameraGizmos[i].scene = scene; - return; - } - } + for (var i = 0; i < 4; ++i) + overlayViews[i].updateCameraGizmoScene(scene, obj); } function updateParticleSystemGizmoScene(scene, obj) { - for (var i = 0; i < particleSystemIconGizmos.length; ++i) { - if (particleSystemIconGizmos[i].targetNode === obj) { - particleSystemIconGizmos[i].scene = scene; - return; - } - } + for (var i = 0; i < 4; ++i) + overlayViews[i].updateParticleSystemGizmoScene(scene, obj); } function updateParticleEmitterGizmoScene(scene, obj) { - for (var i = 0; i < particleEmitterGizmos.length; ++i) { - if (particleEmitterGizmos[i].targetNode === obj) { - particleEmitterGizmos[i].scene = scene; - return; + for (var i = 0; i < 4; ++i) + overlayViews[i].updateParticleEmitterGizmoScene(scene, obj); + } + + function resolveSplitPoint(x, y) + { + if (!splitView || activeSplit === 0) + return Qt.point(x, y); + + if (activeSplit === 1) + return Qt.point(x - viewContainer.width / 2, y); + else if (activeSplit === 2) + return Qt.point(x, y - viewContainer.height / 2); + else + return Qt.point(x - viewContainer.width / 2, y - viewContainer.height / 2); + } + + function updateActiveSplit(x, y) + { + if (splitView) { + if (x <= viewContainer.width / 2) { + if (y <= viewContainer.height / 2) + activeSplit = 0; + else + activeSplit = 2; + } else { + if (y <= viewContainer.height / 2) + activeSplit = 1; + else + activeSplit = 3; } } } function gizmoAt(x, y) { - for (var i = 0; i < lightIconGizmos.length; ++i) { - if (lightIconGizmos[i].visible && lightIconGizmos[i].hasPoint(x, y)) - return lightIconGizmos[i].targetNode; - } - for (var i = 0; i < cameraGizmos.length; ++i) { - if (cameraGizmos[i].visible && cameraGizmos[i].hasPoint(x, y)) - return cameraGizmos[i].targetNode; - } - for (var i = 0; i < particleSystemIconGizmos.length; ++i) { - if (particleSystemIconGizmos[i].visible && particleSystemIconGizmos[i].hasPoint(x, y)) - return particleSystemIconGizmos[i].targetNode; - } - return null; + updateActiveSplit(x, y); + let splitPoint = resolveSplitPoint(x, y); + + return activeOverlayView.gizmoAt(splitPoint.x, splitPoint.y); } Component.onCompleted: { - createEditView(); + createEditViews(); selectObjects([]); // Work-around the fact that the projection matrix for the camera is not calculated until // the first frame is rendered, so any initial calls to mapFrom3DScene() will fail. @@ -697,55 +611,15 @@ Item { target: _generalHelper function onLockedStateChanged(node) { - for (var i = 0; i < cameraGizmos.length; ++i) { - if (cameraGizmos[i].targetNode === node) { - cameraGizmos[i].locked = _generalHelper.isLocked(node); - return; - } - } - for (var i = 0; i < lightIconGizmos.length; ++i) { - if (lightIconGizmos[i].targetNode === node) { - lightIconGizmos[i].locked = _generalHelper.isLocked(node); - return; - } - } - for (var i = 0; i < particleSystemIconGizmos.length; ++i) { - if (particleSystemIconGizmos[i].targetNode === node) { - particleSystemIconGizmos[i].locked = _generalHelper.isLocked(node); - return; - } - } + for (var i = 0; i < 4; ++i) + overlayViews[i].handleLockedStateChange(node); } function onHiddenStateChanged(node) { - for (var i = 0; i < cameraGizmos.length; ++i) { - if (cameraGizmos[i].targetNode === node) { - cameraGizmos[i].hidden = _generalHelper.isHidden(node); - return; - } - } - for (var i = 0; i < lightIconGizmos.length; ++i) { - if (lightIconGizmos[i].targetNode === node) { - lightIconGizmos[i].hidden = _generalHelper.isHidden(node); - return; - } - } - for (var i = 0; i < particleSystemIconGizmos.length; ++i) { - if (particleSystemIconGizmos[i].targetNode === node) { - particleSystemIconGizmos[i].hidden = _generalHelper.isHidden(node); - return; - } - } - for (var i = 0; i < particleEmitterGizmos.length; ++i) { - if (particleEmitterGizmos[i].targetNode === node) { - particleEmitterGizmos[i].hidden = _generalHelper.isHidden(node); - return; - } else if (particleEmitterGizmos[i].targetNode && particleEmitterGizmos[i].targetNode.system === node) { - particleEmitterGizmos[i].systemHidden = _generalHelper.isHidden(node); - return; - } - } + for (var i = 0; i < 4; ++i) + overlayViews[i].handleHiddenStateChange(node); + } function onUpdateDragTooltip() @@ -754,228 +628,225 @@ Item { rotateGizmoLabel.updateLabel(); } - function onSceneEnvDataChanged() { + function onSceneEnvDataChanged() + { updateEnvBackground(); } } + // Shared nodes of the overlay, set as importScene on all overlay views. + // Content here can be used as is on all splits. + // Nodes that utilize autoscaling or otherwise need to have different appearance on each split + // need to have separate copy on each split. Node { id: overlayScene - PerspectiveCamera { - id: overlayPerspectiveCamera - clipFar: viewRoot.editView ? viewRoot.editView.perspectiveCamera.clipFar : 1000 - clipNear: viewRoot.editView ? viewRoot.editView.perspectiveCamera.clipNear : 1 - position: viewRoot.editView ? viewRoot.editView.perspectiveCamera.position : Qt.vector3d(0, 0, 0) - rotation: viewRoot.editView ? viewRoot.editView.perspectiveCamera.rotation : Qt.quaternion(1, 0, 0, 0) - } - - OrthographicCamera { - id: overlayOrthoCamera - clipFar: viewRoot.editView ? viewRoot.editView.orthoCamera.clipFar : 1000 - clipNear: viewRoot.editView ? viewRoot.editView.orthoCamera.clipNear : 1 - position: viewRoot.editView ? viewRoot.editView.orthoCamera.position : Qt.vector3d(0, 0, 0) - rotation: viewRoot.editView ? viewRoot.editView.orthoCamera.rotation : Qt.quaternion(1, 0, 0, 0) - horizontalMagnification: viewRoot.editView ? viewRoot.editView.orthoCamera.horizontalMagnification : 1 - verticalMagnification: viewRoot.editView ? viewRoot.editView.orthoCamera.verticalMagnification : 1 - } - - MouseArea3D { - id: gizmoDragHelper - view3D: overlayView - } - Node { id: multiSelectionNode objectName: "multiSelectionNode" } - - MoveGizmo { - id: moveGizmo - scale: autoScale.getScale(Qt.vector3d(5, 5, 5)) - highlightOnHover: true - targetNode: viewRoot.selectedNode - globalOrientation: viewRoot.globalOrientation - visible: viewRoot.selectedNode && transformMode === EditView3D.TransformMode.Move - view3D: overlayView - dragHelper: gizmoDragHelper - property var propertyNames: ["position"] - - onPositionCommit: { - if (targetNode == multiSelectionNode) - viewRoot.commitObjectProperty(_generalHelper.multiSelectionTargets(), propertyNames); - else - viewRoot.commitObjectProperty([viewRoot.selectedNode], propertyNames); - } - onPositionMove: { - if (targetNode == multiSelectionNode) - viewRoot.changeObjectProperty(_generalHelper.multiSelectionTargets(), propertyNames); - else - viewRoot.changeObjectProperty([viewRoot.selectedNode], propertyNames); - } - } - - ScaleGizmo { - id: scaleGizmo - scale: autoScale.getScale(Qt.vector3d(5, 5, 5)) - highlightOnHover: true - targetNode: viewRoot.selectedNode - visible: viewRoot.selectedNode && transformMode === EditView3D.TransformMode.Scale - view3D: overlayView - dragHelper: gizmoDragHelper - property var propertyNames: ["scale"] - property var propertyNamesMulti: ["position", "scale"] - - onScaleCommit: { - if (targetNode == multiSelectionNode) - viewRoot.commitObjectProperty(_generalHelper.multiSelectionTargets(), propertyNamesMulti); - else - viewRoot.commitObjectProperty([viewRoot.selectedNode], propertyNames); - } - onScaleChange: { - if (targetNode == multiSelectionNode) - viewRoot.changeObjectProperty(_generalHelper.multiSelectionTargets(), propertyNamesMulti); - else - viewRoot.changeObjectProperty([viewRoot.selectedNode], propertyNames); - } - } - - RotateGizmo { - id: rotateGizmo - scale: autoScale.getScale(Qt.vector3d(7, 7, 7)) - highlightOnHover: true - targetNode: viewRoot.selectedNode - globalOrientation: viewRoot.globalOrientation - visible: viewRoot.selectedNode && transformMode === EditView3D.TransformMode.Rotate - view3D: overlayView - dragHelper: gizmoDragHelper - property var propertyNames: ["eulerRotation"] - property var propertyNamesMulti: ["position", "eulerRotation"] - - onRotateCommit: { - if (targetNode == multiSelectionNode) - viewRoot.commitObjectProperty(_generalHelper.multiSelectionTargets(), propertyNamesMulti); - else - viewRoot.commitObjectProperty([viewRoot.selectedNode], propertyNames); - } - onRotateChange: { - if (targetNode == multiSelectionNode) - viewRoot.changeObjectProperty(_generalHelper.multiSelectionTargets(), propertyNamesMulti); - else - viewRoot.changeObjectProperty([viewRoot.selectedNode], propertyNames); - } - onCurrentAngleChanged: rotateGizmoLabel.updateLabel() - } - - LightGizmo { - id: lightGizmo - targetNode: viewRoot.selectedNode != multiSelectionNode ? viewRoot.selectedNode : null - view3D: overlayView - dragHelper: gizmoDragHelper - - onPropertyValueCommit: (propName) => { - viewRoot.commitObjectProperty([targetNode], [propName]); - } - onPropertyValueChange: (propName) => { - viewRoot.changeObjectProperty([targetNode], [propName]); - } - } - - AutoScaleHelper { - id: autoScale - view3D: overlayView - position: moveGizmo.scenePosition - } - - AutoScaleHelper { - id: pivotAutoScale - view3D: overlayView - position: pivotLine.startPos - } - - Line3D { - id: pivotLine - visible: viewRoot.selectedNode && viewRoot.selectedNode != multiSelectionNode - name: "3D Edit View Pivot Line" - color: "#ddd600" - - startPos: viewRoot.selectedNode ? viewRoot.selectedNode.scenePosition - : Qt.vector3d(0, 0, 0) - Connections { - target: viewRoot - function onSelectedNodeChanged() - { - pivotLine.endPos = gizmoDragHelper.pivotScenePosition(viewRoot.selectedNode); - } - } - Connections { - target: viewRoot.selectedNode - function onSceneTransformChanged() - { - pivotLine.endPos = gizmoDragHelper.pivotScenePosition(viewRoot.selectedNode); - } - } - - Model { - id: pivotCap - readonly property bool _edit3dLocked: true // Make this non-pickable - source: "#Sphere" - scale: pivotAutoScale.getScale(Qt.vector3d(0.03, 0.03, 0.03)) - position: pivotLine.startPos - materials: [ - DefaultMaterial { - id: lineMat - lighting: DefaultMaterial.NoLighting - cullMode: Material.NoCulling - diffuseColor: pivotLine.color - } - ] - } - } } Item { id: contentItem anchors.fill: parent - Rectangle { - id: viewRect + Item { + id: viewContainer anchors.fill: parent - gradient: Gradient { + Gradient { + id: bgGradient GradientStop { position: 1.0; color: backgroundGradientColorStart } GradientStop { position: 0.0; color: backgroundGradientColorEnd } } + Rectangle { + id: viewRect0 + width: viewRoot.splitView ? parent.width / 2 : parent.width + height: viewRoot.splitView ? parent.height / 2 : parent.height + x: 0 + y: 0 + visible: viewRoot.splitView || viewRoot.activeSplit == 0 + gradient: bgGradient + OverlayView3D { + id: overlayView0 + editView: viewRoot.editViews[0] + viewRoot: viewRoot + importScene: overlayScene + + onChangeObjectProperty: (nodes, props) => { + viewRoot.changeObjectProperty(nodes, props); + } + onCommitObjectProperty: (nodes, props) => { + viewRoot.commitObjectProperty(nodes, props); + } + } + EditCameraController { + id: cameraControl0 + viewRoot: viewRoot + splitId: 0 + } + } + + Rectangle { + id: viewRect1 + width: viewRoot.splitView ? parent.width / 2 : parent.width + height: viewRoot.splitView ? parent.height / 2 : parent.height + x: viewRoot.splitView ? parent.width / 2 : 0 + y: 0 + visible: viewRoot.splitView || viewRoot.activeSplit == 1 + gradient: bgGradient + OverlayView3D { + id: overlayView1 + editView: viewRoot.editViews[1] + viewRoot: viewRoot + importScene: overlayScene + + onChangeObjectProperty: (nodes, props) => { + viewRoot.changeObjectProperty(nodes, props); + } + onCommitObjectProperty: (nodes, props) => { + viewRoot.commitObjectProperty(nodes, props); + } + } + EditCameraController { + id: cameraControl1 + viewRoot: viewRoot + splitId: 1 + } + } + + Rectangle { + id: viewRect2 + width: viewRoot.splitView ? parent.width / 2 : parent.width + height: viewRoot.splitView ? parent.height / 2 : parent.height + x: 0 + y: viewRoot.splitView ? parent.height / 2 : 0 + visible: viewRoot.splitView || viewRoot.activeSplit == 2 + gradient: bgGradient + OverlayView3D { + id: overlayView2 + editView: viewRoot.editViews[2] + viewRoot: viewRoot + importScene: overlayScene + + onChangeObjectProperty: (nodes, props) => { + viewRoot.changeObjectProperty(nodes, props); + } + onCommitObjectProperty: (nodes, props) => { + viewRoot.commitObjectProperty(nodes, props); + } + } + EditCameraController { + id: cameraControl2 + viewRoot: viewRoot + splitId: 2 + } + } + + Rectangle { + id: viewRect3 + width: viewRoot.splitView ? parent.width / 2 : parent.width + height: viewRoot.splitView ? parent.height / 2 : parent.height + x: viewRoot.splitView ? parent.width / 2 : 0 + y: viewRoot.splitView ? parent.height / 2 : 0 + visible: viewRoot.splitView || viewRoot.activeSplit == 3 + gradient: bgGradient + OverlayView3D { + id: overlayView3 + editView: viewRoot.editViews[3] + viewRoot: viewRoot + importScene: overlayScene + + onChangeObjectProperty: (nodes, props) => { + viewRoot.changeObjectProperty(nodes, props); + } + onCommitObjectProperty: (nodes, props) => { + viewRoot.commitObjectProperty(nodes, props); + } + } + EditCameraController { + id: cameraControl3 + viewRoot: viewRoot + splitId: 3 + } + } + + Rectangle { + // Vertical border between splits + visible: viewRoot.splitView + x: parent.width / 2 + y: 0 + width: 1 + height: parent.height + border.width: 1 + border.color: "#aaaaaa" + } + + Rectangle { + // Horizontal border between splits + visible: viewRoot.splitView + x: 0 + y: parent.height / 2 + height: 1 + width: parent.width + border.width: 1 + border.color: "#aaaaaa" + } + + Rectangle { + // Active split highlight + visible: viewRoot.splitView + x: viewRects[viewRoot.activeSplit].x + y: viewRects[viewRoot.activeSplit].y + height: viewRects[viewRoot.activeSplit].height + + (viewRoot.activeSplit === 0 || viewRoot.activeSplit === 1 ? 1 : 0) + width: viewRects[viewRoot.activeSplit].width + + (viewRoot.activeSplit === 0 || viewRoot.activeSplit === 2 ? 1 : 0) + border.width: 2 + border.color: "#57B9FC" + color: "transparent" + } + MouseArea { anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton hoverEnabled: false + z: -10 property MouseArea3D freeDraggerArea property point pressPoint property bool initialMoveBlock: false onPressed: (mouse) => { - if (viewRoot.editView) { + viewRoot.updateActiveSplit(mouse.x, mouse.y); + + let splitPoint = viewRoot.resolveSplitPoint(mouse.x, mouse.y); + + if (viewRoot.activeEditView) { // First pick overlay to check for hits there - var pickResult = _generalHelper.pickViewAt(overlayView, mouse.x, mouse.y); + var pickResult = _generalHelper.pickViewAt(activeOverlayView, + splitPoint.x, splitPoint.y); var resolvedResult = _generalHelper.resolvePick(pickResult.objectHit); + if (!resolvedResult) { // No hits from overlay view, pick the main scene - pickResult = _generalHelper.pickViewAt(viewRoot.editView, mouse.x, mouse.y); + pickResult = _generalHelper.pickViewAt(viewRoot.activeEditView, + splitPoint.x, splitPoint.y); resolvedResult = _generalHelper.resolvePick(pickResult.objectHit); } - handleObjectClicked(resolvedResult, mouse.button, mouse.modifiers & Qt.ControlModifier); + handleObjectClicked(resolvedResult, mouse.button, + mouse.modifiers & Qt.ControlModifier); if (pickResult.objectHit && pickResult.objectHit instanceof Node) { if (transformMode === EditView3D.TransformMode.Move) - freeDraggerArea = moveGizmo.freeDraggerArea; + freeDraggerArea = activeOverlayView.moveGizmo.freeDraggerArea; else if (transformMode === EditView3D.TransformMode.Rotate) - freeDraggerArea = rotateGizmo.freeDraggerArea; + freeDraggerArea = activeOverlayView.rotateGizmo.freeDraggerArea; else if (transformMode === EditView3D.TransformMode.Scale) - freeDraggerArea = scaleGizmo.freeDraggerArea; + freeDraggerArea = activeOverlayView.scaleGizmo.freeDraggerArea; pressPoint.x = mouse.x; pressPoint.y = mouse.y; initialMoveBlock = true; @@ -986,14 +857,17 @@ Item { } onPositionChanged: (mouse) => { if (freeDraggerArea) { - if (initialMoveBlock && Math.abs(pressPoint.x - mouse.x) + Math.abs(pressPoint.y - mouse.y) > 10) { + let splitPoint = viewRoot.resolveSplitPoint(mouse.x, mouse.y); + let splitPress = viewRoot.resolveSplitPoint(pressPoint.x, pressPoint.y); + if (initialMoveBlock && Math.abs(splitPress.x - splitPoint.x) + + Math.abs(splitPress.y - splitPoint.y) > 10) { // Don't force press event at actual press, as that puts the gizmo // in free-dragging state, which is bad UX if drag is not actually done - freeDraggerArea.forcePressEvent(pressPoint.x, pressPoint.y); - freeDraggerArea.forceMoveEvent(mouse.x, mouse.y); + freeDraggerArea.forcePressEvent(splitPress.x, splitPress.y); + freeDraggerArea.forceMoveEvent(splitPoint.x, splitPoint.y); initialMoveBlock = false; } else { - freeDraggerArea.forceMoveEvent(mouse.x, mouse.y); + freeDraggerArea.forceMoveEvent(splitPoint.x, splitPoint.y); } } } @@ -1001,10 +875,13 @@ Item { function handleRelease(mouse) { if (freeDraggerArea) { - if (initialMoveBlock) - freeDraggerArea.forceReleaseEvent(pressPoint.x, pressPoint.y); - else - freeDraggerArea.forceReleaseEvent(mouse.x, mouse.y); + if (initialMoveBlock) { + let splitPress = viewRoot.resolveSplitPoint(pressPoint.x, pressPoint.y); + freeDraggerArea.forceReleaseEvent(splitPress.x, splitPress.y); + } else { + let splitPoint = viewRoot.resolveSplitPoint(mouse.x, mouse.y); + freeDraggerArea.forceReleaseEvent(splitPoint.x, splitPoint.y); + } freeDraggerArea = null; } } @@ -1021,27 +898,13 @@ Item { anchors.fill: parent } - View3D { - id: overlayView - anchors.fill: parent - camera: viewRoot.usePerspective ? overlayPerspectiveCamera : overlayOrthoCamera - importScene: overlayScene - z: 2 - - environment: sceneEnv - SceneEnvironment { - id: sceneEnv - antialiasingMode: SceneEnvironment.MSAA - antialiasingQuality: SceneEnvironment.High - } - } - Overlay2D { id: gizmoLabel - targetNode: moveGizmo.visible ? moveGizmo : scaleGizmo - targetView: overlayView + targetNode: activeOverlayView.moveGizmo.visible ? activeOverlayView.moveGizmo + : activeOverlayView.scaleGizmo + targetView: activeOverlayView visible: targetNode.dragging - z: 3 + z: 300 function updateLabel() { @@ -1050,7 +913,7 @@ Item { if (!gizmoLabel.visible || !viewRoot.selectedNode || shuttingDown) return; var targetProperty; - if (gizmoLabel.targetNode === moveGizmo) + if (gizmoLabel.targetNode === activeOverlayView.moveGizmo) gizmoLabelText.text = _generalHelper.snapPositionDragTooltip(viewRoot.selectedNode.position); else gizmoLabelText.text = _generalHelper.snapScaleDragTooltip(viewRoot.selectedNode.scale); @@ -1081,26 +944,42 @@ Item { Rectangle { id: rotateGizmoLabel color: "white" - x: rotateGizmo.currentMousePos.x - (10 + width) - y: rotateGizmo.currentMousePos.y - (10 + height) width: rotateGizmoLabelText.width + 4 height: rotateGizmoLabelText.height + 4 border.width: 1 - visible: rotateGizmo.dragging - parent: rotateGizmo.view3D - z: 3 + visible: activeOverlayView.rotateGizmo.dragging + z: 300 - function updateLabel() { + Connections { + target: activeOverlayView.rotateGizmo + function onCurrentMousePosChanged() + { + let newPos = viewContainer.mapFromItem( + activeOverlayView, + activeOverlayView.rotateGizmo.currentMousePos.x, + activeOverlayView.rotateGizmo.currentMousePos.y); + rotateGizmoLabel.x = newPos.x - (10 + rotateGizmoLabel.width); + rotateGizmoLabel.y = newPos.y - (10 + rotateGizmoLabel.height); + } + } + + function updateLabel() + { // This is skipped during application shutdown, as calling QQuickText::setText() // during application shutdown can crash the application. - if (!rotateGizmoLabel.visible || !rotateGizmo.targetNode || shuttingDown) + if (!rotateGizmoLabel.visible || !activeOverlayView.rotateGizmo.targetNode || shuttingDown) return; - var degrees = rotateGizmo.currentAngle * (180 / Math.PI); + var degrees = activeOverlayView.rotateGizmo.currentAngle * (180 / Math.PI); rotateGizmoLabelText.text = _generalHelper.snapRotationDragTooltip(degrees); } onVisibleChanged: rotateGizmoLabel.updateLabel() + Connections { + target: activeOverlayView.rotateGizmo + function onCurrentAngleChanged() { rotateGizmoLabel.updateLabel() } + } + Text { id: rotateGizmoLabelText anchors.centerIn: parent @@ -1110,39 +989,31 @@ Item { Rectangle { id: lightGizmoLabel color: "white" - x: lightGizmo.currentMousePos.x - (10 + width) - y: lightGizmo.currentMousePos.y - (10 + height) width: lightGizmoLabelText.width + 4 height: lightGizmoLabelText.height + 4 border.width: 1 - visible: lightGizmo.dragging - parent: lightGizmo.view3D - z: 3 + visible: activeOverlayView.lightGizmo.dragging + z: 300 + + Connections { + target: activeOverlayView.lightGizmo + function onCurrentMousePosChanged() + { + let newPos = viewContainer.mapFromItem( + activeOverlayView, + activeOverlayView.lightGizmo.currentMousePos.x, + activeOverlayView.lightGizmo.currentMousePos.y); + lightGizmoLabel.x = newPos.x - (10 + lightGizmoLabel.width); + lightGizmoLabel.y = newPos.y - (10 + lightGizmoLabel.height); + } + } Text { id: lightGizmoLabelText - text: lightGizmo.currentLabel + text: activeOverlayView.lightGizmo.currentLabel anchors.centerIn: parent } } - - EditCameraController { - id: cameraControl - camera: viewRoot.editView ? viewRoot.editView.camera : null - anchors.fill: parent - view3d: viewRoot.editView - sceneId: viewRoot.sceneId - } - } - - AxisHelper { - anchors.right: parent.right - anchors.top: parent.top - width: 100 - height: width - editCameraCtrl: cameraControl - selectedNode: viewRoot.selectedNodes.length === 1 ? viewRoot.selectionBoxes[0].model - : viewRoot.selectedNode } Text { diff --git a/src/tools/qml2puppet/mockfiles/qt6/OriginGizmo.qml b/src/tools/qml2puppet/mockfiles/qt6/OriginGizmo.qml new file mode 100644 index 00000000000..5054caccb19 --- /dev/null +++ b/src/tools/qml2puppet/mockfiles/qt6/OriginGizmo.qml @@ -0,0 +1,282 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick3D + +Item { + id: root + required property Node targetNode + + enum Axis { + PositiveX, + PositiveY, + PositiveZ, + NegativeX, + NegativeY, + NegativeZ + } + + readonly property list rotations: [ + Qt.quaternion(Math.SQRT1_2, 0, Math.SQRT1_2, 0), // PositiveX + Qt.quaternion(Math.SQRT1_2, -Math.SQRT1_2, 0, 0), // PositiveY + Qt.quaternion(1, 0, 0, 0), // PositiveZ + Qt.quaternion(Math.SQRT1_2, 0, -Math.SQRT1_2, 0), // NegativeX + Qt.quaternion(Math.SQRT1_2, Math.SQRT1_2, 0, 0), // NegativeY + Qt.quaternion(0, 0, 1, 0) // NegativeZ + ] + + function quaternionForAxis(axis) { + return rotations[axis] + } + + signal axisClicked(int axis) + + QtObject { + id: stylePalette + property color brightBall: "#eeeeee" + property color dimBall: "#111111" + property color xAxis: "#ed324d" + property color yAxis: "#489600" + property color zAxis: "#0075cc" + property color background: "#aa303030" + } + + component LineRectangle : Rectangle { + property vector2d startPoint: Qt.vector2d(0, 0) + property vector2d endPoint: Qt.vector2d(0, 0) + transformOrigin: Item.Left + height: 2 + antialiasing: true + + width: startPoint.minus(endPoint).length() + rotation: Math.atan2(endPoint.y - startPoint.y, endPoint.x - startPoint.x) * 180 / Math.PI + } + + Rectangle { + id: ballBackground + anchors.centerIn: parent + width: parent.width > parent.height ? parent.height : parent.width + height: width + radius: width / 2 + color: ballBackgroundHoverHandler.hovered ? stylePalette.background : "transparent" + antialiasing: true + + readonly property real subBallWidth: width / 5 + readonly property real subBallHalfWidth: subBallWidth * 0.5 + readonly property real subBallOffset: radius - subBallWidth / 2 + + Item { + anchors.centerIn: parent + + component SubBall : Rectangle { + id: subBallRoot + required property Node targetNode + required property real offset + + property alias labelText: label.text + property alias labelColor: label.color + property alias labelVisible: label.visible + property alias hovered: subBallHoverHandler.hovered + property vector3d initialPosition: Qt.vector3d(0, 0, 0) + readonly property vector3d position: quaternionVectorMultiply(targetNode.sceneRotation.inverted(), initialPosition) + + signal tapped() + + function quaternionVectorMultiply(q, v) { + let qv = Qt.vector3d(q.x, q.y, q.z) + let uv = qv.crossProduct(v) + let uuv = qv.crossProduct(uv) + uv = uv.times(2.0 * q.scalar) + uuv = uuv.times(2.0) + return v.plus(uv).plus(uuv) + } + + antialiasing: true + height: width + radius: width / 2 + x: offset * position.x - width / 2 + y: offset * -position.y - height / 2 + z: position.z + + HoverHandler { + id: subBallHoverHandler + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton + onClicked: (e) => { + subBallRoot.tapped() + e.accepted = true + } + } + + Text { + id: label + anchors.fill: parent + antialiasing: true + font.pixelSize: 8 + horizontalAlignment: Qt.AlignHCenter + verticalAlignment: Qt.AlignVCenter + } + } + + SubBall { + id: positiveX + targetNode: root.targetNode + width: ballBackground.subBallWidth + offset: ballBackground.subBallOffset + labelText: qsTr("X") + labelColor: hovered ? stylePalette.brightBall : stylePalette.dimBall + color: stylePalette.xAxis + initialPosition: Qt.vector3d(1, 0, 0) + onTapped: { + let axis = OriginGizmo.Axis.PositiveX + if (_generalHelper.compareQuaternions( + root.targetNode.sceneRotation, + root.quaternionForAxis(OriginGizmo.Axis.PositiveX))) { + axis = OriginGizmo.Axis.NegativeX + } + root.axisClicked(axis) + } + } + + LineRectangle { + endPoint: Qt.vector2d(positiveX.x + ballBackground.subBallHalfWidth, positiveX.y + ballBackground.subBallHalfWidth) + color: stylePalette.xAxis + z: positiveX.z - 0.001 + } + + SubBall { + id: positiveY + targetNode: root.targetNode + width: ballBackground.subBallWidth + offset: ballBackground.subBallOffset + labelText: qsTr("Y") + labelColor: hovered ? stylePalette.brightBall : stylePalette.dimBall + color: stylePalette.yAxis + initialPosition: Qt.vector3d(0, 1, 0) + onTapped: { + let axis = OriginGizmo.Axis.PositiveY + if (_generalHelper.compareQuaternions( + root.targetNode.sceneRotation, + root.quaternionForAxis(OriginGizmo.Axis.PositiveY))) { + axis = OriginGizmo.Axis.NegativeY + } + root.axisClicked(axis) + } + } + + LineRectangle { + endPoint: Qt.vector2d(positiveY.x + ballBackground.subBallHalfWidth, positiveY.y + ballBackground.subBallHalfWidth) + color: stylePalette.yAxis + z: positiveY.z - 0.001 + } + + SubBall { + id: positiveZ + targetNode: root.targetNode + width: ballBackground.subBallWidth + offset: ballBackground.subBallOffset + labelText: qsTr("Z") + labelColor: hovered ? stylePalette.brightBall : stylePalette.dimBall + color: stylePalette.zAxis + initialPosition: Qt.vector3d(0, 0, 1) + onTapped: { + let axis = OriginGizmo.Axis.PositiveZ + if (_generalHelper.compareQuaternions( + root.targetNode.sceneRotation, + root.quaternionForAxis(OriginGizmo.Axis.PositiveZ))) { + axis = OriginGizmo.Axis.NegativeZ + } + root.axisClicked(axis) + } + } + + LineRectangle { + endPoint: Qt.vector2d(positiveZ.x + ballBackground.subBallHalfWidth, positiveZ.y + ballBackground.subBallHalfWidth) + color: stylePalette.zAxis + z: positiveZ.z - 0.001 + } + + SubBall { + targetNode: root.targetNode + width: ballBackground.subBallWidth + offset: ballBackground.subBallOffset + labelText: qsTr("-X") + labelColor: hovered ? stylePalette.brightBall : stylePalette.dimBall + labelVisible: hovered || _generalHelper.compareQuaternions( + root.targetNode.sceneRotation, + root.quaternionForAxis(OriginGizmo.Axis.NegativeX)) + color: Qt.rgba(stylePalette.xAxis.r, stylePalette.xAxis.g, stylePalette.xAxis.b, z + 1 * 0.5) + border.color: stylePalette.xAxis + border.width: 2 + initialPosition: Qt.vector3d(-1, 0, 0) + onTapped: { + let axis = OriginGizmo.Axis.NegativeX + if (_generalHelper.compareQuaternions( + root.targetNode.sceneRotation, + root.quaternionForAxis(OriginGizmo.Axis.NegativeX))) { + axis = OriginGizmo.Axis.PositiveX + } + root.axisClicked(axis) + } + } + + SubBall { + targetNode: root.targetNode + width: ballBackground.subBallWidth + offset: ballBackground.subBallOffset + labelText: qsTr("-Y") + labelColor: hovered ? stylePalette.brightBall : stylePalette.dimBall + labelVisible: hovered || _generalHelper.compareQuaternions( + root.targetNode.sceneRotation, + root.quaternionForAxis(OriginGizmo.Axis.NegativeY)) + color: Qt.rgba(stylePalette.yAxis.r, stylePalette.yAxis.g, stylePalette.yAxis.b, z + 1 * 0.5) + border.color: stylePalette.yAxis + border.width: 2 + initialPosition: Qt.vector3d(0, -1, 0) + onTapped: { + let axis = OriginGizmo.Axis.NegativeY + if (_generalHelper.compareQuaternions( + root.targetNode.sceneRotation, + root.quaternionForAxis(OriginGizmo.Axis.NegativeY))) { + axis = OriginGizmo.Axis.PositiveY + } + root.axisClicked(axis) + } + } + + SubBall { + targetNode: root.targetNode + width: ballBackground.subBallWidth + offset: ballBackground.subBallOffset + labelText: qsTr("-Z") + labelColor: hovered ? stylePalette.brightBall : stylePalette.dimBall + labelVisible: hovered || _generalHelper.compareQuaternions( + root.targetNode.sceneRotation, + root.quaternionForAxis(OriginGizmo.Axis.NegativeZ)) + color: Qt.rgba(stylePalette.zAxis.r, stylePalette.zAxis.g, stylePalette.zAxis.b, z + 1 * 0.5) + border.color: stylePalette.zAxis + border.width: 2 + initialPosition: Qt.vector3d(0, 0, -1) + onTapped: { + let axis = OriginGizmo.Axis.NegativeZ + if (_generalHelper.compareQuaternions( + root.targetNode.sceneRotation, + root.quaternionForAxis(OriginGizmo.Axis.NegativeZ))) { + axis = OriginGizmo.Axis.PositiveZ + } + root.axisClicked(axis) + } + } + } + + HoverHandler { + id: ballBackgroundHoverHandler + acceptedDevices: PointerDevice.Mouse + cursorShape: Qt.PointingHandCursor + } + } +} diff --git a/src/tools/qml2puppet/mockfiles/qt6/Overlay2D.qml b/src/tools/qml2puppet/mockfiles/qt6/Overlay2D.qml index affbb8f25c2..1ffd7d6be93 100644 --- a/src/tools/qml2puppet/mockfiles/qt6/Overlay2D.qml +++ b/src/tools/qml2puppet/mockfiles/qt6/Overlay2D.qml @@ -37,11 +37,13 @@ Item { var scenePosWithOffset = Qt.vector3d(scenePos.x + offset.x, scenePos.y + offset.y, scenePos.z + offset.z); - var viewPos = targetView ? targetView.mapFrom3DScene(scenePosWithOffset) - : Qt.vector3d(0, 0, 0); - root.x = viewPos.x; - root.y = viewPos.y; - isBehindCamera = viewPos.z <= 0; + if (targetView) { + var viewPos = targetView.mapFrom3DScene(scenePosWithOffset); + let newPos = parent.mapFromItem(targetView, viewPos.x, viewPos.y); + x = newPos.x; + y = newPos.y; + isBehindCamera = viewPos.z <= 0; + } } } diff --git a/src/tools/qml2puppet/mockfiles/qt6/OverlayView3D.qml b/src/tools/qml2puppet/mockfiles/qt6/OverlayView3D.qml new file mode 100644 index 00000000000..13a37f4f723 --- /dev/null +++ b/src/tools/qml2puppet/mockfiles/qt6/OverlayView3D.qml @@ -0,0 +1,541 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick3D +import MouseArea3D + +View3D { + id: overlayView + + property alias moveGizmo: moveGizmo + property alias rotateGizmo: rotateGizmo + property alias scaleGizmo: scaleGizmo + property alias lightGizmo: lightGizmo + + property var viewRoot: null + property View3D editView: null + property bool isActive: viewRoot.overlayViews[viewRoot.activeSplit] === overlayView + + property var lightIconGizmos: [] + property var cameraGizmos: [] + property var particleSystemIconGizmos: [] + property var particleEmitterGizmos: [] + + signal commitObjectProperty(var objects, var propNames) + signal changeObjectProperty(var objects, var propNames) + + camera: viewRoot.usePerspective ? overlayPerspectiveCamera : overlayOrthoCamera + + anchors.fill: parent + z: 2 + clip: true + + environment: sceneEnv + + function addLightGizmo(scene, obj) + { + // Insert into first available gizmo if we don't already have gizmo for this object + var slotFound = -1; + for (var i = 0; i < lightIconGizmos.length; ++i) { + if (!lightIconGizmos[i].targetNode) { + slotFound = i; + } else if (lightIconGizmos[i].targetNode === obj) { + lightIconGizmos[i].scene = scene; + return; + } + } + + if (slotFound !== -1) { + lightIconGizmos[slotFound].scene = scene; + lightIconGizmos[slotFound].targetNode = obj; + lightIconGizmos[slotFound].locked = _generalHelper.isLocked(obj); + lightIconGizmos[slotFound].hidden = _generalHelper.isHidden(obj); + return; + } + + // No free gizmos available, create a new one + var gizmoComponent = Qt.createComponent("LightIconGizmo.qml"); + if (gizmoComponent.status === Component.Ready) { + var gizmo = gizmoComponent.createObject(overlayView, + {"view3D": overlayView, + "targetNode": obj, + "selectedNodes": viewRoot.selectedNodes, + "scene": scene, + "activeScene": viewRoot.activeScene, + "locked": _generalHelper.isLocked(obj), + "hidden": _generalHelper.isHidden(obj), + "globalShow": viewRoot.showIconGizmo}); + lightIconGizmos[lightIconGizmos.length] = gizmo; + gizmo.clicked.connect(viewRoot.handleObjectClicked); + gizmo.selectedNodes = Qt.binding(function() {return viewRoot.selectedNodes;}); + gizmo.activeScene = Qt.binding(function() {return viewRoot.activeScene;}); + gizmo.globalShow = Qt.binding(function() {return viewRoot.showIconGizmo;}); + } + } + + function releaseLightGizmo(obj) + { + for (var i = 0; i < lightIconGizmos.length; ++i) { + if (lightIconGizmos[i].targetNode === obj) { + lightIconGizmos[i].scene = null; + lightIconGizmos[i].targetNode = null; + return; + } + } + } + + function updateLightGizmoScene(scene, obj) + { + for (var i = 0; i < lightIconGizmos.length; ++i) { + if (lightIconGizmos[i].targetNode === obj) { + lightIconGizmos[i].scene = scene; + return; + } + } + } + + function addCameraGizmo(scene, obj) + { + // Insert into first available gizmo if we don't already have gizmo for this object + var slotFound = -1; + for (var i = 0; i < cameraGizmos.length; ++i) { + if (!cameraGizmos[i].targetNode) { + slotFound = i; + } else if (cameraGizmos[i].targetNode === obj) { + cameraGizmos[i].scene = scene; + return; + } + } + + if (slotFound !== -1) { + cameraGizmos[slotFound].scene = scene; + cameraGizmos[slotFound].targetNode = obj; + cameraGizmos[slotFound].locked = _generalHelper.isLocked(obj); + cameraGizmos[slotFound].hidden = _generalHelper.isHidden(obj); + return; + } + + // No free gizmos available, create a new one + var gizmoComponent = Qt.createComponent("CameraGizmo.qml"); + var frustumComponent = Qt.createComponent("CameraFrustum.qml"); + if (gizmoComponent.status === Component.Ready && frustumComponent.status === Component.Ready) { + var geometryName = _generalHelper.generateUniqueName("CameraGeometry"); + var frustum = frustumComponent.createObject( + sceneNode, + {"geometryName": geometryName, "viewPortRect": viewRoot.viewPortRect}); + var gizmo = gizmoComponent.createObject( + overlayView, + {"view3D": overlayView, "targetNode": obj, + "selectedNodes": viewRoot.selectedNodes, "scene": scene, "activeScene": viewRoot.activeScene, + "locked": _generalHelper.isLocked(obj), "hidden": _generalHelper.isHidden(obj), + "globalShow": viewRoot.showIconGizmo, "globalShowFrustum": viewRoot.showCameraFrustum}); + + cameraGizmos[cameraGizmos.length] = gizmo; + gizmo.clicked.connect(viewRoot.handleObjectClicked); + gizmo.selectedNodes = Qt.binding(function() {return viewRoot.selectedNodes;}); + gizmo.activeScene = Qt.binding(function() {return viewRoot.activeScene;}); + gizmo.globalShow = Qt.binding(function() {return viewRoot.showIconGizmo;}); + gizmo.globalShowFrustum = Qt.binding(function() {return viewRoot.showCameraFrustum;}); + frustum.viewPortRect = Qt.binding(function() {return viewRoot.viewPortRect;}); + gizmo.connectFrustum(frustum); + } + } + + function releaseCameraGizmo(obj) + { + for (var i = 0; i < cameraGizmos.length; ++i) { + if (cameraGizmos[i].targetNode === obj) { + cameraGizmos[i].scene = null; + cameraGizmos[i].targetNode = null; + return; + } + } + } + + function updateCameraGizmoScene(scene, obj) + { + for (var i = 0; i < cameraGizmos.length; ++i) { + if (cameraGizmos[i].targetNode === obj) { + cameraGizmos[i].scene = scene; + return; + } + } + } + + function addParticleSystemGizmo(scene, obj) + { + // Insert into first available gizmo if we don't already have gizmo for this object + var slotFound = -1; + for (var i = 0; i < particleSystemIconGizmos.length; ++i) { + if (!particleSystemIconGizmos[i].targetNode) { + slotFound = i; + } else if (particleSystemIconGizmos[i].targetNode === obj) { + particleSystemIconGizmos[i].scene = scene; + return; + } + } + + if (slotFound !== -1) { + particleSystemIconGizmos[slotFound].scene = scene; + particleSystemIconGizmos[slotFound].targetNode = obj; + particleSystemIconGizmos[slotFound].locked = _generalHelper.isLocked(obj); + particleSystemIconGizmos[slotFound].hidden = _generalHelper.isHidden(obj); + return; + } + + // No free gizmos available, create a new one + var gizmoComponent = Qt.createComponent("ParticleSystemGizmo.qml"); + if (gizmoComponent.status === Component.Ready) { + var gizmo = gizmoComponent.createObject(overlayView, + {"view3D": overlayView, + "targetNode": obj, + "selectedNodes": viewRoot.selectedNodes, + "scene": scene, + "activeScene": viewRoot.activeScene, + "locked": _generalHelper.isLocked(obj), + "hidden": _generalHelper.isHidden(obj), + "globalShow": viewRoot.showIconGizmo, + "activeParticleSystem": viewRoot.activeParticleSystem}); + particleSystemIconGizmos[particleSystemIconGizmos.length] = gizmo; + gizmo.clicked.connect(viewRoot.handleObjectClicked); + gizmo.selectedNodes = Qt.binding(function() {return viewRoot.selectedNodes;}); + gizmo.activeScene = Qt.binding(function() {return viewRoot.activeScene;}); + gizmo.globalShow = Qt.binding(function() {return viewRoot.showIconGizmo;}); + gizmo.activeParticleSystem = Qt.binding(function() {return viewRoot.activeParticleSystem;}); + } + } + + function releaseParticleSystemGizmo(obj) + { + for (var i = 0; i < particleSystemIconGizmos.length; ++i) { + if (particleSystemIconGizmos[i].targetNode === obj) { + particleSystemIconGizmos[i].scene = null; + particleSystemIconGizmos[i].targetNode = null; + return; + } + } + } + + function updateParticleSystemGizmoScene(scene, obj) + { + for (var i = 0; i < particleSystemIconGizmos.length; ++i) { + if (particleSystemIconGizmos[i].targetNode === obj) { + particleSystemIconGizmos[i].scene = scene; + return; + } + } + } + + function addParticleEmitterGizmo(scene, obj) + { + // Insert into first available gizmo if we don't already have gizmo for this object + var slotFound = -1; + for (var i = 0; i < particleEmitterGizmos.length; ++i) { + if (!particleEmitterGizmos[i].targetNode) { + slotFound = i; + } else if (particleEmitterGizmos[i].targetNode === obj) { + particleEmitterGizmos[i].scene = scene; + return; + } + } + + if (slotFound !== -1) { + particleEmitterGizmos[slotFound].scene = scene; + particleEmitterGizmos[slotFound].targetNode = obj; + particleEmitterGizmos[slotFound].hidden = _generalHelper.isHidden(obj); + particleEmitterGizmos[slotFound].systemHidden = _generalHelper.isHidden(obj.system); + return; + } + + // No free gizmos available, create a new one + var gizmoComponent = Qt.createComponent("ParticleEmitterGizmo.qml"); + if (gizmoComponent.status === Component.Ready) { + var gizmo = gizmoComponent.createObject( + overlayScene, + {"view3d": overlayView, "targetNode": obj, "selectedNodes": viewRoot.selectedNodes, + "activeParticleSystem": viewRoot.activeParticleSystem, "scene": scene, + "activeScene": viewRoot.activeScene, "hidden": _generalHelper.isHidden(obj), + "systemHidden": _generalHelper.isHidden(obj.system), + "globalShow": viewRoot.showParticleEmitter}); + + particleEmitterGizmos[particleEmitterGizmos.length] = gizmo; + gizmo.selectedNodes = Qt.binding(function() {return viewRoot.selectedNodes;}); + gizmo.activeParticleSystem = Qt.binding(function() {return viewRoot.activeParticleSystem;}); + gizmo.globalShow = Qt.binding(function() {return viewRoot.showParticleEmitter;}); + gizmo.activeScene = Qt.binding(function() {return viewRoot.activeScene;}); + } + } + + function releaseParticleEmitterGizmo(obj) + { + for (var i = 0; i < particleEmitterGizmos.length; ++i) { + if (particleEmitterGizmos[i].targetNode === obj) { + particleEmitterGizmos[i].scene = null; + particleEmitterGizmos[i].targetNode = null; + return; + } + } + } + + function updateParticleEmitterGizmoScene(scene, obj) + { + for (var i = 0; i < particleEmitterGizmos.length; ++i) { + if (particleEmitterGizmos[i].targetNode === obj) { + particleEmitterGizmos[i].scene = scene; + return; + } + } + } + + function gizmoAt(x, y) + { + for (var i = 0; i < lightIconGizmos.length; ++i) { + if (lightIconGizmos[i].visible && lightIconGizmos[i].hasPoint(x, y)) + return lightIconGizmos[i].targetNode; + } + for (var i = 0; i < cameraGizmos.length; ++i) { + if (cameraGizmos[i].visible && cameraGizmos[i].hasPoint(x, y)) + return cameraGizmos[i].targetNode; + } + for (var i = 0; i < particleSystemIconGizmos.length; ++i) { + if (particleSystemIconGizmos[i].visible && particleSystemIconGizmos[i].hasPoint(x, y)) + return particleSystemIconGizmos[i].targetNode; + } + return null; + } + + function handleLockedStateChange(node) + { + for (var i = 0; i < lightIconGizmos.length; ++i) { + if (lightIconGizmos[i].targetNode === node) { + lightIconGizmos[i].locked = _generalHelper.isLocked(node); + return; + } + } + for (var i = 0; i < cameraGizmos.length; ++i) { + if (cameraGizmos[i].targetNode === node) { + cameraGizmos[i].locked = _generalHelper.isLocked(node); + return; + } + } + for (var i = 0; i < particleSystemIconGizmos.length; ++i) { + if (particleSystemIconGizmos[i].targetNode === node) { + particleSystemIconGizmos[i].locked = _generalHelper.isLocked(node); + return; + } + } + } + + function handleHiddenStateChange(node) + { + for (var i = 0; i < lightIconGizmos.length; ++i) { + if (lightIconGizmos[i].targetNode === node) { + lightIconGizmos[i].hidden = _generalHelper.isHidden(node); + return; + } + } + for (var i = 0; i < cameraGizmos.length; ++i) { + if (cameraGizmos[i].targetNode === node) { + cameraGizmos[i].hidden = _generalHelper.isHidden(node); + return; + } + } + for (var i = 0; i < particleSystemIconGizmos.length; ++i) { + if (particleSystemIconGizmos[i].targetNode === node) { + particleSystemIconGizmos[i].hidden = _generalHelper.isHidden(node); + return; + } + } + for (var i = 0; i < particleEmitterGizmos.length; ++i) { + if (particleEmitterGizmos[i].targetNode === node) { + particleEmitterGizmos[i].hidden = _generalHelper.isHidden(node); + return; + } else if (particleEmitterGizmos[i].targetNode && particleEmitterGizmos[i].targetNode.system === node) { + particleEmitterGizmos[i].systemHidden = _generalHelper.isHidden(node); + return; + } + } + } + + SceneEnvironment { + id: sceneEnv + antialiasingMode: SceneEnvironment.MSAA + antialiasingQuality: SceneEnvironment.High + } + + Node { + id: sceneNode + + PerspectiveCamera { + id: overlayPerspectiveCamera + clipFar: overlayView.editView ? overlayView.editView.perspectiveCamera.clipFar : 1000 + clipNear: overlayView.editView ? overlayView.editView.perspectiveCamera.clipNear : 1 + position: overlayView.editView ? overlayView.editView.perspectiveCamera.position : Qt.vector3d(0, 0, 0) + rotation: overlayView.editView ? overlayView.editView.perspectiveCamera.rotation : Qt.quaternion(1, 0, 0, 0) + } + + OrthographicCamera { + id: overlayOrthoCamera + clipFar: overlayView.editView ? overlayView.editView.orthoCamera.clipFar : 1000 + clipNear: overlayView.editView ? overlayView.editView.orthoCamera.clipNear : 1 + position: overlayView.editView ? overlayView.editView.orthoCamera.position : Qt.vector3d(0, 0, 0) + rotation: overlayView.editView ? overlayView.editView.orthoCamera.rotation : Qt.quaternion(1, 0, 0, 0) + horizontalMagnification: overlayView.editView ? overlayView.editView.orthoCamera.horizontalMagnification : 1 + verticalMagnification: overlayView.editView ? overlayView.editView.orthoCamera.verticalMagnification : 1 + } + + MouseArea3D { + id: gizmoDragHelper + view3D: overlayView + } + + AutoScaleHelper { + id: autoScale + view3D: overlayView + position: moveGizmo.scenePosition + } + + AutoScaleHelper { + id: pivotAutoScale + view3D: overlayView + position: pivotLine.startPos + } + + MoveGizmo { + id: moveGizmo + scale: autoScale.getScale(Qt.vector3d(5, 5, 5)) + highlightOnHover: true + targetNode: viewRoot.selectedNode + globalOrientation: viewRoot.globalOrientation + visible: viewRoot.selectedNode && viewRoot.transformMode === EditView3D.TransformMode.Move + && overlayView.isActive + view3D: overlayView + dragHelper: gizmoDragHelper + property var propertyNames: ["position"] + + onPositionCommit: { + if (targetNode === viewRoot.multiSelectionNode) + overlayView.commitObjectProperty(_generalHelper.multiSelectionTargets(), propertyNames); + else + overlayView.commitObjectProperty([viewRoot.selectedNode], propertyNames); + } + onPositionMove: { + if (targetNode === viewRoot.multiSelectionNode) + overlayView.changeObjectProperty(_generalHelper.multiSelectionTargets(), propertyNames); + else + overlayView.changeObjectProperty([viewRoot.selectedNode], propertyNames); + } + } + + ScaleGizmo { + id: scaleGizmo + scale: autoScale.getScale(Qt.vector3d(5, 5, 5)) + highlightOnHover: true + targetNode: viewRoot.selectedNode + visible: viewRoot.selectedNode && viewRoot.transformMode === EditView3D.TransformMode.Scale + && overlayView.isActive + view3D: overlayView + dragHelper: gizmoDragHelper + property var propertyNames: ["scale"] + property var propertyNamesMulti: ["position", "scale"] + + onScaleCommit: { + if (targetNode === viewRoot.multiSelectionNode) + overlayView.commitObjectProperty(_generalHelper.multiSelectionTargets(), propertyNamesMulti); + else + overlayView.commitObjectProperty([viewRoot.selectedNode], propertyNames); + } + onScaleChange: { + if (targetNode === viewRoot.multiSelectionNode) + overlayView.changeObjectProperty(_generalHelper.multiSelectionTargets(), propertyNamesMulti); + else + overlayView.changeObjectProperty([viewRoot.selectedNode], propertyNames); + } + } + + RotateGizmo { + id: rotateGizmo + scale: autoScale.getScale(Qt.vector3d(7, 7, 7)) + highlightOnHover: true + targetNode: viewRoot.selectedNode + globalOrientation: viewRoot.globalOrientation + visible: viewRoot.selectedNode && viewRoot.transformMode === EditView3D.TransformMode.Rotate + && overlayView.isActive + view3D: overlayView + dragHelper: gizmoDragHelper + property var propertyNames: ["eulerRotation"] + property var propertyNamesMulti: ["position", "eulerRotation"] + + onRotateCommit: { + if (targetNode === viewRoot.multiSelectionNode) + overlayView.commitObjectProperty(_generalHelper.multiSelectionTargets(), propertyNamesMulti); + else + overlayView.commitObjectProperty([viewRoot.selectedNode], propertyNames); + } + onRotateChange: { + if (targetNode === viewRoot.multiSelectionNode) + overlayView.changeObjectProperty(_generalHelper.multiSelectionTargets(), propertyNamesMulti); + else + overlayView.changeObjectProperty([viewRoot.selectedNode], propertyNames); + } + onCurrentAngleChanged: rotateGizmoLabel.updateLabel() + } + + LightGizmo { + id: lightGizmo + targetNode: viewRoot.selectedNode !== viewRoot.multiSelectionNode ? viewRoot.selectedNode : null + view3D: overlayView + dragHelper: gizmoDragHelper + + onPropertyValueCommit: (propName) => { + overlayView.commitObjectProperty([targetNode], [propName]); + } + onPropertyValueChange: (propName) => { + overlayView.changeObjectProperty([targetNode], [propName]); + } + } + + Line3D { + id: pivotLine + visible: viewRoot.selectedNode && viewRoot.selectedNode !== viewRoot.multiSelectionNode + && overlayView.isActive + name: "3D Edit View Pivot Line" + color: "#ddd600" + + startPos: viewRoot.selectedNode ? viewRoot.selectedNode.scenePosition + : Qt.vector3d(0, 0, 0) + + Connections { + target: viewRoot + function onSelectedNodeChanged() + { + pivotLine.endPos = gizmoDragHelper.pivotScenePosition(viewRoot.selectedNode); + } + } + Connections { + target: viewRoot.selectedNode + function onSceneTransformChanged() + { + pivotLine.endPos = gizmoDragHelper.pivotScenePosition(viewRoot.selectedNode); + } + } + + Model { + id: pivotCap + readonly property bool _edit3dLocked: true // Make this non-pickable + source: "#Sphere" + scale: pivotAutoScale.getScale(Qt.vector3d(0.03, 0.03, 0.03)) + position: pivotLine.startPos + materials: [ + DefaultMaterial { + id: lineMat + lighting: DefaultMaterial.NoLighting + cullMode: Material.NoCulling + diffuseColor: pivotLine.color + } + ] + } + } + } +} diff --git a/src/tools/qml2puppet/mockfiles/qt6/ParticleEmitterGizmo.qml b/src/tools/qml2puppet/mockfiles/qt6/ParticleEmitterGizmo.qml index 17e5123c101..c444e4ca9de 100644 --- a/src/tools/qml2puppet/mockfiles/qt6/ParticleEmitterGizmo.qml +++ b/src/tools/qml2puppet/mockfiles/qt6/ParticleEmitterGizmo.qml @@ -11,6 +11,7 @@ import QtQuick3D.Particles3D Node { id: root + property View3D view3d: null property Node targetNode: null property var selectedNodes: [] property Node activeParticleSystem: null @@ -97,7 +98,7 @@ Node { AutoScaleHelper { id: autoScale - view3D: overlayView + view3D: root.view3d } DefaultMaterial { diff --git a/src/tools/qml2puppet/mockfiles/qt6/SceneView3D.qml b/src/tools/qml2puppet/mockfiles/qt6/SceneView3D.qml index 6945579bf12..6b2cda1398d 100644 --- a/src/tools/qml2puppet/mockfiles/qt6/SceneView3D.qml +++ b/src/tools/qml2puppet/mockfiles/qt6/SceneView3D.qml @@ -12,11 +12,11 @@ View3D { property alias showSceneLight: sceneLight.visible property alias showGrid: helperGrid.visible property alias gridColor: helperGrid.gridColor - property alias sceneHelpers: sceneHelpers property alias perspectiveCamera: scenePerspectiveCamera property alias orthoCamera: sceneOrthoCamera property alias sceneEnv: sceneEnv property vector3d cameraLookAt + property var selectionBoxes: [] // Measuring the distance from camera to lookAt plus the distance of lookAt from grid plane // gives a reasonable grid spacing in most cases while keeping spacing constant when @@ -38,6 +38,24 @@ View3D { camera: usePerspective ? scenePerspectiveCamera : sceneOrthoCamera environment: sceneEnv + + function ensureSelectionBoxes(count) + { + var needMore = count - selectionBoxes.length + if (needMore > 0) { + var component = Qt.createComponent("SelectionBox.qml"); + if (component.status === Component.Ready) { + for (let i = 0; i < needMore; ++i) { + let geometryName = _generalHelper.generateUniqueName("SelectionBoxGeometry"); + let box = component.createObject(sceneHelpers, {"view3D": sceneView, + "geometryName": geometryName}); + selectionBoxes[selectionBoxes.length] = box; + box.showBox = Qt.binding(function() {return showSelectionBox;}); + } + } + } + } + SceneEnvironment { id: sceneEnv antialiasingMode: SceneEnvironment.MSAA diff --git a/src/tools/qml2puppet/qml2puppet/editor3d/generalhelper.cpp b/src/tools/qml2puppet/qml2puppet/editor3d/generalhelper.cpp index f1b4b795e47..b50ea4e932e 100644 --- a/src/tools/qml2puppet/qml2puppet/editor3d/generalhelper.cpp +++ b/src/tools/qml2puppet/qml2puppet/editor3d/generalhelper.cpp @@ -1059,6 +1059,14 @@ void GeneralHelper::setBgColor(const QVariant &colors) } } +QVector3D GeneralHelper::dirForRotation(const QQuaternion &rotation) const +{ + QMatrix4x4 m; + m.rotate(rotation); + const float *dataPtr(m.data()); + return QVector3D(dataPtr[8], dataPtr[9], dataPtr[10]).normalized(); +} + void GeneralHelper::handlePendingToolStateUpdate() { m_toolStateUpdateTimer.stop(); @@ -1217,6 +1225,17 @@ bool GeneralHelper::getBounds(QQuick3DViewport *view3D, QQuick3DNode *node, QVec return hasModel; } +bool GeneralHelper::compareVectors(const QVector3D &v1, const QVector3D &v2) const +{ + return qFuzzyCompare(v1[0], v2[0]) && qFuzzyCompare(v1[1], v2[1]) && qFuzzyCompare(v1[2], v2[2]); +} + +bool GeneralHelper::compareQuaternions(const QQuaternion &q1, const QQuaternion &q2) const +{ + return qFuzzyCompare(q1.x(), q2.x()) && qFuzzyCompare(q1.y(), q2.y()) + && qFuzzyCompare(q1.z(), q2.z()) && qFuzzyCompare(q1.scalar(), q2.scalar()); +} + } } diff --git a/src/tools/qml2puppet/qml2puppet/editor3d/generalhelper.h b/src/tools/qml2puppet/qml2puppet/editor3d/generalhelper.h index 9e54250201a..fd76cadcfc6 100644 --- a/src/tools/qml2puppet/qml2puppet/editor3d/generalhelper.h +++ b/src/tools/qml2puppet/qml2puppet/editor3d/generalhelper.h @@ -135,6 +135,10 @@ public: void setBgColor(const QVariant &colors); QVariant bgColor() const { return m_bgColor; } + Q_INVOKABLE QVector3D dirForRotation(const QQuaternion &rotation) const; + Q_INVOKABLE bool compareVectors(const QVector3D &v1, const QVector3D &v2) const; + Q_INVOKABLE bool compareQuaternions(const QQuaternion &q1, const QQuaternion &q2) const; + signals: void overlayUpdateNeeded(); void toolStateChanged(const QString &sceneId, const QString &tool, const QVariant &toolState); diff --git a/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp b/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp index bff0732d0c1..180390def44 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp +++ b/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp @@ -213,7 +213,7 @@ void Qt5InformationNodeInstanceServer::handleInputEvents() // Peek at next command. If that is also a wheel with same button/modifiers // state, skip this event and add the angle delta to the next one. auto nextCommand = m_pendingInputEventCommands[i + 1]; - if (nextCommand.type() == QEvent::MouseMove + if (nextCommand.type() == QEvent::Wheel && nextCommand.button() == command.button() && nextCommand.buttons() == command.buttons() && nextCommand.modifiers() == command.modifiers()) { @@ -232,6 +232,12 @@ void Qt5InformationNodeInstanceServer::handleInputEvents() QKeyEvent *ke = new QKeyEvent(command.type(), command.key(), command.modifiers(), QString(), command.autoRepeat(), command.count()); QGuiApplication::sendEvent(m_editView3DData.window, ke); + } else if (command.type() == QEvent::Enter) { + QEnterEvent *ee = new QEnterEvent(command.pos(), {}, {}); + QGuiApplication::sendEvent(m_editView3DData.window, ee); + } else if (command.type() == QEvent::Leave) { + QEvent *e = new QEvent(command.type()); + QGuiApplication::sendEvent(m_editView3DData.window, e); } else { if (command.type() == QEvent::MouseMove && i < m_pendingInputEventCommands.size() - 1) { // Peek at next command. If that is also a move with only difference being @@ -422,23 +428,26 @@ void Qt5InformationNodeInstanceServer::getNodeAtPos([[maybe_unused]] const QPoin if (!helper) return; - QQmlProperty editViewProp(m_editView3DData.rootItem, "editView", context()); - QObject *obj = qvariant_cast(editViewProp.read()); - QQuick3DViewport *editView = qobject_cast(obj); - - // Non-model nodes with icon gizmos are also valid results + // Non-model nodes with icon gizmos are also valid results. QVariant gizmoVar; QMetaObject::invokeMethod(m_editView3DData.rootItem, "gizmoAt", Qt::DirectConnection, Q_RETURN_ARG(QVariant, gizmoVar), Q_ARG(QVariant, pos.x()), Q_ARG(QVariant, pos.y())); QObject *gizmoObj = qvariant_cast(gizmoVar); + + // gizmoAt() call above will update the activeEditView + QQmlProperty editViewProp(m_editView3DData.rootItem, "activeEditView", context()); + QObject *obj = qvariant_cast(editViewProp.read()); + QQuick3DViewport *editView = qobject_cast(obj); + QPointF mappedPos = m_editView3DData.rootItem->mapToItem(editView, pos); + qint32 instanceId = -1; if (gizmoObj && hasInstanceForObject(gizmoObj)) { instanceId = instanceForObject(gizmoObj).instanceId(); } else { - QQuick3DModel *hitModel = helper->pickViewAt(editView, pos.x(), pos.y()).objectHit(); + QQuick3DModel *hitModel = helper->pickViewAt(editView, mappedPos.x(), mappedPos.y()).objectHit(); QObject *resolvedPick = helper->resolvePick(hitModel); if (hasInstanceForObject(resolvedPick)) instanceId = instanceForObject(resolvedPick).instanceId(); @@ -450,7 +459,7 @@ void Qt5InformationNodeInstanceServer::getNodeAtPos([[maybe_unused]] const QPoin Internal::MouseArea3D ma; ma.setView3D(editView); ma.setEulerRotation({90, 0, 0}); // Default grid plane (XZ plane) - QVector3D planePos = ma.getMousePosInPlane(nullptr, pos); + QVector3D planePos = ma.getMousePosInPlane(nullptr, mappedPos); const float limit = 10000000; // Remove extremes on nearly parallel plane if (!qFuzzyCompare(planePos.z(), -1.f) && qAbs(planePos.x()) < limit && qAbs(planePos.y()) < limit) pos3d = {planePos.x(), 0, planePos.y()}; @@ -872,6 +881,12 @@ void Qt5InformationNodeInstanceServer::handleActiveSceneChange() #endif } +void Qt5InformationNodeInstanceServer::handleActiveSplitChange(int index) +{ + nodeInstanceClient()->handlePuppetToCreatorCommand({PuppetToCreatorCommand::ActiveSplitChanged, + index}); +} + void Qt5InformationNodeInstanceServer::handleToolStateChanged(const QString &sceneId, const QString &tool, const QVariant &toolState) @@ -1851,6 +1866,8 @@ void Qt5InformationNodeInstanceServer::setup3DEditView( this, SLOT(handleObjectPropertyChange(QVariant, QVariant))); QObject::connect(m_editView3DData.rootItem, SIGNAL(notifyActiveSceneChange()), this, SLOT(handleActiveSceneChange())); + QObject::connect(m_editView3DData.rootItem, SIGNAL(notifyActiveSplitChange(int)), + this, SLOT(handleActiveSplitChange(int))); QObject::connect(&m_propertyChangeTimer, &QTimer::timeout, this, &Qt5InformationNodeInstanceServer::handleObjectPropertyChangeTimeout); QObject::connect(&m_selectionChangeTimer, &QTimer::timeout, @@ -2214,7 +2231,7 @@ void Qt5InformationNodeInstanceServer::changeSelection(const ChangeSelectionComm // Ensure the UI has enough selection box items. If it doesn't yet have them, which can be the // case when the first selection processed is a multiselection, we wait a bit as // using the new boxes immediately leads to visual glitches. - int boxCount = m_editView3DData.rootItem->property("selectionBoxes").value().size(); + int boxCount = m_editView3DData.rootItem->property("selectionBoxCount").toInt(); if (boxCount < selectedObjs.size()) { QMetaObject::invokeMethod(m_editView3DData.rootItem, "ensureSelectionBoxes", Q_ARG(QVariant, QVariant::fromValue(selectedObjs.size()))); @@ -2491,6 +2508,15 @@ void Qt5InformationNodeInstanceServer::view3DAction(const View3DActionCommand &c return; } #endif + case View3DActionType::SplitViewToggle: + updatedToolState.insert("splitView", command.isEnabled()); + break; + case View3DActionType::ShowWireframe: + updatedToolState.insert("showWireframe", command.value().toList()); + break; + case View3DActionType::MaterialOverride: + updatedToolState.insert("matOverride", command.value().toList()); + break; default: break; diff --git a/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.h b/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.h index 3ac044cbace..b603bb134eb 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.h +++ b/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.h @@ -68,6 +68,7 @@ private slots: void handleObjectPropertyCommit(const QVariant &objects, const QVariant &propNames); void handleObjectPropertyChange(const QVariant &objects, const QVariant &propNames); void handleActiveSceneChange(); + void handleActiveSplitChange(int index); void handleToolStateChanged(const QString &sceneId, const QString &tool, const QVariant &toolState); void handleView3DSizeChange(); diff --git a/src/tools/qml2puppet/qml2puppet/runner/qmlruntime.cpp b/src/tools/qml2puppet/qml2puppet/runner/qmlruntime.cpp index a4ade2b2b44..c1817e934d5 100644 --- a/src/tools/qml2puppet/qml2puppet/runner/qmlruntime.cpp +++ b/src/tools/qml2puppet/qml2puppet/runner/qmlruntime.cpp @@ -1,8 +1,6 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include - #include "loadwatcher.h" #include "qmlruntime.h" @@ -12,9 +10,41 @@ #include #endif +#include +#include +#include + #define FILE_OPEN_EVENT_WAIT_TIME 3000 // ms #define QSL QStringLiteral +static void registerFonts(const QDir &projectDir) +{ + // Autoregister all fonts found inside the project + QDirIterator it{projectDir.absolutePath(), + {"*.ttf", "*.otf"}, + QDir::Files, + QDirIterator::Subdirectories}; + while (it.hasNext()) { + QFontDatabase::addApplicationFont(it.next()); + } +} + +static QDir findProjectFolder(const QDir ¤tDir, int ret = 0) +{ + if (ret > 2) + QDir::current(); + + QDirIterator it{currentDir.absolutePath(), + {"*.qmlproject"}, + QDir::Files, + QDirIterator::NoIteratorFlags}; + while (it.hasNext()) + return currentDir; + QDir newDir = currentDir; + newDir.cdUp(); + return findProjectFolder(newDir, ret + 1); +} + void QmlRuntime::populateParser() { m_argParser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); @@ -127,6 +157,7 @@ void QmlRuntime::initCoreApp() void QmlRuntime::initQmlRunner() { + registerFonts(findProjectFolder(QDir::current())); m_qmlEngine.reset(new QQmlApplicationEngine()); QStringList files; diff --git a/tests/auto/qml/qmldesigner/coretests/CMakeLists.txt b/tests/auto/qml/qmldesigner/coretests/CMakeLists.txt index 1eaf90376d8..7cb456d03fc 100644 --- a/tests/auto/qml/qmldesigner/coretests/CMakeLists.txt +++ b/tests/auto/qml/qmldesigner/coretests/CMakeLists.txt @@ -13,6 +13,7 @@ add_qtc_test(tst_qml_testcore IDE_DATA_PATH="${PROJECT_BINARY_DIR}/${IDE_DATA_PATH}" TESTSRCDIR="${CMAKE_CURRENT_SOURCE_DIR}" QMLDESIGNERCORE_STATIC_LIBRARY QMLDESIGNERUTILS_STATIC_LIBRARY + $<$:QDS_USE_PROJECTSTORAGE> INCLUDES "${CMAKE_CURRENT_LIST_DIR}/../../../../../share/qtcreator/qml/qmlpuppet/commands" SOURCES diff --git a/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp b/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp index dfeea21344a..9be03d22612 100644 --- a/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp +++ b/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -15,10 +14,14 @@ #include #include #include -#include #include #include +#ifndef QDS_USE_PROJECTSTORAGE +#include +#include +#endif + #include "../testconnectionmanager.h" #include "../testview.h" #include @@ -160,6 +163,7 @@ public: QStringList modulePaths() const override { return {}; } QStringList projectModulePaths() const override { return {}; } bool isQt6Project() const override { return {}; } + bool isQtForMcusProject() const override { return {}; } QString qtQuickVersion() const override { return {}; } Utils::FilePath resourcePath(const QString &) const override { return {}; } @@ -230,7 +234,9 @@ tst_TestCore::~tst_TestCore() = default; void tst_TestCore::initTestCase() { QmlModelNodeFacade::enableUglyWorkaroundForIsValidQmlModelNodeFacadeInTests(); +#ifndef QDS_USE_PROJECTSTORAGE MetaInfo::disableParseItemLibraryDescriptionsUgly(); +#endif Exception::setShouldAssert(false); if (!QmlJS::ModelManagerInterface::instance()) @@ -257,7 +263,9 @@ void tst_TestCore::initTestCase() qDebug() << pluginPath; Q_ASSERT(QFileInfo::exists(pluginPath)); +#ifndef QDS_USE_PROJECTSTORAGE MetaInfo::initializeGlobal({pluginPath}, *externalDependencies); +#endif QFileInfo builtins(IDE_DATA_PATH "/qml-type-descriptions/builtins.qmltypes"); QStringList errors, warnings; @@ -4498,6 +4506,7 @@ bool contains(const QmlDesigner::PropertyMetaInfos &properties, QUtf8StringView } } // namespace +#ifndef QDS_USE_PROJECTSTORAGE void tst_TestCore::testSubComponentManager() { const QString qmlString("import QtQuick 2.15\n" @@ -4530,10 +4539,10 @@ void tst_TestCore::testSubComponentManager() auto model(createModel("QtQuick.Rectangle", 2, 15)); model->setFileUrl(QUrl::fromLocalFile(fileName)); ExternalDependenciesFake externalDependenciesFake{model.get()}; + QScopedPointer subComponentManager( new SubComponentManager(model.get(), externalDependenciesFake)); subComponentManager->update(QUrl::fromLocalFile(fileName), model->imports()); - QVERIFY(model->hasNodeMetaInfo("QtQuick.Rectangle", 2, 15)); QVERIFY(contains(model->metaInfo("QtQuick.Rectangle").properties(), "border.width")); @@ -4554,6 +4563,7 @@ void tst_TestCore::testSubComponentManager() QVERIFY(contains(myButtonMetaInfo.properties(), "border.width")); QVERIFY(myButtonMetaInfo.hasProperty("border.width")); } +#endif void tst_TestCore::testAnchorsAndRewriting() { diff --git a/tests/auto/qml/qmldesigner/coretests/tst_testcore.h b/tests/auto/qml/qmldesigner/coretests/tst_testcore.h index 1304f4aa654..a38bf36faf7 100644 --- a/tests/auto/qml/qmldesigner/coretests/tst_testcore.h +++ b/tests/auto/qml/qmldesigner/coretests/tst_testcore.h @@ -171,7 +171,9 @@ private slots: void testCopyModelRewriter2(); void testMergeModelRewriter1_data(); void testMergeModelRewriter1(); +#ifndef QDS_USE_PROJECTSTORAGE void testSubComponentManager(); +#endif void testAnchorsAndRewriting(); void testAnchorsAndRewritingCenter(); diff --git a/tests/unit/tests/matchers/CMakeLists.txt b/tests/unit/tests/matchers/CMakeLists.txt index 32a7dad99c3..1eba9d4747e 100644 --- a/tests/unit/tests/matchers/CMakeLists.txt +++ b/tests/unit/tests/matchers/CMakeLists.txt @@ -7,6 +7,7 @@ add_qtc_library(TestMatchers OBJECT SOURCES info_exportedtypenames-matcher.h import-matcher.h + projectstorage-matcher.h strippedstring-matcher.h unittest-matchers.h version-matcher.h diff --git a/tests/unit/tests/matchers/projectstorage-matcher.h b/tests/unit/tests/matchers/projectstorage-matcher.h new file mode 100644 index 00000000000..5ce6512c14e --- /dev/null +++ b/tests/unit/tests/matchers/projectstorage-matcher.h @@ -0,0 +1,55 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "../utils/googletest.h" + +#include + +MATCHER_P2(IsTypeHint, + name, + expression, + std::string(negation ? "isn't " : "is ") + + PrintToString(QmlDesigner::Storage::Info::TypeHint{name, expression})) +{ + const QmlDesigner::Storage::Info::TypeHint &typeHint = arg; + + return typeHint.name == name && typeHint.expression == expression; +} + +template +auto IsItemLibraryEntry(QmlDesigner::TypeId typeId, + Utils::SmallStringView name, + Utils::SmallStringView iconPath, + Utils::SmallStringView category, + Utils::SmallStringView import, + Utils::SmallStringView toolTip, + Utils::SmallStringView templatePath, + PropertiesMatcher propertiesMatcher, + ExtraFilePathsMatcher extraFilePathsMatcher) +{ + using QmlDesigner::Storage::Info::ItemLibraryEntry; + return AllOf(Field("typeId", &ItemLibraryEntry::typeId, typeId), + Field("name", &ItemLibraryEntry::name, name), + Field("iconPath", &ItemLibraryEntry::iconPath, iconPath), + Field("category", &ItemLibraryEntry::category, category), + Field("import", &ItemLibraryEntry::import, import), + Field("toolTip", &ItemLibraryEntry::toolTip, toolTip), + Field("templatePath", &ItemLibraryEntry::templatePath, templatePath), + Field("properties", &ItemLibraryEntry::properties, propertiesMatcher), + Field("extraFilePaths", &ItemLibraryEntry::extraFilePaths, extraFilePathsMatcher)); +} + +MATCHER_P3(IsItemLibraryProperty, + name, + type, + value, + std::string(negation ? "isn't " : "is ") + + PrintToString(QmlDesigner::Storage::Info::ItemLibraryProperty( + name, type, Sqlite::ValueView::create(value)))) +{ + const QmlDesigner::Storage::Info::ItemLibraryProperty &property = arg; + + return property.name == name && property.type == type && property.value == value; +} diff --git a/tests/unit/tests/matchers/strippedstring-matcher.h b/tests/unit/tests/matchers/strippedstring-matcher.h index 3c491cba8f8..f3acbfb09e5 100644 --- a/tests/unit/tests/matchers/strippedstring-matcher.h +++ b/tests/unit/tests/matchers/strippedstring-matcher.h @@ -19,6 +19,11 @@ public: : m_content(strip(std::move(content))) {} + bool MatchAndExplain(std::string_view s, testing::MatchResultListener *listener) const + { + return MatchAndExplain(QString::fromUtf8(s.data(), Utils::ssize(s)), listener); + } + bool MatchAndExplain(const QString &s, testing::MatchResultListener *listener) const { auto strippedContent = strip(s); @@ -79,3 +84,9 @@ inline auto StrippedStringEq(const QStringView &content) { return ::testing::PolymorphicMatcher(Internal::StippedStringEqMatcher(content.toString())); } + +inline auto StrippedStringEq(const std::string_view &content) +{ + return ::testing::PolymorphicMatcher( + Internal::StippedStringEqMatcher(QString::fromUtf8(content.data(), Utils::ssize(content)))); +} diff --git a/tests/unit/tests/mocks/CMakeLists.txt b/tests/unit/tests/mocks/CMakeLists.txt index bd34e15c68c..d2090432626 100644 --- a/tests/unit/tests/mocks/CMakeLists.txt +++ b/tests/unit/tests/mocks/CMakeLists.txt @@ -24,6 +24,7 @@ add_qtc_library(TestMocks OBJECT propertycomponentgeneratormock.h projectstoragemock.cpp projectstoragemock.h + projectstorageobservermock.h projectstoragepathwatchermock.h projectstoragepathwatchernotifiermock.h qmldocumentparsermock.h diff --git a/tests/unit/tests/mocks/externaldependenciesmock.h b/tests/unit/tests/mocks/externaldependenciesmock.h index 673363a2148..c4cfe6cd3b5 100644 --- a/tests/unit/tests/mocks/externaldependenciesmock.h +++ b/tests/unit/tests/mocks/externaldependenciesmock.h @@ -37,6 +37,7 @@ public: MOCK_METHOD(QStringList, modulePaths, (), (const, override)); MOCK_METHOD(QStringList, projectModulePaths, (), (const, override)); MOCK_METHOD(bool, isQt6Project, (), (const, override)); + MOCK_METHOD(bool, isQtForMcusProject, (), (const, override)); MOCK_METHOD(QString, qtQuickVersion, (), (const, override)); MOCK_METHOD(Utils::FilePath, resourcePath, (const QString &relativePath), (const, override)); }; diff --git a/tests/unit/tests/mocks/imagecachecollectormock.h b/tests/unit/tests/mocks/imagecachecollectormock.h index d8a8608faa6..0947df3f4d1 100644 --- a/tests/unit/tests/mocks/imagecachecollectormock.h +++ b/tests/unit/tests/mocks/imagecachecollectormock.h @@ -16,7 +16,8 @@ public: Utils::SmallStringView state, const QmlDesigner::ImageCache::AuxiliaryData &auxiliaryData, ImageCacheCollectorInterface::CaptureCallback captureCallback, - ImageCacheCollectorInterface::AbortCallback abortCallback), + ImageCacheCollectorInterface::AbortCallback abortCallback, + QmlDesigner::ImageCache::TraceToken), (override)); MOCK_METHOD(ImageTuple, diff --git a/tests/unit/tests/mocks/mockimagecachegenerator.h b/tests/unit/tests/mocks/mockimagecachegenerator.h index 30d322ac7f2..ee673c8ed4c 100644 --- a/tests/unit/tests/mocks/mockimagecachegenerator.h +++ b/tests/unit/tests/mocks/mockimagecachegenerator.h @@ -16,8 +16,9 @@ public: Utils::SmallStringView state, Sqlite::TimeStamp timeStamp, QmlDesigner::ImageCache::CaptureImageWithScaledImagesCallback &&captureCallback, - QmlDesigner::ImageCache::AbortCallback &&abortCallback, - QmlDesigner::ImageCache::AuxiliaryData &&auxiliaryData), + QmlDesigner::ImageCache::InternalAbortCallback &&abortCallback, + QmlDesigner::ImageCache::AuxiliaryData &&auxiliaryData, + QmlDesigner::ImageCache::TraceToken), (override)); MOCK_METHOD(void, clean, (), (override)); MOCK_METHOD(void, waitForFinished, (), (override)); diff --git a/tests/unit/tests/mocks/projectstoragemock.cpp b/tests/unit/tests/mocks/projectstoragemock.cpp index ac829d21a3f..83ff85fe9a3 100644 --- a/tests/unit/tests/mocks/projectstoragemock.cpp +++ b/tests/unit/tests/mocks/projectstoragemock.cpp @@ -203,6 +203,29 @@ void ProjectStorageMock::setPropertyEditorPathId(QmlDesigner::TypeId typeId, ON_CALL(*this, propertyEditorPathId(Eq(typeId))).WillByDefault(Return(sourceId)); } +void ProjectStorageMock::setTypeHints(QmlDesigner::TypeId typeId, + const Storage::Info::TypeHints &typeHints) +{ + ON_CALL(*this, typeHints(Eq(typeId))).WillByDefault(Return(typeHints)); +} + +void ProjectStorageMock::setTypeIconPath(QmlDesigner::TypeId typeId, Utils::SmallStringView path) +{ + ON_CALL(*this, typeIconPath(Eq(typeId))).WillByDefault(Return(path)); +} + +void ProjectStorageMock::setItemLibraryEntries( + QmlDesigner::TypeId typeId, const QmlDesigner::Storage::Info::ItemLibraryEntries &entries) +{ + ON_CALL(*this, itemLibraryEntries(TypedEq(typeId))).WillByDefault(Return(entries)); +} + +void ProjectStorageMock::setItemLibraryEntries( + QmlDesigner::SourceId sourceId, const QmlDesigner::Storage::Info::ItemLibraryEntries &entries) +{ + ON_CALL(*this, itemLibraryEntries(TypedEq(sourceId))).WillByDefault(Return(entries)); +} + namespace { void addBaseProperties(TypeId typeId, TypeIds baseTypeIds, ProjectStorageMock &storage) { @@ -310,7 +333,7 @@ TypeId ProjectStorageMock::createObject(ModuleId moduleId, defaultPropertyName, defaultPropertyTraits, defaultPropertyTypeId, - Storage::TypeTraits::Reference, + Storage::TypeTraitsKind::Reference, baseTypeIds, sourceId); } @@ -319,14 +342,19 @@ TypeId ProjectStorageMock::createObject(ModuleId moduleId, Utils::SmallStringView typeName, TypeIds baseTypeIds) { - return createType(moduleId, typeName, Storage::TypeTraits::Reference, baseTypeIds); + return createType(moduleId, typeName, Storage::TypeTraitsKind::Reference, baseTypeIds); } QmlDesigner::TypeId ProjectStorageMock::createValue(QmlDesigner::ModuleId moduleId, Utils::SmallStringView typeName, QmlDesigner::TypeIds baseTypeIds) { - return createType(moduleId, typeName, Storage::TypeTraits::Value, baseTypeIds); + return createType(moduleId, typeName, Storage::TypeTraitsKind::Value, baseTypeIds); +} + +void ProjectStorageMock::setHeirs(QmlDesigner::TypeId typeId, QmlDesigner::TypeIds heirIds) +{ + ON_CALL(*this, heirIds(typeId)).WillByDefault(Return(heirIds)); } ProjectStorageMock::ProjectStorageMock() diff --git a/tests/unit/tests/mocks/projectstoragemock.h b/tests/unit/tests/mocks/projectstoragemock.h index e55841f4786..198e54b370b 100644 --- a/tests/unit/tests/mocks/projectstoragemock.h +++ b/tests/unit/tests/mocks/projectstoragemock.h @@ -84,6 +84,8 @@ public: Utils::SmallStringView typeName, QmlDesigner::TypeIds baseTypeIds = {}); + void setHeirs(QmlDesigner::TypeId typeId, QmlDesigner::TypeIds heirIds); + QmlDesigner::PropertyDeclarationId createProperty( QmlDesigner::TypeId typeId, Utils::SmallString name, @@ -100,6 +102,14 @@ public: void createFunction(QmlDesigner::TypeId typeId, Utils::SmallString name); void setPropertyEditorPathId(QmlDesigner::TypeId typeId, QmlDesigner::SourceId sourceId); + void setTypeHints(QmlDesigner::TypeId typeId, + const QmlDesigner::Storage::Info::TypeHints &typeHints); + void setTypeIconPath(QmlDesigner::TypeId typeId, Utils::SmallStringView path); + void setItemLibraryEntries(QmlDesigner::TypeId typeId, + const QmlDesigner::Storage::Info::ItemLibraryEntries &entries); + void setItemLibraryEntries(QmlDesigner::SourceId sourceId, + const QmlDesigner::Storage::Info::ItemLibraryEntries &entries); + MOCK_METHOD(void, synchronize, (QmlDesigner::Storage::Synchronization::SynchronizationPackage package), @@ -109,14 +119,8 @@ public: (const QmlDesigner::Storage::Imports imports, QmlDesigner::SourceId sourceId), (override)); - MOCK_METHOD(void, - addRefreshCallback, - (std::function * callback), - (override)); - MOCK_METHOD(void, - removeRefreshCallback, - (std::function * callback), - (override)); + MOCK_METHOD(void, addObserver, (QmlDesigner::ProjectStorageObserver *), (override)); + MOCK_METHOD(void, removeObserver, (QmlDesigner::ProjectStorageObserver *), (override)); MOCK_METHOD(QmlDesigner::ModuleId, moduleId, (::Utils::SmallStringView), (const, override)); @@ -140,7 +144,10 @@ public: ::Utils::SmallStringView exportedTypeName, QmlDesigner::Storage::Version version), (const, override)); - + MOCK_METHOD((QVarLengthArray), + typeIds, + (QmlDesigner::ModuleId moduleId), + (const, override)); MOCK_METHOD(QmlDesigner::Storage::Info::ExportedTypeNames, exportedTypeNames, (QmlDesigner::TypeId), @@ -179,6 +186,23 @@ public: type, (QmlDesigner::TypeId typeId), (const, override)); + MOCK_METHOD(Utils::PathString, typeIconPath, (QmlDesigner::TypeId typeId), (const, override)); + MOCK_METHOD(QmlDesigner::Storage::Info::TypeHints, + typeHints, + (QmlDesigner::TypeId typeId), + (const, override)); + MOCK_METHOD(QmlDesigner::Storage::Info::ItemLibraryEntries, + itemLibraryEntries, + (QmlDesigner::TypeId typeId), + (const, override)); + MOCK_METHOD(QmlDesigner::Storage::Info::ItemLibraryEntries, + itemLibraryEntries, + (QmlDesigner::SourceId sourceId), + (const, override)); + MOCK_METHOD(QmlDesigner::Storage::Info::ItemLibraryEntries, + allItemLibraryEntries, + (), + (const, override)); MOCK_METHOD(std::vector<::Utils::SmallString>, signalDeclarationNames, (QmlDesigner::TypeId typeId), @@ -193,6 +217,7 @@ public: (const, override)); MOCK_METHOD(QmlDesigner::TypeIds, prototypeAndSelfIds, (QmlDesigner::TypeId type), (const, override)); MOCK_METHOD(QmlDesigner::TypeIds, prototypeIds, (QmlDesigner::TypeId type), (const, override)); + MOCK_METHOD(QmlDesigner::TypeIds, heirIds, (QmlDesigner::TypeId type), (const, override)); MOCK_METHOD(bool, isBasedOn, (QmlDesigner::TypeId typeId, QmlDesigner::TypeId), (const, override)); MOCK_METHOD(bool, isBasedOn, diff --git a/tests/unit/tests/mocks/projectstorageobservermock.h b/tests/unit/tests/mocks/projectstorageobservermock.h new file mode 100644 index 00000000000..93a14bc09d0 --- /dev/null +++ b/tests/unit/tests/mocks/projectstorageobservermock.h @@ -0,0 +1,14 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "../utils/googletest.h" + +#include + +class ProjectStorageObserverMock : public QmlDesigner::ProjectStorageObserver +{ +public: + MOCK_METHOD(void, removedTypeIds, (const QmlDesigner::TypeIds &), (override)); +}; diff --git a/tests/unit/tests/mocks/sqlitereadstatementmock.h b/tests/unit/tests/mocks/sqlitereadstatementmock.h index 615e3d23b14..ed23409b97d 100644 --- a/tests/unit/tests/mocks/sqlitereadstatementmock.h +++ b/tests/unit/tests/mocks/sqlitereadstatementmock.h @@ -28,7 +28,6 @@ class SqliteDatabaseMock; class SqliteReadStatementMockBase { public: - SqliteReadStatementMockBase() = default; SqliteReadStatementMockBase(Utils::SmallStringView sqlStatement, SqliteDatabaseMock &databaseMock); MOCK_METHOD(std::vector, valuesReturnStringVector, (std::size_t), ()); diff --git a/tests/unit/tests/printers/gtest-creator-printing.cpp b/tests/unit/tests/printers/gtest-creator-printing.cpp index d4654dba19b..1bff6df492c 100644 --- a/tests/unit/tests/printers/gtest-creator-printing.cpp +++ b/tests/unit/tests/printers/gtest-creator-printing.cpp @@ -157,6 +157,11 @@ void PrintTo(const Utils::SmallString &text, ::std::ostream *os) *os << "\"" << text << "\""; } +void PrintTo(const Utils::BasicSmallString<94> &text, ::std::ostream *os) +{ + *os << "\"" << text << "\""; +} + void PrintTo(const Utils::PathString &text, ::std::ostream *os) { *os << "\"" << text << "\""; @@ -512,6 +517,23 @@ std::ostream &operator<<(std::ostream &out, const ModelResourceSet &set) << set.setExpressions << ")"; } +std::ostream &operator<<(std::ostream &out, FlagIs flagIs) +{ + switch (flagIs) { + case QmlDesigner::FlagIs::False: + out << "False"; + break; + case QmlDesigner::FlagIs::True: + out << "True"; + break; + case QmlDesigner::FlagIs::Set: + out << "Set"; + break; + } + + return out; +} + namespace Cache { std::ostream &operator<<(std::ostream &out, const SourceContext &sourceContext) @@ -522,45 +544,87 @@ std::ostream &operator<<(std::ostream &out, const SourceContext &sourceContext) namespace Storage { -namespace { -TypeTraits cleanFlags(TypeTraits traits) +std::ostream &operator<<(std::ostream &out, TypeTraitsKind kind) { - auto data = static_cast(traits); - data &= ~static_cast(TypeTraits::IsEnum); - return static_cast(data); -} - -const char *typeTraitsToString(TypeTraits traits) -{ - switch (cleanFlags(traits)) { - case TypeTraits::None: - return "None"; - case TypeTraits::Reference: - return "Reference"; - case TypeTraits::Sequence: - return "Sequence"; - case TypeTraits::Value: - return "Value"; + switch (kind) { + case TypeTraitsKind::None: + out << "None"; + break; + case TypeTraitsKind::Reference: + out << "Reference"; + break; + case TypeTraitsKind::Sequence: + out << "Sequence"; + break; + case TypeTraitsKind::Value: + out << "Value"; + break; default: break; } - return ""; + return out; } -const char *typeTraitsFlagsToString(TypeTraits traits) -{ - if (bool(traits & TypeTraits::IsEnum)) - return "(IsEnum)"; - - return ""; -} - -} // namespace - std::ostream &operator<<(std::ostream &out, TypeTraits traits) { - return out << typeTraitsToString(traits) << typeTraitsFlagsToString(traits); + out << "(" << traits.kind; + + if (traits.isEnum) + out << " | isEnum"; + + if (traits.isFileComponent) + out << " | isFileComponent"; + + if (traits.isProjectComponent) + out << " | isProjectComponent"; + + if (traits.isInProjectModule) + out << " | isInProjectModule"; + + if (traits.usesCustomParser) + out << " | usesCustomParser"; + + if (traits.canBeContainer != QmlDesigner::FlagIs::False) + out << " | canBeContainer(" << traits.canBeContainer << ")"; + + if (traits.forceClip != QmlDesigner::FlagIs::False) + out << " | forceClip(" << traits.forceClip << ")"; + + if (traits.doesLayoutChildren != QmlDesigner::FlagIs::False) + out << " | doesLayoutChildren(" << traits.doesLayoutChildren << ")"; + + if (traits.canBeDroppedInFormEditor != QmlDesigner::FlagIs::False) + out << " | canBeDroppedInFormEditor(" << traits.canBeDroppedInFormEditor << ")"; + + if (traits.canBeDroppedInNavigator != QmlDesigner::FlagIs::False) + out << " | canBeDroppedInNavigator(" << traits.canBeDroppedInNavigator << ")"; + + if (traits.canBeDroppedInView3D != QmlDesigner::FlagIs::False) + out << " | canBeDroppedInView3D(" << traits.canBeDroppedInView3D << ")"; + + if (traits.isMovable != QmlDesigner::FlagIs::False) + out << " | isMovable(" << traits.isMovable << ")"; + + if (traits.isResizable != QmlDesigner::FlagIs::False) + out << " | isResizable(" << traits.isResizable << ")"; + + if (traits.hasFormEditorItem != QmlDesigner::FlagIs::False) + out << " | hasFormEditorItem(" << traits.hasFormEditorItem << ")"; + + if (traits.isStackedContainer != QmlDesigner::FlagIs::False) + out << " | isStackedContainer(" << traits.isStackedContainer << ")"; + + if (traits.takesOverRenderingOfChildren != QmlDesigner::FlagIs::False) + out << " | takesOverRenderingOfChildren(" << traits.takesOverRenderingOfChildren << ")"; + + if (traits.visibleInNavigator != QmlDesigner::FlagIs::False) + out << " | visibleInNavigator(" << traits.visibleInNavigator << ")"; + + if (traits.visibleInLibrary != QmlDesigner::FlagIs::False) + out << " | visibleInLibrary(" << traits.visibleInLibrary << ")"; + + return out << ")"; } std::ostream &operator<<(std::ostream &out, PropertyDeclarationTraits traits) @@ -616,8 +680,27 @@ std::ostream &operator<<(std::ostream &out, const Type &type) std::ostream &operator<<(std::ostream &out, const ExportedTypeName &name) { - return out << "(\"" << name.name << "\"," << name.moduleId << ", " << name.version << ")"; + return out << "(\"" << name.name << "\", " << name.moduleId << ", " << name.version << ")"; } + +std::ostream &operator<<(std::ostream &out, const TypeHint &hint) +{ + return out << "(\"" << hint.name << "\", \"" << hint.expression << "\")"; +} + +std::ostream &operator<<(std::ostream &out, const ItemLibraryProperty &property) +{ + return out << "(\"" << property.name << "\"," << property.type << "," << property.value << ")"; +} + +std::ostream &operator<<(std::ostream &out, const ItemLibraryEntry &entry) +{ + return out << R"((")" << entry.name << R"(", ")" << entry.iconPath << R"(", ")" + << entry.category << R"(", ")" << entry.import << R"(", ")" << entry.toolTip + << R"(", ")" << entry.templatePath << R"(", )" << entry.properties << ", " + << entry.extraFilePaths << ")"; +} + } // namespace Storage::Info namespace Storage::Synchronization { @@ -816,6 +899,14 @@ std::ostream &operator<<(std::ostream &out, const PropertyEditorQmlPath &path) return out << "(" << path.moduleId << ", " << path.typeName << ", " << path.pathId << ")"; } +std::ostream &operator<<(std::ostream &out, const TypeAnnotation &annotation) +{ + return out << R"x((")x" << annotation.typeName << R"(", )" << annotation.moduleId << ", " + << annotation.sourceId << R"(, ")" << annotation.iconPath << R"(", )" + << annotation.traits << R"(, ")" << annotation.hintsJson << R"(", ")" + << annotation.itemLibraryJson << R"x("))x"; +} + } // namespace Storage::Synchronization namespace ImageCache { diff --git a/tests/unit/tests/printers/gtest-creator-printing.h b/tests/unit/tests/printers/gtest-creator-printing.h index b01bbf1fb8d..8d0c77888ec 100644 --- a/tests/unit/tests/printers/gtest-creator-printing.h +++ b/tests/unit/tests/printers/gtest-creator-printing.h @@ -93,6 +93,7 @@ void PrintTo(const std::optional &optional, ::std::ostream *os) void PrintTo(Utils::SmallStringView text, ::std::ostream *os); void PrintTo(const Utils::SmallString &text, ::std::ostream *os); +void PrintTo(const Utils::BasicSmallString<94> &text, ::std::ostream *os); void PrintTo(const Utils::PathString &text, ::std::ostream *os); } // namespace Utils @@ -124,6 +125,7 @@ class Import; class NodeMetaInfo; class PropertyMetaInfo; struct CompoundPropertyMetaInfo; +enum class FlagIs : unsigned int; std::ostream &operator<<(std::ostream &out, const ModelNode &node); std::ostream &operator<<(std::ostream &out, const VariantProperty &property); @@ -139,6 +141,7 @@ std::ostream &operator<<(std::ostream &out, const ModelResourceSet &modelResourc std::ostream &operator<<(std::ostream &out, const NodeMetaInfo &metaInfo); std::ostream &operator<<(std::ostream &out, const PropertyMetaInfo &metaInfo); std::ostream &operator<<(std::ostream &out, const CompoundPropertyMetaInfo &metaInfo); +std::ostream &operator<<(std::ostream &out, FlagIs flagIs); namespace Cache { class SourceContext; @@ -158,12 +161,14 @@ std::ostream &operator<<(std::ostream &out, const FontCollectorSizesAuxiliaryDat namespace Storage { enum class PropertyDeclarationTraits : int; -enum class TypeTraits : int; +enum class TypeTraitsKind : unsigned int; +struct TypeTraits; class Import; class Version; class VersionNumber; std::ostream &operator<<(std::ostream &out, PropertyDeclarationTraits traits); +std::ostream &operator<<(std::ostream &out, TypeTraitsKind kind); std::ostream &operator<<(std::ostream &out, TypeTraits traits); std::ostream &operator<<(std::ostream &out, const Import &import); std::ostream &operator<<(std::ostream &out, VersionNumber versionNumber); @@ -175,10 +180,16 @@ namespace Storage::Info { class ProjectDeclaration; class Type; class ExportedTypeName; +struct TypeHint; +struct ItemLibraryProperty; +struct ItemLibraryEntry; std::ostream &operator<<(std::ostream &out, const ProjectDeclaration &declaration); std::ostream &operator<<(std::ostream &out, const Type &type); std::ostream &operator<<(std::ostream &out, const ExportedTypeName &name); +std::ostream &operator<<(std::ostream &out, const TypeHint &hint); +std::ostream &operator<<(std::ostream &out, const ItemLibraryProperty &property); +std::ostream &operator<<(std::ostream &out, const ItemLibraryEntry &entry); } // namespace Storage::Info @@ -202,6 +213,7 @@ enum class FileType : char; enum class ChangeLevel : char; class ModuleExportedImport; class PropertyEditorQmlPath; +class TypeAnnotation; std::ostream &operator<<(std::ostream &out, const Type &type); std::ostream &operator<<(std::ostream &out, const ExportedType &exportedType); @@ -221,6 +233,7 @@ std::ostream &operator<<(std::ostream &out, FileType fileType); std::ostream &operator<<(std::ostream &out, ChangeLevel changeLevel); std::ostream &operator<<(std::ostream &out, const ModuleExportedImport &import); std::ostream &operator<<(std::ostream &out, const PropertyEditorQmlPath &path); +std::ostream &operator<<(std::ostream &out, const TypeAnnotation &annotation); } // namespace Storage::Synchronization diff --git a/tests/unit/tests/printers/gtest-qt-printing.h b/tests/unit/tests/printers/gtest-qt-printing.h index ceec63817cc..97e6822a7bd 100644 --- a/tests/unit/tests/printers/gtest-qt-printing.h +++ b/tests/unit/tests/printers/gtest-qt-printing.h @@ -6,6 +6,7 @@ #include #include +#include QT_BEGIN_NAMESPACE @@ -15,6 +16,24 @@ class QStringView; class QTextCharFormat; class QImage; class QIcon; +template +class QVarLengthArray; + +template +std::ostream &operator<<(std::ostream &out, const QVarLengthArray &array) +{ + out << "["; + + int i = 0; + for (auto &&value : array) { + i++; + out << value; + if (i < array.size()) + out << ", "; + } + + return out << "]"; +} std::ostream &operator<<(std::ostream &out, const QVariant &QVariant); std::ostream &operator<<(std::ostream &out, const QString &text); diff --git a/tests/unit/tests/testdesignercore/CMakeLists.txt b/tests/unit/tests/testdesignercore/CMakeLists.txt index 0847caa3230..0bdf3452d6a 100644 --- a/tests/unit/tests/testdesignercore/CMakeLists.txt +++ b/tests/unit/tests/testdesignercore/CMakeLists.txt @@ -61,9 +61,7 @@ add_qtc_library(TestDesignerCore OBJECT include/bindingproperty.h include/imagecacheauxiliarydata.h include/import.h - include/itemlibraryinfo.h - include/metainfo.h - include/metainforeader.h + include/itemlibraryentry.h include/modelnode.h include/module.h include/nodeabstractproperty.h @@ -78,9 +76,7 @@ add_qtc_library(TestDesignerCore OBJECT include/signalhandlerproperty.h include/synchronousimagecache.h include/variantproperty.h - metainfo/itemlibraryinfo.cpp - metainfo/metainfo.cpp - metainfo/metainforeader.cpp + metainfo/itemlibraryentry.cpp metainfo/nodemetainfo.cpp model/abstractproperty.cpp model/abstractview.cpp @@ -127,6 +123,7 @@ add_qtc_library(TestDesignerCore OBJECT projectstorage/nonlockingmutex.h projectstorage/projectstorageexceptions.cpp projectstorage/projectstorageexceptions.h projectstorage/projectstorageinterface.h + projectstorage/projectstorageobserver.h projectstorage/projectstorage.cpp projectstorage/projectstorage.h projectstorage/projectstoragepathwatcher.h projectstorage/projectstoragepathwatcherinterface.h @@ -143,8 +140,11 @@ add_qtc_library(TestDesignerCore OBJECT projectstorage/storagecache.h projectstorage/storagecacheentry.h projectstorage/storagecachefwd.h + projectstorage/typeannotationreader.cpp + projectstorage/typeannotationreader.h projectstorage/qmldocumentparserinterface.h projectstorage/qmltypesparserinterface.h + tracing/qmldesignertracing.cpp tracing/qmldesignertracing.h rewritertransaction.cpp rewritertransaction.h ) diff --git a/tests/unit/tests/unittests/CMakeLists.txt b/tests/unit/tests/unittests/CMakeLists.txt index 7088bedcc0a..77cf7a716cd 100644 --- a/tests/unit/tests/unittests/CMakeLists.txt +++ b/tests/unit/tests/unittests/CMakeLists.txt @@ -25,6 +25,12 @@ add_qtc_test(unittest GTEST unittests-main.cpp ) +extend_qtc_test(unittest + CONDITION TARGET Nanotrace + DEPENDS Nanotrace +) + + finalize_qtc_gtest(unittest EXCLUDE_SOURCES_REGEX ".c$" EXCLUDE_ALL_FROM_PRECHECK diff --git a/tests/unit/tests/unittests/imagecache/asynchronousimagecache-test.cpp b/tests/unit/tests/unittests/imagecache/asynchronousimagecache-test.cpp index e7c51c40fc4..7dd283579d5 100644 --- a/tests/unit/tests/unittests/imagecache/asynchronousimagecache-test.cpp +++ b/tests/unit/tests/unittests/imagecache/asynchronousimagecache-test.cpp @@ -93,8 +93,8 @@ TEST_F(AsynchronousImageCache, request_image_request_image_from_generator) .WillByDefault(Return(Sqlite::TimeStamp{123})); EXPECT_CALL(mockGenerator, - generateImage(Eq("/path/to/Component.qml"), _, Eq(Sqlite::TimeStamp{123}), _, _, _)) - .WillRepeatedly([&](auto, auto, auto, auto, auto, auto) { notification.notify(); }); + generateImage(Eq("/path/to/Component.qml"), _, Eq(Sqlite::TimeStamp{123}), _, _, _, _)) + .WillRepeatedly([&](auto, auto, auto, auto, auto, auto, auto) { notification.notify(); }); cache.requestImage("/path/to/Component.qml", mockCaptureCallback.AsStdFunction(), @@ -104,9 +104,9 @@ TEST_F(AsynchronousImageCache, request_image_request_image_from_generator) TEST_F(AsynchronousImageCache, request_image_calls_capture_callback_with_image_from_generator) { - ON_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto &&callback, auto, auto) { - callback(QImage{image1}, QImage{midSizeImage1}, QImage{smallImage1}); + ON_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto &&callback, auto, auto, auto) { + callback(QImage{image1}, QImage{midSizeImage1}, QImage{smallImage1}, {}); notification.notify(); }); @@ -120,9 +120,9 @@ TEST_F(AsynchronousImageCache, request_image_calls_capture_callback_with_image_f TEST_F(AsynchronousImageCache, request_image_calls_abort_callback_from_generator) { - ON_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto &&, auto &&abortCallback, auto) { - abortCallback(QmlDesigner::ImageCache::AbortReason::Failed); + ON_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto &&, auto &&abortCallback, auto, auto) { + abortCallback(QmlDesigner::ImageCache::AbortReason::Failed, {}); notification.notify(); }); @@ -200,8 +200,8 @@ TEST_F(AsynchronousImageCache, request_mid_size_image_request_image_from_generat .WillByDefault(Return(Sqlite::TimeStamp{123})); EXPECT_CALL(mockGenerator, - generateImage(Eq("/path/to/Component.qml"), _, Eq(Sqlite::TimeStamp{123}), _, _, _)) - .WillRepeatedly([&](auto, auto, auto, auto, auto, auto) { notification.notify(); }); + generateImage(Eq("/path/to/Component.qml"), _, Eq(Sqlite::TimeStamp{123}), _, _, _, _)) + .WillRepeatedly([&](auto, auto, auto, auto, auto, auto, auto) { notification.notify(); }); cache.requestMidSizeImage("/path/to/Component.qml", mockCaptureCallback.AsStdFunction(), @@ -211,9 +211,9 @@ TEST_F(AsynchronousImageCache, request_mid_size_image_request_image_from_generat TEST_F(AsynchronousImageCache, request_mid_size_image_calls_capture_callback_with_image_from_generator) { - ON_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto &&callback, auto, auto) { - callback(QImage{image1}, QImage{midSizeImage1}, QImage{smallImage1}); + ON_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto &&callback, auto, auto, auto) { + callback(QImage{image1}, QImage{midSizeImage1}, QImage{smallImage1}, {}); notification.notify(); }); @@ -227,9 +227,9 @@ TEST_F(AsynchronousImageCache, request_mid_size_image_calls_capture_callback_wit TEST_F(AsynchronousImageCache, request_mid_size_image_calls_abort_callback_from_generator) { - ON_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto &&, auto &&abortCallback, auto) { - abortCallback(QmlDesigner::ImageCache::AbortReason::Failed); + ON_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto &&, auto &&abortCallback, auto, auto) { + abortCallback(QmlDesigner::ImageCache::AbortReason::Failed, {}); notification.notify(); }); @@ -306,8 +306,8 @@ TEST_F(AsynchronousImageCache, request_small_image_request_image_from_generator) .WillByDefault(Return(Sqlite::TimeStamp{123})); EXPECT_CALL(mockGenerator, - generateImage(Eq("/path/to/Component.qml"), _, Eq(Sqlite::TimeStamp{123}), _, _, _)) - .WillRepeatedly([&](auto, auto, auto, auto, auto, auto) { notification.notify(); }); + generateImage(Eq("/path/to/Component.qml"), _, Eq(Sqlite::TimeStamp{123}), _, _, _, _)) + .WillRepeatedly([&](auto, auto, auto, auto, auto, auto, auto) { notification.notify(); }); cache.requestSmallImage("/path/to/Component.qml", mockCaptureCallback.AsStdFunction(), @@ -317,9 +317,9 @@ TEST_F(AsynchronousImageCache, request_small_image_request_image_from_generator) TEST_F(AsynchronousImageCache, request_small_image_calls_capture_callback_with_image_from_generator) { - ON_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto &&callback, auto, auto) { - callback(QImage{image1}, QImage{midSizeImage1}, QImage{smallImage1}); + ON_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto &&callback, auto, auto, auto) { + callback(QImage{image1}, QImage{midSizeImage1}, QImage{smallImage1}, {}); notification.notify(); }); @@ -333,9 +333,9 @@ TEST_F(AsynchronousImageCache, request_small_image_calls_capture_callback_with_i TEST_F(AsynchronousImageCache, request_small_image_calls_abort_callback_from_generator) { - ON_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto &&, auto &&abortCallback, auto) { - abortCallback(QmlDesigner::ImageCache::AbortReason::Failed); + ON_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto &&, auto &&abortCallback, auto, auto) { + abortCallback(QmlDesigner::ImageCache::AbortReason::Failed, {}); notification.notify(); }); @@ -349,9 +349,9 @@ TEST_F(AsynchronousImageCache, request_small_image_calls_abort_callback_from_gen TEST_F(AsynchronousImageCache, clean_removes_entries) { - EXPECT_CALL(mockGenerator, generateImage(_, _, _, _, _, _)) - .WillRepeatedly([&](auto, auto, auto, auto &&captureCallback, auto &&, auto) { - captureCallback(QImage{}, QImage{}, QImage{}); + EXPECT_CALL(mockGenerator, generateImage(_, _, _, _, _, _, _)) + .WillRepeatedly([&](auto, auto, auto, auto &&captureCallback, auto &&, auto, auto) { + captureCallback(QImage{}, QImage{}, QImage{}, {}); waitInThread.wait(); }); cache.requestSmallImage("/path/to/Component1.qml", @@ -369,8 +369,8 @@ TEST_F(AsynchronousImageCache, clean_removes_entries) TEST_F(AsynchronousImageCache, clean_calls_abort) { - ON_CALL(mockGenerator, generateImage(_, _, _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto, auto &&, auto) { waitInThread.wait(); }); + ON_CALL(mockGenerator, generateImage(_, _, _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto, auto &&, auto, auto) { waitInThread.wait(); }); cache.requestSmallImage("/path/to/Component1.qml", mockCaptureCallback.AsStdFunction(), mockAbortCallback.AsStdFunction()); @@ -399,8 +399,9 @@ TEST_F(AsynchronousImageCache, after_clean_new_jobs_works) { cache.clean(); - EXPECT_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _, _, _)) - .WillRepeatedly([&](auto, auto, auto, auto &&, auto &&, auto) { notification.notify(); }); + EXPECT_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _, _, _, _)) + .WillRepeatedly( + [&](auto, auto, auto, auto &&, auto &&, auto, auto) { notification.notify(); }); cache.requestSmallImage("/path/to/Component.qml", mockCaptureCallback.AsStdFunction(), @@ -460,8 +461,8 @@ TEST_F(AsynchronousImageCache, request_image_with_extra_id_request_image_from_ge EXPECT_CALL(mockGenerator, generateImage( - Eq("/path/to/Component.qml"), Eq("extraId1"), Eq(Sqlite::TimeStamp{123}), _, _, _)) - .WillRepeatedly([&](auto, auto, auto, auto &&, auto, auto) { notification.notify(); }); + Eq("/path/to/Component.qml"), Eq("extraId1"), Eq(Sqlite::TimeStamp{123}), _, _, _, _)) + .WillRepeatedly([&](auto, auto, auto, auto &&, auto, auto, auto) { notification.notify(); }); cache.requestImage("/path/to/Component.qml", mockCaptureCallback.AsStdFunction(), @@ -477,8 +478,8 @@ TEST_F(AsynchronousImageCache, request_mid_size_image_with_extra_id_request_imag EXPECT_CALL(mockGenerator, generateImage( - Eq("/path/to/Component.qml"), Eq("extraId1"), Eq(Sqlite::TimeStamp{123}), _, _, _)) - .WillRepeatedly([&](auto, auto, auto, auto &&, auto, auto) { notification.notify(); }); + Eq("/path/to/Component.qml"), Eq("extraId1"), Eq(Sqlite::TimeStamp{123}), _, _, _, _)) + .WillRepeatedly([&](auto, auto, auto, auto &&, auto, auto, auto) { notification.notify(); }); cache.requestMidSizeImage("/path/to/Component.qml", mockCaptureCallback.AsStdFunction(), @@ -494,8 +495,8 @@ TEST_F(AsynchronousImageCache, request_small_image_with_extra_id_request_image_f EXPECT_CALL(mockGenerator, generateImage( - Eq("/path/to/Component.qml"), Eq("extraId1"), Eq(Sqlite::TimeStamp{123}), _, _, _)) - .WillRepeatedly([&](auto, auto, auto, auto &&, auto, auto) { notification.notify(); }); + Eq("/path/to/Component.qml"), Eq("extraId1"), Eq(Sqlite::TimeStamp{123}), _, _, _, _)) + .WillRepeatedly([&](auto, auto, auto, auto &&, auto, auto, auto) { notification.notify(); }); cache.requestSmallImage("/path/to/Component.qml", mockCaptureCallback.AsStdFunction(), @@ -521,8 +522,9 @@ TEST_F(AsynchronousImageCache, request_image_with_auxiliary_data_request_image_f AllOf(Field(&FontCollectorSizesAuxiliaryData::sizes, ElementsAre(QSize{20, 11})), Field(&FontCollectorSizesAuxiliaryData::colorName, - Eq(u"color")))))) - .WillRepeatedly([&](auto, auto, auto, auto &&, auto, auto) { notification.notify(); }); + Eq(u"color")))), + _)) + .WillRepeatedly([&](auto, auto, auto, auto &&, auto, auto, auto) { notification.notify(); }); cache.requestImage("/path/to/Component.qml", mockCaptureCallback.AsStdFunction(), @@ -549,8 +551,9 @@ TEST_F(AsynchronousImageCache, request_mid_size_image_with_auxiliary_data_reques AllOf(Field(&FontCollectorSizesAuxiliaryData::sizes, ElementsAre(QSize{20, 11})), Field(&FontCollectorSizesAuxiliaryData::colorName, - Eq(u"color")))))) - .WillRepeatedly([&](auto, auto, auto, auto &&, auto, auto) { notification.notify(); }); + Eq(u"color")))), + _)) + .WillRepeatedly([&](auto, auto, auto, auto &&, auto, auto, auto) { notification.notify(); }); cache.requestMidSizeImage("/path/to/Component.qml", mockCaptureCallback.AsStdFunction(), @@ -577,8 +580,9 @@ TEST_F(AsynchronousImageCache, request_small_image_with_auxiliary_data_request_i AllOf(Field(&FontCollectorSizesAuxiliaryData::sizes, ElementsAre(QSize{20, 11})), Field(&FontCollectorSizesAuxiliaryData::colorName, - Eq(u"color")))))) - .WillRepeatedly([&](auto, auto, auto, auto &&, auto, auto) { notification.notify(); }); + Eq(u"color")))), + _)) + .WillRepeatedly([&](auto, auto, auto, auto &&, auto, auto, auto) { notification.notify(); }); cache.requestSmallImage("/path/to/Component.qml", mockCaptureCallback.AsStdFunction(), diff --git a/tests/unit/tests/unittests/imagecache/asynchronousimagefactory-test.cpp b/tests/unit/tests/unittests/imagecache/asynchronousimagefactory-test.cpp index b26b4b7c708..966d18f95e6 100644 --- a/tests/unit/tests/unittests/imagecache/asynchronousimagefactory-test.cpp +++ b/tests/unit/tests/unittests/imagecache/asynchronousimagefactory-test.cpp @@ -43,8 +43,9 @@ TEST_F(AsynchronousImageFactory, request_image_request_image_from_collector) IsEmpty(), VariantWith(std::monostate{}), _, + _, _)) - .WillRepeatedly([&](auto, auto, auto, auto, auto) { notification.notify(); }); + .WillRepeatedly([&](auto, auto, auto, auto, auto, auto) { notification.notify(); }); factory.generate("/path/to/Component.qml"); notification.wait(); @@ -57,8 +58,9 @@ TEST_F(AsynchronousImageFactory, request_image_with_extra_id_request_image_from_ Eq("foo"), VariantWith(std::monostate{}), _, + _, _)) - .WillRepeatedly([&](auto, auto, auto, auto, auto) { notification.notify(); }); + .WillRepeatedly([&](auto, auto, auto, auto, auto, auto) { notification.notify(); }); factory.generate("/path/to/Component.qml", "foo"); notification.wait(); @@ -77,8 +79,9 @@ TEST_F(AsynchronousImageFactory, request_image_with_auxiliary_data_request_image Field(&FontCollectorSizesAuxiliaryData::colorName, Eq(u"color")), Field(&FontCollectorSizesAuxiliaryData::text, Eq(u"some text")))), _, + _, _)) - .WillRepeatedly([&](auto, auto, auto, auto, auto) { notification.notify(); }); + .WillRepeatedly([&](auto, auto, auto, auto, auto, auto) { notification.notify(); }); factory.generate("/path/to/Component.qml", "foo", @@ -99,7 +102,7 @@ TEST_F(AsynchronousImageFactory, dont_request_image_request_image_from_collector ON_CALL(timeStampProviderMock, timeStamp(Eq("/path/to/Component.qml"))) .WillByDefault(Return(Sqlite::TimeStamp{1})); - EXPECT_CALL(collectorMock, start(_, _, _, _, _)).Times(0); + EXPECT_CALL(collectorMock, start(_, _, _, _, _, _)).Times(0); factory.generate("/path/to/Component.qml"); notification.wait(); @@ -113,7 +116,7 @@ TEST_F(AsynchronousImageFactory, request_image_request_image_from_collector_if_f .WillByDefault(Return(Sqlite::TimeStamp{125})); ON_CALL(timeStampProviderMock, pause()).WillByDefault(Return(Sqlite::TimeStamp{1})); - EXPECT_CALL(collectorMock, start(_, _, _, _, _)).WillOnce([&](auto, auto, auto, auto, auto) { + EXPECT_CALL(collectorMock, start(_, _, _, _, _, _)).WillOnce([&](auto, auto, auto, auto, auto, auto) { notification.notify(); }); @@ -123,15 +126,15 @@ TEST_F(AsynchronousImageFactory, request_image_request_image_from_collector_if_f TEST_F(AsynchronousImageFactory, clean_removes_entries) { - EXPECT_CALL(collectorMock, start(Eq("/path/to/Component1.qml"), _, _, _, _)) - .WillRepeatedly([&](auto, auto, auto, auto, auto) { + EXPECT_CALL(collectorMock, start(Eq("/path/to/Component1.qml"), _, _, _, _, _)) + .WillRepeatedly([&](auto, auto, auto, auto, auto, auto) { notification.notify(); waitInThread.wait(); }); factory.generate("/path/to/Component1.qml"); notification.wait(); - EXPECT_CALL(collectorMock, start(Eq("/path/to/Component3.qml"), _, _, _, _)).Times(0); + EXPECT_CALL(collectorMock, start(Eq("/path/to/Component3.qml"), _, _, _, _, _)).Times(0); factory.generate("/path/to/Component3.qml"); factory.clean(); @@ -147,8 +150,9 @@ TEST_F(AsynchronousImageFactory, after_clean_new_jobs_works) IsEmpty(), VariantWith(std::monostate{}), _, + _, _)) - .WillRepeatedly([&](auto, auto, auto, auto, auto) { notification.notify(); }); + .WillRepeatedly([&](auto, auto, auto, auto, auto, auto) { notification.notify(); }); factory.generate("/path/to/Component.qml"); notification.wait(); @@ -166,9 +170,10 @@ TEST_F(AsynchronousImageFactory, capture_image_callback_stores_image) Eq("id"), VariantWith(std::monostate{}), _, + _, _)) - .WillByDefault([&](auto, auto, auto, auto capture, auto) { - capture(image1, midSizeImage1, smallImage1); + .WillByDefault([&](auto, auto, auto, auto capture, auto, auto) { + capture(image1, midSizeImage1, smallImage1, {}); }); EXPECT_CALL(storageMock, diff --git a/tests/unit/tests/unittests/imagecache/imagecachedispatchcollector-test.cpp b/tests/unit/tests/unittests/imagecache/imagecachedispatchcollector-test.cpp index 8d459916ffc..b463a5a698e 100644 --- a/tests/unit/tests/unittests/imagecache/imagecachedispatchcollector-test.cpp +++ b/tests/unit/tests/unittests/imagecache/imagecachedispatchcollector-test.cpp @@ -12,6 +12,7 @@ namespace { using QmlDesigner::ImageCache::FontCollectorSizesAuxiliaryData; +using QmlDesigner::ImageCache::TraceToken; MATCHER_P(IsIcon, icon, std::string(negation ? "isn't " : "is ") + PrintToString(icon)) { @@ -47,8 +48,8 @@ protected: protected: std::vector sizes{{20, 11}}; - NiceMock> captureCallbackMock; - NiceMock> abortCallbackMock; + NiceMock> captureCallbackMock; + NiceMock> abortCallbackMock; NiceMock collectorMock1; NiceMock collectorMock2; QImage image1{1, 1, QImage::Format_ARGB32}; @@ -83,8 +84,8 @@ TEST_F(ImageCacheDispatchCollector, call_qml_collector_start) }, &collectorMock2))}; - EXPECT_CALL(captureCallbackMock, Call(_, _, _)); - EXPECT_CALL(abortCallbackMock, Call(_)); + EXPECT_CALL(captureCallbackMock, Call(_, _, _, _)); + EXPECT_CALL(abortCallbackMock, Call(_, _)); EXPECT_CALL(collectorMock2, start(Eq("foo.qml"), Eq("state"), @@ -93,18 +94,20 @@ TEST_F(ImageCacheDispatchCollector, call_qml_collector_start) ElementsAre(QSize{20, 11})), Field(&FontCollectorSizesAuxiliaryData::colorName, Eq(u"color")))), _, + _, _)) - .WillRepeatedly([&](auto, auto, auto, auto captureCallback, auto abortCallback) { - captureCallback({}, {}, {}); - abortCallback(QmlDesigner::ImageCache::AbortReason::Abort); + .WillRepeatedly([&](auto, auto, auto, auto captureCallback, auto abortCallback, auto) { + captureCallback({}, {}, {}, {}); + abortCallback(QmlDesigner::ImageCache::AbortReason::Abort, {}); }); - EXPECT_CALL(collectorMock1, start(_, _, _, _, _)).Times(0); + EXPECT_CALL(collectorMock1, start(_, _, _, _, _, _)).Times(0); collector.start("foo.qml", "state", FontCollectorSizesAuxiliaryData{sizes, "color", "text"}, captureCallbackMock.AsStdFunction(), - abortCallbackMock.AsStdFunction()); + abortCallbackMock.AsStdFunction(), + {}); } TEST_F(ImageCacheDispatchCollector, call_ui_file_collector_start) @@ -119,8 +122,8 @@ TEST_F(ImageCacheDispatchCollector, call_ui_file_collector_start) const QmlDesigner::ImageCache::AuxiliaryData &) { return true; }, &collectorMock2))}; - EXPECT_CALL(captureCallbackMock, Call(_, _, _)); - EXPECT_CALL(abortCallbackMock, Call(_)); + EXPECT_CALL(captureCallbackMock, Call(_, _, _, _)); + EXPECT_CALL(abortCallbackMock, Call(_, _)); EXPECT_CALL(collectorMock1, start(Eq("foo.ui.qml"), Eq("state"), @@ -129,18 +132,20 @@ TEST_F(ImageCacheDispatchCollector, call_ui_file_collector_start) ElementsAre(QSize{20, 11})), Field(&FontCollectorSizesAuxiliaryData::colorName, Eq(u"color")))), _, + _, _)) - .WillRepeatedly([&](auto, auto, auto, auto captureCallback, auto abortCallback) { - captureCallback({}, {}, {}); - abortCallback(QmlDesigner::ImageCache::AbortReason::Abort); + .WillRepeatedly([&](auto, auto, auto, auto captureCallback, auto abortCallback, auto) { + captureCallback({}, {}, {}, {}); + abortCallback(QmlDesigner::ImageCache::AbortReason::Abort, {}); }); - EXPECT_CALL(collectorMock2, start(_, _, _, _, _)).Times(0); + EXPECT_CALL(collectorMock2, start(_, _, _, _, _, _)).Times(0); collector.start("foo.ui.qml", "state", FontCollectorSizesAuxiliaryData{sizes, "color", "text"}, captureCallbackMock.AsStdFunction(), - abortCallbackMock.AsStdFunction()); + abortCallbackMock.AsStdFunction(), + {}); } TEST_F(ImageCacheDispatchCollector, dont_call_collector_start_for_unknown_file) @@ -155,14 +160,15 @@ TEST_F(ImageCacheDispatchCollector, dont_call_collector_start_for_unknown_file) const QmlDesigner::ImageCache::AuxiliaryData &) { return false; }, &collectorMock2))}; - EXPECT_CALL(collectorMock2, start(_, _, _, _, _)).Times(0); - EXPECT_CALL(collectorMock1, start(_, _, _, _, _)).Times(0); + EXPECT_CALL(collectorMock2, start(_, _, _, _, _, _)).Times(0); + EXPECT_CALL(collectorMock1, start(_, _, _, _, _, _)).Times(0); collector.start("foo.mesh", "state", FontCollectorSizesAuxiliaryData{sizes, "color", "text"}, captureCallbackMock.AsStdFunction(), - abortCallbackMock.AsStdFunction()); + abortCallbackMock.AsStdFunction(), + {}); } TEST_F(ImageCacheDispatchCollector, call_first_collector_create_icon) diff --git a/tests/unit/tests/unittests/imagecache/imagecachegenerator-test.cpp b/tests/unit/tests/unittests/imagecache/imagecachegenerator-test.cpp index 878552964a8..15e31764ad7 100644 --- a/tests/unit/tests/unittests/imagecache/imagecachegenerator-test.cpp +++ b/tests/unit/tests/unittests/imagecache/imagecachegenerator-test.cpp @@ -13,6 +13,8 @@ namespace { +using QmlDesigner::ImageCache::TraceToken; + class ImageCacheGenerator : public testing::Test { protected: @@ -34,8 +36,8 @@ protected: QImage image1{10, 10, QImage::Format_ARGB32}; QImage midSizeImage1{5, 5, QImage::Format_ARGB32}; QImage smallImage1{1, 1, QImage::Format_ARGB32}; - NiceMock> imageCallbackMock; - NiceMock> abortCallbackMock; + NiceMock> imageCallbackMock; + NiceMock> abortCallbackMock; NiceMock collectorMock; NiceMock storageMock; QmlDesigner::ImageCacheGenerator generator{collectorMock, storageMock}; @@ -43,13 +45,13 @@ protected: TEST_F(ImageCacheGenerator, calls_collector_with_capture_callback) { - EXPECT_CALL(collectorMock, start(Eq("name"), _, _, _, _)) - .WillRepeatedly([&](auto, auto, auto, auto captureCallback, auto) { - captureCallback(QImage{image1}, QImage{midSizeImage1}, QImage{smallImage1}); + EXPECT_CALL(collectorMock, start(Eq("name"), _, _, _, _, _)) + .WillRepeatedly([&](auto, auto, auto, auto captureCallback, auto, auto) { + captureCallback(QImage{image1}, QImage{midSizeImage1}, QImage{smallImage1}, {}); }); - EXPECT_CALL(imageCallbackMock, Call(_, _, _)) + EXPECT_CALL(imageCallbackMock, Call(_, _, _, _)) .WillRepeatedly( - [&](const QImage &, const QImage &, const QImage &) { notification.notify(); }); + [&](const QImage &, const QImage &, const QImage &, auto) { notification.notify(); }); generator.generateImage("name", {}, {}, imageCallbackMock.AsStdFunction(), {}, {}); notification.wait(); @@ -57,8 +59,8 @@ TEST_F(ImageCacheGenerator, calls_collector_with_capture_callback) TEST_F(ImageCacheGenerator, calls_collector_only_if_not_processing) { - EXPECT_CALL(collectorMock, start(AnyOf(Eq("name"), Eq("name2")), _, _, _, _)) - .WillRepeatedly([&](auto, auto, auto, auto, auto) { notification.notify(); }); + EXPECT_CALL(collectorMock, start(AnyOf(Eq("name"), Eq("name2")), _, _, _, _, _)) + .WillRepeatedly([&](auto, auto, auto, auto, auto, auto) { notification.notify(); }); generator.generateImage("name", {}, {}, imageCallbackMock.AsStdFunction(), {}, {}); generator.generateImage("name2", {}, {}, imageCallbackMock.AsStdFunction(), {}, {}); @@ -67,16 +69,17 @@ TEST_F(ImageCacheGenerator, calls_collector_only_if_not_processing) TEST_F(ImageCacheGenerator, process_task_after_first_finished) { - ON_CALL(imageCallbackMock, Call(_, _, _)) - .WillByDefault([&](const QImage &, const QImage &, const QImage &) { notification.notify(); }); + ON_CALL(imageCallbackMock, Call(_, _, _, _)) + .WillByDefault( + [&](const QImage &, const QImage &, const QImage &, auto) { notification.notify(); }); - EXPECT_CALL(collectorMock, start(Eq("name"), _, _, _, _)) - .WillOnce([&](auto, auto, auto, auto captureCallback, auto) { - captureCallback(QImage{image1}, QImage{midSizeImage1}, QImage{smallImage1}); + EXPECT_CALL(collectorMock, start(Eq("name"), _, _, _, _, _)) + .WillOnce([&](auto, auto, auto, auto captureCallback, auto, auto) { + captureCallback(QImage{image1}, QImage{midSizeImage1}, QImage{smallImage1}, {}); }); - EXPECT_CALL(collectorMock, start(Eq("name2"), _, _, _, _)) - .WillOnce([&](auto, auto, auto, auto captureCallback, auto) { - captureCallback(QImage{image1}, QImage{midSizeImage1}, QImage{smallImage1}); + EXPECT_CALL(collectorMock, start(Eq("name2"), _, _, _, _, _)) + .WillOnce([&](auto, auto, auto, auto captureCallback, auto, auto) { + captureCallback(QImage{image1}, QImage{midSizeImage1}, QImage{smallImage1}, {}); }); generator.generateImage("name", {}, {}, imageCallbackMock.AsStdFunction(), {}, {}); @@ -86,13 +89,13 @@ TEST_F(ImageCacheGenerator, process_task_after_first_finished) TEST_F(ImageCacheGenerator, dont_crash_at_destructing_generator) { - ON_CALL(collectorMock, start(Eq("name"), _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto captureCallback, auto) { - captureCallback(QImage{image1}, QImage{midSizeImage1}, QImage{smallImage1}); + ON_CALL(collectorMock, start(Eq("name"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto captureCallback, auto, auto) { + captureCallback(QImage{image1}, QImage{midSizeImage1}, QImage{smallImage1}, {}); }); generator.generateImage( - "name", {}, {}, imageCallbackMock.AsStdFunction(), abortCallbackMock.AsStdFunction(), {}); + "name", {}, {}, imageCallbackMock.AsStdFunction(), abortCallbackMock.AsStdFunction(), {}, {}); generator.generateImage( "name2", {}, {}, imageCallbackMock.AsStdFunction(), abortCallbackMock.AsStdFunction(), {}); generator.generateImage( @@ -103,9 +106,9 @@ TEST_F(ImageCacheGenerator, dont_crash_at_destructing_generator) TEST_F(ImageCacheGenerator, store_image) { - ON_CALL(collectorMock, start(Eq("name"), _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto captureCallback, auto) { - captureCallback(QImage{image1}, QImage{midSizeImage1}, QImage{smallImage1}); + ON_CALL(collectorMock, start(Eq("name"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto captureCallback, auto, auto) { + captureCallback(QImage{image1}, QImage{midSizeImage1}, QImage{smallImage1}, {}); }); EXPECT_CALL(storageMock, @@ -122,9 +125,9 @@ TEST_F(ImageCacheGenerator, store_image) TEST_F(ImageCacheGenerator, store_image_with_extra_id) { - ON_CALL(collectorMock, start(Eq("name"), _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto captureCallback, auto) { - captureCallback(QImage{image1}, QImage{midSizeImage1}, QImage{smallImage1}); + ON_CALL(collectorMock, start(Eq("name"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto captureCallback, auto, auto) { + captureCallback(QImage{image1}, QImage{midSizeImage1}, QImage{smallImage1}, {}); }); EXPECT_CALL(storageMock, @@ -141,9 +144,9 @@ TEST_F(ImageCacheGenerator, store_image_with_extra_id) TEST_F(ImageCacheGenerator, store_null_image) { - ON_CALL(collectorMock, start(Eq("name"), _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto captureCallback, auto) { - captureCallback(QImage{}, QImage{}, QImage{}); + ON_CALL(collectorMock, start(Eq("name"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto captureCallback, auto, auto) { + captureCallback(QImage{}, QImage{}, QImage{}, {}); }); EXPECT_CALL( @@ -158,9 +161,9 @@ TEST_F(ImageCacheGenerator, store_null_image) TEST_F(ImageCacheGenerator, store_null_image_with_extra_id) { - ON_CALL(collectorMock, start(Eq("name"), _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto captureCallback, auto) { - captureCallback(QImage{}, QImage{}, QImage{}); + ON_CALL(collectorMock, start(Eq("name"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto captureCallback, auto, auto) { + captureCallback(QImage{}, QImage{}, QImage{}, {}); }); EXPECT_CALL(storageMock, @@ -182,19 +185,20 @@ TEST_F(ImageCacheGenerator, store_null_image_with_extra_id) TEST_F(ImageCacheGenerator, abort_callback) { - ON_CALL(collectorMock, start(Eq("name"), _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto captureCallback, auto) { - captureCallback(QImage{image1}, QImage{midSizeImage1}, QImage{smallImage1}); + ON_CALL(collectorMock, start(Eq("name"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto captureCallback, auto, auto) { + captureCallback(QImage{image1}, QImage{midSizeImage1}, QImage{smallImage1}, {}); }); - ON_CALL(collectorMock, start(Eq("name2"), _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto, auto abortCallback) { - abortCallback(QmlDesigner::ImageCache::AbortReason::Failed); + ON_CALL(collectorMock, start(Eq("name2"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto, auto abortCallback, auto) { + abortCallback(QmlDesigner::ImageCache::AbortReason::Failed, {}); }); - EXPECT_CALL(imageCallbackMock, Call(_, _, _)) - .WillOnce([&](const QImage &, const QImage &, const QImage &) { notification.notify(); }); - EXPECT_CALL(abortCallbackMock, Call(Eq(QmlDesigner::ImageCache::AbortReason::Failed))) - .WillOnce([&](auto) { notification.notify(); }); + EXPECT_CALL(imageCallbackMock, Call(_, _, _, _)) + .WillOnce( + [&](const QImage &, const QImage &, const QImage &, auto) { notification.notify(); }); + EXPECT_CALL(abortCallbackMock, Call(Eq(QmlDesigner::ImageCache::AbortReason::Failed), _)) + .WillOnce([&](auto, auto) { notification.notify(); }); generator.generateImage( "name", {}, {}, imageCallbackMock.AsStdFunction(), abortCallbackMock.AsStdFunction(), {}); @@ -205,12 +209,12 @@ TEST_F(ImageCacheGenerator, abort_callback) TEST_F(ImageCacheGenerator, store_null_image_for_abort_callback_abort) { - ON_CALL(collectorMock, start(Eq("name"), _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto, auto abortCallback) { - abortCallback(QmlDesigner::ImageCache::AbortReason::Abort); + ON_CALL(collectorMock, start(Eq("name"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto, auto abortCallback, auto) { + abortCallback(QmlDesigner::ImageCache::AbortReason::Abort, {}); }); - ON_CALL(collectorMock, start(Eq("dummyNotify"), _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto, auto) { notification.notify(); }); + ON_CALL(collectorMock, start(Eq("dummyNotify"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto, auto, auto) { notification.notify(); }); EXPECT_CALL(storageMock, storeImage(Eq("name"), _, _, _, _)).Times(0); @@ -222,9 +226,9 @@ TEST_F(ImageCacheGenerator, store_null_image_for_abort_callback_abort) TEST_F(ImageCacheGenerator, dont_store_null_image_for_abort_callback_failed) { - ON_CALL(collectorMock, start(_, _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto, auto abortCallback) { - abortCallback(QmlDesigner::ImageCache::AbortReason::Failed); + ON_CALL(collectorMock, start(_, _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto, auto abortCallback, auto) { + abortCallback(QmlDesigner::ImageCache::AbortReason::Failed, {}); }); EXPECT_CALL( @@ -239,13 +243,13 @@ TEST_F(ImageCacheGenerator, dont_store_null_image_for_abort_callback_failed) TEST_F(ImageCacheGenerator, abort_for_null_image) { - ON_CALL(collectorMock, start(Eq("name"), _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto captureCallback, auto) { - captureCallback(QImage{}, QImage{}, QImage{}); + ON_CALL(collectorMock, start(Eq("name"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto captureCallback, auto, auto) { + captureCallback(QImage{}, QImage{}, QImage{}, {}); }); - EXPECT_CALL(abortCallbackMock, Call(Eq(QmlDesigner::ImageCache::AbortReason::Failed))) - .WillOnce([&](auto) { notification.notify(); }); + EXPECT_CALL(abortCallbackMock, Call(Eq(QmlDesigner::ImageCache::AbortReason::Failed), _)) + .WillOnce([&](auto, auto) { notification.notify(); }); generator.generateImage( "name", {}, {}, imageCallbackMock.AsStdFunction(), abortCallbackMock.AsStdFunction(), {}); @@ -254,13 +258,13 @@ TEST_F(ImageCacheGenerator, abort_for_null_image) TEST_F(ImageCacheGenerator, call_image_callback_if_small_image_is_not_null) { - ON_CALL(collectorMock, start(Eq("name"), _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto captureCallback, auto) { - captureCallback({}, {}, smallImage1); + ON_CALL(collectorMock, start(Eq("name"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto captureCallback, auto, auto) { + captureCallback({}, {}, smallImage1, {}); }); - EXPECT_CALL(imageCallbackMock, Call(Eq(QImage()), Eq(QImage()), Eq(smallImage1))) - .WillOnce([&](auto, auto, auto) { notification.notify(); }); + EXPECT_CALL(imageCallbackMock, Call(Eq(QImage()), Eq(QImage()), Eq(smallImage1), _)) + .WillOnce([&](auto, auto, auto, auto) { notification.notify(); }); generator.generateImage( "name", {}, {}, imageCallbackMock.AsStdFunction(), abortCallbackMock.AsStdFunction(), {}); @@ -269,9 +273,9 @@ TEST_F(ImageCacheGenerator, call_image_callback_if_small_image_is_not_null) TEST_F(ImageCacheGenerator, store_image_if_small_image_is_not_null) { - ON_CALL(collectorMock, start(Eq("name"), _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto captureCallback, auto) { - captureCallback({}, {}, smallImage1); + ON_CALL(collectorMock, start(Eq("name"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto captureCallback, auto, auto) { + captureCallback({}, {}, smallImage1, {}); }); EXPECT_CALL(storageMock, storeImage(_, _, Eq(QImage()), Eq(QImage()), Eq(smallImage1))) @@ -284,13 +288,13 @@ TEST_F(ImageCacheGenerator, store_image_if_small_image_is_not_null) TEST_F(ImageCacheGenerator, call_image_callback_if_mid_size_image_is_not_null) { - ON_CALL(collectorMock, start(Eq("name"), _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto captureCallback, auto) { - captureCallback({}, midSizeImage1, {}); + ON_CALL(collectorMock, start(Eq("name"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto captureCallback, auto, auto) { + captureCallback({}, midSizeImage1, {}, {}); }); - EXPECT_CALL(imageCallbackMock, Call(Eq(QImage()), Eq(midSizeImage1), Eq(QImage{}))) - .WillOnce([&](auto, auto, auto) { notification.notify(); }); + EXPECT_CALL(imageCallbackMock, Call(Eq(QImage()), Eq(midSizeImage1), Eq(QImage{}), _)) + .WillOnce([&](auto, auto, auto, auto) { notification.notify(); }); generator.generateImage( "name", {}, {}, imageCallbackMock.AsStdFunction(), abortCallbackMock.AsStdFunction(), {}); @@ -299,9 +303,9 @@ TEST_F(ImageCacheGenerator, call_image_callback_if_mid_size_image_is_not_null) TEST_F(ImageCacheGenerator, store_image_if_mid_size_image_is_not_null) { - ON_CALL(collectorMock, start(Eq("name"), _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto captureCallback, auto) { - captureCallback({}, midSizeImage1, {}); + ON_CALL(collectorMock, start(Eq("name"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto captureCallback, auto, auto) { + captureCallback({}, midSizeImage1, {}, {}); }); EXPECT_CALL(storageMock, storeImage(_, _, Eq(QImage()), Eq(midSizeImage1), Eq(QImage()))) @@ -314,12 +318,13 @@ TEST_F(ImageCacheGenerator, store_image_if_mid_size_image_is_not_null) TEST_F(ImageCacheGenerator, call_image_callback_if_image_is_not_null) { - ON_CALL(collectorMock, start(Eq("name"), _, _, _, _)) - .WillByDefault( - [&](auto, auto, auto, auto captureCallback, auto) { captureCallback(image1, {}, {}); }); + ON_CALL(collectorMock, start(Eq("name"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto captureCallback, auto, auto) { + captureCallback(image1, {}, {}, {}); + }); - EXPECT_CALL(imageCallbackMock, Call(Eq(image1), Eq(QImage{}), Eq(QImage{}))) - .WillOnce([&](auto, auto, auto) { notification.notify(); }); + EXPECT_CALL(imageCallbackMock, Call(Eq(image1), Eq(QImage{}), Eq(QImage{}), _)) + .WillOnce([&](auto, auto, auto, auto) { notification.notify(); }); generator.generateImage( "name", {}, {}, imageCallbackMock.AsStdFunction(), abortCallbackMock.AsStdFunction(), {}); @@ -328,9 +333,10 @@ TEST_F(ImageCacheGenerator, call_image_callback_if_image_is_not_null) TEST_F(ImageCacheGenerator, store_image_if_image_is_not_null) { - ON_CALL(collectorMock, start(Eq("name"), _, _, _, _)) - .WillByDefault( - [&](auto, auto, auto, auto captureCallback, auto) { captureCallback(image1, {}, {}); }); + ON_CALL(collectorMock, start(Eq("name"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto captureCallback, auto, auto) { + captureCallback(image1, {}, {}, {}); + }); EXPECT_CALL(storageMock, storeImage(_, _, Eq(image1), Eq(QImage{}), Eq(QImage{}))) .WillOnce([&](auto, auto, auto, auto, auto) { notification.notify(); }); @@ -342,9 +348,10 @@ TEST_F(ImageCacheGenerator, store_image_if_image_is_not_null) TEST_F(ImageCacheGenerator, call_wal_checkpoint_full_if_queue_is_empty) { - ON_CALL(collectorMock, start(Eq("name"), _, _, _, _)) - .WillByDefault( - [&](auto, auto, auto, auto captureCallback, auto) { captureCallback({}, {}, {}); }); + ON_CALL(collectorMock, start(Eq("name"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto captureCallback, auto, auto) { + captureCallback({}, {}, {}, {}); + }); EXPECT_CALL(storageMock, walCheckpointFull()).WillRepeatedly([&]() { notification.notify(); }); @@ -357,17 +364,16 @@ TEST_F(ImageCacheGenerator, call_wal_checkpoint_full_if_queue_is_empty) TEST_F(ImageCacheGenerator, clean_is_calling_abort_callback) { - ON_CALL(collectorMock, start(_, _, _, _, _)).WillByDefault([&](auto, auto, auto, auto, auto) { - notification.wait(); - }); + ON_CALL(collectorMock, start(_, _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto, auto, auto) { notification.wait(); }); generator.generateImage( "name", {}, {11}, imageCallbackMock.AsStdFunction(), abortCallbackMock.AsStdFunction(), {}); generator.generateImage( "name2", {}, {11}, imageCallbackMock.AsStdFunction(), abortCallbackMock.AsStdFunction(), {}); - EXPECT_CALL(abortCallbackMock, Call(Eq(QmlDesigner::ImageCache::AbortReason::Abort))) + EXPECT_CALL(abortCallbackMock, Call(Eq(QmlDesigner::ImageCache::AbortReason::Abort), _)) .Times(AtLeast(1)) - .WillRepeatedly([&](auto) { waitInThread.notify(); }); + .WillRepeatedly([&](auto, auto) { waitInThread.notify(); }); generator.clean(); notification.notify(); @@ -376,14 +382,14 @@ TEST_F(ImageCacheGenerator, clean_is_calling_abort_callback) TEST_F(ImageCacheGenerator, wait_for_finished) { - ON_CALL(collectorMock, start(Eq("name"), _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto captureCallback, auto) { + ON_CALL(collectorMock, start(Eq("name"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto captureCallback, auto, auto) { waitInThread.wait(); - captureCallback(QImage{image1}, QImage{midSizeImage1}, QImage{smallImage1}); + captureCallback(QImage{image1}, QImage{midSizeImage1}, QImage{smallImage1}, {}); }); - ON_CALL(collectorMock, start(Eq("name2"), _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto captureCallback, auto) { - captureCallback(QImage{image1}, QImage{midSizeImage1}, QImage{smallImage1}); + ON_CALL(collectorMock, start(Eq("name2"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto captureCallback, auto, auto) { + captureCallback(QImage{image1}, QImage{midSizeImage1}, QImage{smallImage1}, {}); }); generator.generateImage( @@ -391,7 +397,7 @@ TEST_F(ImageCacheGenerator, wait_for_finished) generator.generateImage( "name2", {}, {11}, imageCallbackMock.AsStdFunction(), abortCallbackMock.AsStdFunction(), {}); - EXPECT_CALL(imageCallbackMock, Call(_, _, _)).Times(AtMost(2)); + EXPECT_CALL(imageCallbackMock, Call(_, _, _, _)).Times(AtMost(2)); waitInThread.notify(); generator.waitForFinished(); @@ -399,8 +405,8 @@ TEST_F(ImageCacheGenerator, wait_for_finished) TEST_F(ImageCacheGenerator, calls_collector_with_extra_id) { - EXPECT_CALL(collectorMock, start(Eq("name"), Eq("extraId1"), _, _, _)) - .WillRepeatedly([&](auto, auto, auto, auto, auto) { notification.notify(); }); + EXPECT_CALL(collectorMock, start(Eq("name"), Eq("extraId1"), _, _, _, _)) + .WillRepeatedly([&](auto, auto, auto, auto, auto, auto) { notification.notify(); }); generator.generateImage("name", "extraId1", {}, imageCallbackMock.AsStdFunction(), {}, {}); notification.wait(); @@ -419,8 +425,9 @@ TEST_F(ImageCacheGenerator, calls_collector_with_auxiliary_data) ElementsAre(QSize{20, 11})), Field(&FontCollectorSizesAuxiliaryData::colorName, Eq(u"color")))), _, + _, _)) - .WillRepeatedly([&](auto, auto, auto, auto, auto) { notification.notify(); }); + .WillRepeatedly([&](auto, auto, auto, auto, auto, auto) { notification.notify(); }); generator.generateImage("name", {}, @@ -433,12 +440,12 @@ TEST_F(ImageCacheGenerator, calls_collector_with_auxiliary_data) TEST_F(ImageCacheGenerator, merge_tasks) { - EXPECT_CALL(collectorMock, start(Eq("waitDummy"), _, _, _, _)) - .WillRepeatedly([&](auto, auto, auto, auto, auto) { waitInThread.wait(); }); - EXPECT_CALL(collectorMock, start(Eq("notificationDummy"), _, _, _, _)) - .WillRepeatedly([&](auto, auto, auto, auto, auto) { notification.notify(); }); + EXPECT_CALL(collectorMock, start(Eq("waitDummy"), _, _, _, _, _)) + .WillRepeatedly([&](auto, auto, auto, auto, auto, auto) { waitInThread.wait(); }); + EXPECT_CALL(collectorMock, start(Eq("notificationDummy"), _, _, _, _, _)) + .WillRepeatedly([&](auto, auto, auto, auto, auto, auto) { notification.notify(); }); - EXPECT_CALL(collectorMock, start(Eq("name"), _, _, _, _)); + EXPECT_CALL(collectorMock, start(Eq("name"), _, _, _, _, _)); generator.generateImage("waitDummy", {}, {}, {}, {}, {}); generator.generateImage("name", {}, {}, {}, {}, {}); @@ -450,13 +457,13 @@ TEST_F(ImageCacheGenerator, merge_tasks) TEST_F(ImageCacheGenerator, dont_merge_tasks_with_different_id) { - EXPECT_CALL(collectorMock, start(Eq("waitDummy"), _, _, _, _)) - .WillRepeatedly([&](auto, auto, auto, auto, auto) { waitInThread.wait(); }); + EXPECT_CALL(collectorMock, start(Eq("waitDummy"), _, _, _, _, _)) + .WillRepeatedly([&](auto, auto, auto, auto, auto, auto) { waitInThread.wait(); }); - EXPECT_CALL(collectorMock, start(Eq("name"), _, _, _, _)) - .WillRepeatedly([&](auto, auto, auto, auto, auto) { notification.notify(); }); - EXPECT_CALL(collectorMock, start(Eq("name2"), _, _, _, _)) - .WillRepeatedly([&](auto, auto, auto, auto, auto) { notification.notify(); }); + EXPECT_CALL(collectorMock, start(Eq("name"), _, _, _, _, _)) + .WillRepeatedly([&](auto, auto, auto, auto, auto, auto) { notification.notify(); }); + EXPECT_CALL(collectorMock, start(Eq("name2"), _, _, _, _, _)) + .WillRepeatedly([&](auto, auto, auto, auto, auto, auto) { notification.notify(); }); generator.generateImage("waitDummy", {}, {}, {}, {}, {}); generator.generateImage("name", {}, {}, {}, {}, {}); @@ -467,12 +474,12 @@ TEST_F(ImageCacheGenerator, dont_merge_tasks_with_different_id) TEST_F(ImageCacheGenerator, merge_tasks_with_same_extra_id) { - EXPECT_CALL(collectorMock, start(Eq("waitDummy"), _, _, _, _)) - .WillRepeatedly([&](auto, auto, auto, auto, auto) { waitInThread.wait(); }); - EXPECT_CALL(collectorMock, start(Eq("notificationDummy"), _, _, _, _)) - .WillRepeatedly([&](auto, auto, auto, auto, auto) { notification.notify(); }); + EXPECT_CALL(collectorMock, start(Eq("waitDummy"), _, _, _, _, _)) + .WillRepeatedly([&](auto, auto, auto, auto, auto, auto) { waitInThread.wait(); }); + EXPECT_CALL(collectorMock, start(Eq("notificationDummy"), _, _, _, _, _)) + .WillRepeatedly([&](auto, auto, auto, auto, auto, auto) { notification.notify(); }); - EXPECT_CALL(collectorMock, start(Eq("name"), _, _, _, _)); + EXPECT_CALL(collectorMock, start(Eq("name"), _, _, _, _, _)); generator.generateImage("waitDummy", {}, {}, {}, {}, {}); generator.generateImage("name", "id1", {}, {}, {}, {}); @@ -484,12 +491,12 @@ TEST_F(ImageCacheGenerator, merge_tasks_with_same_extra_id) TEST_F(ImageCacheGenerator, dont_merge_tasks_with_different_extra_id) { - EXPECT_CALL(collectorMock, start(Eq("waitDummy"), _, _, _, _)) - .WillRepeatedly([&](auto, auto, auto, auto, auto) { waitInThread.wait(); }); + EXPECT_CALL(collectorMock, start(Eq("waitDummy"), _, _, _, _, _)) + .WillRepeatedly([&](auto, auto, auto, auto, auto, auto) { waitInThread.wait(); }); - EXPECT_CALL(collectorMock, start(Eq("name"), _, _, _, _)) + EXPECT_CALL(collectorMock, start(Eq("name"), _, _, _, _, _)) .Times(2) - .WillRepeatedly([&](auto, auto, auto, auto, auto) { notification.notify(); }); + .WillRepeatedly([&](auto, auto, auto, auto, auto, auto) { notification.notify(); }); generator.generateImage("waitDummy", {}, {}, {}, {}, {}); generator.generateImage("name", "id1", {}, {}, {}, {}); @@ -500,13 +507,13 @@ TEST_F(ImageCacheGenerator, dont_merge_tasks_with_different_extra_id) TEST_F(ImageCacheGenerator, use_last_time_stamp_if_tasks_are_merged) { - ON_CALL(collectorMock, start(Eq("waitDummy"), _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto, auto) { waitInThread.wait(); }); - ON_CALL(collectorMock, start(Eq("notificationDummy"), _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto, auto) { notification.notify(); }); - ON_CALL(collectorMock, start(Eq("name"), _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto, auto abortCallback) { - abortCallback(QmlDesigner::ImageCache::AbortReason::Failed); + ON_CALL(collectorMock, start(Eq("waitDummy"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto, auto, auto) { waitInThread.wait(); }); + ON_CALL(collectorMock, start(Eq("notificationDummy"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto, auto, auto) { notification.notify(); }); + ON_CALL(collectorMock, start(Eq("name"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto, auto abortCallback, auto) { + abortCallback(QmlDesigner::ImageCache::AbortReason::Failed, {}); }); EXPECT_CALL(storageMock, storeImage(Eq("name"), Eq(Sqlite::TimeStamp{4}), _, _, _)); @@ -521,18 +528,18 @@ TEST_F(ImageCacheGenerator, use_last_time_stamp_if_tasks_are_merged) TEST_F(ImageCacheGenerator, merge_capture_callback_if_tasks_are_merged) { - NiceMock> newerImageCallbackMock; - ON_CALL(collectorMock, start(Eq("waitDummy"), _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto, auto) { waitInThread.wait(); }); - ON_CALL(collectorMock, start(Eq("notificationDummy"), _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto, auto) { notification.notify(); }); - ON_CALL(collectorMock, start(Eq("name"), _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto imageCallback, auto) { - imageCallback(QImage{image1}, QImage{midSizeImage1}, QImage{smallImage1}); + NiceMock> newerImageCallbackMock; + ON_CALL(collectorMock, start(Eq("waitDummy"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto, auto, auto) { waitInThread.wait(); }); + ON_CALL(collectorMock, start(Eq("notificationDummy"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto, auto, auto) { notification.notify(); }); + ON_CALL(collectorMock, start(Eq("name"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto imageCallback, auto, auto) { + imageCallback(QImage{image1}, QImage{midSizeImage1}, QImage{smallImage1}, {}); }); - EXPECT_CALL(imageCallbackMock, Call(_, _, _)); - EXPECT_CALL(newerImageCallbackMock, Call(_, _, _)); + EXPECT_CALL(imageCallbackMock, Call(_, _, _, _)); + EXPECT_CALL(newerImageCallbackMock, Call(_, _, _, _)); generator.generateImage("waitDummy", {}, {}, {}, {}, {}); generator.generateImage("name", {}, {}, imageCallbackMock.AsStdFunction(), {}, {}); @@ -544,18 +551,18 @@ TEST_F(ImageCacheGenerator, merge_capture_callback_if_tasks_are_merged) TEST_F(ImageCacheGenerator, merge_abort_callback_if_tasks_are_merged) { - NiceMock> newerAbortCallbackMock; - ON_CALL(collectorMock, start(Eq("waitDummy"), _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto, auto) { waitInThread.wait(); }); - ON_CALL(collectorMock, start(Eq("notificationDummy"), _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto, auto) { notification.notify(); }); - ON_CALL(collectorMock, start(Eq("name"), _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto, auto abortCallback) { - abortCallback(QmlDesigner::ImageCache::AbortReason::Failed); + NiceMock> newerAbortCallbackMock; + ON_CALL(collectorMock, start(Eq("waitDummy"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto, auto, auto) { waitInThread.wait(); }); + ON_CALL(collectorMock, start(Eq("notificationDummy"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto, auto, auto) { notification.notify(); }); + ON_CALL(collectorMock, start(Eq("name"), _, _, _, _, _)) + .WillByDefault([&](auto, auto, auto, auto, auto abortCallback, auto) { + abortCallback(QmlDesigner::ImageCache::AbortReason::Failed, {}); }); - EXPECT_CALL(abortCallbackMock, Call(Eq(QmlDesigner::ImageCache::AbortReason::Failed))); - EXPECT_CALL(newerAbortCallbackMock, Call(Eq(QmlDesigner::ImageCache::AbortReason::Failed))); + EXPECT_CALL(abortCallbackMock, Call(Eq(QmlDesigner::ImageCache::AbortReason::Failed), _)); + EXPECT_CALL(newerAbortCallbackMock, Call(Eq(QmlDesigner::ImageCache::AbortReason::Failed), _)); generator.generateImage("waitDummy", {}, {}, {}, {}, {}); generator.generateImage("name", {}, {}, {}, abortCallbackMock.AsStdFunction(), {}); @@ -567,9 +574,9 @@ TEST_F(ImageCacheGenerator, merge_abort_callback_if_tasks_are_merged) TEST_F(ImageCacheGenerator, dont_call_null_image_callback) { - EXPECT_CALL(collectorMock, start(_, _, _, _, _)) - .WillOnce([&](auto, auto, auto, auto captureCallback, auto) { - captureCallback(image1, midSizeImage1, smallImage1); + EXPECT_CALL(collectorMock, start(_, _, _, _, _, _)) + .WillOnce([&](auto, auto, auto, auto captureCallback, auto, auto) { + captureCallback(image1, midSizeImage1, smallImage1, {}); notification.notify(); }); @@ -579,9 +586,9 @@ TEST_F(ImageCacheGenerator, dont_call_null_image_callback) TEST_F(ImageCacheGenerator, dont_call_null_abort_callback_for_null_image) { - EXPECT_CALL(collectorMock, start(_, _, _, _, _)) - .WillOnce([&](auto, auto, auto, auto captureCallback, auto) { - captureCallback(QImage{}, QImage{}, QImage{}); + EXPECT_CALL(collectorMock, start(_, _, _, _, _, _)) + .WillOnce([&](auto, auto, auto, auto captureCallback, auto, auto) { + captureCallback(QImage{}, QImage{}, QImage{}, {}); notification.notify(); }); @@ -591,9 +598,9 @@ TEST_F(ImageCacheGenerator, dont_call_null_abort_callback_for_null_image) TEST_F(ImageCacheGenerator, dont_call_null_abort_callback) { - EXPECT_CALL(collectorMock, start(_, _, _, _, _)) - .WillOnce([&](auto, auto, auto, auto, auto abortCallback) { - abortCallback(QmlDesigner::ImageCache::AbortReason::Failed); + EXPECT_CALL(collectorMock, start(_, _, _, _, _, _)) + .WillOnce([&](auto, auto, auto, auto, auto abortCallback, auto) { + abortCallback(QmlDesigner::ImageCache::AbortReason::Failed, {}); notification.notify(); }); diff --git a/tests/unit/tests/unittests/metainfo/nodemetainfo-test.cpp b/tests/unit/tests/unittests/metainfo/nodemetainfo-test.cpp index c21d3259365..af8c4bd2209 100644 --- a/tests/unit/tests/unittests/metainfo/nodemetainfo-test.cpp +++ b/tests/unit/tests/unittests/metainfo/nodemetainfo-test.cpp @@ -4,6 +4,7 @@ #include "../utils/googletest.h" #include +#include #include #include @@ -13,8 +14,11 @@ namespace { +using QmlDesigner::FlagIs; using QmlDesigner::ModelNode; using QmlDesigner::ModelNodes; +using QmlDesigner::Storage::TypeTraits; +using QmlDesigner::Storage::TypeTraitsKind; template auto PropertyId(const Matcher &matcher) @@ -211,11 +215,10 @@ TEST_F(NodeMetaInfo, invalid_is_not_file_component) TEST_F(NodeMetaInfo, component_is_file_component) { - using QmlDesigner::Storage::TypeTraits; auto moduleId = projectStorageMock.createModule("/path/to/project"); - auto typeId = projectStorageMock.createType(moduleId, - "Foo", - TypeTraits::IsFileComponent | TypeTraits::Reference); + TypeTraits traits{TypeTraitsKind::Reference}; + traits.isFileComponent = true; + auto typeId = projectStorageMock.createType(moduleId, "Foo", traits); QmlDesigner::NodeMetaInfo metaInfo{typeId, &projectStorageMock}; bool isFileComponent = metaInfo.isFileComponent(); @@ -225,9 +228,10 @@ TEST_F(NodeMetaInfo, component_is_file_component) TEST_F(NodeMetaInfo, is_project_component) { - using QmlDesigner::Storage::TypeTraits; auto moduleId = projectStorageMock.createModule("/path/to/project"); - auto typeId = projectStorageMock.createType(moduleId, "Foo", TypeTraits::IsProjectComponent); + TypeTraits traits{TypeTraitsKind::Reference}; + traits.isProjectComponent = true; + auto typeId = projectStorageMock.createType(moduleId, "Foo", traits); QmlDesigner::NodeMetaInfo metaInfo{typeId, &projectStorageMock}; bool isProjectComponent = metaInfo.isProjectComponent(); @@ -258,9 +262,10 @@ TEST_F(NodeMetaInfo, invalid_is_not_project_component) TEST_F(NodeMetaInfo, is_in_project_module) { - using QmlDesigner::Storage::TypeTraits; auto moduleId = projectStorageMock.createModule("/path/to/project"); - auto typeId = projectStorageMock.createType(moduleId, "Foo", TypeTraits::IsInProjectModule); + TypeTraits traits{TypeTraitsKind::Reference}; + traits.isInProjectModule = true; + auto typeId = projectStorageMock.createType(moduleId, "Foo", traits); QmlDesigner::NodeMetaInfo metaInfo{typeId, &projectStorageMock}; bool isInProjectModule = metaInfo.isInProjectModule(); @@ -793,6 +798,25 @@ TEST_F(NodeMetaInfo, prototypes_returns_empty_container_for_default) ASSERT_THAT(prototypes, IsEmpty()); } +TEST_F(NodeMetaInfo, heirs) +{ + auto metaInfo = model.qmlQtObjectMetaInfo(); + projectStorageMock.setHeirs(metaInfo.id(), {model.qtQuickItemMetaInfo().id()}); + + auto heirs = metaInfo.heirs(); + + ASSERT_THAT(heirs, ElementsAre(model.qtQuickItemMetaInfo())); +} + +TEST_F(NodeMetaInfo, heirs_returns_empty_container_for_default) +{ + auto metaInfo = QmlDesigner::NodeMetaInfo(); + + auto heirs = metaInfo.heirs(); + + ASSERT_THAT(heirs, IsEmpty()); +} + TEST_F(NodeMetaInfo, common_base_is_root) { auto metaInfo = model.flowViewFlowActionAreaMetaInfo(); @@ -2402,7 +2426,9 @@ TEST_F(NodeMetaInfo, default_is_not_view) TEST_F(NodeMetaInfo, is_enumeration) { - auto metaInfo = createMetaInfo("QML", "Foo", QmlDesigner::Storage::TypeTraits::IsEnum); + TypeTraits traits; + traits.isEnum = true; + auto metaInfo = createMetaInfo("QML", "Foo", traits); bool isType = metaInfo.isEnumeration(); @@ -2562,7 +2588,7 @@ TEST_F(NodeMetaInfo, default_property_editor_specifics_path_is_empty) TEST_F(NodeMetaInfo, is_reference) { - auto metaInfo = createMetaInfo("QtQuick", "Item", QmlDesigner::Storage::TypeTraits::Reference); + auto metaInfo = createMetaInfo("QtQuick", "Item", TypeTraitsKind::Reference); auto type = metaInfo.type(); @@ -2571,7 +2597,7 @@ TEST_F(NodeMetaInfo, is_reference) TEST_F(NodeMetaInfo, is_value) { - auto metaInfo = createMetaInfo("QML", "bool", QmlDesigner::Storage::TypeTraits::Value); + auto metaInfo = createMetaInfo("QML", "bool", TypeTraitsKind::Value); auto type = metaInfo.type(); @@ -2580,7 +2606,7 @@ TEST_F(NodeMetaInfo, is_value) TEST_F(NodeMetaInfo, is_sequence) { - auto metaInfo = createMetaInfo("QML", "QObjectList", QmlDesigner::Storage::TypeTraits::Sequence); + auto metaInfo = createMetaInfo("QML", "QObjectList", TypeTraitsKind::Sequence); auto type = metaInfo.type(); @@ -2589,7 +2615,7 @@ TEST_F(NodeMetaInfo, is_sequence) TEST_F(NodeMetaInfo, is_none) { - auto metaInfo = createMetaInfo("QML", "void", QmlDesigner::Storage::TypeTraits::None); + auto metaInfo = createMetaInfo("QML", "void", TypeTraitsKind::None); auto type = metaInfo.type(); @@ -2605,4 +2631,555 @@ TEST_F(NodeMetaInfo, default_is_none) ASSERT_THAT(type, QmlDesigner::MetaInfoType::None); } +TEST_F(NodeMetaInfo, object_can_not_be_container) +{ + auto canBeContainer = objectMetaInfo.canBeContainer(); + + ASSERT_THAT(canBeContainer, FlagIs::False); +} + +TEST_F(NodeMetaInfo, default_can_not_be_container) +{ + auto canBeContainer = QmlDesigner::NodeMetaInfo{}.canBeContainer(); + + ASSERT_THAT(canBeContainer, FlagIs::False); +} + +TEST_F(NodeMetaInfo, invalid_can_not_be_container) +{ + auto node = model.createModelNode("Foo"); + auto metaInfo = node.metaInfo(); + + auto canBeContainer = metaInfo.canBeContainer(); + + ASSERT_THAT(canBeContainer, FlagIs::False); +} + +TEST_F(NodeMetaInfo, component_can_be_container) +{ + auto moduleId = projectStorageMock.createModule("/path/to/project"); + TypeTraits traits{TypeTraitsKind::Reference}; + traits.canBeContainer = FlagIs::True; + auto typeId = projectStorageMock.createType(moduleId, "Foo", traits); + QmlDesigner::NodeMetaInfo metaInfo{typeId, &projectStorageMock}; + + auto canBeContainer = metaInfo.canBeContainer(); + + ASSERT_THAT(canBeContainer, FlagIs::True); +} + +TEST_F(NodeMetaInfo, object_do_no_forces_clipping) +{ + auto forceClip = objectMetaInfo.forceClip(); + + ASSERT_THAT(forceClip, FlagIs::False); +} + +TEST_F(NodeMetaInfo, default_do_no_forces_clipping) +{ + auto forceClip = QmlDesigner::NodeMetaInfo{}.forceClip(); + + ASSERT_THAT(forceClip, FlagIs::False); +} + +TEST_F(NodeMetaInfo, invalid_do_no_forces_clipping) +{ + auto node = model.createModelNode("Foo"); + auto metaInfo = node.metaInfo(); + + auto forceClip = metaInfo.forceClip(); + + ASSERT_THAT(forceClip, FlagIs::False); +} + +TEST_F(NodeMetaInfo, component_forces_clipping) +{ + auto moduleId = projectStorageMock.createModule("/path/to/project"); + TypeTraits traits{TypeTraitsKind::Reference}; + traits.forceClip = FlagIs::True; + auto typeId = projectStorageMock.createType(moduleId, "Foo", traits); + QmlDesigner::NodeMetaInfo metaInfo{typeId, &projectStorageMock}; + + auto forceClip = metaInfo.forceClip(); + + ASSERT_THAT(forceClip, FlagIs::True); +} + +TEST_F(NodeMetaInfo, object_does_not_layout_children) +{ + auto doesLayoutChildren = objectMetaInfo.doesLayoutChildren(); + + ASSERT_THAT(doesLayoutChildren, FlagIs::False); +} + +TEST_F(NodeMetaInfo, default_does_not_layout_children) +{ + auto doesLayoutChildren = QmlDesigner::NodeMetaInfo{}.doesLayoutChildren(); + + ASSERT_THAT(doesLayoutChildren, FlagIs::False); +} + +TEST_F(NodeMetaInfo, invalid_does_not_layout_children) +{ + auto node = model.createModelNode("Foo"); + auto metaInfo = node.metaInfo(); + + auto doesLayoutChildren = metaInfo.doesLayoutChildren(); + + ASSERT_THAT(doesLayoutChildren, FlagIs::False); +} + +TEST_F(NodeMetaInfo, component_layouts_children) +{ + auto moduleId = projectStorageMock.createModule("/path/to/project"); + TypeTraits traits{TypeTraitsKind::Reference}; + traits.doesLayoutChildren = FlagIs::True; + auto typeId = projectStorageMock.createType(moduleId, "Foo", traits); + QmlDesigner::NodeMetaInfo metaInfo{typeId, &projectStorageMock}; + + auto doesLayoutChildren = metaInfo.doesLayoutChildren(); + + ASSERT_THAT(doesLayoutChildren, FlagIs::True); +} + +TEST_F(NodeMetaInfo, object_cannot_be_dropped_in_form_editor) +{ + auto canBeDroppedInFormEditor = objectMetaInfo.canBeDroppedInFormEditor(); + + ASSERT_THAT(canBeDroppedInFormEditor, FlagIs::False); +} + +TEST_F(NodeMetaInfo, default_cannot_be_dropped_in_form_editor) +{ + auto canBeDroppedInFormEditor = QmlDesigner::NodeMetaInfo{}.canBeDroppedInFormEditor(); + + ASSERT_THAT(canBeDroppedInFormEditor, FlagIs::False); +} + +TEST_F(NodeMetaInfo, invalid_cannot_be_dropped_in_form_editor) +{ + auto node = model.createModelNode("Foo"); + auto metaInfo = node.metaInfo(); + + auto canBeDroppedInFormEditor = metaInfo.canBeDroppedInFormEditor(); + + ASSERT_THAT(canBeDroppedInFormEditor, FlagIs::False); +} + +TEST_F(NodeMetaInfo, component_can_be_dropped_in_form_editor) +{ + auto moduleId = projectStorageMock.createModule("/path/to/project"); + TypeTraits traits{TypeTraitsKind::Reference}; + traits.canBeDroppedInFormEditor = FlagIs::True; + auto typeId = projectStorageMock.createType(moduleId, "Foo", traits); + QmlDesigner::NodeMetaInfo metaInfo{typeId, &projectStorageMock}; + + auto canBeDroppedInFormEditor = metaInfo.canBeDroppedInFormEditor(); + + ASSERT_THAT(canBeDroppedInFormEditor, FlagIs::True); +} + +TEST_F(NodeMetaInfo, object_cannot_be_dropped_in_navigator) +{ + auto canBeDroppedInNavigator = objectMetaInfo.canBeDroppedInNavigator(); + + ASSERT_THAT(canBeDroppedInNavigator, FlagIs::False); +} + +TEST_F(NodeMetaInfo, default_cannot_be_dropped_in_navigator) +{ + auto canBeDroppedInNavigator = QmlDesigner::NodeMetaInfo{}.canBeDroppedInNavigator(); + + ASSERT_THAT(canBeDroppedInNavigator, FlagIs::False); +} + +TEST_F(NodeMetaInfo, invalid_cannot_be_dropped_in_navigator) +{ + auto node = model.createModelNode("Foo"); + auto metaInfo = node.metaInfo(); + + auto canBeDroppedInNavigator = metaInfo.canBeDroppedInNavigator(); + + ASSERT_THAT(canBeDroppedInNavigator, FlagIs::False); +} + +TEST_F(NodeMetaInfo, component_can_be_dropped_in_navigator) +{ + auto moduleId = projectStorageMock.createModule("/path/to/project"); + TypeTraits traits{TypeTraitsKind::Reference}; + traits.canBeDroppedInNavigator = FlagIs::True; + auto typeId = projectStorageMock.createType(moduleId, "Foo", traits); + QmlDesigner::NodeMetaInfo metaInfo{typeId, &projectStorageMock}; + + auto canBeDroppedInNavigator = metaInfo.canBeDroppedInNavigator(); + + ASSERT_THAT(canBeDroppedInNavigator, FlagIs::True); +} + +TEST_F(NodeMetaInfo, object_cannot_be_dropped_in_3d_view) +{ + auto canBeDroppedInView3D = objectMetaInfo.canBeDroppedInView3D(); + + ASSERT_THAT(canBeDroppedInView3D, FlagIs::False); +} + +TEST_F(NodeMetaInfo, default_cannot_be_dropped_in_3d_view) +{ + auto canBeDroppedInView3D = QmlDesigner::NodeMetaInfo{}.canBeDroppedInView3D(); + + ASSERT_THAT(canBeDroppedInView3D, FlagIs::False); +} + +TEST_F(NodeMetaInfo, invalid_cannot_be_dropped_in_3d_view) +{ + auto node = model.createModelNode("Foo"); + auto metaInfo = node.metaInfo(); + + auto canBeDroppedInView3D = metaInfo.canBeDroppedInView3D(); + + ASSERT_THAT(canBeDroppedInView3D, FlagIs::False); +} + +TEST_F(NodeMetaInfo, component_can_be_dropped_in_3d_view) +{ + auto moduleId = projectStorageMock.createModule("/path/to/project"); + TypeTraits traits{TypeTraitsKind::Reference}; + traits.canBeDroppedInView3D = FlagIs::True; + auto typeId = projectStorageMock.createType(moduleId, "Foo", traits); + QmlDesigner::NodeMetaInfo metaInfo{typeId, &projectStorageMock}; + + auto canBeDroppedInView3D = metaInfo.canBeDroppedInView3D(); + + ASSERT_THAT(canBeDroppedInView3D, FlagIs::True); +} + +TEST_F(NodeMetaInfo, object_is_not_movable) +{ + auto isMovable = objectMetaInfo.isMovable(); + + ASSERT_THAT(isMovable, FlagIs::False); +} + +TEST_F(NodeMetaInfo, default_is_not_movable) +{ + auto isMovable = QmlDesigner::NodeMetaInfo{}.isMovable(); + + ASSERT_THAT(isMovable, FlagIs::False); +} + +TEST_F(NodeMetaInfo, invalid_is_not_movable) +{ + auto node = model.createModelNode("Foo"); + auto metaInfo = node.metaInfo(); + + auto isMovable = metaInfo.isMovable(); + + ASSERT_THAT(isMovable, FlagIs::False); +} + +TEST_F(NodeMetaInfo, component_is_movable) +{ + auto moduleId = projectStorageMock.createModule("/path/to/project"); + TypeTraits traits{TypeTraitsKind::Reference}; + traits.isMovable = FlagIs::True; + auto typeId = projectStorageMock.createType(moduleId, "Foo", traits); + QmlDesigner::NodeMetaInfo metaInfo{typeId, &projectStorageMock}; + + auto isMovable = metaInfo.isMovable(); + + ASSERT_THAT(isMovable, FlagIs::True); +} + +TEST_F(NodeMetaInfo, object_is_not_resizable) +{ + auto isResizable = objectMetaInfo.isResizable(); + + ASSERT_THAT(isResizable, FlagIs::False); +} + +TEST_F(NodeMetaInfo, default_is_not_resizable) +{ + auto isResizable = QmlDesigner::NodeMetaInfo{}.isResizable(); + + ASSERT_THAT(isResizable, FlagIs::False); +} + +TEST_F(NodeMetaInfo, invalid_is_not_resizable) +{ + auto node = model.createModelNode("Foo"); + auto metaInfo = node.metaInfo(); + + auto isResizable = metaInfo.isResizable(); + + ASSERT_THAT(isResizable, FlagIs::False); +} + +TEST_F(NodeMetaInfo, component_is_resizable) +{ + auto moduleId = projectStorageMock.createModule("/path/to/project"); + TypeTraits traits{TypeTraitsKind::Reference}; + traits.isResizable = FlagIs::True; + auto typeId = projectStorageMock.createType(moduleId, "Foo", traits); + QmlDesigner::NodeMetaInfo metaInfo{typeId, &projectStorageMock}; + + auto isResizable = metaInfo.isResizable(); + + ASSERT_THAT(isResizable, FlagIs::True); +} + +TEST_F(NodeMetaInfo, object_has_not_form_editor_item) +{ + auto hasFormEditorItem = objectMetaInfo.hasFormEditorItem(); + + ASSERT_THAT(hasFormEditorItem, FlagIs::False); +} + +TEST_F(NodeMetaInfo, default_has_not_form_editor_item) +{ + auto hasFormEditorItem = QmlDesigner::NodeMetaInfo{}.hasFormEditorItem(); + + ASSERT_THAT(hasFormEditorItem, FlagIs::False); +} + +TEST_F(NodeMetaInfo, invalid_has_not_form_editor_item) +{ + auto node = model.createModelNode("Foo"); + auto metaInfo = node.metaInfo(); + + auto hasFormEditorItem = metaInfo.hasFormEditorItem(); + + ASSERT_THAT(hasFormEditorItem, FlagIs::False); +} + +TEST_F(NodeMetaInfo, component_has_form_editor_item) +{ + auto moduleId = projectStorageMock.createModule("/path/to/project"); + TypeTraits traits{TypeTraitsKind::Reference}; + traits.hasFormEditorItem = FlagIs::True; + auto typeId = projectStorageMock.createType(moduleId, "Foo", traits); + QmlDesigner::NodeMetaInfo metaInfo{typeId, &projectStorageMock}; + + auto hasFormEditorItem = metaInfo.hasFormEditorItem(); + + ASSERT_THAT(hasFormEditorItem, FlagIs::True); +} + +TEST_F(NodeMetaInfo, object_is_not_stacked_container) +{ + auto isStackedContainer = objectMetaInfo.isStackedContainer(); + + ASSERT_THAT(isStackedContainer, FlagIs::False); +} + +TEST_F(NodeMetaInfo, default_is_not_stacked_container) +{ + auto isStackedContainer = QmlDesigner::NodeMetaInfo{}.isStackedContainer(); + + ASSERT_THAT(isStackedContainer, FlagIs::False); +} + +TEST_F(NodeMetaInfo, invalid_is_not_stacked_container) +{ + auto node = model.createModelNode("Foo"); + auto metaInfo = node.metaInfo(); + + auto isStackedContainer = metaInfo.isStackedContainer(); + + ASSERT_THAT(isStackedContainer, FlagIs::False); +} + +TEST_F(NodeMetaInfo, component_is_stacked_container) +{ + auto moduleId = projectStorageMock.createModule("/path/to/project"); + TypeTraits traits{TypeTraitsKind::Reference}; + traits.isStackedContainer = FlagIs::True; + auto typeId = projectStorageMock.createType(moduleId, "Foo", traits); + QmlDesigner::NodeMetaInfo metaInfo{typeId, &projectStorageMock}; + + auto isStackedContainer = metaInfo.isStackedContainer(); + + ASSERT_THAT(isStackedContainer, FlagIs::True); +} + +TEST_F(NodeMetaInfo, object_dont_takes_over_rendering_of_children) +{ + auto takesOverRenderingOfChildren = objectMetaInfo.takesOverRenderingOfChildren(); + + ASSERT_THAT(takesOverRenderingOfChildren, FlagIs::False); +} + +TEST_F(NodeMetaInfo, default_dont_takes_over_rendering_of_children) +{ + auto takesOverRenderingOfChildren = QmlDesigner::NodeMetaInfo{}.takesOverRenderingOfChildren(); + + ASSERT_THAT(takesOverRenderingOfChildren, FlagIs::False); +} + +TEST_F(NodeMetaInfo, invalid_dont_takes_over_rendering_of_children) +{ + auto node = model.createModelNode("Foo"); + auto metaInfo = node.metaInfo(); + + auto takesOverRenderingOfChildren = metaInfo.takesOverRenderingOfChildren(); + + ASSERT_THAT(takesOverRenderingOfChildren, FlagIs::False); +} + +TEST_F(NodeMetaInfo, component_takes_over_rendering_of_children) +{ + auto moduleId = projectStorageMock.createModule("/path/to/project"); + TypeTraits traits{TypeTraitsKind::Reference}; + traits.takesOverRenderingOfChildren = FlagIs::True; + auto typeId = projectStorageMock.createType(moduleId, "Foo", traits); + QmlDesigner::NodeMetaInfo metaInfo{typeId, &projectStorageMock}; + + auto takesOverRenderingOfChildren = metaInfo.takesOverRenderingOfChildren(); + + ASSERT_THAT(takesOverRenderingOfChildren, FlagIs::True); +} + +TEST_F(NodeMetaInfo, object_is_not_visible_in_navigator) +{ + auto visibleInNavigator = objectMetaInfo.visibleInNavigator(); + + ASSERT_THAT(visibleInNavigator, FlagIs::False); +} + +TEST_F(NodeMetaInfo, default_is_not_visible_in_navigator) +{ + auto visibleInNavigator = QmlDesigner::NodeMetaInfo{}.visibleInNavigator(); + + ASSERT_THAT(visibleInNavigator, FlagIs::False); +} + +TEST_F(NodeMetaInfo, invalid_is_not_visible_in_navigator) +{ + auto node = model.createModelNode("Foo"); + auto metaInfo = node.metaInfo(); + + auto visibleInNavigator = metaInfo.visibleInNavigator(); + + ASSERT_THAT(visibleInNavigator, FlagIs::False); +} + +TEST_F(NodeMetaInfo, component_is_visible_in_navigator) +{ + auto moduleId = projectStorageMock.createModule("/path/to/project"); + TypeTraits traits{TypeTraitsKind::Reference}; + traits.visibleInNavigator = FlagIs::True; + auto typeId = projectStorageMock.createType(moduleId, "Foo", traits); + QmlDesigner::NodeMetaInfo metaInfo{typeId, &projectStorageMock}; + + auto visibleInNavigator = metaInfo.visibleInNavigator(); + + ASSERT_THAT(visibleInNavigator, FlagIs::True); +} + +TEST_F(NodeMetaInfo, object_is_not_visible_in_library) +{ + auto visibleInLibrary = objectMetaInfo.visibleInLibrary(); + + ASSERT_THAT(visibleInLibrary, FlagIs::False); +} + +TEST_F(NodeMetaInfo, default_is_not_visible_in_library) +{ + auto visibleInLibrary = QmlDesigner::NodeMetaInfo{}.visibleInLibrary(); + + ASSERT_THAT(visibleInLibrary, FlagIs::False); +} + +TEST_F(NodeMetaInfo, invalid_is_not_visible_in_library) +{ + auto node = model.createModelNode("Foo"); + auto metaInfo = node.metaInfo(); + + auto visibleInLibrary = metaInfo.visibleInLibrary(); + + ASSERT_THAT(visibleInLibrary, FlagIs::False); +} + +TEST_F(NodeMetaInfo, component_is_visible_in_library) +{ + auto moduleId = projectStorageMock.createModule("/path/to/project"); + TypeTraits traits{TypeTraitsKind::Reference}; + traits.visibleInLibrary = FlagIs::True; + auto typeId = projectStorageMock.createType(moduleId, "Foo", traits); + QmlDesigner::NodeMetaInfo metaInfo{typeId, &projectStorageMock}; + + auto visibleInLibrary = metaInfo.visibleInLibrary(); + + ASSERT_THAT(visibleInLibrary, FlagIs::True); +} + +TEST_F(NodeMetaInfo, type_hints) +{ + projectStorageMock.setTypeHints(objectMetaInfo.id(), {{"inContainer", "true"}}); + + auto typeHints = objectMetaInfo.typeHints(); + + ASSERT_THAT(typeHints, ElementsAre(IsTypeHint("inContainer", "true"))); +} + +TEST_F(NodeMetaInfo, no_type_hints_for_default) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + auto typeHints = metaInfo.typeHints(); + + ASSERT_THAT(typeHints, IsEmpty()); +} + +TEST_F(NodeMetaInfo, icon_path) +{ + projectStorageMock.setTypeIconPath(objectMetaInfo.id(), "/icon/path"); + + auto path = objectMetaInfo.iconPath(); + + ASSERT_THAT(path, Eq("/icon/path")); +} + +TEST_F(NodeMetaInfo, no_icon_path_for_default) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + auto path = metaInfo.iconPath(); + + ASSERT_THAT(path, IsEmpty()); +} + +TEST_F(NodeMetaInfo, item_library_entries) +{ + projectStorageMock.setItemLibraryEntries(objectMetaInfo.id(), + {{objectMetaInfo.id(), + "Object", + "/icon/path", + "Basic", + "QtQuick", + "An object", + {{"x", "double", Sqlite::ValueView::create(1)}}}}); + + auto entries = objectMetaInfo.itemLibrariesEntries(); + + ASSERT_THAT(entries, + ElementsAre(IsItemLibraryEntry(objectMetaInfo.id(), + "Object", + "/icon/path", + "Basic", + "QtQuick", + "An object", + "", + ElementsAre(IsItemLibraryProperty("x", "double", 1)), + IsEmpty()))); +} + +TEST_F(NodeMetaInfo, no_item_library_entries_for_default) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + auto entries = metaInfo.itemLibrariesEntries(); + + ASSERT_THAT(entries, IsEmpty()); +} + } // namespace diff --git a/tests/unit/tests/unittests/metainfo/propertymetainfo-test.cpp b/tests/unit/tests/unittests/metainfo/propertymetainfo-test.cpp index 923743799e3..25436264aee 100644 --- a/tests/unit/tests/unittests/metainfo/propertymetainfo-test.cpp +++ b/tests/unit/tests/unittests/metainfo/propertymetainfo-test.cpp @@ -179,7 +179,9 @@ TEST_F(PropertyMetaInfo, default_is_not_list) TEST_F(PropertyMetaInfo, is_enumeration) { - auto enumInfo = createNodeMetaInfo("QtQuick", "MyEnum", TypeTraits::IsEnum); + TypeTraits traits; + traits.isEnum = true; + auto enumInfo = createNodeMetaInfo("QtQuick", "MyEnum", traits); projectStorageMock.createProperty(nodeInfo.id(), "bar", {}, enumInfo.id()); auto propertyInfo = nodeInfo.property("bar"); @@ -271,7 +273,9 @@ TEST_F(PropertyMetaInfo, default_is_not_pointer) TEST_F(PropertyMetaInfo, cast_to_enumeration) { - auto propertyTypeInfo = createNodeMetaInfo("QtQuick", "MyEnum", TypeTraits::IsEnum); + TypeTraits traits; + traits.isEnum = true; + auto propertyTypeInfo = createNodeMetaInfo("QtQuick", "MyEnum", traits); projectStorageMock.createProperty(nodeInfo.id(), "bar", {}, propertyTypeInfo.id()); auto propertyInfo = nodeInfo.property("bar"); Enumeration enumeration{"MyEnum.Foo"}; @@ -297,7 +301,9 @@ TEST_F(PropertyMetaInfo, dont_to_cast_enumeration_if_property_type_is_not_enumer TEST_F(PropertyMetaInfo, dont_to_cast_enumeration_if_value_is_not_Enumeration) { - auto propertyTypeInfo = createNodeMetaInfo("QtQuick", "MyEnum", TypeTraits::IsEnum); + TypeTraits traits; + traits.isEnum = true; + auto propertyTypeInfo = createNodeMetaInfo("QtQuick", "MyEnum", traits); projectStorageMock.createProperty(nodeInfo.id(), "bar", {}, propertyTypeInfo.id()); auto propertyInfo = nodeInfo.property("bar"); auto value = QVariant::fromValue(QString{"enumeration"}); diff --git a/tests/unit/tests/unittests/model/model-test.cpp b/tests/unit/tests/unittests/model/model-test.cpp index 71f6462c7cc..949efb3ee0a 100644 --- a/tests/unit/tests/unittests/model/model-test.cpp +++ b/tests/unit/tests/unittests/model/model-test.cpp @@ -7,9 +7,11 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -32,6 +34,40 @@ MATCHER(IsSorted, std::string(negation ? "isn't sorted" : "is sorted")) return std::is_sorted(begin(arg), end(arg)); } +template +auto IsItemLibraryEntry(const QmlDesigner::NodeMetaInfo &metaInfo, + QStringView name, + QStringView iconPath, + QStringView category, + QStringView import, + QStringView toolTip, + QStringView templatePath, + PropertiesMatcher propertiesMatcher, + ExtraFilePathsMatcher extraFilePathsMatcher) +{ + using QmlDesigner::ItemLibraryEntry; + return AllOf(Property("metaInfo", &ItemLibraryEntry::metaInfo, metaInfo), + Property("name", &ItemLibraryEntry::name, name), + Property("libraryEntryIconPath", &ItemLibraryEntry::libraryEntryIconPath, iconPath), + Property("category", &ItemLibraryEntry::category, category), + Property("requiredImport", &ItemLibraryEntry::requiredImport, import), + Property("toolTip", &ItemLibraryEntry::toolTip, toolTip), + Property("qmlSource", &ItemLibraryEntry::qmlSource, templatePath), + Property("properties", &ItemLibraryEntry::properties, propertiesMatcher), + Property("extraFilePath", &ItemLibraryEntry::extraFilePaths, extraFilePathsMatcher)); +} + +MATCHER_P3(IsItemLibraryProperty, + name, + type, + value, + std::string(negation ? "isn't " : "is ") + + PrintToString(QmlDesigner::PropertyContainer(name, type, value))) +{ + const QmlDesigner::PropertyContainer &property = arg; + + return property.name() == name && property.type() == type && property.value() == value; +} class Model : public ::testing::Test { protected: @@ -912,16 +948,16 @@ TEST_F(Model, get_invalid_meta_info_by_module_for_wrong_module) ASSERT_THAT(metaInfo, IsFalse()); } -TEST_F(Model, add_refresh_callback_to_project_storage) +TEST_F(Model, add_project_storage_observer_to_project_storage) { - EXPECT_CALL(projectStorageMock, addRefreshCallback(_)); + EXPECT_CALL(projectStorageMock, addObserver(_)); QmlDesigner::Model model{{projectStorageMock, pathCacheMock}, "Item", -1, -1, nullptr, {}}; } -TEST_F(Model, remove_refresh_callback_from_project_storage) +TEST_F(Model, remove_project_storage_observer_from_project_storage) { - EXPECT_CALL(projectStorageMock, removeRefreshCallback(_)).Times(2); // there is a model in the fixture + EXPECT_CALL(projectStorageMock, removeObserver(_)).Times(2); // the fixture model is calling it too QmlDesigner::Model model{{projectStorageMock, pathCacheMock}, "Item", -1, -1, nullptr, {}}; } @@ -930,14 +966,54 @@ TEST_F(Model, refresh_callback_is_calling_abstract_view) { const QmlDesigner::TypeIds typeIds = {QmlDesigner::TypeId::create(3), QmlDesigner::TypeId::create(1)}; - std::function *callback = nullptr; - ON_CALL(projectStorageMock, addRefreshCallback(_)).WillByDefault([&](auto *c) { callback = c; }); + ProjectStorageObserverMock observerMock; + QmlDesigner::ProjectStorageObserver *observer = nullptr; + ON_CALL(projectStorageMock, addObserver(_)).WillByDefault([&](auto *o) { observer = o; }); + QmlDesigner::Model model{{projectStorageMock, pathCacheMock}, "Item", -1, -1, nullptr, {}}; model.attachView(&viewMock); EXPECT_CALL(viewMock, refreshMetaInfos(typeIds)); - (*callback)(typeIds); + observer->removedTypeIds(typeIds); +} + +TEST_F(Model, meta_infos_for_mdoule) +{ + projectStorageMock.createModule("Foo"); + auto module = model.module("Foo"); + auto typeId = projectStorageMock.createObject(module.id(), "Bar"); + ON_CALL(projectStorageMock, typeIds(module.id())) + .WillByDefault(Return(QVarLengthArray{typeId})); + + auto types = model.metaInfosForModule(module); + + ASSERT_THAT(types, ElementsAre(Eq(QmlDesigner::NodeMetaInfo{typeId, &projectStorageMock}))); +} + +TEST_F(Model, item_library_entries) +{ + using namespace Qt::StringLiterals; + QmlDesigner::Storage::Info::ItemLibraryEntries storageEntries{ + {itemTypeId, "Item", "/path/to/icon", "basic category", "QtQuick", "It's a item", "/path/to/template"}}; + storageEntries.front().properties.emplace_back("x", "double", Sqlite::ValueView::create(1)); + storageEntries.front().extraFilePaths.emplace_back("/extra/file/path"); + projectStorageMock.setItemLibraryEntries(pathCacheMock.sourceId, storageEntries); + QmlDesigner::NodeMetaInfo metaInfo{itemTypeId, &projectStorageMock}; + + auto entries = model.itemLibraryEntries(); + + ASSERT_THAT(entries, + ElementsAre( + IsItemLibraryEntry(metaInfo, + u"Item", + u"/path/to/icon", + u"basic category", + u"QtQuick", + u"It's a item", + u"/path/to/template", + ElementsAre(IsItemLibraryProperty("x", "double"_L1, QVariant{1})), + ElementsAre(u"/extra/file/path")))); } } // namespace diff --git a/tests/unit/tests/unittests/model/modelutils-test.cpp b/tests/unit/tests/unittests/model/modelutils-test.cpp index 8d69e69aba8..c7a70cfbdb3 100644 --- a/tests/unit/tests/unittests/model/modelutils-test.cpp +++ b/tests/unit/tests/unittests/model/modelutils-test.cpp @@ -31,11 +31,9 @@ protected: TEST_F(ModelUtils, component_file_path) { - auto typeId = projectStorageMock.createType(moduleId, - "Foo", - QmlDesigner::Storage::TypeTraits::IsFileComponent, - {}, - sourceId); + QmlDesigner::Storage::TypeTraits traits{QmlDesigner::Storage::TypeTraitsKind::Reference}; + traits.isFileComponent = true; + auto typeId = projectStorageMock.createType(moduleId, "Foo", traits, {}, sourceId); QmlDesigner::NodeMetaInfo metaInfo{typeId, &projectStorageMock}; auto path = QmlDesigner::ModelUtils::componentFilePath(pathCacheMock, metaInfo); @@ -64,11 +62,9 @@ TEST_F(ModelUtils, empty_component_file_path_for_invalid_meta_info) TEST_F(ModelUtils, component_file_path_for_node) { - auto typeId = projectStorageMock.createType(moduleId, - "Foo", - QmlDesigner::Storage::TypeTraits::IsFileComponent, - {}, - sourceId); + QmlDesigner::Storage::TypeTraits traits{QmlDesigner::Storage::TypeTraitsKind::Reference}; + traits.isFileComponent = true; + auto typeId = projectStorageMock.createType(moduleId, "Foo", traits, {}, sourceId); projectStorageMock.createImportedTypeNameId(pathCacheMock.sourceId, "Foo", typeId); auto node = model.createModelNode("Foo"); @@ -86,11 +82,9 @@ TEST_F(ModelUtils, component_file_path_for_invalid_node_is_empty) TEST_F(ModelUtils, component_file_path_for_node_without_metainfo_is_empty) { - projectStorageMock.createType(moduleId, - "Foo", - QmlDesigner::Storage::TypeTraits::IsFileComponent, - {}, - sourceId); + QmlDesigner::Storage::TypeTraits traits{QmlDesigner::Storage::TypeTraitsKind::Reference}; + traits.isFileComponent = true; + projectStorageMock.createType(moduleId, "Foo", traits, {}, sourceId); auto node = model.createModelNode("Foo"); auto path = QmlDesigner::ModelUtils::componentFilePath(node); diff --git a/tests/unit/tests/unittests/projectstorage/CMakeLists.txt b/tests/unit/tests/unittests/projectstorage/CMakeLists.txt index 5c3fd4011e2..a2374ae265f 100644 --- a/tests/unit/tests/unittests/projectstorage/CMakeLists.txt +++ b/tests/unit/tests/unittests/projectstorage/CMakeLists.txt @@ -11,6 +11,7 @@ extend_qtc_test(unittest sourcepathcache-test.cpp sourcepathview-test.cpp storagecache-test.cpp + typeannotationreader-test.cpp ) extend_qtc_test(unittest diff --git a/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp b/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp index f6ea7a015a7..4b3f0a18692 100644 --- a/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp +++ b/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp @@ -4,6 +4,8 @@ #include "../utils/googletest.h" #include +#include +#include #include #include @@ -16,18 +18,21 @@ namespace { +using QmlDesigner::Cache::Source; +using QmlDesigner::Cache::SourceContext; using QmlDesigner::FileStatus; using QmlDesigner::FileStatuses; +using QmlDesigner::FlagIs; using QmlDesigner::ModuleId; using QmlDesigner::PropertyDeclarationId; using QmlDesigner::SourceContextId; using QmlDesigner::SourceId; using QmlDesigner::SourceIds; -using QmlDesigner::TypeId; -using QmlDesigner::Cache::Source; -using QmlDesigner::Cache::SourceContext; -using QmlDesigner::Storage::TypeTraits; using QmlDesigner::Storage::Synchronization::SynchronizationPackage; +using QmlDesigner::Storage::Synchronization::TypeAnnotations; +using QmlDesigner::Storage::TypeTraits; +using QmlDesigner::Storage::TypeTraitsKind; +using QmlDesigner::TypeId; namespace Storage = QmlDesigner::Storage; @@ -264,6 +269,22 @@ MATCHER_P3(IsInfoType, class ProjectStorage : public testing::Test { protected: + static void SetUpTestSuite() + { + static_database = std::make_unique(":memory:", Sqlite::JournalMode::Memory); + + static_projectStorage = std::make_unique>( + *static_database, static_database->isInitialized()); + } + + static void TearDownTestSuite() + { + static_projectStorage.reset(); + static_database.reset(); + } + + ~ProjectStorage() { static_projectStorage->resetForTestsOnly(); } + template static auto toValues(Range &&range) { @@ -295,37 +316,25 @@ protected: package.imports.emplace_back(qmlModuleId, Storage::Version{}, sourceId1); package.imports.emplace_back(qtQuickModuleId, Storage::Version{}, sourceId1); package.imports.emplace_back(qmlModuleId, Storage::Version{}, sourceId2); - package.moduleDependencies.emplace_back(qmlNativeModuleId, - Storage::Version{}, - sourceId1); - package.moduleDependencies.emplace_back(qtQuickNativeModuleId, - Storage::Version{}, - sourceId1); - package.moduleDependencies.emplace_back(qmlNativeModuleId, - Storage::Version{}, - sourceId2); + package.moduleDependencies.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId1); + package.moduleDependencies.emplace_back(qtQuickNativeModuleId, Storage::Version{}, sourceId1); + package.moduleDependencies.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId2); package.updatedModuleDependencySourceIds.push_back(sourceId1); package.updatedModuleDependencySourceIds.push_back(sourceId2); importsSourceId1.emplace_back(qmlModuleId, Storage::Version{}, sourceId1); importsSourceId1.emplace_back(qtQuickModuleId, Storage::Version{}, sourceId1); - moduleDependenciesSourceId1.emplace_back(qmlNativeModuleId, - Storage::Version{}, - sourceId1); - moduleDependenciesSourceId1.emplace_back(qtQuickNativeModuleId, - Storage::Version{}, - sourceId1); + moduleDependenciesSourceId1.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId1); + moduleDependenciesSourceId1.emplace_back(qtQuickNativeModuleId, Storage::Version{}, sourceId1); importsSourceId2.emplace_back(qmlModuleId, Storage::Version{}, sourceId2); - moduleDependenciesSourceId2.emplace_back(qmlNativeModuleId, - Storage::Version{}, - sourceId2); + moduleDependenciesSourceId2.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId2); package.types.push_back(Storage::Synchronization::Type{ "QQuickItem", Storage::Synchronization::ImportedType{"QObject"}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId1, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item"}, Storage::Synchronization::ExportedType{qtQuickNativeModuleId, "QQuickItem"}}, @@ -369,14 +378,10 @@ protected: "QObject", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId2, - {Storage::Synchronization::ExportedType{qmlModuleId, - "Object", - Storage::Version{2}}, - Storage::Synchronization::ExportedType{qmlModuleId, - "Obj", - Storage::Version{2}}, + {Storage::Synchronization::ExportedType{qmlModuleId, "Object", Storage::Version{2}}, + Storage::Synchronization::ExportedType{qmlModuleId, "Obj", Storage::Version{2}}, Storage::Synchronization::ExportedType{qmlNativeModuleId, "QObject"}}}); package.updatedSourceIds = {sourceId1, sourceId2}; @@ -389,9 +394,7 @@ protected: SynchronizationPackage package; package.imports.emplace_back(QMLModuleId, Storage::Version{}, sourceId1); - package.moduleDependencies.emplace_back(QMLModuleId, - Storage::Version{}, - sourceId1); + package.moduleDependencies.emplace_back(QMLModuleId, Storage::Version{}, sourceId1); package.updatedModuleDependencySourceIds.push_back(sourceId1); importsSourceId1.emplace_back(QMLModuleId, Storage::Version{}, sourceId1); @@ -401,7 +404,7 @@ protected: Storage::Synchronization::Type{"bool", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Value, + TypeTraitsKind::Value, sourceId1, {Storage::Synchronization::ExportedType{QMLModuleId, "bool"}}}); @@ -409,7 +412,7 @@ protected: Storage::Synchronization::Type{"int", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Value, + TypeTraitsKind::Value, sourceId1, {Storage::Synchronization::ExportedType{QMLModuleId, "int"}}}); @@ -417,23 +420,23 @@ protected: Storage::Synchronization::Type{"uint", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Value, + TypeTraitsKind::Value, sourceId1, {Storage::Synchronization::ExportedType{QMLNativeModuleId, "uint"}}}); - package.types.push_back( - Storage::Synchronization::Type{"double", - Storage::Synchronization::ImportedType{}, - Storage::Synchronization::ImportedType{}, - TypeTraits::Value, - sourceId1, - {Storage::Synchronization::ExportedType{QMLModuleId, - "double"}}}); + package.types.push_back(Storage::Synchronization::Type{ + "double", + Storage::Synchronization::ImportedType{}, + Storage::Synchronization::ImportedType{}, + TypeTraitsKind::Value, + sourceId1, + {Storage::Synchronization::ExportedType{QMLModuleId, "double"}, + Storage::Synchronization::ExportedType{QMLModuleId, "real"}}}); package.types.push_back( Storage::Synchronization::Type{"float", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Value, + TypeTraitsKind::Value, sourceId1, {Storage::Synchronization::ExportedType{QMLNativeModuleId, "float"}}}); @@ -441,7 +444,7 @@ protected: Storage::Synchronization::Type{"date", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Value, + TypeTraitsKind::Value, sourceId1, {Storage::Synchronization::ExportedType{QMLModuleId, "date"}}}); @@ -449,7 +452,7 @@ protected: Storage::Synchronization::Type{"string", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Value, + TypeTraitsKind::Value, sourceId1, {Storage::Synchronization::ExportedType{QMLModuleId, "string"}}}); @@ -457,7 +460,7 @@ protected: Storage::Synchronization::Type{"url", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Value, + TypeTraitsKind::Value, sourceId1, {Storage::Synchronization::ExportedType{QMLModuleId, "url"}}}); @@ -465,7 +468,7 @@ protected: Storage::Synchronization::Type{"var", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Value, + TypeTraitsKind::Value, sourceId1, {Storage::Synchronization::ExportedType{QMLModuleId, "var"}}}); @@ -481,35 +484,23 @@ protected: package.imports.emplace_back(qmlModuleId, Storage::Version{}, sourceId3); package.imports.emplace_back(qtQuickModuleId, Storage::Version{}, sourceId3); - package.moduleDependencies.emplace_back(qmlNativeModuleId, - Storage::Version{}, - sourceId3); - package.moduleDependencies.emplace_back(qtQuickNativeModuleId, - Storage::Version{}, - sourceId3); + package.moduleDependencies.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId3); + package.moduleDependencies.emplace_back(qtQuickNativeModuleId, Storage::Version{}, sourceId3); package.updatedModuleDependencySourceIds.push_back(sourceId3); package.imports.emplace_back(qmlModuleId, Storage::Version{}, sourceId4); package.imports.emplace_back(pathToModuleId, Storage::Version{}, sourceId4); - package.moduleDependencies.emplace_back(qmlNativeModuleId, - Storage::Version{}, - sourceId4); + package.moduleDependencies.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId4); package.updatedModuleDependencySourceIds.push_back(sourceId4); importsSourceId3.emplace_back(qmlModuleId, Storage::Version{}, sourceId3); importsSourceId3.emplace_back(qtQuickModuleId, Storage::Version{}, sourceId3); - moduleDependenciesSourceId3.emplace_back(qmlNativeModuleId, - Storage::Version{}, - sourceId3); - moduleDependenciesSourceId3.emplace_back(qtQuickNativeModuleId, - Storage::Version{}, - sourceId3); + moduleDependenciesSourceId3.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId3); + moduleDependenciesSourceId3.emplace_back(qtQuickNativeModuleId, Storage::Version{}, sourceId3); importsSourceId4.emplace_back(qmlModuleId, Storage::Version{}, sourceId4); importsSourceId4.emplace_back(pathToModuleId, Storage::Version{}, sourceId4); - moduleDependenciesSourceId4.emplace_back(qmlNativeModuleId, - Storage::Version{}, - sourceId4); + moduleDependenciesSourceId4.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId4); package.types[1].propertyDeclarations.push_back( Storage::Synchronization::PropertyDeclaration{"objects", @@ -520,7 +511,7 @@ protected: "QAliasItem", Storage::Synchronization::ImportedType{"Item"}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId3, {Storage::Synchronization::ExportedType{qtQuickModuleId, "AliasItem"}, Storage::Synchronization::ExportedType{qtQuickNativeModuleId, "QAliasItem"}}}); @@ -540,7 +531,7 @@ protected: "QObject2", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId4, {Storage::Synchronization::ExportedType{pathToModuleId, "Object2"}, Storage::Synchronization::ExportedType{pathToModuleId, "Obj2"}}}); @@ -564,38 +555,26 @@ protected: package.imports.emplace_back(qtQuickModuleId, Storage::Version{}, sourceId1); package.imports.emplace_back(qmlModuleId, Storage::Version{}, sourceId2); - package.moduleDependencies.emplace_back(qmlNativeModuleId, - Storage::Version{}, - sourceId1); - package.moduleDependencies.emplace_back(qtQuickNativeModuleId, - Storage::Version{}, - sourceId1); - package.moduleDependencies.emplace_back(qmlNativeModuleId, - Storage::Version{}, - sourceId2); + package.moduleDependencies.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId1); + package.moduleDependencies.emplace_back(qtQuickNativeModuleId, Storage::Version{}, sourceId1); + package.moduleDependencies.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId2); package.updatedModuleDependencySourceIds.push_back(sourceId1); package.updatedModuleDependencySourceIds.push_back(sourceId2); importsSourceId1.emplace_back(qmlModuleId, Storage::Version{}, sourceId1); importsSourceId1.emplace_back(qtQuickModuleId, Storage::Version{}, sourceId1); - moduleDependenciesSourceId1.emplace_back(qmlNativeModuleId, - Storage::Version{}, - sourceId1); - moduleDependenciesSourceId1.emplace_back(qtQuickNativeModuleId, - Storage::Version{}, - sourceId1); + moduleDependenciesSourceId1.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId1); + moduleDependenciesSourceId1.emplace_back(qtQuickNativeModuleId, Storage::Version{}, sourceId1); importsSourceId2.emplace_back(qmlModuleId, Storage::Version{}, sourceId2); - moduleDependenciesSourceId2.emplace_back(qmlNativeModuleId, - Storage::Version{}, - sourceId2); + moduleDependenciesSourceId2.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId2); package.types.push_back(Storage::Synchronization::Type{ "QQuickItem", Storage::Synchronization::ImportedType{"QObject"}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId1, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item"}, Storage::Synchronization::ExportedType{qtQuickNativeModuleId, "QQuickItem"}}, @@ -612,57 +591,37 @@ protected: "QObject", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId2, - {Storage::Synchronization::ExportedType{qmlModuleId, - "Object", - Storage::Version{2}}, - Storage::Synchronization::ExportedType{qmlModuleId, - "Obj", - Storage::Version{2}}, + {Storage::Synchronization::ExportedType{qmlModuleId, "Object", Storage::Version{2}}, + Storage::Synchronization::ExportedType{qmlModuleId, "Obj", Storage::Version{2}}, Storage::Synchronization::ExportedType{qmlNativeModuleId, "QObject"}}}); package.updatedSourceIds = {sourceId1, sourceId2}; package.imports.emplace_back(qmlModuleId, Storage::Version{}, sourceId3); package.imports.emplace_back(qtQuickModuleId, Storage::Version{}, sourceId3); - package.moduleDependencies.emplace_back(qmlNativeModuleId, - Storage::Version{}, - sourceId3); - package.moduleDependencies.emplace_back(qtQuickNativeModuleId, - Storage::Version{}, - sourceId3); + package.moduleDependencies.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId3); + package.moduleDependencies.emplace_back(qtQuickNativeModuleId, Storage::Version{}, sourceId3); package.updatedModuleDependencySourceIds.push_back(sourceId3); package.imports.emplace_back(qmlModuleId, Storage::Version{}, sourceId4); package.imports.emplace_back(pathToModuleId, Storage::Version{}, sourceId4); package.imports.emplace_back(qtQuickModuleId, Storage::Version{}, sourceId4); - package.moduleDependencies.emplace_back(qmlNativeModuleId, - Storage::Version{}, - sourceId4); - package.moduleDependencies.emplace_back(qtQuickNativeModuleId, - Storage::Version{}, - sourceId4); + package.moduleDependencies.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId4); + package.moduleDependencies.emplace_back(qtQuickNativeModuleId, Storage::Version{}, sourceId4); package.updatedModuleDependencySourceIds.push_back(sourceId4); importsSourceId3.emplace_back(qmlModuleId, Storage::Version{}, sourceId3); importsSourceId3.emplace_back(qtQuickModuleId, Storage::Version{}, sourceId3); - moduleDependenciesSourceId3.emplace_back(qmlNativeModuleId, - Storage::Version{}, - sourceId3); - moduleDependenciesSourceId3.emplace_back(qtQuickNativeModuleId, - Storage::Version{}, - sourceId3); + moduleDependenciesSourceId3.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId3); + moduleDependenciesSourceId3.emplace_back(qtQuickNativeModuleId, Storage::Version{}, sourceId3); importsSourceId4.emplace_back(qmlModuleId, Storage::Version{}, sourceId4); importsSourceId4.emplace_back(pathToModuleId, Storage::Version{}, sourceId4); importsSourceId4.emplace_back(qtQuickModuleId, Storage::Version{}, sourceId4); - moduleDependenciesSourceId4.emplace_back(qmlNativeModuleId, - Storage::Version{}, - sourceId4); - moduleDependenciesSourceId4.emplace_back(qtQuickNativeModuleId, - Storage::Version{}, - sourceId4); + moduleDependenciesSourceId4.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId4); + moduleDependenciesSourceId4.emplace_back(qtQuickNativeModuleId, Storage::Version{}, sourceId4); package.types[1].propertyDeclarations.push_back( Storage::Synchronization::PropertyDeclaration{"objects", @@ -673,7 +632,7 @@ protected: "QAliasItem", Storage::Synchronization::ImportedType{"Item"}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId3, {Storage::Synchronization::ExportedType{qtQuickModuleId, "AliasItem"}, Storage::Synchronization::ExportedType{qtQuickNativeModuleId, "QAliasItem"}}}); @@ -688,7 +647,7 @@ protected: "QObject2", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId4, {Storage::Synchronization::ExportedType{pathToModuleId, "Object2"}, Storage::Synchronization::ExportedType{pathToModuleId, "Obj2"}}}); @@ -704,11 +663,9 @@ protected: "QChildren", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId5, - {Storage::Synchronization::ExportedType{qtQuickModuleId, - "Children", - Storage::Version{2}}, + {Storage::Synchronization::ExportedType{qtQuickModuleId, "Children", Storage::Version{2}}, Storage::Synchronization::ExportedType{qtQuickNativeModuleId, "QChildren"}}, {Storage::Synchronization::PropertyDeclaration{"items", Storage::Synchronization::ImportedType{ @@ -722,20 +679,12 @@ protected: package.imports.emplace_back(qmlModuleId, Storage::Version{}, sourceId5); package.imports.emplace_back(qtQuickModuleId, Storage::Version{}, sourceId5); - package.moduleDependencies.emplace_back(qmlNativeModuleId, - Storage::Version{}, - sourceId5); - package.moduleDependencies.emplace_back(qtQuickNativeModuleId, - Storage::Version{}, - sourceId5); + package.moduleDependencies.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId5); + package.moduleDependencies.emplace_back(qtQuickNativeModuleId, Storage::Version{}, sourceId5); importsSourceId5.emplace_back(qmlModuleId, Storage::Version{}, sourceId5); importsSourceId5.emplace_back(qtQuickModuleId, Storage::Version{}, sourceId5); - moduleDependenciesSourceId5.emplace_back(qmlNativeModuleId, - Storage::Version{}, - sourceId5); - moduleDependenciesSourceId5.emplace_back(qtQuickNativeModuleId, - Storage::Version{}, - sourceId5); + moduleDependenciesSourceId5.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId5); + moduleDependenciesSourceId5.emplace_back(qtQuickNativeModuleId, Storage::Version{}, sourceId5); package.updatedModuleDependencySourceIds.push_back(sourceId5); package.updatedSourceIds.push_back(sourceId5); @@ -743,11 +692,9 @@ protected: "QChildren2", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId6, - {Storage::Synchronization::ExportedType{qtQuickModuleId, - "Children2", - Storage::Version{2}}, + {Storage::Synchronization::ExportedType{qtQuickModuleId, "Children2", Storage::Version{2}}, Storage::Synchronization::ExportedType{qtQuickNativeModuleId, "QChildren2"}}, {Storage::Synchronization::PropertyDeclaration{"items", Storage::Synchronization::ImportedType{ @@ -762,12 +709,8 @@ protected: package.imports.emplace_back(qmlModuleId, Storage::Version{}, sourceId6); package.imports.emplace_back(pathToModuleId, Storage::Version{}, sourceId6); package.imports.emplace_back(qtQuickModuleId, Storage::Version{}, sourceId6); - package.moduleDependencies.emplace_back(qmlNativeModuleId, - Storage::Version{}, - sourceId6); - package.moduleDependencies.emplace_back(qtQuickNativeModuleId, - Storage::Version{}, - sourceId6); + package.moduleDependencies.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId6); + package.moduleDependencies.emplace_back(qtQuickNativeModuleId, Storage::Version{}, sourceId6); package.updatedModuleDependencySourceIds.push_back(sourceId6); package.updatedSourceIds.push_back(sourceId6); @@ -796,7 +739,7 @@ protected: "QAliasItem2", Storage::Synchronization::ImportedType{"Object"}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId5, {Storage::Synchronization::ExportedType{qtQuickModuleId, "AliasItem2"}, Storage::Synchronization::ExportedType{qtQuickNativeModuleId, "QAliasItem2"}}}); @@ -827,57 +770,39 @@ protected: "QObject", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId1, - {Storage::Synchronization::ExportedType{qmlModuleId, - "Object", - Storage::Version{1}}, - Storage::Synchronization::ExportedType{qmlModuleId, - "Obj", - Storage::Version{1, 2}}, + {Storage::Synchronization::ExportedType{qmlModuleId, "Object", Storage::Version{1}}, + Storage::Synchronization::ExportedType{qmlModuleId, "Obj", Storage::Version{1, 2}}, Storage::Synchronization::ExportedType{qmlModuleId, "BuiltInObj"}, Storage::Synchronization::ExportedType{qmlNativeModuleId, "QObject"}}}); package.types.push_back(Storage::Synchronization::Type{ "QObject2", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId1, - {Storage::Synchronization::ExportedType{qmlModuleId, - "Object", - Storage::Version{2, 0}}, - Storage::Synchronization::ExportedType{qmlModuleId, - "Obj", - Storage::Version{2, 3}}, + {Storage::Synchronization::ExportedType{qmlModuleId, "Object", Storage::Version{2, 0}}, + Storage::Synchronization::ExportedType{qmlModuleId, "Obj", Storage::Version{2, 3}}, Storage::Synchronization::ExportedType{qmlNativeModuleId, "QObject2"}}}); package.types.push_back(Storage::Synchronization::Type{ "QObject3", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId1, - {Storage::Synchronization::ExportedType{qmlModuleId, - "Object", - Storage::Version{2, 11}}, - Storage::Synchronization::ExportedType{qmlModuleId, - "Obj", - Storage::Version{2, 11}}, + {Storage::Synchronization::ExportedType{qmlModuleId, "Object", Storage::Version{2, 11}}, + Storage::Synchronization::ExportedType{qmlModuleId, "Obj", Storage::Version{2, 11}}, Storage::Synchronization::ExportedType{qmlNativeModuleId, "QObject3"}}}); package.types.push_back(Storage::Synchronization::Type{ "QObject4", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId1, - {Storage::Synchronization::ExportedType{qmlModuleId, - "Object", - Storage::Version{3, 4}}, - Storage::Synchronization::ExportedType{qmlModuleId, - "Obj", - Storage::Version{3, 4}}, - Storage::Synchronization::ExportedType{qmlModuleId, - "BuiltInObj", - Storage::Version{3, 4}}, + {Storage::Synchronization::ExportedType{qmlModuleId, "Object", Storage::Version{3, 4}}, + Storage::Synchronization::ExportedType{qmlModuleId, "Obj", Storage::Version{3, 4}}, + Storage::Synchronization::ExportedType{qmlModuleId, "BuiltInObj", Storage::Version{3, 4}}, Storage::Synchronization::ExportedType{qmlNativeModuleId, "QObject4"}}}); package.updatedSourceIds.push_back(sourceId1); @@ -897,11 +822,9 @@ protected: "QObject", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId1, - {Storage::Synchronization::ExportedType{qmlModuleId, - "Object", - Storage::Version{}}}, + {Storage::Synchronization::ExportedType{qmlModuleId, "Object", Storage::Version{}}}, {Storage::Synchronization::PropertyDeclaration{"data", Storage::Synchronization::ImportedType{"Object"}, Storage::PropertyDeclarationTraits::IsList}, @@ -916,11 +839,9 @@ protected: "QObject2", Storage::Synchronization::ImportedType{"Object"}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId1, - {Storage::Synchronization::ExportedType{qmlModuleId, - "Object2", - Storage::Version{}}}, + {Storage::Synchronization::ExportedType{qmlModuleId, "Object2", Storage::Version{}}}, {Storage::Synchronization::PropertyDeclaration{ "data2", Storage::Synchronization::ImportedType{"Object3"}, @@ -936,11 +857,9 @@ protected: "QObject3", Storage::Synchronization::ImportedType{"Object2"}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId1, - {Storage::Synchronization::ExportedType{qmlModuleId, - "Object3", - Storage::Version{}}}, + {Storage::Synchronization::ExportedType{qmlModuleId, "Object3", Storage::Version{}}}, {Storage::Synchronization::PropertyDeclaration{"data3", Storage::Synchronization::ImportedType{ "Object2"}, @@ -960,6 +879,34 @@ protected: return package; } + auto createHeirPackage() + { + auto package = createPackageWithProperties(); + + package.types.push_back( + Storage::Synchronization::Type{"QObject4", + Storage::Synchronization::ImportedType{}, + Storage::Synchronization::ImportedType{"Object2"}, + TypeTraitsKind::Reference, + sourceId1, + {}, + {}, + {}, + {}}); + package.types.push_back( + Storage::Synchronization::Type{"QObject5", + Storage::Synchronization::ImportedType{}, + Storage::Synchronization::ImportedType{"Object2"}, + TypeTraitsKind::Reference, + sourceId1, + {}, + {}, + {}, + {}}); + + return package; + } + auto createModuleExportedImportSynchronizationPackage() { SynchronizationPackage package; @@ -970,11 +917,9 @@ protected: "QQuickItem", Storage::Synchronization::ImportedType{"Object"}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId1, - {Storage::Synchronization::ExportedType{qtQuickModuleId, - "Item", - Storage::Version{1, 0}}}}); + {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{1, 0}}}}); package.updatedModuleIds.push_back(qmlModuleId); package.moduleExportedImports.emplace_back(qtQuickModuleId, @@ -985,11 +930,9 @@ protected: "QObject", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId2, - {Storage::Synchronization::ExportedType{qmlModuleId, - "Object", - Storage::Version{1, 0}}}}); + {Storage::Synchronization::ExportedType{qmlModuleId, "Object", Storage::Version{1, 0}}}}); package.imports.emplace_back(qtQuickModuleId, Storage::Version{1}, sourceId3); package.moduleExportedImports.emplace_back(qtQuick3DModuleId, @@ -997,26 +940,24 @@ protected: Storage::Version{}, Storage::Synchronization::IsAutoVersion::Yes); package.updatedModuleIds.push_back(qtQuick3DModuleId); - package.types.push_back(Storage::Synchronization::Type{ - "QQuickItem3d", - Storage::Synchronization::ImportedType{"Item"}, - Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, - sourceId3, - {Storage::Synchronization::ExportedType{qtQuick3DModuleId, - "Item3D", - Storage::Version{1, 0}}}}); + package.types.push_back( + Storage::Synchronization::Type{"QQuickItem3d", + Storage::Synchronization::ImportedType{"Item"}, + Storage::Synchronization::ImportedType{}, + TypeTraitsKind::Reference, + sourceId3, + {Storage::Synchronization::ExportedType{ + qtQuick3DModuleId, "Item3D", Storage::Version{1, 0}}}}); package.imports.emplace_back(qtQuick3DModuleId, Storage::Version{1}, sourceId4); - package.types.push_back(Storage::Synchronization::Type{ - "MyItem", - Storage::Synchronization::ImportedType{"Object"}, - Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, - sourceId4, - {Storage::Synchronization::ExportedType{myModuleModuleId, - "MyItem", - Storage::Version{1, 0}}}}); + package.types.push_back( + Storage::Synchronization::Type{"MyItem", + Storage::Synchronization::ImportedType{"Object"}, + Storage::Synchronization::ImportedType{}, + TypeTraitsKind::Reference, + sourceId4, + {Storage::Synchronization::ExportedType{ + myModuleModuleId, "MyItem", Storage::Version{1, 0}}}}); package.updatedSourceIds = {sourceId1, sourceId2, sourceId3, sourceId4}; @@ -1032,14 +973,14 @@ protected: "QQuickItem", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId1, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{1, 0}}}}); package.types.push_back( Storage::Synchronization::Type{"QObject", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId2, {Storage::Synchronization::ExportedType{ qtQuickModuleId, "QtObject", Storage::Version{1, 0}}}}); @@ -1048,7 +989,7 @@ protected: Storage::Synchronization::Type{"QQuickItem3d", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId3, {Storage::Synchronization::ExportedType{ qtQuickModuleId, "Item3D", Storage::Version{1, 0}}}}); @@ -1065,6 +1006,58 @@ protected: return package; } + auto createTypeAnnotions() const + { + TypeAnnotations annotations; + + TypeTraits traits{TypeTraitsKind::Reference}; + traits.canBeContainer = FlagIs::True; + traits.visibleInLibrary = FlagIs::True; + + annotations.emplace_back(sourceId4, + "Object", + qmlModuleId, + "/path/to/icon.png", + traits, + R"xy({"canBeContainer": "true", "hints": "false"})xy", + R"xy([{"name":"Foo", + "iconPath":"/path/icon", + "category":"Basic Items", + "import":"QtQuick", + "toolTip":"Foo is a Item", + "templatePath":"/path/templates/item.qml", + "properties":[["x", "double", 32.1],["y", "double", 12.3]], + "extraFilePaths":["/path/templates/frame.png", "/path/templates/frame.frag"]}, + {"name":"Bar", + "iconPath":"/path/icon2", + "category":"Basic Items", + "import":"QtQuick", + "toolTip":"Bar is a Item", + "properties":[["color", "color", "#blue"]]}])xy"); + + annotations.emplace_back(sourceId5, + "Item", + qtQuickModuleId, + "/path/to/quick.png", + traits, + R"xy({"canBeContainer": "true", "forceClip": "false"})xy", + R"xy([{"name":"Item", + "iconPath":"/path/icon3", + "category":"Advanced Items", + "import":"QtQuick", + "toolTip":"Item is an Object", + "properties":[["x", "double", 1], ["y", "double", 2]]}])xy"); + + return annotations; + } + + static auto createUpdatedTypeAnnotionSourceIds(const TypeAnnotations &annotations) + { + return Utils::transform(annotations, [](const auto &annotation) { + return annotation.sourceId; + }); + } + template static FileStatuses convert(const Range &range) { @@ -1116,9 +1109,11 @@ protected: } protected: - Sqlite::Database database{":memory:", Sqlite::JournalMode::Memory}; + inline static std::unique_ptr static_database; + Sqlite::Database &database = *static_database; //Sqlite::Database database{"/tmp/aaaaa.db", Sqlite::JournalMode::Wal}; - QmlDesigner::ProjectStorage storage{database, database.isInitialized()}; + inline static std::unique_ptr> static_projectStorage; + QmlDesigner::ProjectStorage &storage = *static_projectStorage; QmlDesigner::SourcePathCache> sourcePathCache{ storage}; QmlDesigner::SourcePathView path1{"/path1/to"}; @@ -1370,7 +1365,7 @@ TEST_F(ProjectStorage, synchronize_types_adds_new_types) ASSERT_THAT(storage.fetchTypes(), UnorderedElementsAre( - AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraits::Reference), + AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qmlModuleId, "Object"), IsExportedType(qmlModuleId, "Obj"), @@ -1378,7 +1373,7 @@ TEST_F(ProjectStorage, synchronize_types_adds_new_types) AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuickModuleId, "Item"), IsExportedType(qtQuickNativeModuleId, @@ -1394,7 +1389,7 @@ TEST_F(ProjectStorage, synchronize_types_adds_new_types_with_extension_chain) ASSERT_THAT(storage.fetchTypes(), UnorderedElementsAre( - AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraits::Reference), + AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qmlModuleId, "Object"), IsExportedType(qmlModuleId, "Obj"), @@ -1403,7 +1398,7 @@ TEST_F(ProjectStorage, synchronize_types_adds_new_types_with_extension_chain) "QQuickItem", TypeId{}, fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuickModuleId, "Item"), IsExportedType(qtQuickNativeModuleId, @@ -1420,7 +1415,7 @@ TEST_F(ProjectStorage, synchronize_types_adds_new_types_with_exported_prototype_ ASSERT_THAT( storage.fetchTypes(), UnorderedElementsAre( - AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeId{}, TypeTraits::Reference), + AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeId{}, TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qmlModuleId, "Object"), IsExportedType(qmlModuleId, "Obj"), @@ -1429,7 +1424,7 @@ TEST_F(ProjectStorage, synchronize_types_adds_new_types_with_exported_prototype_ "QQuickItem", fetchTypeId(sourceId2, "QObject"), TypeId{}, - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuickModuleId, "Item"), IsExportedType(qtQuickNativeModuleId, "QQuickItem")))))); @@ -1445,7 +1440,7 @@ TEST_F(ProjectStorage, synchronize_types_adds_new_types_with_exported_extension_ ASSERT_THAT(storage.fetchTypes(), UnorderedElementsAre( - AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraits::Reference), + AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qmlModuleId, "Object"), IsExportedType(qmlModuleId, "Obj"), @@ -1454,7 +1449,7 @@ TEST_F(ProjectStorage, synchronize_types_adds_new_types_with_exported_extension_ "QQuickItem", TypeId{}, fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuickModuleId, "Item"), IsExportedType(qtQuickNativeModuleId, @@ -1484,7 +1479,7 @@ TEST_F(ProjectStorage, synchronize_types_adds_new_types_with_missing_module) "QObject2", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId3, {Storage::Synchronization::ExportedType{ModuleId::create(22), "Object2"}, Storage::Synchronization::ExportedType{pathToModuleId, "Obj2"}}}); @@ -1501,7 +1496,7 @@ TEST_F(ProjectStorage, synchronize_types_adds_new_types_reverse_order) ASSERT_THAT(storage.fetchTypes(), UnorderedElementsAre( - AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraits::Reference), + AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qmlModuleId, "Object"), IsExportedType(qmlModuleId, "Obj"), @@ -1509,7 +1504,7 @@ TEST_F(ProjectStorage, synchronize_types_adds_new_types_reverse_order) AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuickModuleId, "Item"), IsExportedType(qtQuickNativeModuleId, @@ -1520,23 +1515,26 @@ TEST_F(ProjectStorage, synchronize_types_overwrites_type_traits) { auto package{createSimpleSynchronizationPackage()}; storage.synchronize(package); - package.types[0].traits = TypeTraits::Value; - package.types[1].traits = TypeTraits::Value; + package.types[0].traits = TypeTraitsKind::Value; + package.types[1].traits = TypeTraitsKind::Value; storage.synchronize(SynchronizationPackage{package.imports, package.types, {sourceId1, sourceId2}}); - ASSERT_THAT( - storage.fetchTypes(), - UnorderedElementsAre( - AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraits::Value), - Field(&Storage::Synchronization::Type::exportedTypes, - UnorderedElementsAre(IsExportedType(qmlModuleId, "Object"), - IsExportedType(qmlModuleId, "Obj"), - IsExportedType(qmlNativeModuleId, "QObject")))), - AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), TypeTraits::Value), - Field(&Storage::Synchronization::Type::exportedTypes, - UnorderedElementsAre(IsExportedType(qtQuickModuleId, "Item"), - IsExportedType(qtQuickNativeModuleId, "QQuickItem")))))); + ASSERT_THAT(storage.fetchTypes(), + UnorderedElementsAre( + AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraitsKind::Value), + Field(&Storage::Synchronization::Type::exportedTypes, + UnorderedElementsAre(IsExportedType(qmlModuleId, "Object"), + IsExportedType(qmlModuleId, "Obj"), + IsExportedType(qmlNativeModuleId, "QObject")))), + AllOf(IsStorageType(sourceId1, + "QQuickItem", + fetchTypeId(sourceId2, "QObject"), + TypeTraitsKind::Value), + Field(&Storage::Synchronization::Type::exportedTypes, + UnorderedElementsAre(IsExportedType(qtQuickModuleId, "Item"), + IsExportedType(qtQuickNativeModuleId, + "QQuickItem")))))); } TEST_F(ProjectStorage, synchronize_types_overwrites_sources) @@ -1559,7 +1557,7 @@ TEST_F(ProjectStorage, synchronize_types_overwrites_sources) ASSERT_THAT(storage.fetchTypes(), UnorderedElementsAre( - AllOf(IsStorageType(sourceId4, "QObject", TypeId{}, TypeTraits::Reference), + AllOf(IsStorageType(sourceId4, "QObject", TypeId{}, TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qmlModuleId, "Object"), IsExportedType(qmlModuleId, "Obj"), @@ -1567,7 +1565,7 @@ TEST_F(ProjectStorage, synchronize_types_overwrites_sources) AllOf(IsStorageType(sourceId3, "QQuickItem", fetchTypeId(sourceId4, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuickModuleId, "Item"), IsExportedType(qtQuickNativeModuleId, @@ -1583,7 +1581,7 @@ TEST_F(ProjectStorage, synchronize_types_insert_type_into_prototype_chain) "QQuickObject", Storage::Synchronization::ImportedType{"QObject"}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId1, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Object"}, Storage::Synchronization::ExportedType{qtQuickNativeModuleId, "QQuickObject"}}}); @@ -1594,7 +1592,7 @@ TEST_F(ProjectStorage, synchronize_types_insert_type_into_prototype_chain) ASSERT_THAT( storage.fetchTypes(), UnorderedElementsAre( - AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeId{}, TypeTraits::Reference), + AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeId{}, TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qmlModuleId, "Object"), IsExportedType(qmlModuleId, "Obj"), @@ -1603,7 +1601,7 @@ TEST_F(ProjectStorage, synchronize_types_insert_type_into_prototype_chain) "QQuickObject", fetchTypeId(sourceId2, "QObject"), TypeId{}, - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuickModuleId, "Object"), IsExportedType(qtQuickNativeModuleId, "QQuickObject")))), @@ -1611,7 +1609,7 @@ TEST_F(ProjectStorage, synchronize_types_insert_type_into_prototype_chain) "QQuickItem", fetchTypeId(sourceId1, "QQuickObject"), TypeId{}, - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuickModuleId, "Item"), IsExportedType(qtQuickNativeModuleId, "QQuickItem")))))); @@ -1627,7 +1625,7 @@ TEST_F(ProjectStorage, synchronize_types_insert_type_into_extension_chain) "QQuickObject", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{"QObject"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId1, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Object"}, Storage::Synchronization::ExportedType{qtQuickNativeModuleId, "QQuickObject"}}}); @@ -1638,7 +1636,7 @@ TEST_F(ProjectStorage, synchronize_types_insert_type_into_extension_chain) ASSERT_THAT( storage.fetchTypes(), UnorderedElementsAre( - AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeId{}, TypeTraits::Reference), + AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeId{}, TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qmlModuleId, "Object"), IsExportedType(qmlModuleId, "Obj"), @@ -1647,7 +1645,7 @@ TEST_F(ProjectStorage, synchronize_types_insert_type_into_extension_chain) "QQuickObject", TypeId{}, fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuickModuleId, "Object"), IsExportedType(qtQuickNativeModuleId, "QQuickObject")))), @@ -1655,7 +1653,7 @@ TEST_F(ProjectStorage, synchronize_types_insert_type_into_extension_chain) "QQuickItem", TypeId{}, fetchTypeId(sourceId1, "QQuickObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuickModuleId, "Item"), IsExportedType(qtQuickNativeModuleId, "QQuickItem")))))); @@ -1670,7 +1668,7 @@ TEST_F(ProjectStorage, synchronize_types_add_qualified_prototype) "QQuickObject", Storage::Synchronization::ImportedType{"QObject"}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId1, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Object"}, Storage::Synchronization::ExportedType{qtQuickNativeModuleId, "QQuickObject"}}}); @@ -1680,7 +1678,7 @@ TEST_F(ProjectStorage, synchronize_types_add_qualified_prototype) ASSERT_THAT( storage.fetchTypes(), UnorderedElementsAre( - AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeId{}, TypeTraits::Reference), + AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeId{}, TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qmlModuleId, "Object"), IsExportedType(qmlModuleId, "Obj"), @@ -1689,7 +1687,7 @@ TEST_F(ProjectStorage, synchronize_types_add_qualified_prototype) "QQuickObject", fetchTypeId(sourceId2, "QObject"), TypeId{}, - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuickModuleId, "Object"), IsExportedType(qtQuickNativeModuleId, "QQuickObject")))), @@ -1697,7 +1695,7 @@ TEST_F(ProjectStorage, synchronize_types_add_qualified_prototype) "QQuickItem", fetchTypeId(sourceId1, "QQuickObject"), TypeId{}, - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuickModuleId, "Item"), IsExportedType(qtQuickNativeModuleId, "QQuickItem")))))); @@ -1713,7 +1711,7 @@ TEST_F(ProjectStorage, synchronize_types_add_qualified_extension) "QQuickObject", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{"QObject"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId1, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Object"}, Storage::Synchronization::ExportedType{qtQuickNativeModuleId, "QQuickObject"}}}); @@ -1723,7 +1721,7 @@ TEST_F(ProjectStorage, synchronize_types_add_qualified_extension) ASSERT_THAT( storage.fetchTypes(), UnorderedElementsAre( - AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeId{}, TypeTraits::Reference), + AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeId{}, TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qmlModuleId, "Object"), IsExportedType(qmlModuleId, "Obj"), @@ -1732,7 +1730,7 @@ TEST_F(ProjectStorage, synchronize_types_add_qualified_extension) "QQuickObject", TypeId{}, fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuickModuleId, "Object"), IsExportedType(qtQuickNativeModuleId, "QQuickObject")))), @@ -1740,7 +1738,7 @@ TEST_F(ProjectStorage, synchronize_types_add_qualified_extension) "QQuickItem", TypeId{}, fetchTypeId(sourceId1, "QQuickObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuickModuleId, "Item"), IsExportedType(qtQuickNativeModuleId, "QQuickItem")))))); @@ -1753,7 +1751,7 @@ TEST_F(ProjectStorage, synchronize_types_throws_for_missing_prototype) "QQuickItem", Storage::Synchronization::ImportedType{"QObject"}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId1, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item"}, Storage::Synchronization::ExportedType{qtQuickNativeModuleId, "QQuickItem"}}}}; @@ -1768,7 +1766,7 @@ TEST_F(ProjectStorage, synchronize_types_throws_for_missing_extension) "QQuickItem", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{"QObject"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId1, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item"}, Storage::Synchronization::ExportedType{qtQuickNativeModuleId, "QQuickItem"}}}}; @@ -1783,7 +1781,7 @@ TEST_F(ProjectStorage, synchronize_types_throws_for_invalid_module) Storage::Synchronization::Type{"QQuickItem", Storage::Synchronization::ImportedType{"QObject"}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId1, {Storage::Synchronization::ExportedType{ModuleId{}, "Item"}}}}; @@ -1797,7 +1795,7 @@ TEST_F(ProjectStorage, type_with_invalid_source_id_throws) Storage::Synchronization::Type{"QQuickItem", Storage::Synchronization::ImportedType{""}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, SourceId{}, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item"}}}}; @@ -1815,7 +1813,7 @@ TEST_F(ProjectStorage, delete_type_if_source_id_is_synchronized) ASSERT_THAT(storage.fetchTypes(), UnorderedElementsAre( - AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraits::Reference), + AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qmlModuleId, "Object"), IsExportedType(qmlModuleId, "Obj"), @@ -1831,7 +1829,7 @@ TEST_F(ProjectStorage, dont_delete_type_if_source_id_is_not_synchronized) ASSERT_THAT(storage.fetchTypes(), UnorderedElementsAre( - AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraits::Reference), + AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qmlModuleId, "Object"), IsExportedType(qmlModuleId, "Obj"), @@ -1839,7 +1837,7 @@ TEST_F(ProjectStorage, dont_delete_type_if_source_id_is_not_synchronized) AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuickModuleId, "Item"), IsExportedType(qtQuickNativeModuleId, @@ -1856,7 +1854,7 @@ TEST_F(ProjectStorage, update_exported_types_if_type_name_changes) ASSERT_THAT(storage.fetchTypes(), UnorderedElementsAre( - AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraits::Reference), + AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qmlModuleId, "Object"), IsExportedType(qmlModuleId, "Obj"), @@ -1864,7 +1862,7 @@ TEST_F(ProjectStorage, update_exported_types_if_type_name_changes) AllOf(IsStorageType(sourceId1, "QQuickItem2", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuickModuleId, "Item"), IsExportedType(qtQuickNativeModuleId, @@ -1900,19 +1898,22 @@ TEST_F(ProjectStorage, synchronize_types_add_property_declarations) storage.synchronize(package); - ASSERT_THAT( - storage.fetchTypes(), - Contains(AllOf( - IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), TypeTraits::Reference), - Field(&Storage::Synchronization::Type::propertyDeclarations, - UnorderedElementsAre( - IsPropertyDeclaration("data", - fetchTypeId(sourceId2, "QObject"), - Storage::PropertyDeclarationTraits::IsList), - IsPropertyDeclaration("children", - fetchTypeId(sourceId1, "QQuickItem"), - Storage::PropertyDeclarationTraits::IsList - | Storage::PropertyDeclarationTraits::IsReadOnly)))))); + ASSERT_THAT(storage.fetchTypes(), + Contains( + AllOf(IsStorageType(sourceId1, + "QQuickItem", + fetchTypeId(sourceId2, "QObject"), + TypeTraitsKind::Reference), + Field(&Storage::Synchronization::Type::propertyDeclarations, + UnorderedElementsAre( + IsPropertyDeclaration("data", + fetchTypeId(sourceId2, "QObject"), + Storage::PropertyDeclarationTraits::IsList), + IsPropertyDeclaration( + "children", + fetchTypeId(sourceId1, "QQuickItem"), + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly)))))); } TEST_F(ProjectStorage, synchronize_types_add_property_declarations_with_missing_imports) @@ -1932,26 +1933,29 @@ TEST_F(ProjectStorage, synchronize_types_add_property_declaration_qualified_type Storage::Synchronization::Type{"QQuickObject", Storage::Synchronization::ImportedType{"QObject"}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId1, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Object"}}}); storage.synchronize(package); - ASSERT_THAT( - storage.fetchTypes(), - Contains(AllOf( - IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), TypeTraits::Reference), - Field(&Storage::Synchronization::Type::propertyDeclarations, - UnorderedElementsAre( - IsPropertyDeclaration("data", - fetchTypeId(sourceId1, "QQuickObject"), - Storage::PropertyDeclarationTraits::IsList), - IsPropertyDeclaration("children", - fetchTypeId(sourceId1, "QQuickItem"), - Storage::PropertyDeclarationTraits::IsList - | Storage::PropertyDeclarationTraits::IsReadOnly)))))); + ASSERT_THAT(storage.fetchTypes(), + Contains( + AllOf(IsStorageType(sourceId1, + "QQuickItem", + fetchTypeId(sourceId2, "QObject"), + TypeTraitsKind::Reference), + Field(&Storage::Synchronization::Type::propertyDeclarations, + UnorderedElementsAre( + IsPropertyDeclaration("data", + fetchTypeId(sourceId1, "QQuickObject"), + Storage::PropertyDeclarationTraits::IsList), + IsPropertyDeclaration( + "children", + fetchTypeId(sourceId1, "QQuickItem"), + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly)))))); } TEST_F(ProjectStorage, synchronize_types_changes_property_declaration_type) @@ -1963,19 +1967,22 @@ TEST_F(ProjectStorage, synchronize_types_changes_property_declaration_type) storage.synchronize(SynchronizationPackage{importsSourceId1, {package.types[0]}, {sourceId1}}); - ASSERT_THAT( - storage.fetchTypes(), - Contains(AllOf( - IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), TypeTraits::Reference), - Field(&Storage::Synchronization::Type::propertyDeclarations, - UnorderedElementsAre( - IsPropertyDeclaration("data", - fetchTypeId(sourceId1, "QQuickItem"), - Storage::PropertyDeclarationTraits::IsList), - IsPropertyDeclaration("children", - fetchTypeId(sourceId1, "QQuickItem"), - Storage::PropertyDeclarationTraits::IsList - | Storage::PropertyDeclarationTraits::IsReadOnly)))))); + ASSERT_THAT(storage.fetchTypes(), + Contains( + AllOf(IsStorageType(sourceId1, + "QQuickItem", + fetchTypeId(sourceId2, "QObject"), + TypeTraitsKind::Reference), + Field(&Storage::Synchronization::Type::propertyDeclarations, + UnorderedElementsAre( + IsPropertyDeclaration("data", + fetchTypeId(sourceId1, "QQuickItem"), + Storage::PropertyDeclarationTraits::IsList), + IsPropertyDeclaration( + "children", + fetchTypeId(sourceId1, "QQuickItem"), + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly)))))); } TEST_F(ProjectStorage, synchronize_types_changes_declaration_traits) @@ -1989,7 +1996,7 @@ TEST_F(ProjectStorage, synchronize_types_changes_declaration_traits) ASSERT_THAT( storage.fetchTypes(), Contains(AllOf( - IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), TypeTraits::Reference), + IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::propertyDeclarations, UnorderedElementsAre( IsPropertyDeclaration("data", @@ -2014,7 +2021,7 @@ TEST_F(ProjectStorage, synchronize_types_changes_declaration_traits_and_type) ASSERT_THAT( storage.fetchTypes(), Contains(AllOf( - IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), TypeTraits::Reference), + IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::propertyDeclarations, UnorderedElementsAre( IsPropertyDeclaration("data", @@ -2038,7 +2045,7 @@ TEST_F(ProjectStorage, synchronize_types_removes_a_property_declaration) Contains(AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::propertyDeclarations, UnorderedElementsAre(IsPropertyDeclaration( "data", @@ -2061,7 +2068,7 @@ TEST_F(ProjectStorage, synchronize_types_adds_a_property_declaration) ASSERT_THAT( storage.fetchTypes(), Contains(AllOf( - IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), TypeTraits::Reference), + IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::propertyDeclarations, UnorderedElementsAre( IsPropertyDeclaration("object", @@ -2084,19 +2091,22 @@ TEST_F(ProjectStorage, synchronize_types_rename_a_property_declaration) storage.synchronize(SynchronizationPackage{importsSourceId1, {package.types[0]}, {sourceId1}}); - ASSERT_THAT( - storage.fetchTypes(), - Contains(AllOf( - IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), TypeTraits::Reference), - Field(&Storage::Synchronization::Type::propertyDeclarations, - UnorderedElementsAre( - IsPropertyDeclaration("data", - fetchTypeId(sourceId2, "QObject"), - Storage::PropertyDeclarationTraits::IsList), - IsPropertyDeclaration("objects", - fetchTypeId(sourceId1, "QQuickItem"), - Storage::PropertyDeclarationTraits::IsList - | Storage::PropertyDeclarationTraits::IsReadOnly)))))); + ASSERT_THAT(storage.fetchTypes(), + Contains( + AllOf(IsStorageType(sourceId1, + "QQuickItem", + fetchTypeId(sourceId2, "QObject"), + TypeTraitsKind::Reference), + Field(&Storage::Synchronization::Type::propertyDeclarations, + UnorderedElementsAre( + IsPropertyDeclaration("data", + fetchTypeId(sourceId2, "QObject"), + Storage::PropertyDeclarationTraits::IsList), + IsPropertyDeclaration( + "objects", + fetchTypeId(sourceId1, "QQuickItem"), + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly)))))); } TEST_F(ProjectStorage, using_non_existing_property_type_throws) @@ -2154,7 +2164,7 @@ TEST_F(ProjectStorage, synchronize_types_add_function_declarations) AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::functionDeclarations, UnorderedElementsAre(Eq(package.types[0].functionDeclarations[0]), Eq(package.types[0].functionDeclarations[1])))))); @@ -2173,7 +2183,7 @@ TEST_F(ProjectStorage, synchronize_types_changes_function_declaration_return_typ AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::functionDeclarations, UnorderedElementsAre(Eq(package.types[0].functionDeclarations[0]), Eq(package.types[0].functionDeclarations[1])))))); @@ -2192,7 +2202,7 @@ TEST_F(ProjectStorage, synchronize_types_changes_function_declaration_name) AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::functionDeclarations, UnorderedElementsAre(Eq(package.types[0].functionDeclarations[0]), Eq(package.types[0].functionDeclarations[1])))))); @@ -2211,7 +2221,7 @@ TEST_F(ProjectStorage, synchronize_types_changes_function_declaration_pop_parame AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::functionDeclarations, UnorderedElementsAre(Eq(package.types[0].functionDeclarations[0]), Eq(package.types[0].functionDeclarations[1])))))); @@ -2231,7 +2241,7 @@ TEST_F(ProjectStorage, synchronize_types_changes_function_declaration_append_par AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::functionDeclarations, UnorderedElementsAre(Eq(package.types[0].functionDeclarations[0]), Eq(package.types[0].functionDeclarations[1])))))); @@ -2250,7 +2260,7 @@ TEST_F(ProjectStorage, synchronize_types_changes_function_declaration_change_par AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::functionDeclarations, UnorderedElementsAre(Eq(package.types[0].functionDeclarations[0]), Eq(package.types[0].functionDeclarations[1])))))); @@ -2269,7 +2279,7 @@ TEST_F(ProjectStorage, synchronize_types_changes_function_declaration_change_par AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::functionDeclarations, UnorderedElementsAre(Eq(package.types[0].functionDeclarations[0]), Eq(package.types[0].functionDeclarations[1])))))); @@ -2289,7 +2299,7 @@ TEST_F(ProjectStorage, synchronize_types_changes_function_declaration_change_par AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::functionDeclarations, UnorderedElementsAre(Eq(package.types[0].functionDeclarations[0]), Eq(package.types[0].functionDeclarations[1])))))); @@ -2308,7 +2318,7 @@ TEST_F(ProjectStorage, synchronize_types_removes_function_declaration) AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::functionDeclarations, UnorderedElementsAre(Eq(package.types[0].functionDeclarations[0])))))); } @@ -2327,7 +2337,7 @@ TEST_F(ProjectStorage, synchronize_types_add_function_declaration) AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::functionDeclarations, UnorderedElementsAre(Eq(package.types[0].functionDeclarations[0]), Eq(package.types[0].functionDeclarations[1]), @@ -2347,7 +2357,7 @@ TEST_F(ProjectStorage, synchronize_types_add_function_declarations_with_overload AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::functionDeclarations, UnorderedElementsAre(Eq(package.types[0].functionDeclarations[0]), Eq(package.types[0].functionDeclarations[1]), @@ -2368,7 +2378,7 @@ TEST_F(ProjectStorage, synchronize_types_function_declarations_adding_overload) AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::functionDeclarations, UnorderedElementsAre(Eq(package.types[0].functionDeclarations[0]), Eq(package.types[0].functionDeclarations[1]), @@ -2390,7 +2400,7 @@ TEST_F(ProjectStorage, synchronize_types_function_declarations_removing_overload AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::functionDeclarations, UnorderedElementsAre(Eq(package.types[0].functionDeclarations[0]), Eq(package.types[0].functionDeclarations[1])))))); @@ -2407,7 +2417,7 @@ TEST_F(ProjectStorage, synchronize_types_add_signal_declarations) AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::signalDeclarations, UnorderedElementsAre(Eq(package.types[0].signalDeclarations[0]), Eq(package.types[0].signalDeclarations[1])))))); @@ -2426,7 +2436,7 @@ TEST_F(ProjectStorage, synchronize_types_changes_signal_declaration_name) AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::signalDeclarations, UnorderedElementsAre(Eq(package.types[0].signalDeclarations[0]), Eq(package.types[0].signalDeclarations[1])))))); @@ -2445,7 +2455,7 @@ TEST_F(ProjectStorage, synchronize_types_changes_signal_declaration_pop_paramete AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::signalDeclarations, UnorderedElementsAre(Eq(package.types[0].signalDeclarations[0]), Eq(package.types[0].signalDeclarations[1])))))); @@ -2465,7 +2475,7 @@ TEST_F(ProjectStorage, synchronize_types_changes_signal_declaration_append_param AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::signalDeclarations, UnorderedElementsAre(Eq(package.types[0].signalDeclarations[0]), Eq(package.types[0].signalDeclarations[1])))))); @@ -2484,7 +2494,7 @@ TEST_F(ProjectStorage, synchronize_types_changes_signal_declaration_change_param AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::signalDeclarations, UnorderedElementsAre(Eq(package.types[0].signalDeclarations[0]), Eq(package.types[0].signalDeclarations[1])))))); @@ -2503,7 +2513,7 @@ TEST_F(ProjectStorage, synchronize_types_changes_signal_declaration_change_param AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::signalDeclarations, UnorderedElementsAre(Eq(package.types[0].signalDeclarations[0]), Eq(package.types[0].signalDeclarations[1])))))); @@ -2522,7 +2532,7 @@ TEST_F(ProjectStorage, synchronize_types_changes_signal_declaration_change_param AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::signalDeclarations, UnorderedElementsAre(Eq(package.types[0].signalDeclarations[0]), Eq(package.types[0].signalDeclarations[1])))))); @@ -2541,7 +2551,7 @@ TEST_F(ProjectStorage, synchronize_types_removes_signal_declaration) AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::signalDeclarations, UnorderedElementsAre(Eq(package.types[0].signalDeclarations[0])))))); } @@ -2560,7 +2570,7 @@ TEST_F(ProjectStorage, synchronize_types_add_signal_declaration) AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::signalDeclarations, UnorderedElementsAre(Eq(package.types[0].signalDeclarations[0]), Eq(package.types[0].signalDeclarations[1]), @@ -2580,7 +2590,7 @@ TEST_F(ProjectStorage, synchronize_types_add_signal_declarations_with_overloads) AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::signalDeclarations, UnorderedElementsAre(Eq(package.types[0].signalDeclarations[0]), Eq(package.types[0].signalDeclarations[1]), @@ -2601,7 +2611,7 @@ TEST_F(ProjectStorage, synchronize_types_signal_declarations_adding_overload) AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::signalDeclarations, UnorderedElementsAre(Eq(package.types[0].signalDeclarations[0]), Eq(package.types[0].signalDeclarations[1]), @@ -2623,7 +2633,7 @@ TEST_F(ProjectStorage, synchronize_types_signal_declarations_removing_overload) AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::signalDeclarations, UnorderedElementsAre(Eq(package.types[0].signalDeclarations[0]), Eq(package.types[0].signalDeclarations[1])))))); @@ -2640,7 +2650,7 @@ TEST_F(ProjectStorage, synchronize_types_add_enumeration_declarations) IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::enumerationDeclarations, UnorderedElementsAre(Eq(package.types[0].enumerationDeclarations[0]), Eq(package.types[0].enumerationDeclarations[1])))))); @@ -2659,7 +2669,7 @@ TEST_F(ProjectStorage, synchronize_types_changes_enumeration_declaration_name) IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::enumerationDeclarations, UnorderedElementsAre(Eq(package.types[0].enumerationDeclarations[0]), Eq(package.types[0].enumerationDeclarations[1])))))); @@ -2678,7 +2688,7 @@ TEST_F(ProjectStorage, synchronize_types_changes_enumeration_declaration_pop_enu IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::enumerationDeclarations, UnorderedElementsAre(Eq(package.types[0].enumerationDeclarations[0]), Eq(package.types[0].enumerationDeclarations[1])))))); @@ -2698,13 +2708,14 @@ TEST_F(ProjectStorage, synchronize_types_changes_enumeration_declaration_append_ IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::enumerationDeclarations, UnorderedElementsAre(Eq(package.types[0].enumerationDeclarations[0]), Eq(package.types[0].enumerationDeclarations[1])))))); } -TEST_F(ProjectStorage, synchronize_types_changes_enumeration_declaration_change_enumerator_declaration_name) +TEST_F(ProjectStorage, + synchronize_types_changes_enumeration_declaration_change_enumerator_declaration_name) { auto package{createSimpleSynchronizationPackage()}; storage.synchronize(package); @@ -2717,13 +2728,14 @@ TEST_F(ProjectStorage, synchronize_types_changes_enumeration_declaration_change_ IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::enumerationDeclarations, UnorderedElementsAre(Eq(package.types[0].enumerationDeclarations[0]), Eq(package.types[0].enumerationDeclarations[1])))))); } -TEST_F(ProjectStorage, synchronize_types_changes_enumeration_declaration_change_enumerator_declaration_value) +TEST_F(ProjectStorage, + synchronize_types_changes_enumeration_declaration_change_enumerator_declaration_value) { auto package{createSimpleSynchronizationPackage()}; storage.synchronize(package); @@ -2736,7 +2748,7 @@ TEST_F(ProjectStorage, synchronize_types_changes_enumeration_declaration_change_ IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::enumerationDeclarations, UnorderedElementsAre(Eq(package.types[0].enumerationDeclarations[0]), Eq(package.types[0].enumerationDeclarations[1])))))); @@ -2757,7 +2769,7 @@ TEST_F(ProjectStorage, IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::enumerationDeclarations, UnorderedElementsAre(Eq(package.types[0].enumerationDeclarations[0]), Eq(package.types[0].enumerationDeclarations[1])))))); @@ -2777,7 +2789,7 @@ TEST_F(ProjectStorage, IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::enumerationDeclarations, UnorderedElementsAre(Eq(package.types[0].enumerationDeclarations[0]), Eq(package.types[0].enumerationDeclarations[1])))))); @@ -2795,7 +2807,7 @@ TEST_F(ProjectStorage, synchronize_types_removes_enumeration_declaration) Contains(AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::enumerationDeclarations, UnorderedElementsAre( Eq(package.types[0].enumerationDeclarations[0])))))); @@ -2815,7 +2827,7 @@ TEST_F(ProjectStorage, synchronize_types_add_enumeration_declaration) IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::enumerationDeclarations, UnorderedElementsAre(Eq(package.types[0].enumerationDeclarations[0]), Eq(package.types[0].enumerationDeclarations[1]), @@ -2895,7 +2907,7 @@ TEST_F(ProjectStorage, synchronize_types_add_alias_declarations) IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::propertyDeclarations, UnorderedElementsAre( IsPropertyDeclaration("items", @@ -2922,7 +2934,7 @@ TEST_F(ProjectStorage, synchronize_types_add_alias_declarations_again) IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::propertyDeclarations, UnorderedElementsAre( IsPropertyDeclaration("items", @@ -2950,7 +2962,7 @@ TEST_F(ProjectStorage, synchronize_types_remove_alias_declarations) IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::propertyDeclarations, UnorderedElementsAre( IsPropertyDeclaration("items", @@ -2975,6 +2987,7 @@ TEST_F(ProjectStorage, synchronize_types_add_alias_declarations_throws_for_wrong {sourceId4}}), QmlDesigner::TypeNameDoesNotExists); } + TEST_F(ProjectStorage, synchronize_types_add_alias_declarations_throws_for_wrong_property_name) { auto package{createSynchronizationPackageWithAliases()}; @@ -3003,7 +3016,7 @@ TEST_F(ProjectStorage, synchronize_types_change_alias_declarations_type_name) IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::propertyDeclarations, UnorderedElementsAre( IsPropertyDeclaration("items", @@ -3028,21 +3041,24 @@ TEST_F(ProjectStorage, synchronize_types_change_alias_declarations_property_name ASSERT_THAT( storage.fetchTypes(), - Contains(AllOf( - IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), TypeTraits::Reference), - Field(&Storage::Synchronization::Type::propertyDeclarations, - UnorderedElementsAre( - IsPropertyDeclaration("items", - fetchTypeId(sourceId1, "QQuickItem"), - Storage::PropertyDeclarationTraits::IsList - | Storage::PropertyDeclarationTraits::IsReadOnly), - IsPropertyDeclaration("objects", - fetchTypeId(sourceId1, "QQuickItem"), - Storage::PropertyDeclarationTraits::IsList - | Storage::PropertyDeclarationTraits::IsReadOnly), - IsPropertyDeclaration("data", - fetchTypeId(sourceId2, "QObject"), - Storage::PropertyDeclarationTraits::IsList)))))); + Contains( + AllOf(IsStorageType(sourceId3, + "QAliasItem", + fetchTypeId(sourceId1, "QQuickItem"), + TypeTraitsKind::Reference), + Field(&Storage::Synchronization::Type::propertyDeclarations, + UnorderedElementsAre( + IsPropertyDeclaration("items", + fetchTypeId(sourceId1, "QQuickItem"), + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly), + IsPropertyDeclaration("objects", + fetchTypeId(sourceId1, "QQuickItem"), + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly), + IsPropertyDeclaration("data", + fetchTypeId(sourceId2, "QObject"), + Storage::PropertyDeclarationTraits::IsList)))))); } TEST_F(ProjectStorage, synchronize_types_change_alias_declarations_to_property_declaration) @@ -3059,21 +3075,24 @@ TEST_F(ProjectStorage, synchronize_types_change_alias_declarations_to_property_d ASSERT_THAT( storage.fetchTypes(), - Contains(AllOf( - IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), TypeTraits::Reference), - Field(&Storage::Synchronization::Type::propertyDeclarations, - UnorderedElementsAre( - IsPropertyDeclaration("items", - fetchTypeId(sourceId1, "QQuickItem"), - Storage::PropertyDeclarationTraits::IsList - | Storage::PropertyDeclarationTraits::IsReadOnly), - IsPropertyDeclaration("objects", - fetchTypeId(sourceId1, "QQuickItem"), - Storage::PropertyDeclarationTraits::IsList - | Storage::PropertyDeclarationTraits::IsReadOnly), - IsPropertyDeclaration("data", - fetchTypeId(sourceId2, "QObject"), - Storage::PropertyDeclarationTraits::IsList)))))); + Contains( + AllOf(IsStorageType(sourceId3, + "QAliasItem", + fetchTypeId(sourceId1, "QQuickItem"), + TypeTraitsKind::Reference), + Field(&Storage::Synchronization::Type::propertyDeclarations, + UnorderedElementsAre( + IsPropertyDeclaration("items", + fetchTypeId(sourceId1, "QQuickItem"), + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly), + IsPropertyDeclaration("objects", + fetchTypeId(sourceId1, "QQuickItem"), + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly), + IsPropertyDeclaration("data", + fetchTypeId(sourceId2, "QObject"), + Storage::PropertyDeclarationTraits::IsList)))))); } TEST_F(ProjectStorage, synchronize_types_change_property_declarations_to_alias_declaration) @@ -3094,7 +3113,7 @@ TEST_F(ProjectStorage, synchronize_types_change_property_declarations_to_alias_d IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::propertyDeclarations, UnorderedElementsAre( IsPropertyDeclaration("items", @@ -3120,21 +3139,24 @@ TEST_F(ProjectStorage, synchronize_types_change_alias_target_property_declaratio ASSERT_THAT( storage.fetchTypes(), - Contains(AllOf( - IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), TypeTraits::Reference), - Field(&Storage::Synchronization::Type::propertyDeclarations, - UnorderedElementsAre( - IsPropertyDeclaration("items", - fetchTypeId(sourceId1, "QQuickItem"), - Storage::PropertyDeclarationTraits::IsList - | Storage::PropertyDeclarationTraits::IsReadOnly), - IsPropertyDeclaration("objects", - fetchTypeId(sourceId2, "QObject"), - Storage::PropertyDeclarationTraits::IsList - | Storage::PropertyDeclarationTraits::IsReadOnly), - IsPropertyDeclaration("data", - fetchTypeId(sourceId2, "QObject"), - Storage::PropertyDeclarationTraits::IsList)))))); + Contains( + AllOf(IsStorageType(sourceId3, + "QAliasItem", + fetchTypeId(sourceId1, "QQuickItem"), + TypeTraitsKind::Reference), + Field(&Storage::Synchronization::Type::propertyDeclarations, + UnorderedElementsAre( + IsPropertyDeclaration("items", + fetchTypeId(sourceId1, "QQuickItem"), + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly), + IsPropertyDeclaration("objects", + fetchTypeId(sourceId2, "QObject"), + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly), + IsPropertyDeclaration("data", + fetchTypeId(sourceId2, "QObject"), + Storage::PropertyDeclarationTraits::IsList)))))); } TEST_F(ProjectStorage, synchronize_types_change_alias_target_property_declaration_type_name) @@ -3152,7 +3174,7 @@ TEST_F(ProjectStorage, synchronize_types_change_alias_target_property_declaratio IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::propertyDeclarations, UnorderedElementsAre( IsPropertyDeclaration("items", @@ -3194,7 +3216,7 @@ TEST_F(ProjectStorage, synchronize_types_remove_property_declaration_and_alias) IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::propertyDeclarations, UnorderedElementsAre( IsPropertyDeclaration("items", @@ -3236,7 +3258,7 @@ TEST_F(ProjectStorage, synchronize_types_remove_type_and_alias_property_declarat IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::propertyDeclarations, UnorderedElementsAre( IsPropertyDeclaration("items", @@ -3261,21 +3283,24 @@ TEST_F(ProjectStorage, update_alias_property_if_property_is_overloaded) ASSERT_THAT( storage.fetchTypes(), - Contains(AllOf( - IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), TypeTraits::Reference), - Field(&Storage::Synchronization::Type::propertyDeclarations, - UnorderedElementsAre( - IsPropertyDeclaration("items", - fetchTypeId(sourceId1, "QQuickItem"), - Storage::PropertyDeclarationTraits::IsList - | Storage::PropertyDeclarationTraits::IsReadOnly), - IsPropertyDeclaration("objects", - fetchTypeId(sourceId1, "QQuickItem"), - Storage::PropertyDeclarationTraits::IsList - | Storage::PropertyDeclarationTraits::IsReadOnly), - IsPropertyDeclaration("data", - fetchTypeId(sourceId2, "QObject"), - Storage::PropertyDeclarationTraits::IsList)))))); + Contains( + AllOf(IsStorageType(sourceId3, + "QAliasItem", + fetchTypeId(sourceId1, "QQuickItem"), + TypeTraitsKind::Reference), + Field(&Storage::Synchronization::Type::propertyDeclarations, + UnorderedElementsAre( + IsPropertyDeclaration("items", + fetchTypeId(sourceId1, "QQuickItem"), + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly), + IsPropertyDeclaration("objects", + fetchTypeId(sourceId1, "QQuickItem"), + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly), + IsPropertyDeclaration("data", + fetchTypeId(sourceId2, "QObject"), + Storage::PropertyDeclarationTraits::IsList)))))); } TEST_F(ProjectStorage, alias_property_is_overloaded) @@ -3290,21 +3315,24 @@ TEST_F(ProjectStorage, alias_property_is_overloaded) ASSERT_THAT( storage.fetchTypes(), - Contains(AllOf( - IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), TypeTraits::Reference), - Field(&Storage::Synchronization::Type::propertyDeclarations, - UnorderedElementsAre( - IsPropertyDeclaration("items", - fetchTypeId(sourceId1, "QQuickItem"), - Storage::PropertyDeclarationTraits::IsList - | Storage::PropertyDeclarationTraits::IsReadOnly), - IsPropertyDeclaration("objects", - fetchTypeId(sourceId1, "QQuickItem"), - Storage::PropertyDeclarationTraits::IsList - | Storage::PropertyDeclarationTraits::IsReadOnly), - IsPropertyDeclaration("data", - fetchTypeId(sourceId2, "QObject"), - Storage::PropertyDeclarationTraits::IsList)))))); + Contains( + AllOf(IsStorageType(sourceId3, + "QAliasItem", + fetchTypeId(sourceId1, "QQuickItem"), + TypeTraitsKind::Reference), + Field(&Storage::Synchronization::Type::propertyDeclarations, + UnorderedElementsAre( + IsPropertyDeclaration("items", + fetchTypeId(sourceId1, "QQuickItem"), + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly), + IsPropertyDeclaration("objects", + fetchTypeId(sourceId1, "QQuickItem"), + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly), + IsPropertyDeclaration("data", + fetchTypeId(sourceId2, "QObject"), + Storage::PropertyDeclarationTraits::IsList)))))); } TEST_F(ProjectStorage, update_alias_property_if_overloaded_property_is_removed) @@ -3324,7 +3352,7 @@ TEST_F(ProjectStorage, update_alias_property_if_overloaded_property_is_removed) IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::propertyDeclarations, UnorderedElementsAre( IsPropertyDeclaration("items", @@ -3356,7 +3384,7 @@ TEST_F(ProjectStorage, relink_alias_property) IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::propertyDeclarations, UnorderedElementsAre( IsPropertyDeclaration("items", @@ -3397,7 +3425,7 @@ TEST_F(ProjectStorage, "QObject2", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId5, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Object2"}, Storage::Synchronization::ExportedType{qtQuickModuleId, "Obj2"}}}); @@ -3410,7 +3438,7 @@ TEST_F(ProjectStorage, IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::propertyDeclarations, UnorderedElementsAre( IsPropertyDeclaration("items", @@ -3435,22 +3463,24 @@ TEST_F(ProjectStorage, relink_alias_property_react_to_type_name_change) storage.synchronize(SynchronizationPackage{importsSourceId1, {package.types[0]}, {sourceId1}}); - ASSERT_THAT( - storage.fetchTypes(), - Contains(AllOf( - IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId2, "QObject"), TypeTraits::Reference), - Field(&Storage::Synchronization::Type::propertyDeclarations, - UnorderedElementsAre( - IsPropertyDeclaration("items", - fetchTypeId(sourceId1, "QQuickItem2"), - Storage::PropertyDeclarationTraits::IsList - | Storage::PropertyDeclarationTraits::IsReadOnly), - IsPropertyDeclaration("objects", - fetchTypeId(sourceId2, "QObject"), - Storage::PropertyDeclarationTraits::IsList), - IsPropertyDeclaration("data", - fetchTypeId(sourceId2, "QObject"), - Storage::PropertyDeclarationTraits::IsList)))))); + ASSERT_THAT(storage.fetchTypes(), + Contains(AllOf( + IsStorageType(sourceId3, + "QAliasItem", + fetchTypeId(sourceId2, "QObject"), + TypeTraitsKind::Reference), + Field(&Storage::Synchronization::Type::propertyDeclarations, + UnorderedElementsAre( + IsPropertyDeclaration("items", + fetchTypeId(sourceId1, "QQuickItem2"), + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly), + IsPropertyDeclaration("objects", + fetchTypeId(sourceId2, "QObject"), + Storage::PropertyDeclarationTraits::IsList), + IsPropertyDeclaration("data", + fetchTypeId(sourceId2, "QObject"), + Storage::PropertyDeclarationTraits::IsList)))))); } TEST_F(ProjectStorage, do_not_relink_alias_property_for_deleted_type) @@ -3470,7 +3500,7 @@ TEST_F(ProjectStorage, do_not_relink_alias_property_for_deleted_type) Not(Contains(IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), - TypeTraits::Reference)))); + TypeTraitsKind::Reference)))); } TEST_F(ProjectStorage, do_not_relink_alias_property_for_deleted_type_and_property_type) @@ -3516,7 +3546,7 @@ TEST_F(ProjectStorage, do_not_relink_alias_property_for_deleted_type_and_propert Not(Contains(IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), - TypeTraits::Reference)))); + TypeTraitsKind::Reference)))); } TEST_F(ProjectStorage, do_not_relink_property_type_does_not_exists) @@ -3557,7 +3587,7 @@ TEST_F(ProjectStorage, change_prototype_type_name) "QQuickItem", fetchTypeId(sourceId2, "QObject3"), TypeId{}, - TypeTraits::Reference))); + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, change_extension_type_name) @@ -3574,7 +3604,7 @@ TEST_F(ProjectStorage, change_extension_type_name) "QQuickItem", TypeId{}, fetchTypeId(sourceId2, "QObject3"), - TypeTraits::Reference))); + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, change_prototype_type_module_id) @@ -3590,7 +3620,7 @@ TEST_F(ProjectStorage, change_prototype_type_module_id) "QQuickItem", fetchTypeId(sourceId2, "QObject"), TypeId{}, - TypeTraits::Reference))); + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, change_extension_type_module_id) @@ -3607,7 +3637,7 @@ TEST_F(ProjectStorage, change_extension_type_module_id) "QQuickItem", TypeId{}, fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference))); + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, change_qualified_prototype_type_module_id_throws) @@ -3656,7 +3686,7 @@ TEST_F(ProjectStorage, change_qualified_prototype_type_module_id) "QQuickItem", fetchTypeId(sourceId2, "QObject"), TypeId{}, - TypeTraits::Reference))); + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, change_qualified_extension_type_module_id) @@ -3679,7 +3709,7 @@ TEST_F(ProjectStorage, change_qualified_extension_type_module_id) "QQuickItem", TypeId{}, fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference))); + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, change_prototype_type_name_and_module_id) @@ -3702,7 +3732,7 @@ TEST_F(ProjectStorage, change_prototype_type_name_and_module_id) "QQuickItem", fetchTypeId(sourceId2, "QObject3"), TypeId{}, - TypeTraits::Reference))); + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, change_extension_type_name_and_module_id) @@ -3726,7 +3756,7 @@ TEST_F(ProjectStorage, change_extension_type_name_and_module_id) "QQuickItem", TypeId{}, fetchTypeId(sourceId2, "QObject3"), - TypeTraits::Reference))); + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, change_prototype_type_name_throws_for_wrong_native_prototupe_type_name) @@ -3766,7 +3796,7 @@ TEST_F(ProjectStorage, throw_for_prototype_chain_cycles) "QObject2", Storage::Synchronization::ImportedType{"Item"}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId3, {Storage::Synchronization::ExportedType{pathToModuleId, "Object2"}, Storage::Synchronization::ExportedType{pathToModuleId, "Obj2"}}}); @@ -3791,7 +3821,7 @@ TEST_F(ProjectStorage, throw_for_extension_chain_cycles) "QObject2", Storage::Synchronization::ImportedType{"Item"}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId3, {Storage::Synchronization::ExportedType{pathToModuleId, "Object2"}, Storage::Synchronization::ExportedType{pathToModuleId, "Obj2"}}}); @@ -3861,7 +3891,7 @@ TEST_F(ProjectStorage, recursive_aliases) Contains(AllOf(IsStorageType(sourceId5, "QAliasItem2", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::propertyDeclarations, ElementsAre(IsPropertyDeclaration( "objects", @@ -3883,7 +3913,7 @@ TEST_F(ProjectStorage, recursive_aliases_change_property_type) Contains(AllOf(IsStorageType(sourceId5, "QAliasItem2", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::propertyDeclarations, ElementsAre(IsPropertyDeclaration( "objects", @@ -3906,7 +3936,7 @@ TEST_F(ProjectStorage, update_aliases_after_injecting_property) Contains(AllOf(IsStorageType(sourceId5, "QAliasItem2", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::propertyDeclarations, ElementsAre(IsPropertyDeclaration( "objects", @@ -3931,7 +3961,7 @@ TEST_F(ProjectStorage, update_aliases_after_change_alias_to_property) AllOf(Contains(AllOf(IsStorageType(sourceId5, "QAliasItem2", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::propertyDeclarations, ElementsAre(IsPropertyDeclaration( "objects", @@ -3942,7 +3972,7 @@ TEST_F(ProjectStorage, update_aliases_after_change_alias_to_property) Contains(AllOf(IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::propertyDeclarations, ElementsAre(IsPropertyDeclaration( "objects", @@ -3969,7 +3999,7 @@ TEST_F(ProjectStorage, update_aliases_after_change_property_to_alias) Contains(AllOf(IsStorageType(sourceId5, "QAliasItem2", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::propertyDeclarations, ElementsAre(IsPropertyDeclaration( "objects", @@ -4013,7 +4043,7 @@ TEST_F(ProjectStorage, qualified_prototype) Storage::Synchronization::Type{"QQuickObject", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId3, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Object"}}}); @@ -4027,7 +4057,7 @@ TEST_F(ProjectStorage, qualified_prototype) "QQuickItem", fetchTypeId(sourceId2, "QObject"), TypeId{}, - TypeTraits::Reference))); + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, qualified_extension) @@ -4040,7 +4070,7 @@ TEST_F(ProjectStorage, qualified_extension) Storage::Synchronization::Type{"QQuickObject", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId3, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Object"}}}); @@ -4054,7 +4084,7 @@ TEST_F(ProjectStorage, qualified_extension) "QQuickItem", TypeId{}, fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference))); + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, qualified_prototype_upper_down_the_module_chain_throws) @@ -4085,7 +4115,7 @@ TEST_F(ProjectStorage, qualified_prototype_upper_in_the_module_chain) Storage::Synchronization::Type{"QQuickObject", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId3, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Object"}}}); @@ -4099,7 +4129,7 @@ TEST_F(ProjectStorage, qualified_prototype_upper_in_the_module_chain) "QQuickItem", fetchTypeId(sourceId3, "QQuickObject"), TypeId{}, - TypeTraits::Reference))); + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, qualified_extension_upper_in_the_module_chain) @@ -4112,7 +4142,7 @@ TEST_F(ProjectStorage, qualified_extension_upper_in_the_module_chain) Storage::Synchronization::Type{"QQuickObject", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId3, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Object"}}}); @@ -4126,7 +4156,7 @@ TEST_F(ProjectStorage, qualified_extension_upper_in_the_module_chain) "QQuickItem", TypeId{}, fetchTypeId(sourceId3, "QQuickObject"), - TypeTraits::Reference))); + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, qualified_prototype_with_wrong_version_throws) @@ -4138,7 +4168,7 @@ TEST_F(ProjectStorage, qualified_prototype_with_wrong_version_throws) Storage::Synchronization::Type{"QQuickObject", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId3, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Object"}}}); @@ -4158,7 +4188,7 @@ TEST_F(ProjectStorage, qualified_extension_with_wrong_version_throws) Storage::Synchronization::Type{"QQuickObject", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId3, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Object"}}}); @@ -4178,7 +4208,7 @@ TEST_F(ProjectStorage, qualified_prototype_with_version) Storage::Synchronization::Type{"QQuickObject", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId3, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Object"}}}); @@ -4192,7 +4222,7 @@ TEST_F(ProjectStorage, qualified_prototype_with_version) "QQuickItem", fetchTypeId(sourceId2, "QObject"), TypeId{}, - TypeTraits::Reference))); + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, qualified_extension_with_version) @@ -4206,7 +4236,7 @@ TEST_F(ProjectStorage, qualified_extension_with_version) Storage::Synchronization::Type{"QQuickObject", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId3, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Object"}}}); @@ -4220,7 +4250,7 @@ TEST_F(ProjectStorage, qualified_extension_with_version) "QQuickItem", TypeId{}, fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference))); + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, qualified_prototype_with_version_in_the_proto_type_chain) @@ -4234,11 +4264,9 @@ TEST_F(ProjectStorage, qualified_prototype_with_version_in_the_proto_type_chain) "QQuickObject", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId3, - {Storage::Synchronization::ExportedType{qtQuickModuleId, - "Object", - Storage::Version{2}}}}); + {Storage::Synchronization::ExportedType{qtQuickModuleId, "Object", Storage::Version{2}}}}); package.imports.emplace_back(qtQuickModuleId, Storage::Version{}, sourceId3); package.updatedSourceIds.push_back(sourceId3); @@ -4249,7 +4277,7 @@ TEST_F(ProjectStorage, qualified_prototype_with_version_in_the_proto_type_chain) "QQuickItem", fetchTypeId(sourceId3, "QQuickObject"), TypeId{}, - TypeTraits::Reference))); + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, qualified_extension_with_version_in_the_proto_type_chain) @@ -4264,11 +4292,9 @@ TEST_F(ProjectStorage, qualified_extension_with_version_in_the_proto_type_chain) "QQuickObject", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId3, - {Storage::Synchronization::ExportedType{qtQuickModuleId, - "Object", - Storage::Version{2}}}}); + {Storage::Synchronization::ExportedType{qtQuickModuleId, "Object", Storage::Version{2}}}}); package.imports.emplace_back(qtQuickModuleId, Storage::Version{}, sourceId3); package.updatedSourceIds.push_back(sourceId3); @@ -4279,7 +4305,7 @@ TEST_F(ProjectStorage, qualified_extension_with_version_in_the_proto_type_chain) "QQuickItem", TypeId{}, fetchTypeId(sourceId3, "QQuickObject"), - TypeTraits::Reference))); + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, qualified_prototype_with_version_down_the_proto_type_chain_throws) @@ -4310,7 +4336,7 @@ TEST_F(ProjectStorage, qualified_property_declaration_type_name) Storage::Synchronization::Type{"QQuickObject", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId3, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Object"}}}); @@ -4345,7 +4371,7 @@ TEST_F(ProjectStorage, qualified_property_declaration_type_name_in_the_module_ch Storage::Synchronization::Type{"QQuickObject", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId3, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Object"}}}); @@ -4405,14 +4431,16 @@ TEST_F(ProjectStorage, change_property_type_module_id_with_qualified_type) storage.synchronize(package); - ASSERT_THAT( - storage.fetchTypes(), - Contains(AllOf( - IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), TypeTraits::Reference), - Field(&Storage::Synchronization::Type::propertyDeclarations, - Contains(IsPropertyDeclaration("data", - fetchTypeId(sourceId2, "QObject"), - Storage::PropertyDeclarationTraits::IsList)))))); + ASSERT_THAT(storage.fetchTypes(), + Contains(AllOf(IsStorageType(sourceId1, + "QQuickItem", + fetchTypeId(sourceId2, "QObject"), + TypeTraitsKind::Reference), + Field(&Storage::Synchronization::Type::propertyDeclarations, + Contains(IsPropertyDeclaration( + "data", + fetchTypeId(sourceId2, "QObject"), + Storage::PropertyDeclarationTraits::IsList)))))); } TEST_F(ProjectStorage, add_file_statuses) @@ -4504,7 +4532,7 @@ TEST_F(ProjectStorage, synchronize_types_without_type_name) Contains(AllOf(IsStorageType(sourceId4, "QObject2", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType("Object2"), IsExportedType("Obj2")))))); @@ -4514,22 +4542,23 @@ TEST_F(ProjectStorage, fetch_by_major_version_for_imported_type) { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); - Storage::Synchronization::Type type{ - "Item", - Storage::Synchronization::ImportedType{"Object"}, - Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, - sourceId2, - {Storage::Synchronization::ExportedType{qtQuickModuleId, - "Item", - Storage::Version{}}}}; + Storage::Synchronization::Type type{"Item", + Storage::Synchronization::ImportedType{"Object"}, + Storage::Synchronization::ImportedType{}, + TypeTraitsKind::Reference, + sourceId2, + {Storage::Synchronization::ExportedType{qtQuickModuleId, + "Item", + Storage::Version{}}}}; Storage::Import import{qmlModuleId, Storage::Version{1}, sourceId2}; storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); ASSERT_THAT(storage.fetchTypes(), - Contains(IsStorageType( - sourceId2, "Item", fetchTypeId(sourceId1, "QObject"), TypeTraits::Reference))); + Contains(IsStorageType(sourceId2, + "Item", + fetchTypeId(sourceId1, "QObject"), + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, fetch_by_major_version_for_qualified_imported_type) @@ -4541,39 +4570,40 @@ TEST_F(ProjectStorage, fetch_by_major_version_for_qualified_imported_type) "Item", Storage::Synchronization::QualifiedImportedType{"Object", import}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId2, - {Storage::Synchronization::ExportedType{qtQuickModuleId, - "Item", - Storage::Version{}}}}; + {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{}}}}; storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); ASSERT_THAT(storage.fetchTypes(), - Contains(IsStorageType( - sourceId2, "Item", fetchTypeId(sourceId1, "QObject"), TypeTraits::Reference))); + Contains(IsStorageType(sourceId2, + "Item", + fetchTypeId(sourceId1, "QObject"), + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, fetch_by_major_version_and_minor_version_for_imported_type) { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); - Storage::Synchronization::Type type{ - "Item", - Storage::Synchronization::ImportedType{"Obj"}, - Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, - sourceId2, - {Storage::Synchronization::ExportedType{qtQuickModuleId, - "Item", - Storage::Version{}}}}; + Storage::Synchronization::Type type{"Item", + Storage::Synchronization::ImportedType{"Obj"}, + Storage::Synchronization::ImportedType{}, + TypeTraitsKind::Reference, + sourceId2, + {Storage::Synchronization::ExportedType{qtQuickModuleId, + "Item", + Storage::Version{}}}}; Storage::Import import{qmlModuleId, Storage::Version{1, 2}, sourceId2}; storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); ASSERT_THAT(storage.fetchTypes(), - Contains(IsStorageType( - sourceId2, "Item", fetchTypeId(sourceId1, "QObject"), TypeTraits::Reference))); + Contains(IsStorageType(sourceId2, + "Item", + fetchTypeId(sourceId1, "QObject"), + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, fetch_by_major_version_and_minor_version_for_qualified_imported_type) @@ -4585,17 +4615,17 @@ TEST_F(ProjectStorage, fetch_by_major_version_and_minor_version_for_qualified_im "Item", Storage::Synchronization::QualifiedImportedType{"Obj", import}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId2, - {Storage::Synchronization::ExportedType{qtQuickModuleId, - "Item", - Storage::Version{}}}}; + {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{}}}}; storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); ASSERT_THAT(storage.fetchTypes(), - Contains(IsStorageType( - sourceId2, "Item", fetchTypeId(sourceId1, "QObject"), TypeTraits::Reference))); + Contains(IsStorageType(sourceId2, + "Item", + fetchTypeId(sourceId1, "QObject"), + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, @@ -4603,15 +4633,14 @@ TEST_F(ProjectStorage, { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); - Storage::Synchronization::Type type{ - "Item", - Storage::Synchronization::ImportedType{"Object"}, - Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, - sourceId2, - {Storage::Synchronization::ExportedType{qtQuickModuleId, - "Item", - Storage::Version{}}}}; + Storage::Synchronization::Type type{"Item", + Storage::Synchronization::ImportedType{"Object"}, + Storage::Synchronization::ImportedType{}, + TypeTraitsKind::Reference, + sourceId2, + {Storage::Synchronization::ExportedType{qtQuickModuleId, + "Item", + Storage::Version{}}}}; Storage::Import import{qmlModuleId, Storage::Version{1, 1}, sourceId2}; ASSERT_THROW(storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}), @@ -4628,11 +4657,9 @@ TEST_F(ProjectStorage, "Item", Storage::Synchronization::QualifiedImportedType{"Object", import}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId2, - {Storage::Synchronization::ExportedType{qtQuickModuleId, - "Item", - Storage::Version{}}}}; + {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{}}}}; ASSERT_THROW(storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}), QmlDesigner::TypeNameDoesNotExists); @@ -4642,15 +4669,14 @@ TEST_F(ProjectStorage, fetch_low_minor_version_for_imported_type_throws) { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); - Storage::Synchronization::Type type{ - "Item", - Storage::Synchronization::ImportedType{"Obj"}, - Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, - sourceId2, - {Storage::Synchronization::ExportedType{qtQuickModuleId, - "Item", - Storage::Version{}}}}; + Storage::Synchronization::Type type{"Item", + Storage::Synchronization::ImportedType{"Obj"}, + Storage::Synchronization::ImportedType{}, + TypeTraitsKind::Reference, + sourceId2, + {Storage::Synchronization::ExportedType{qtQuickModuleId, + "Item", + Storage::Version{}}}}; Storage::Import import{qmlModuleId, Storage::Version{1, 1}, sourceId2}; ASSERT_THROW(storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}), @@ -4666,11 +4692,9 @@ TEST_F(ProjectStorage, fetch_low_minor_version_for_qualified_imported_type_throw "Item", Storage::Synchronization::QualifiedImportedType{"Obj", import}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId2, - {Storage::Synchronization::ExportedType{qtQuickModuleId, - "Item", - Storage::Version{}}}}; + {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{}}}}; ASSERT_THROW(storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}), QmlDesigner::TypeNameDoesNotExists); @@ -4680,22 +4704,23 @@ TEST_F(ProjectStorage, fetch_higher_minor_version_for_imported_type) { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); - Storage::Synchronization::Type type{ - "Item", - Storage::Synchronization::ImportedType{"Obj"}, - Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, - sourceId2, - {Storage::Synchronization::ExportedType{qtQuickModuleId, - "Item", - Storage::Version{}}}}; + Storage::Synchronization::Type type{"Item", + Storage::Synchronization::ImportedType{"Obj"}, + Storage::Synchronization::ImportedType{}, + TypeTraitsKind::Reference, + sourceId2, + {Storage::Synchronization::ExportedType{qtQuickModuleId, + "Item", + Storage::Version{}}}}; Storage::Import import{qmlModuleId, Storage::Version{1, 3}, sourceId2}; storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); ASSERT_THAT(storage.fetchTypes(), - Contains(IsStorageType( - sourceId2, "Item", fetchTypeId(sourceId1, "QObject"), TypeTraits::Reference))); + Contains(IsStorageType(sourceId2, + "Item", + fetchTypeId(sourceId1, "QObject"), + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, fetch_higher_minor_version_for_qualified_imported_type) @@ -4707,32 +4732,31 @@ TEST_F(ProjectStorage, fetch_higher_minor_version_for_qualified_imported_type) "Item", Storage::Synchronization::QualifiedImportedType{"Obj", import}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId2, - {Storage::Synchronization::ExportedType{qtQuickModuleId, - "Item", - Storage::Version{}}}}; + {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{}}}}; storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); ASSERT_THAT(storage.fetchTypes(), - Contains(IsStorageType( - sourceId2, "Item", fetchTypeId(sourceId1, "QObject"), TypeTraits::Reference))); + Contains(IsStorageType(sourceId2, + "Item", + fetchTypeId(sourceId1, "QObject"), + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, fetch_different_major_version_for_imported_type_throws) { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); - Storage::Synchronization::Type type{ - "Item", - Storage::Synchronization::ImportedType{"Obj"}, - Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, - sourceId2, - {Storage::Synchronization::ExportedType{qtQuickModuleId, - "Item", - Storage::Version{}}}}; + Storage::Synchronization::Type type{"Item", + Storage::Synchronization::ImportedType{"Obj"}, + Storage::Synchronization::ImportedType{}, + TypeTraitsKind::Reference, + sourceId2, + {Storage::Synchronization::ExportedType{qtQuickModuleId, + "Item", + Storage::Version{}}}}; Storage::Import import{qmlModuleId, Storage::Version{3, 1}, sourceId2}; ASSERT_THROW(storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}), @@ -4748,11 +4772,9 @@ TEST_F(ProjectStorage, fetch_different_major_version_for_qualified_imported_type "Item", Storage::Synchronization::QualifiedImportedType{"Obj", import}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId2, - {Storage::Synchronization::ExportedType{qtQuickModuleId, - "Item", - Storage::Version{}}}}; + {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{}}}}; ASSERT_THROW(storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}), QmlDesigner::TypeNameDoesNotExists); @@ -4762,22 +4784,23 @@ TEST_F(ProjectStorage, fetch_other_type_by_different_version_for_imported_type) { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); - Storage::Synchronization::Type type{ - "Item", - Storage::Synchronization::ImportedType{"Obj"}, - Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, - sourceId2, - {Storage::Synchronization::ExportedType{qtQuickModuleId, - "Item", - Storage::Version{}}}}; + Storage::Synchronization::Type type{"Item", + Storage::Synchronization::ImportedType{"Obj"}, + Storage::Synchronization::ImportedType{}, + TypeTraitsKind::Reference, + sourceId2, + {Storage::Synchronization::ExportedType{qtQuickModuleId, + "Item", + Storage::Version{}}}}; Storage::Import import{qmlModuleId, Storage::Version{2, 3}, sourceId2}; storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); ASSERT_THAT(storage.fetchTypes(), - Contains(IsStorageType( - sourceId2, "Item", fetchTypeId(sourceId1, "QObject2"), TypeTraits::Reference))); + Contains(IsStorageType(sourceId2, + "Item", + fetchTypeId(sourceId1, "QObject2"), + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, fetch_other_type_by_different_version_for_qualified_imported_type) @@ -4789,39 +4812,40 @@ TEST_F(ProjectStorage, fetch_other_type_by_different_version_for_qualified_impor "Item", Storage::Synchronization::QualifiedImportedType{"Obj", import}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId2, - {Storage::Synchronization::ExportedType{qtQuickModuleId, - "Item", - Storage::Version{}}}}; + {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{}}}}; storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); ASSERT_THAT(storage.fetchTypes(), - Contains(IsStorageType( - sourceId2, "Item", fetchTypeId(sourceId1, "QObject2"), TypeTraits::Reference))); + Contains(IsStorageType(sourceId2, + "Item", + fetchTypeId(sourceId1, "QObject2"), + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, fetch_highest_version_for_import_without_version_for_imported_type) { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); - Storage::Synchronization::Type type{ - "Item", - Storage::Synchronization::ImportedType{"Obj"}, - Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, - sourceId2, - {Storage::Synchronization::ExportedType{qtQuickModuleId, - "Item", - Storage::Version{}}}}; + Storage::Synchronization::Type type{"Item", + Storage::Synchronization::ImportedType{"Obj"}, + Storage::Synchronization::ImportedType{}, + TypeTraitsKind::Reference, + sourceId2, + {Storage::Synchronization::ExportedType{qtQuickModuleId, + "Item", + Storage::Version{}}}}; Storage::Import import{qmlModuleId, Storage::Version{}, sourceId2}; storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); ASSERT_THAT(storage.fetchTypes(), - Contains(IsStorageType( - sourceId2, "Item", fetchTypeId(sourceId1, "QObject4"), TypeTraits::Reference))); + Contains(IsStorageType(sourceId2, + "Item", + fetchTypeId(sourceId1, "QObject4"), + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, fetch_highest_version_for_import_without_version_for_qualified_imported_type) @@ -4833,39 +4857,40 @@ TEST_F(ProjectStorage, fetch_highest_version_for_import_without_version_for_qual "Item", Storage::Synchronization::QualifiedImportedType{"Obj", import}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId2, - {Storage::Synchronization::ExportedType{qtQuickModuleId, - "Item", - Storage::Version{}}}}; + {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{}}}}; storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); ASSERT_THAT(storage.fetchTypes(), - Contains(IsStorageType( - sourceId2, "Item", fetchTypeId(sourceId1, "QObject4"), TypeTraits::Reference))); + Contains(IsStorageType(sourceId2, + "Item", + fetchTypeId(sourceId1, "QObject4"), + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, fetch_highest_version_for_import_with_major_version_for_imported_type) { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); - Storage::Synchronization::Type type{ - "Item", - Storage::Synchronization::ImportedType{"Obj"}, - Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, - sourceId2, - {Storage::Synchronization::ExportedType{qtQuickModuleId, - "Item", - Storage::Version{}}}}; + Storage::Synchronization::Type type{"Item", + Storage::Synchronization::ImportedType{"Obj"}, + Storage::Synchronization::ImportedType{}, + TypeTraitsKind::Reference, + sourceId2, + {Storage::Synchronization::ExportedType{qtQuickModuleId, + "Item", + Storage::Version{}}}}; Storage::Import import{qmlModuleId, Storage::Version{2}, sourceId2}; storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); ASSERT_THAT(storage.fetchTypes(), - Contains(IsStorageType( - sourceId2, "Item", fetchTypeId(sourceId1, "QObject3"), TypeTraits::Reference))); + Contains(IsStorageType(sourceId2, + "Item", + fetchTypeId(sourceId1, "QObject3"), + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, fetch_highest_version_for_import_with_major_version_for_qualified_imported_type) @@ -4877,39 +4902,40 @@ TEST_F(ProjectStorage, fetch_highest_version_for_import_with_major_version_for_q "Item", Storage::Synchronization::QualifiedImportedType{"Obj", import}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId2, - {Storage::Synchronization::ExportedType{qtQuickModuleId, - "Item", - Storage::Version{}}}}; + {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{}}}}; storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); ASSERT_THAT(storage.fetchTypes(), - Contains(IsStorageType( - sourceId2, "Item", fetchTypeId(sourceId1, "QObject3"), TypeTraits::Reference))); + Contains(IsStorageType(sourceId2, + "Item", + fetchTypeId(sourceId1, "QObject3"), + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, fetch_exported_type_without_version_first_for_imported_type) { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); - Storage::Synchronization::Type type{ - "Item", - Storage::Synchronization::ImportedType{"BuiltInObj"}, - Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, - sourceId2, - {Storage::Synchronization::ExportedType{qtQuickModuleId, - "Item", - Storage::Version{}}}}; + Storage::Synchronization::Type type{"Item", + Storage::Synchronization::ImportedType{"BuiltInObj"}, + Storage::Synchronization::ImportedType{}, + TypeTraitsKind::Reference, + sourceId2, + {Storage::Synchronization::ExportedType{qtQuickModuleId, + "Item", + Storage::Version{}}}}; Storage::Import import{qmlModuleId, Storage::Version{}, sourceId2}; storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); ASSERT_THAT(storage.fetchTypes(), - Contains(IsStorageType( - sourceId2, "Item", fetchTypeId(sourceId1, "QObject"), TypeTraits::Reference))); + Contains(IsStorageType(sourceId2, + "Item", + fetchTypeId(sourceId1, "QObject"), + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, fetch_exported_type_without_version_first_for_qualified_imported_type) @@ -4921,17 +4947,17 @@ TEST_F(ProjectStorage, fetch_exported_type_without_version_first_for_qualified_i "Item", Storage::Synchronization::QualifiedImportedType{"BuiltInObj", import}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId2, - {Storage::Synchronization::ExportedType{qtQuickModuleId, - "Item", - Storage::Version{}}}}; + {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{}}}}; storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); ASSERT_THAT(storage.fetchTypes(), - Contains(IsStorageType( - sourceId2, "Item", fetchTypeId(sourceId1, "QObject"), TypeTraits::Reference))); + Contains(IsStorageType(sourceId2, + "Item", + fetchTypeId(sourceId1, "QObject"), + TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, ensure_that_properties_for_removed_types_are_not_anymore_relinked) @@ -4940,11 +4966,9 @@ TEST_F(ProjectStorage, ensure_that_properties_for_removed_types_are_not_anymore_ "QObject", Storage::Synchronization::ImportedType{""}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId1, - {Storage::Synchronization::ExportedType{qmlModuleId, - "Object", - Storage::Version{}}}, + {Storage::Synchronization::ExportedType{qmlModuleId, "Object", Storage::Version{}}}, {Storage::Synchronization::PropertyDeclaration{"data", Storage::Synchronization::ImportedType{ "Object"}, @@ -4980,11 +5004,9 @@ TEST_F(ProjectStorage, minimal_updates) "QQuickItem", {}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId1, - {Storage::Synchronization::ExportedType{qtQuickModuleId, - "Item", - Storage::Version{2, 0}}, + {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{2, 0}}, Storage::Synchronization::ExportedType{qtQuickNativeModuleId, "QQuickItem"}}, {}, {}, @@ -4997,7 +5019,7 @@ TEST_F(ProjectStorage, minimal_updates) ASSERT_THAT( storage.fetchTypes(), UnorderedElementsAre( - AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraits::Reference), + AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qmlModuleId, "Object"), IsExportedType(qmlModuleId, "Obj"), @@ -5005,7 +5027,7 @@ TEST_F(ProjectStorage, minimal_updates) AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuickModuleId, "Item", 2, 0), IsExportedType(qtQuickNativeModuleId, "QQuickItem"))), @@ -5286,7 +5308,7 @@ TEST_F(ProjectStorage, exclude_exported_types) ASSERT_THAT(storage.fetchTypes(), UnorderedElementsAre( - AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraits::Reference), + AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qmlModuleId, "Object"), IsExportedType(qmlModuleId, "Obj"), @@ -5294,7 +5316,7 @@ TEST_F(ProjectStorage, exclude_exported_types) AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuickModuleId, "Item"), IsExportedType(qtQuickNativeModuleId, @@ -5312,22 +5334,22 @@ TEST_F(ProjectStorage, module_exported_import) AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuickModuleId, "Item")))), - AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraits::Reference), + AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qmlModuleId, "Object")))), AllOf(IsStorageType(sourceId3, "QQuickItem3d", fetchTypeId(sourceId1, "QQuickItem"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuick3DModuleId, "Item3D")))), AllOf(IsStorageType(sourceId4, "MyItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(myModuleModuleId, "MyItem")))))); } @@ -5360,22 +5382,22 @@ TEST_F(ProjectStorage, module_exported_import_with_different_versions) AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuickModuleId, "Item")))), - AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraits::Reference), + AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qmlModuleId, "Object")))), AllOf(IsStorageType(sourceId3, "QQuickItem3d", fetchTypeId(sourceId1, "QQuickItem"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuick3DModuleId, "Item3D")))), AllOf(IsStorageType(sourceId4, "MyItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(myModuleModuleId, "MyItem")))))); } @@ -5397,27 +5419,28 @@ TEST_F(ProjectStorage, module_exported_import_with_indirect_different_versions) AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuickModuleId, "Item")))), - AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraits::Reference), + AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qmlModuleId, "Object")))), AllOf(IsStorageType(sourceId3, "QQuickItem3d", fetchTypeId(sourceId1, "QQuickItem"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuick3DModuleId, "Item3D")))), AllOf(IsStorageType(sourceId4, "MyItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(myModuleModuleId, "MyItem")))))); } -TEST_F(ProjectStorage, module_exported_import_prevent_collision_if_module_is_indirectly_reexported_multiple_times) +TEST_F(ProjectStorage, + module_exported_import_prevent_collision_if_module_is_indirectly_reexported_multiple_times) { ModuleId qtQuick4DModuleId{storage.moduleId("QtQuick4D")}; auto package{createModuleExportedImportSynchronizationPackage()}; @@ -5435,11 +5458,9 @@ TEST_F(ProjectStorage, module_exported_import_prevent_collision_if_module_is_ind "QQuickItem4d", Storage::Synchronization::ImportedType{"Item"}, Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, sourceId5, - {Storage::Synchronization::ExportedType{qtQuick4DModuleId, - "Item4D", - Storage::Version{1, 0}}}}); + {Storage::Synchronization::ExportedType{qtQuick4DModuleId, "Item4D", Storage::Version{1, 0}}}}); package.imports.emplace_back(qtQuick4DModuleId, Storage::Version{1}, sourceId4); storage.synchronize(std::move(package)); @@ -5449,28 +5470,28 @@ TEST_F(ProjectStorage, module_exported_import_prevent_collision_if_module_is_ind AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuickModuleId, "Item")))), - AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraits::Reference), + AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qmlModuleId, "Object")))), AllOf(IsStorageType(sourceId3, "QQuickItem3d", fetchTypeId(sourceId1, "QQuickItem"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuick3DModuleId, "Item3D")))), AllOf(IsStorageType(sourceId5, "QQuickItem4d", fetchTypeId(sourceId1, "QQuickItem"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuick4DModuleId, "Item4D")))), AllOf(IsStorageType(sourceId4, "MyItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(myModuleModuleId, "MyItem")))))); } @@ -5481,20 +5502,16 @@ TEST_F(ProjectStorage, distinguish_between_import_kinds) ModuleId qml11ModuleId{storage.moduleId("Qml11")}; auto package{createSimpleSynchronizationPackage()}; package.moduleDependencies.emplace_back(qmlModuleId, Storage::Version{}, sourceId1); - package.moduleDependencies.emplace_back(qml1ModuleId, - Storage::Version{1}, - sourceId1); + package.moduleDependencies.emplace_back(qml1ModuleId, Storage::Version{1}, sourceId1); package.imports.emplace_back(qml1ModuleId, Storage::Version{}, sourceId1); - package.moduleDependencies.emplace_back(qml11ModuleId, - Storage::Version{1, 1}, - sourceId1); + package.moduleDependencies.emplace_back(qml11ModuleId, Storage::Version{1, 1}, sourceId1); package.imports.emplace_back(qml11ModuleId, Storage::Version{1, 1}, sourceId1); storage.synchronize(std::move(package)); ASSERT_THAT(storage.fetchTypes(), UnorderedElementsAre( - AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraits::Reference), + AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qmlModuleId, "Object"), IsExportedType(qmlModuleId, "Obj"), @@ -5502,7 +5519,7 @@ TEST_F(ProjectStorage, distinguish_between_import_kinds) AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuickModuleId, "Item"), IsExportedType(qtQuickNativeModuleId, @@ -5512,9 +5529,7 @@ TEST_F(ProjectStorage, distinguish_between_import_kinds) TEST_F(ProjectStorage, module_exported_import_distinguish_between_dependency_and_import_re_exports) { auto package{createModuleExportedImportSynchronizationPackage()}; - package.moduleDependencies.emplace_back(qtQuick3DModuleId, - Storage::Version{1}, - sourceId4); + package.moduleDependencies.emplace_back(qtQuick3DModuleId, Storage::Version{1}, sourceId4); storage.synchronize(std::move(package)); @@ -5523,22 +5538,22 @@ TEST_F(ProjectStorage, module_exported_import_distinguish_between_dependency_and AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuickModuleId, "Item")))), - AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraits::Reference), + AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qmlModuleId, "Object")))), AllOf(IsStorageType(sourceId3, "QQuickItem3d", fetchTypeId(sourceId1, "QQuickItem"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuick3DModuleId, "Item3D")))), AllOf(IsStorageType(sourceId4, "MyItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(myModuleModuleId, "MyItem")))))); } @@ -5556,22 +5571,22 @@ TEST_F(ProjectStorage, module_exported_import_with_qualified_imported_type) AllOf(IsStorageType(sourceId1, "QQuickItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuickModuleId, "Item")))), - AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraits::Reference), + AllOf(IsStorageType(sourceId2, "QObject", TypeId{}, TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qmlModuleId, "Object")))), AllOf(IsStorageType(sourceId3, "QQuickItem3d", fetchTypeId(sourceId1, "QQuickItem"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQuick3DModuleId, "Item3D")))), AllOf(IsStorageType(sourceId4, "MyItem", fetchTypeId(sourceId2, "QObject"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(myModuleModuleId, "MyItem")))))); } @@ -5582,19 +5597,22 @@ TEST_F(ProjectStorage, synchronize_types_add_indirect_alias_declarations) storage.synchronize(package); - ASSERT_THAT( - storage.fetchTypes(), - Contains(AllOf( - IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), TypeTraits::Reference), - Field(&Storage::Synchronization::Type::propertyDeclarations, - UnorderedElementsAre( - IsPropertyDeclaration("items", - fetchTypeId(sourceId1, "QQuickItem"), - Storage::PropertyDeclarationTraits::IsList), - IsPropertyDeclaration("objects", - fetchTypeId(sourceId2, "QObject"), - Storage::PropertyDeclarationTraits::IsList - | Storage::PropertyDeclarationTraits::IsReadOnly)))))); + ASSERT_THAT(storage.fetchTypes(), + Contains( + AllOf(IsStorageType(sourceId3, + "QAliasItem", + fetchTypeId(sourceId1, "QQuickItem"), + TypeTraitsKind::Reference), + Field(&Storage::Synchronization::Type::propertyDeclarations, + UnorderedElementsAre( + IsPropertyDeclaration("items", + fetchTypeId(sourceId1, "QQuickItem"), + Storage::PropertyDeclarationTraits::IsList), + IsPropertyDeclaration( + "objects", + fetchTypeId(sourceId2, "QObject"), + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly)))))); } TEST_F(ProjectStorage, synchronize_types_add_indirect_alias_declarations_again) @@ -5604,19 +5622,22 @@ TEST_F(ProjectStorage, synchronize_types_add_indirect_alias_declarations_again) storage.synchronize(package); - ASSERT_THAT( - storage.fetchTypes(), - Contains(AllOf( - IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), TypeTraits::Reference), - Field(&Storage::Synchronization::Type::propertyDeclarations, - UnorderedElementsAre( - IsPropertyDeclaration("items", - fetchTypeId(sourceId1, "QQuickItem"), - Storage::PropertyDeclarationTraits::IsList), - IsPropertyDeclaration("objects", - fetchTypeId(sourceId2, "QObject"), - Storage::PropertyDeclarationTraits::IsList - | Storage::PropertyDeclarationTraits::IsReadOnly)))))); + ASSERT_THAT(storage.fetchTypes(), + Contains( + AllOf(IsStorageType(sourceId3, + "QAliasItem", + fetchTypeId(sourceId1, "QQuickItem"), + TypeTraitsKind::Reference), + Field(&Storage::Synchronization::Type::propertyDeclarations, + UnorderedElementsAre( + IsPropertyDeclaration("items", + fetchTypeId(sourceId1, "QQuickItem"), + Storage::PropertyDeclarationTraits::IsList), + IsPropertyDeclaration( + "objects", + fetchTypeId(sourceId2, "QObject"), + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly)))))); } TEST_F(ProjectStorage, synchronize_types_remove_indirect_alias_declaration) @@ -5631,7 +5652,7 @@ TEST_F(ProjectStorage, synchronize_types_remove_indirect_alias_declaration) Contains(AllOf(IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::propertyDeclarations, UnorderedElementsAre(IsPropertyDeclaration( "items", @@ -5668,7 +5689,8 @@ TEST_F(ProjectStorage, synchronize_types_add_indirect_alias_declarations_throws_ QmlDesigner::PropertyNameDoesNotExists); } -TEST_F(ProjectStorage, synchronize_types_add_indirect_alias_declarations_throws_for_wrong_property_name_tail) +TEST_F(ProjectStorage, + synchronize_types_add_indirect_alias_declarations_throws_for_wrong_property_name_tail) { auto package{createSynchronizationPackageWithIndirectAliases()}; storage.synchronize(package); @@ -5693,19 +5715,22 @@ TEST_F(ProjectStorage, synchronize_types_change_indirect_alias_declaration_type_ storage.synchronize(SynchronizationPackage{ importsSourceId3, {package.types[2]}, {sourceId3}, moduleDependenciesSourceId3, {sourceId3}}); - ASSERT_THAT( - storage.fetchTypes(), - Contains(AllOf( - IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), TypeTraits::Reference), - Field(&Storage::Synchronization::Type::propertyDeclarations, - UnorderedElementsAre( - IsPropertyDeclaration("items", - fetchTypeId(sourceId1, "QQuickItem"), - Storage::PropertyDeclarationTraits::IsList), - IsPropertyDeclaration("objects", - fetchTypeId(sourceId4, "QObject2"), - Storage::PropertyDeclarationTraits::IsList - | Storage::PropertyDeclarationTraits::IsReadOnly)))))); + ASSERT_THAT(storage.fetchTypes(), + Contains( + AllOf(IsStorageType(sourceId3, + "QAliasItem", + fetchTypeId(sourceId1, "QQuickItem"), + TypeTraitsKind::Reference), + Field(&Storage::Synchronization::Type::propertyDeclarations, + UnorderedElementsAre( + IsPropertyDeclaration("items", + fetchTypeId(sourceId1, "QQuickItem"), + Storage::PropertyDeclarationTraits::IsList), + IsPropertyDeclaration( + "objects", + fetchTypeId(sourceId4, "QObject2"), + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly)))))); } TEST_F(ProjectStorage, synchronize_types_change_indirect_alias_declaration_tails_type_name) @@ -5719,19 +5744,22 @@ TEST_F(ProjectStorage, synchronize_types_change_indirect_alias_declaration_tails storage.synchronize(SynchronizationPackage{ importsSourceId5, {package.types[4]}, {sourceId5}, moduleDependenciesSourceId5, {sourceId5}}); - ASSERT_THAT( - storage.fetchTypes(), - Contains(AllOf( - IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), TypeTraits::Reference), - Field(&Storage::Synchronization::Type::propertyDeclarations, - UnorderedElementsAre( - IsPropertyDeclaration("items", - fetchTypeId(sourceId1, "QQuickItem"), - Storage::PropertyDeclarationTraits::IsList), - IsPropertyDeclaration("objects", - fetchTypeId(sourceId4, "QObject2"), - Storage::PropertyDeclarationTraits::IsList - | Storage::PropertyDeclarationTraits::IsReadOnly)))))); + ASSERT_THAT(storage.fetchTypes(), + Contains( + AllOf(IsStorageType(sourceId3, + "QAliasItem", + fetchTypeId(sourceId1, "QQuickItem"), + TypeTraitsKind::Reference), + Field(&Storage::Synchronization::Type::propertyDeclarations, + UnorderedElementsAre( + IsPropertyDeclaration("items", + fetchTypeId(sourceId1, "QQuickItem"), + Storage::PropertyDeclarationTraits::IsList), + IsPropertyDeclaration( + "objects", + fetchTypeId(sourceId4, "QObject2"), + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly)))))); } TEST_F(ProjectStorage, synchronize_types_change_indirect_alias_declarations_property_name) @@ -5743,19 +5771,22 @@ TEST_F(ProjectStorage, synchronize_types_change_indirect_alias_declarations_prop storage.synchronize(SynchronizationPackage{ importsSourceId3, {package.types[2]}, {sourceId3}, moduleDependenciesSourceId3, {sourceId3}}); - ASSERT_THAT( - storage.fetchTypes(), - Contains(AllOf( - IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), TypeTraits::Reference), - Field(&Storage::Synchronization::Type::propertyDeclarations, - UnorderedElementsAre( - IsPropertyDeclaration("items", - fetchTypeId(sourceId1, "QQuickItem"), - Storage::PropertyDeclarationTraits::IsList), - IsPropertyDeclaration("objects", - fetchTypeId(sourceId4, "QObject2"), - Storage::PropertyDeclarationTraits::IsList - | Storage::PropertyDeclarationTraits::IsReadOnly)))))); + ASSERT_THAT(storage.fetchTypes(), + Contains( + AllOf(IsStorageType(sourceId3, + "QAliasItem", + fetchTypeId(sourceId1, "QQuickItem"), + TypeTraitsKind::Reference), + Field(&Storage::Synchronization::Type::propertyDeclarations, + UnorderedElementsAre( + IsPropertyDeclaration("items", + fetchTypeId(sourceId1, "QQuickItem"), + Storage::PropertyDeclarationTraits::IsList), + IsPropertyDeclaration( + "objects", + fetchTypeId(sourceId4, "QObject2"), + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly)))))); } TEST_F(ProjectStorage, synchronize_types_change_indirect_alias_declarations_property_name_tail) @@ -5772,7 +5803,7 @@ TEST_F(ProjectStorage, synchronize_types_change_indirect_alias_declarations_prop IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::propertyDeclarations, UnorderedElementsAre( IsPropertyDeclaration("items", @@ -5796,19 +5827,22 @@ TEST_F(ProjectStorage, synchronize_types_change_indirect_alias_declarations_to_p storage.synchronize(SynchronizationPackage{ importsSourceId3, {package.types[2]}, {sourceId3}, moduleDependenciesSourceId3, {sourceId3}}); - ASSERT_THAT( - storage.fetchTypes(), - Contains(AllOf( - IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), TypeTraits::Reference), - Field(&Storage::Synchronization::Type::propertyDeclarations, - UnorderedElementsAre( - IsPropertyDeclaration("items", - fetchTypeId(sourceId1, "QQuickItem"), - Storage::PropertyDeclarationTraits::IsList), - IsPropertyDeclaration("objects", - fetchTypeId(sourceId1, "QQuickItem"), - Storage::PropertyDeclarationTraits::IsList - | Storage::PropertyDeclarationTraits::IsReadOnly)))))); + ASSERT_THAT(storage.fetchTypes(), + Contains( + AllOf(IsStorageType(sourceId3, + "QAliasItem", + fetchTypeId(sourceId1, "QQuickItem"), + TypeTraitsKind::Reference), + Field(&Storage::Synchronization::Type::propertyDeclarations, + UnorderedElementsAre( + IsPropertyDeclaration("items", + fetchTypeId(sourceId1, "QQuickItem"), + Storage::PropertyDeclarationTraits::IsList), + IsPropertyDeclaration( + "objects", + fetchTypeId(sourceId1, "QQuickItem"), + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly)))))); } TEST_F(ProjectStorage, synchronize_types_change_property_declarations_to_indirect_alias_declaration) @@ -5830,19 +5864,22 @@ TEST_F(ProjectStorage, synchronize_types_change_property_declarations_to_indirec storage.synchronize(SynchronizationPackage{ importsSourceId3, {package.types[2]}, {sourceId3}, moduleDependenciesSourceId3, {sourceId3}}); - ASSERT_THAT( - storage.fetchTypes(), - Contains(AllOf( - IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), TypeTraits::Reference), - Field(&Storage::Synchronization::Type::propertyDeclarations, - UnorderedElementsAre( - IsPropertyDeclaration("items", - fetchTypeId(sourceId1, "QQuickItem"), - Storage::PropertyDeclarationTraits::IsList), - IsPropertyDeclaration("objects", - fetchTypeId(sourceId2, "QObject"), - Storage::PropertyDeclarationTraits::IsList - | Storage::PropertyDeclarationTraits::IsReadOnly)))))); + ASSERT_THAT(storage.fetchTypes(), + Contains( + AllOf(IsStorageType(sourceId3, + "QAliasItem", + fetchTypeId(sourceId1, "QQuickItem"), + TypeTraitsKind::Reference), + Field(&Storage::Synchronization::Type::propertyDeclarations, + UnorderedElementsAre( + IsPropertyDeclaration("items", + fetchTypeId(sourceId1, "QQuickItem"), + Storage::PropertyDeclarationTraits::IsList), + IsPropertyDeclaration( + "objects", + fetchTypeId(sourceId2, "QObject"), + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly)))))); } TEST_F(ProjectStorage, synchronize_types_change_indirect_alias_target_property_declaration_traits) @@ -5858,7 +5895,10 @@ TEST_F(ProjectStorage, synchronize_types_change_indirect_alias_target_property_d ASSERT_THAT( storage.fetchTypes(), Contains(AllOf( - IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), TypeTraits::Reference), + IsStorageType(sourceId3, + "QAliasItem", + fetchTypeId(sourceId1, "QQuickItem"), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::propertyDeclarations, UnorderedElementsAre( IsPropertyDeclaration("items", @@ -5881,19 +5921,22 @@ TEST_F(ProjectStorage, synchronize_types_change_indirect_alias_target_property_d storage.synchronize(SynchronizationPackage{ importsSourceId5, {package.types[4]}, {sourceId5}, moduleDependenciesSourceId5, {sourceId5}}); - ASSERT_THAT( - storage.fetchTypes(), - Contains(AllOf( - IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), TypeTraits::Reference), - Field(&Storage::Synchronization::Type::propertyDeclarations, - UnorderedElementsAre( - IsPropertyDeclaration("items", - fetchTypeId(sourceId1, "QQuickItem"), - Storage::PropertyDeclarationTraits::IsList), - IsPropertyDeclaration("objects", - fetchTypeId(sourceId1, "QQuickItem"), - Storage::PropertyDeclarationTraits::IsList - | Storage::PropertyDeclarationTraits::IsReadOnly)))))); + ASSERT_THAT(storage.fetchTypes(), + Contains( + AllOf(IsStorageType(sourceId3, + "QAliasItem", + fetchTypeId(sourceId1, "QQuickItem"), + TypeTraitsKind::Reference), + Field(&Storage::Synchronization::Type::propertyDeclarations, + UnorderedElementsAre( + IsPropertyDeclaration("items", + fetchTypeId(sourceId1, "QQuickItem"), + Storage::PropertyDeclarationTraits::IsList), + IsPropertyDeclaration( + "objects", + fetchTypeId(sourceId1, "QQuickItem"), + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly)))))); } TEST_F(ProjectStorage, synchronize_types_remove_property_declaration_with_an_indirect_alias_throws) @@ -5910,7 +5953,8 @@ TEST_F(ProjectStorage, synchronize_types_remove_property_declaration_with_an_ind Sqlite::ConstraintPreventsModification); } -TEST_F(ProjectStorage, DISABLED_synchronize_types_remove_stem_property_declaration_with_an_indirect_alias_throws) +TEST_F(ProjectStorage, + DISABLED_synchronize_types_remove_stem_property_declaration_with_an_indirect_alias_throws) { auto package{createSynchronizationPackageWithIndirectAliases()}; storage.synchronize(package); @@ -5941,7 +5985,7 @@ TEST_F(ProjectStorage, synchronize_types_remove_property_declaration_and_indirec Contains(AllOf(IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::propertyDeclarations, UnorderedElementsAre(IsPropertyDeclaration( "items", @@ -5967,7 +6011,7 @@ TEST_F(ProjectStorage, synchronize_types_remove_property_declaration_and_indirec AllOf(IsStorageType(sourceId3, "QAliasItem", fetchTypeId(sourceId1, "QQuickItem"), - TypeTraits::Reference), + TypeTraitsKind::Reference), Field(&Storage::Synchronization::Type::propertyDeclarations, IsEmpty())))); } @@ -6644,7 +6688,7 @@ TEST_F(ProjectStorage, get_type) auto type = storage.type(typeId); - ASSERT_THAT(type, Optional(IsInfoType(defaultPropertyId, sourceId1, TypeTraits::Reference))); + ASSERT_THAT(type, Optional(IsInfoType(defaultPropertyId, sourceId1, TypeTraitsKind::Reference))); } TEST_F(ProjectStorage, dont_get_type_for_invalid_id) @@ -7016,16 +7060,14 @@ TEST_F(ProjectStorage, storage.synchronize(package); auto importId = storage.importId(Storage::Import{qmlModuleId, Storage::Version{}, sourceId1}); auto expectedImportedTypeNameId = storage.importedTypeNameId(importId, "Item"); - auto importId2 = storage.importId( - Storage::Import{qtQuickModuleId, Storage::Version{}, sourceId1}); + auto importId2 = storage.importId(Storage::Import{qtQuickModuleId, Storage::Version{}, sourceId1}); auto importedTypeNameId = storage.importedTypeNameId(importId2, "Item"); ASSERT_THAT(importedTypeNameId, Not(expectedImportedTypeNameId)); } -TEST_F(ProjectStorage, - get_imported_type_name_id_for_import_id_returns_different_id_for_different_name) +TEST_F(ProjectStorage, get_imported_type_name_id_for_import_id_returns_different_id_for_different_name) { auto package{createSimpleSynchronizationPackage()}; storage.synchronize(package); @@ -7205,16 +7247,440 @@ TEST_F(ProjectStorage, synchronize_property_editor_with_non_existing_type_name) ASSERT_THAT(storage.propertyEditorPathId(fetchTypeId(sourceId4, "Item4D")), IsFalse()); } -TEST_F(ProjectStorage, call_refresh_callback_after_synchronization) +TEST_F(ProjectStorage, call_remove_type_ids_in_observer_after_synchronization) { auto package{createSimpleSynchronizationPackage()}; - MockFunction callbackMock; - auto callback = callbackMock.AsStdFunction(); - storage.addRefreshCallback(&callback); + ProjectStorageObserverMock observerMock; + storage.addObserver(&observerMock); + storage.synchronize(package); + package.types.clear(); - EXPECT_CALL(callbackMock, Call(_)); + EXPECT_CALL(observerMock, removedTypeIds(_)); storage.synchronize(package); } +TEST_F(ProjectStorage, synchronize_type_annotation_type_traits) +{ + auto package{createSimpleSynchronizationPackage()}; + package.typeAnnotations = createTypeAnnotions(); + package.updatedTypeAnnotationSourceIds = createUpdatedTypeAnnotionSourceIds( + package.typeAnnotations); + TypeTraits traits{TypeTraitsKind::Reference}; + traits.canBeContainer = FlagIs::True; + traits.visibleInLibrary = FlagIs::True; + + storage.synchronize(package); + + ASSERT_THAT(storage.type(fetchTypeId(sourceId2, "QObject"))->traits, traits); +} + +TEST_F(ProjectStorage, synchronize_updates_type_annotation_type_traits) +{ + auto package{createSimpleSynchronizationPackage()}; + storage.synchronize(package); + SynchronizationPackage annotationPackage; + annotationPackage.typeAnnotations = createTypeAnnotions(); + package.updatedTypeAnnotationSourceIds = createUpdatedTypeAnnotionSourceIds( + package.typeAnnotations); + TypeTraits traits{TypeTraitsKind::Reference}; + traits.canBeContainer = FlagIs::True; + traits.visibleInLibrary = FlagIs::True; + + storage.synchronize(annotationPackage); + + ASSERT_THAT(storage.type(fetchTypeId(sourceId2, "QObject"))->traits, traits); +} + +TEST_F(ProjectStorage, synchronize_clears_annotation_type_traits_if_annotation_was_removed) +{ + auto package{createSimpleSynchronizationPackage()}; + package.typeAnnotations = createTypeAnnotions(); + package.updatedTypeAnnotationSourceIds = createUpdatedTypeAnnotionSourceIds( + package.typeAnnotations); + storage.synchronize(package); + package.typeAnnotations[0].traits.isStackedContainer = FlagIs::True; + TypeTraits traits{TypeTraitsKind::Reference}; + traits.canBeContainer = FlagIs::True; + traits.visibleInLibrary = FlagIs::True; + traits.isStackedContainer = FlagIs::True; + + storage.synchronize(package); + + ASSERT_THAT(storage.type(fetchTypeId(sourceId2, "QObject"))->traits, traits); +} + +TEST_F(ProjectStorage, synchronize_updatesannotation_type_traits) +{ + auto package{createSimpleSynchronizationPackage()}; + package.typeAnnotations = createTypeAnnotions(); + package.updatedTypeAnnotationSourceIds = createUpdatedTypeAnnotionSourceIds( + package.typeAnnotations); + storage.synchronize(package); + TypeTraits traits{TypeTraitsKind::Value}; + package.types[1].traits.kind = TypeTraitsKind::Value; + package.typeAnnotations.clear(); + + storage.synchronize(package); + + ASSERT_THAT(storage.type(fetchTypeId(sourceId2, "QObject"))->traits, traits); +} + +TEST_F(ProjectStorage, synchronize_type_annotation_type_icon_path) +{ + auto package{createSimpleSynchronizationPackage()}; + package.typeAnnotations = createTypeAnnotions(); + package.updatedTypeAnnotationSourceIds = createUpdatedTypeAnnotionSourceIds( + package.typeAnnotations); + + storage.synchronize(package); + + ASSERT_THAT(storage.typeIconPath(fetchTypeId(sourceId2, "QObject")), Eq("/path/to/icon.png")); +} + +TEST_F(ProjectStorage, synchronize_removes_type_annotation_type_icon_path) +{ + auto package{createSimpleSynchronizationPackage()}; + package.typeAnnotations = createTypeAnnotions(); + package.updatedTypeAnnotationSourceIds = createUpdatedTypeAnnotionSourceIds( + package.typeAnnotations); + storage.synchronize(package); + package.typeAnnotations.clear(); + + storage.synchronize(package); + + ASSERT_THAT(storage.typeIconPath(fetchTypeId(sourceId2, "QObject")), IsEmpty()); +} + +TEST_F(ProjectStorage, synchronize_updates_type_annotation_type_icon_path) +{ + auto package{createSimpleSynchronizationPackage()}; + package.typeAnnotations = createTypeAnnotions(); + package.updatedTypeAnnotationSourceIds = createUpdatedTypeAnnotionSourceIds( + package.typeAnnotations); + storage.synchronize(package); + package.typeAnnotations[0].iconPath = "/path/to/icon2.png"; + + storage.synchronize(package); + + ASSERT_THAT(storage.typeIconPath(fetchTypeId(sourceId2, "QObject")), Eq("/path/to/icon2.png")); +} + +TEST_F(ProjectStorage, return_empty_path_if_no_type_icon_exists) +{ + auto package{createSimpleSynchronizationPackage()}; + storage.synchronize(package); + + auto iconPath = storage.typeIconPath(fetchTypeId(sourceId2, "QObject")); + + ASSERT_THAT(iconPath, IsEmpty()); +} + +TEST_F(ProjectStorage, return_empty_path_if_that_type_does_not_exists) +{ + auto iconPath = storage.typeIconPath(fetchTypeId(sourceId2, "QObject")); + + ASSERT_THAT(iconPath, IsEmpty()); +} + +TEST_F(ProjectStorage, synchronize_type_hints) +{ + auto package{createSimpleSynchronizationPackage()}; + package.typeAnnotations = createTypeAnnotions(); + package.updatedTypeAnnotationSourceIds = createUpdatedTypeAnnotionSourceIds( + package.typeAnnotations); + + storage.synchronize(package); + + ASSERT_THAT(storage.typeHints(fetchTypeId(sourceId1, "QQuickItem")), + UnorderedElementsAre(IsTypeHint("canBeContainer", "true"), + IsTypeHint("forceClip", "false"))); +} + +TEST_F(ProjectStorage, synchronize_removes_type_hints) +{ + auto package{createSimpleSynchronizationPackage()}; + package.typeAnnotations = createTypeAnnotions(); + package.updatedTypeAnnotationSourceIds = createUpdatedTypeAnnotionSourceIds( + package.typeAnnotations); + storage.synchronize(package); + package.typeAnnotations.clear(); + + storage.synchronize(package); + + ASSERT_THAT(storage.typeHints(fetchTypeId(sourceId2, "QObject")), IsEmpty()); +} + +TEST_F(ProjectStorage, return_empty_type_hints_if_no_type_hints_exists) +{ + auto package{createSimpleSynchronizationPackage()}; + storage.synchronize(package); + + auto typeHints = storage.typeHints(fetchTypeId(sourceId2, "QObject")); + + ASSERT_THAT(typeHints, IsEmpty()); +} + +TEST_F(ProjectStorage, return_empty_type_hints_if_type_does_not_exists) +{ + auto typeHints = storage.typeHints(fetchTypeId(sourceId2, "QObject")); + + ASSERT_THAT(typeHints, IsEmpty()); +} + +TEST_F(ProjectStorage, synchronize_item_library_entries) +{ + auto package{createSimpleSynchronizationPackage()}; + package.typeAnnotations = createTypeAnnotions(); + package.updatedTypeAnnotationSourceIds = createUpdatedTypeAnnotionSourceIds( + package.typeAnnotations); + + storage.synchronize(package); + + ASSERT_THAT( + storage.allItemLibraryEntries(), + UnorderedElementsAre( + IsItemLibraryEntry(fetchTypeId(sourceId2, "QObject"), + "Foo", + "/path/icon", + "Basic Items", + "QtQuick", + "Foo is a Item", + "/path/templates/item.qml", + UnorderedElementsAre(IsItemLibraryProperty("x", "double", 32.1), + IsItemLibraryProperty("y", "double", 12.3)), + UnorderedElementsAre("/path/templates/frame.png", + "/path/templates/frame.frag")), + IsItemLibraryEntry(fetchTypeId(sourceId2, "QObject"), + "Bar", + "/path/icon2", + "Basic Items", + "QtQuick", + "Bar is a Item", + "", + UnorderedElementsAre(IsItemLibraryProperty("color", "color", "#blue")), + IsEmpty()), + IsItemLibraryEntry(fetchTypeId(sourceId1, "QQuickItem"), + "Item", + "/path/icon3", + "Advanced Items", + "QtQuick", + "Item is an Object", + "", + UnorderedElementsAre(IsItemLibraryProperty("x", "double", 1), + IsItemLibraryProperty("y", "double", 2)), + IsEmpty()))); +} + +TEST_F(ProjectStorage, synchronize_removes_item_library_entries) +{ + auto package{createSimpleSynchronizationPackage()}; + package.typeAnnotations = createTypeAnnotions(); + package.updatedTypeAnnotationSourceIds = createUpdatedTypeAnnotionSourceIds( + package.typeAnnotations); + storage.synchronize(package); + package.typeAnnotations.clear(); + + storage.synchronize(package); + + ASSERT_THAT(storage.allItemLibraryEntries(), IsEmpty()); +} + +TEST_F(ProjectStorage, synchronize_udpates_item_library_entries) +{ + auto package{createSimpleSynchronizationPackage()}; + package.typeAnnotations = createTypeAnnotions(); + package.updatedTypeAnnotationSourceIds = createUpdatedTypeAnnotionSourceIds( + package.typeAnnotations); + storage.synchronize(package); + package.typeAnnotations[0].itemLibraryJson + = R"xy([{"name":"Foo","iconPath":"/path/icon","category":"Basic Items", "import":"QtQuick","toolTip":"Foo is a Item", "properties":[["x", "double", 32.1], ["y", "double", 12.3]]}])xy"; + + storage.synchronize(package); + + ASSERT_THAT(storage.itemLibraryEntries(fetchTypeId(sourceId2, "QObject")), + ElementsAre( + IsItemLibraryEntry(fetchTypeId(sourceId2, "QObject"), + "Foo", + "/path/icon", + "Basic Items", + "QtQuick", + "Foo is a Item", + "", + UnorderedElementsAre(IsItemLibraryProperty("x", "double", 32.1), + IsItemLibraryProperty("y", "double", 12.3)), + IsEmpty()))); +} + +TEST_F(ProjectStorage, get_all_item_library_entries) +{ + auto package{createSimpleSynchronizationPackage()}; + package.typeAnnotations = createTypeAnnotions(); + package.updatedTypeAnnotationSourceIds = createUpdatedTypeAnnotionSourceIds( + package.typeAnnotations); + storage.synchronize(package); + + auto entries = storage.allItemLibraryEntries(); + + ASSERT_THAT( + entries, + UnorderedElementsAre( + IsItemLibraryEntry(fetchTypeId(sourceId2, "QObject"), + "Foo", + "/path/icon", + "Basic Items", + "QtQuick", + "Foo is a Item", + "/path/templates/item.qml", + UnorderedElementsAre(IsItemLibraryProperty("x", "double", 32.1), + IsItemLibraryProperty("y", "double", 12.3)), + UnorderedElementsAre("/path/templates/frame.png", + "/path/templates/frame.frag")), + IsItemLibraryEntry(fetchTypeId(sourceId2, "QObject"), + "Bar", + "/path/icon2", + "Basic Items", + "QtQuick", + "Bar is a Item", + "", + UnorderedElementsAre(IsItemLibraryProperty("color", "color", "#blue")), + IsEmpty()), + IsItemLibraryEntry(fetchTypeId(sourceId1, "QQuickItem"), + "Item", + "/path/icon3", + "Advanced Items", + "QtQuick", + "Item is an Object", + "", + UnorderedElementsAre(IsItemLibraryProperty("x", "double", 1), + IsItemLibraryProperty("y", "double", 2)), + IsEmpty()))); +} + +TEST_F(ProjectStorage, get_item_library_entries_by_type_id) +{ + auto package{createSimpleSynchronizationPackage()}; + package.typeAnnotations = createTypeAnnotions(); + package.updatedTypeAnnotationSourceIds = createUpdatedTypeAnnotionSourceIds( + package.typeAnnotations); + storage.synchronize(package); + auto typeId = fetchTypeId(sourceId2, "QObject"); + + auto entries = storage.itemLibraryEntries(typeId); + + ASSERT_THAT( + entries, + UnorderedElementsAre( + IsItemLibraryEntry(fetchTypeId(sourceId2, "QObject"), + "Foo", + "/path/icon", + "Basic Items", + "QtQuick", + "Foo is a Item", + "/path/templates/item.qml", + UnorderedElementsAre(IsItemLibraryProperty("x", "double", 32.1), + IsItemLibraryProperty("y", "double", 12.3)), + UnorderedElementsAre("/path/templates/frame.png", + "/path/templates/frame.frag")), + IsItemLibraryEntry(fetchTypeId(sourceId2, "QObject"), + "Bar", + "/path/icon2", + "Basic Items", + "QtQuick", + "Bar is a Item", + "", + UnorderedElementsAre(IsItemLibraryProperty("color", "color", "#blue")), + IsEmpty()))); +} + +TEST_F(ProjectStorage, get_no_item_library_entries_if_type_id_is_invalid) +{ + auto package{createSimpleSynchronizationPackage()}; + package.typeAnnotations = createTypeAnnotions(); + package.updatedTypeAnnotationSourceIds = createUpdatedTypeAnnotionSourceIds( + package.typeAnnotations); + storage.synchronize(package); + + auto entries = storage.itemLibraryEntries(TypeId()); + + ASSERT_THAT(entries, IsEmpty()); +} + +TEST_F(ProjectStorage, get_item_library_entries_by_source_id) +{ + auto package{createSimpleSynchronizationPackage()}; + package.typeAnnotations = createTypeAnnotions(); + package.updatedTypeAnnotationSourceIds = createUpdatedTypeAnnotionSourceIds( + package.typeAnnotations); + storage.synchronize(package); + + auto entries = storage.itemLibraryEntries(sourceId2); + + ASSERT_THAT( + entries, + UnorderedElementsAre( + IsItemLibraryEntry(fetchTypeId(sourceId2, "QObject"), + "Foo", + "/path/icon", + "Basic Items", + "QtQuick", + "Foo is a Item", + "/path/templates/item.qml", + UnorderedElementsAre(IsItemLibraryProperty("x", "double", 32.1), + IsItemLibraryProperty("y", "double", 12.3)), + UnorderedElementsAre("/path/templates/frame.png", + "/path/templates/frame.frag")), + IsItemLibraryEntry(fetchTypeId(sourceId2, "QObject"), + "Bar", + "/path/icon2", + "Basic Items", + "QtQuick", + "Bar is a Item", + "", + UnorderedElementsAre(IsItemLibraryProperty("color", "color", "#blue")), + IsEmpty()))); +} + +TEST_F(ProjectStorage, return_type_ids_for_module_id) +{ + auto package{createBuiltinSynchronizationPackage()}; + storage.synchronize(package); + + auto typeIds = storage.typeIds(QMLModuleId); + + ASSERT_THAT(typeIds, + UnorderedElementsAre(fetchTypeId(sourceId1, "bool"), + fetchTypeId(sourceId1, "int"), + fetchTypeId(sourceId1, "double"), + fetchTypeId(sourceId1, "date"), + fetchTypeId(sourceId1, "string"), + fetchTypeId(sourceId1, "url"), + fetchTypeId(sourceId1, "var"))); +} + +TEST_F(ProjectStorage, get_hair_ids) +{ + auto package{createHeirPackage()}; + storage.synchronize(package); + auto typeId = fetchTypeId(sourceId1, "QObject"); + + auto heirIds = storage.heirIds(typeId); + + ASSERT_THAT(heirIds, + UnorderedElementsAre(fetchTypeId(sourceId1, "QObject2"), + fetchTypeId(sourceId1, "QObject3"), + fetchTypeId(sourceId1, "QObject4"), + fetchTypeId(sourceId1, "QObject5"))); +} + +TEST_F(ProjectStorage, get_no_hair_ids_for_invalid_type_id) +{ + auto package{createHeirPackage()}; + storage.synchronize(package); + auto typeId = TypeId{}; + + auto heirIds = storage.heirIds(typeId); + + ASSERT_THAT(heirIds, IsEmpty()); +} } // namespace diff --git a/tests/unit/tests/unittests/projectstorage/projectstoragepathwatcher-test.cpp b/tests/unit/tests/unittests/projectstorage/projectstoragepathwatcher-test.cpp index 9fc9c30551b..d6ed96d0cfc 100644 --- a/tests/unit/tests/unittests/projectstorage/projectstoragepathwatcher-test.cpp +++ b/tests/unit/tests/unittests/projectstorage/projectstoragepathwatcher-test.cpp @@ -39,6 +39,22 @@ using QmlDesigner::WatcherEntry; class ProjectStoragePathWatcher : public testing::Test { protected: + static void SetUpTestSuite() + { + static_database = std::make_unique(":memory:", Sqlite::JournalMode::Memory); + + static_projectStorage = std::make_unique>( + *static_database, static_database->isInitialized()); + } + + static void TearDownTestSuite() + { + static_projectStorage.reset(); + static_database.reset(); + } + + ~ProjectStoragePathWatcher() { static_projectStorage->resetForTestsOnly(); } + ProjectStoragePathWatcher() { ON_CALL(mockFileSystem, fileStatus(_)).WillByDefault([](auto sourceId) { @@ -52,6 +68,7 @@ protected: ON_CALL(mockFileSystem, directoryEntries(Eq(sourceContextPath3))) .WillByDefault(Return(SourceIds{sourceIds[4]})); } + static WatcherEntries sorted(WatcherEntries &&entries) { std::stable_sort(entries.begin(), entries.end()); @@ -62,8 +79,10 @@ protected: protected: NiceMock notifier; NiceMock mockFileSystem; - Sqlite::Database database{":memory:", Sqlite::JournalMode::Memory}; - QmlDesigner::ProjectStorage storage{database, database.isInitialized()}; + inline static std::unique_ptr static_database; + Sqlite::Database &database = *static_database; + inline static std::unique_ptr> static_projectStorage; + QmlDesigner::ProjectStorage &storage = *static_projectStorage; SourcePathCache pathCache{storage}; Watcher watcher{pathCache, mockFileSystem, ¬ifier}; NiceMock &mockQFileSytemWatcher = watcher.fileSystemWatcher(); diff --git a/tests/unit/tests/unittests/projectstorage/projectstorageupdater-test.cpp b/tests/unit/tests/unittests/projectstorage/projectstorageupdater-test.cpp index c167e3eb8b5..d9578d2f1b6 100644 --- a/tests/unit/tests/unittests/projectstorage/projectstorageupdater-test.cpp +++ b/tests/unit/tests/unittests/projectstorage/projectstorageupdater-test.cpp @@ -31,6 +31,7 @@ using QmlDesigner::Storage::Synchronization::ModuleExportedImport; using QmlDesigner::Storage::Synchronization::ProjectData; using QmlDesigner::Storage::Synchronization::SynchronizationPackage; using QmlDesigner::Storage::TypeTraits; +using QmlDesigner::Storage::TypeTraitsKind; using QmlDesigner::Storage::Version; MATCHER_P5(IsStorageType, @@ -136,6 +137,20 @@ auto IsPropertyEditorQmlPath(const ModuleIdMatcher &moduleIdMatcher, class ProjectStorageUpdater : public testing::Test { public: + static void SetUpTestSuite() + { + static_database = std::make_unique(":memory:", Sqlite::JournalMode::Memory); + + static_projectStorage = std::make_unique>( + *static_database, static_database->isInitialized()); + } + + static void TearDownTestSuite() + { + static_projectStorage.reset(); + static_database.reset(); + } + ProjectStorageUpdater() { setFilesChanged({qmltypesPathSourceId, @@ -207,6 +222,8 @@ public: }); } + ~ProjectStorageUpdater() { static_projectStorage->resetForTestsOnly(); } + void setFilesDontChanged(const QmlDesigner::SourceIds &sourceIds) { for (auto sourceId : sourceIds) { @@ -287,8 +304,10 @@ protected: NiceMock qmlTypesParserMock; NiceMock qmlDocumentParserMock; QmlDesigner::FileStatusCache fileStatusCache{fileSystemMock}; - Sqlite::Database database{":memory:", Sqlite::JournalMode::Memory}; - QmlDesigner::ProjectStorage storage{database, database.isInitialized()}; + inline static std::unique_ptr static_database; + Sqlite::Database &database = *static_database; + inline static std::unique_ptr> static_projectStorage; + QmlDesigner::ProjectStorage &storage = *static_projectStorage; QmlDesigner::SourcePathCache> sourcePathCache{ storage}; NiceMock patchWatcherMock; @@ -323,14 +342,14 @@ protected: "QObject", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - Storage::TypeTraits::Reference, + Storage::TypeTraitsKind::Reference, qmltypesPathSourceId, {Storage::Synchronization::ExportedType{exampleModuleId, "Object"}, Storage::Synchronization::ExportedType{exampleModuleId, "Obj"}}}; Storage::Synchronization::Type itemType{"QItem", Storage::Synchronization::ImportedType{}, Storage::Synchronization::ImportedType{}, - Storage::TypeTraits::Reference, + Storage::TypeTraitsKind::Reference, qmltypes2PathSourceId, {Storage::Synchronization::ExportedType{exampleModuleId, "Item"}}}; @@ -580,7 +599,7 @@ TEST_F(ProjectStorageUpdater, synchronize_qml_documents) UnorderedElementsAre( AllOf(IsStorageType("First.qml", Storage::Synchronization::ImportedType{"Object"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -588,7 +607,7 @@ TEST_F(ProjectStorageUpdater, synchronize_qml_documents) IsExportedType(pathModuleId, "First", -1, -1)))), AllOf(IsStorageType("First2.qml", Storage::Synchronization::ImportedType{"Object2"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId2, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -596,7 +615,7 @@ TEST_F(ProjectStorageUpdater, synchronize_qml_documents) IsExportedType(pathModuleId, "First2", -1, -1)))), AllOf(IsStorageType("Second.qml", Storage::Synchronization::ImportedType{"Object3"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId3, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -655,7 +674,7 @@ TEST_F(ProjectStorageUpdater, synchronize_add_only_qml_document_in_directory) UnorderedElementsAre( AllOf(IsStorageType("First.qml", Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::Minimal), Field(&Storage::Synchronization::Type::exportedTypes, @@ -664,7 +683,7 @@ TEST_F(ProjectStorageUpdater, synchronize_add_only_qml_document_in_directory) IsExportedType(pathModuleId, "First", -1, -1)))), AllOf(IsStorageType("First2.qml", Storage::Synchronization::ImportedType{"Object2"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId2, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -715,7 +734,7 @@ TEST_F(ProjectStorageUpdater, synchronize_removes_qml_document) UnorderedElementsAre( AllOf(IsStorageType("First.qml", Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::Minimal), Field(&Storage::Synchronization::Type::exportedTypes, @@ -724,7 +743,7 @@ TEST_F(ProjectStorageUpdater, synchronize_removes_qml_document) IsExportedType(pathModuleId, "First", -1, -1)))), AllOf(IsStorageType("First2.qml", Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId2, Storage::Synchronization::ChangeLevel::Minimal), Field(&Storage::Synchronization::Type::exportedTypes, @@ -776,7 +795,7 @@ TEST_F(ProjectStorageUpdater, synchronize_removes_qml_document_in_qmldir_only) UnorderedElementsAre( AllOf(IsStorageType("First.qml", Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::Minimal), Field(&Storage::Synchronization::Type::exportedTypes, @@ -784,7 +803,7 @@ TEST_F(ProjectStorageUpdater, synchronize_removes_qml_document_in_qmldir_only) IsExportedType(pathModuleId, "First", -1, -1)))), AllOf(IsStorageType("First2.qml", Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId2, Storage::Synchronization::ChangeLevel::Minimal), Field(&Storage::Synchronization::Type::exportedTypes, @@ -833,7 +852,7 @@ TEST_F(ProjectStorageUpdater, synchronize_add_qml_document_to_qmldir) UnorderedElementsAre( AllOf(IsStorageType("First.qml", Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::Minimal), Field(&Storage::Synchronization::Type::exportedTypes, @@ -841,7 +860,7 @@ TEST_F(ProjectStorageUpdater, synchronize_add_qml_document_to_qmldir) IsExportedType(pathModuleId, "First", -1, -1)))), AllOf(IsStorageType("First2.qml", Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId2, Storage::Synchronization::ChangeLevel::Minimal), Field(&Storage::Synchronization::Type::exportedTypes, @@ -889,7 +908,7 @@ TEST_F(ProjectStorageUpdater, synchronize_remove_qml_document_from_qmldir) UnorderedElementsAre( AllOf(IsStorageType("First.qml", Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::Minimal), Field(&Storage::Synchronization::Type::exportedTypes, @@ -897,7 +916,7 @@ TEST_F(ProjectStorageUpdater, synchronize_remove_qml_document_from_qmldir) IsExportedType(pathModuleId, "First", -1, -1)))), AllOf(IsStorageType("First2.qml", Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId2, Storage::Synchronization::ChangeLevel::Minimal), Field(&Storage::Synchronization::Type::exportedTypes, @@ -941,7 +960,7 @@ TEST_F(ProjectStorageUpdater, synchronize_qml_documents_dont_update_if_up_to_dat UnorderedElementsAre( AllOf(IsStorageType("First.qml", Storage::Synchronization::ImportedType{"Object"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -949,7 +968,7 @@ TEST_F(ProjectStorageUpdater, synchronize_qml_documents_dont_update_if_up_to_dat IsExportedType(pathModuleId, "First", -1, -1)))), AllOf(IsStorageType("First2.qml", Storage::Synchronization::ImportedType{"Object2"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId2, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -957,7 +976,7 @@ TEST_F(ProjectStorageUpdater, synchronize_qml_documents_dont_update_if_up_to_dat IsExportedType(pathModuleId, "First2", -1, -1)))), AllOf(IsStorageType("Second.qml", Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId3, Storage::Synchronization::ChangeLevel::Minimal), Field(&Storage::Synchronization::Type::exportedTypes, @@ -1014,13 +1033,13 @@ TEST_F(ProjectStorageUpdater, synchroniz_if_qmldir_file_has_not_changed) Eq(itemType), AllOf(IsStorageType("First.qml", Storage::Synchronization::ImportedType{"Object"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::ExcludeExportedTypes), Field(&Storage::Synchronization::Type::exportedTypes, IsEmpty())), AllOf(IsStorageType("First2.qml", Storage::Synchronization::ImportedType{"Object2"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId2, Storage::Synchronization::ChangeLevel::ExcludeExportedTypes), Field(&Storage::Synchronization::Type::exportedTypes, IsEmpty())))), @@ -1063,7 +1082,7 @@ TEST_F(ProjectStorageUpdater, synchroniz_if_qmldir_file_has_not_changed_and_some Eq(objectType), AllOf(IsStorageType("First.qml", Storage::Synchronization::ImportedType{"Object"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::ExcludeExportedTypes), Field(&Storage::Synchronization::Type::exportedTypes, IsEmpty())))), @@ -1118,7 +1137,7 @@ TEST_F(ProjectStorageUpdater, synchroniz_if_qmldir_file_has_changed_and_some_rem UnorderedElementsAre(AllOf( IsStorageType("First2.qml", Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId2, Storage::Synchronization::ChangeLevel::Minimal), Field(&Storage::Synchronization::Type::exportedTypes, @@ -1230,7 +1249,7 @@ TEST_F(ProjectStorageUpdater, synchronize_qml_documents_with_different_version_b UnorderedElementsAre(AllOf( IsStorageType("First.qml", Storage::Synchronization::ImportedType{"Object"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -1272,7 +1291,7 @@ TEST_F(ProjectStorageUpdater, synchronize_qml_documents_with_different_type_name UnorderedElementsAre(AllOf( IsStorageType("First.qml", Storage::Synchronization::ImportedType{"Object"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -1820,21 +1839,21 @@ TEST_F(ProjectStorageUpdater, synchronize_qml_documents_without_qmldir) UnorderedElementsAre( AllOf(IsStorageType("First.qml", Storage::Synchronization::ImportedType{"Object"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(pathModuleId, "First", -1, -1)))), AllOf(IsStorageType("First2.qml", Storage::Synchronization::ImportedType{"Object2"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId2, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(pathModuleId, "First2", -1, -1)))), AllOf(IsStorageType("Second.qml", Storage::Synchronization::ImportedType{"Object3"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId3, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -1930,7 +1949,7 @@ TEST_F(ProjectStorageUpdater, synchronize_qml_documents_without_qmldir_add_qml_d UnorderedElementsAre(AllOf( IsStorageType("Second.qml", Storage::Synchronization::ImportedType{"Object3"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId3, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -2019,7 +2038,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_directories) UnorderedElementsAre( AllOf(IsStorageType("First.qml", Storage::Synchronization::ImportedType{"Object"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -2027,7 +2046,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_directories) IsExportedType(pathModuleId, "First", -1, -1)))), AllOf(IsStorageType("First2.qml", Storage::Synchronization::ImportedType{"Object2"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId2, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -2035,7 +2054,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_directories) IsExportedType(pathModuleId, "First2", -1, -1)))), AllOf(IsStorageType("Second.qml", Storage::Synchronization::ImportedType{"Object3"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId3, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -2162,7 +2181,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_directories_and_qmldir) UnorderedElementsAre( AllOf(IsStorageType("First.qml", Storage::Synchronization::ImportedType{"Object"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -2170,7 +2189,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_directories_and_qmldir) IsExportedType(pathModuleId, "First", -1, -1)))), AllOf(IsStorageType("First2.qml", Storage::Synchronization::ImportedType{"Object2"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId2, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -2178,7 +2197,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_directories_and_qmldir) IsExportedType(pathModuleId, "First2", -1, -1)))), AllOf(IsStorageType("Second.qml", Storage::Synchronization::ImportedType{"Object3"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId3, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -2276,7 +2295,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_add_only_qml_document_in_directory UnorderedElementsAre( AllOf(IsStorageType("First.qml", Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::Minimal), Field(&Storage::Synchronization::Type::exportedTypes, @@ -2285,7 +2304,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_add_only_qml_document_in_directory IsExportedType(pathModuleId, "First", -1, -1)))), AllOf(IsStorageType("First2.qml", Storage::Synchronization::ImportedType{"Object2"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId2, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -2336,7 +2355,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_removes_qml_document) UnorderedElementsAre( AllOf(IsStorageType("First.qml", Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::Minimal), Field(&Storage::Synchronization::Type::exportedTypes, @@ -2345,7 +2364,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_removes_qml_document) IsExportedType(pathModuleId, "First", -1, -1)))), AllOf(IsStorageType("First2.qml", Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId2, Storage::Synchronization::ChangeLevel::Minimal), Field(&Storage::Synchronization::Type::exportedTypes, @@ -2397,7 +2416,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_removes_qml_document_in_qmldir_onl UnorderedElementsAre( AllOf(IsStorageType("First.qml", Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::Minimal), Field(&Storage::Synchronization::Type::exportedTypes, @@ -2405,7 +2424,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_removes_qml_document_in_qmldir_onl IsExportedType(pathModuleId, "First", -1, -1)))), AllOf(IsStorageType("First2.qml", Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId2, Storage::Synchronization::ChangeLevel::Minimal), Field(&Storage::Synchronization::Type::exportedTypes, @@ -2454,7 +2473,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_directories_add_qml_document_to_qm UnorderedElementsAre( AllOf(IsStorageType("First.qml", Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::Minimal), Field(&Storage::Synchronization::Type::exportedTypes, @@ -2462,7 +2481,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_directories_add_qml_document_to_qm IsExportedType(pathModuleId, "First", -1, -1)))), AllOf(IsStorageType("First2.qml", Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId2, Storage::Synchronization::ChangeLevel::Minimal), Field(&Storage::Synchronization::Type::exportedTypes, @@ -2510,7 +2529,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_directories_remove_qml_document_fr UnorderedElementsAre( AllOf(IsStorageType("First.qml", Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::Minimal), Field(&Storage::Synchronization::Type::exportedTypes, @@ -2518,7 +2537,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_directories_remove_qml_document_fr IsExportedType(pathModuleId, "First", -1, -1)))), AllOf(IsStorageType("First2.qml", Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId2, Storage::Synchronization::ChangeLevel::Minimal), Field(&Storage::Synchronization::Type::exportedTypes, @@ -2562,7 +2581,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_directories_dont_update_qml_docume UnorderedElementsAre( AllOf(IsStorageType("First.qml", Storage::Synchronization::ImportedType{"Object"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -2570,7 +2589,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_directories_dont_update_qml_docume IsExportedType(pathModuleId, "First", -1, -1)))), AllOf(IsStorageType("First2.qml", Storage::Synchronization::ImportedType{"Object2"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId2, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -2578,7 +2597,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_directories_dont_update_qml_docume IsExportedType(pathModuleId, "First2", -1, -1)))), AllOf(IsStorageType("Second.qml", Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId3, Storage::Synchronization::ChangeLevel::Minimal), Field(&Storage::Synchronization::Type::exportedTypes, @@ -2632,7 +2651,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_qmldirs_dont_update_qml_documents_ UnorderedElementsAre( AllOf(IsStorageType("First.qml", Storage::Synchronization::ImportedType{"Object"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -2640,7 +2659,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_qmldirs_dont_update_qml_documents_ IsExportedType(pathModuleId, "First", -1, -1)))), AllOf(IsStorageType("First2.qml", Storage::Synchronization::ImportedType{"Object2"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId2, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -2648,7 +2667,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_qmldirs_dont_update_qml_documents_ IsExportedType(pathModuleId, "First2", -1, -1)))), AllOf(IsStorageType("Second.qml", Storage::Synchronization::ImportedType{}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId3, Storage::Synchronization::ChangeLevel::Minimal), Field(&Storage::Synchronization::Type::exportedTypes, @@ -2705,13 +2724,13 @@ TEST_F(ProjectStorageUpdater, watcher_updates_directory_but_not_qmldir) Eq(itemType), AllOf(IsStorageType("First.qml", Storage::Synchronization::ImportedType{"Object"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::ExcludeExportedTypes), Field(&Storage::Synchronization::Type::exportedTypes, IsEmpty())), AllOf(IsStorageType("First2.qml", Storage::Synchronization::ImportedType{"Object2"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId2, Storage::Synchronization::ChangeLevel::ExcludeExportedTypes), Field(&Storage::Synchronization::Type::exportedTypes, IsEmpty())))), @@ -2745,13 +2764,13 @@ TEST_F(ProjectStorageUpdater, watcher_updates_qml_documents) UnorderedElementsAre( AllOf(IsStorageType("First.qml", Storage::Synchronization::ImportedType{"Object"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::ExcludeExportedTypes), Field(&Storage::Synchronization::Type::exportedTypes, IsEmpty())), AllOf(IsStorageType("First2.qml", Storage::Synchronization::ImportedType{"Object2"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId2, Storage::Synchronization::ChangeLevel::ExcludeExportedTypes), Field(&Storage::Synchronization::Type::exportedTypes, IsEmpty())))), @@ -2779,7 +2798,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_removed_qml_documents) UnorderedElementsAre(AllOf( IsStorageType("First.qml", Storage::Synchronization::ImportedType{"Object"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::ExcludeExportedTypes), Field(&Storage::Synchronization::Type::exportedTypes, IsEmpty())))), @@ -2944,7 +2963,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_directories_and_but_not_included_q UnorderedElementsAre( AllOf(IsStorageType("First.qml", Storage::Synchronization::ImportedType{"Object"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -2952,7 +2971,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_directories_and_but_not_included_q IsExportedType(pathModuleId, "First", -1, -1)))), AllOf(IsStorageType("First2.qml", Storage::Synchronization::ImportedType{"Object2"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId2, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -2960,7 +2979,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_directories_and_but_not_included_q IsExportedType(pathModuleId, "First2", -1, -1)))), AllOf(IsStorageType("Second.qml", Storage::Synchronization::ImportedType{"Object3"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId3, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -3018,7 +3037,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_qmldir_and_but_not_included_qml_do UnorderedElementsAre( AllOf(IsStorageType("First.qml", Storage::Synchronization::ImportedType{"Object"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -3026,7 +3045,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_qmldir_and_but_not_included_qml_do IsExportedType(pathModuleId, "First", -1, -1)))), AllOf(IsStorageType("First2.qml", Storage::Synchronization::ImportedType{"Object2"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId2, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -3034,7 +3053,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_qmldir_and_but_not_included_qml_do IsExportedType(pathModuleId, "First2", -1, -1)))), AllOf(IsStorageType("Second.qml", Storage::Synchronization::ImportedType{"Object3"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId3, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -3111,7 +3130,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_qmldir_and_but_not_included_qmltyp Eq(itemType), AllOf(IsStorageType("First.qml", Storage::Synchronization::ImportedType{"Object"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -3119,7 +3138,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_qmldir_and_but_not_included_qmltyp IsExportedType(pathModuleId, "First", -1, -1)))), AllOf(IsStorageType("First2.qml", Storage::Synchronization::ImportedType{"Object2"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId2, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -3127,7 +3146,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_qmldir_and_but_not_included_qmltyp IsExportedType(pathModuleId, "First2", -1, -1)))), AllOf(IsStorageType("Second.qml", Storage::Synchronization::ImportedType{"Object3"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId3, Storage::Synchronization::ChangeLevel::Full), Field(&Storage::Synchronization::Type::exportedTypes, @@ -3224,13 +3243,13 @@ TEST_F(ProjectStorageUpdater, input_is_reused_next_call_if_an_error_happens) Eq(itemType), AllOf(IsStorageType("First.qml", Storage::Synchronization::ImportedType{"Object"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::ExcludeExportedTypes), Field(&Storage::Synchronization::Type::exportedTypes, IsEmpty())), AllOf(IsStorageType("First2.qml", Storage::Synchronization::ImportedType{"Object2"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId2, Storage::Synchronization::ChangeLevel::ExcludeExportedTypes), Field(&Storage::Synchronization::Type::exportedTypes, IsEmpty())))), @@ -3277,23 +3296,22 @@ TEST_F(ProjectStorageUpdater, input_is_reused_next_call_if_an_error_happens_and_ synchronize(AllOf( Field(&SynchronizationPackage::imports, UnorderedElementsAre(import1, import2, import4, import5)), - Field( - &SynchronizationPackage::types, - UnorderedElementsAre( - Eq(objectType), - Eq(itemType), - AllOf(IsStorageType("First.qml", - Storage::Synchronization::ImportedType{"Object"}, - TypeTraits::Reference, - qmlDocumentSourceId1, - Storage::Synchronization::ChangeLevel::ExcludeExportedTypes), - Field(&Storage::Synchronization::Type::exportedTypes, IsEmpty())), - AllOf(IsStorageType("First2.qml", - Storage::Synchronization::ImportedType{"Object2"}, - TypeTraits::Reference, - qmlDocumentSourceId2, - Storage::Synchronization::ChangeLevel::ExcludeExportedTypes), - Field(&Storage::Synchronization::Type::exportedTypes, IsEmpty())))), + Field(&SynchronizationPackage::types, + UnorderedElementsAre( + Eq(objectType), + Eq(itemType), + AllOf(IsStorageType("First.qml", + Storage::Synchronization::ImportedType{"Object"}, + TypeTraitsKind::Reference, + qmlDocumentSourceId1, + Storage::Synchronization::ChangeLevel::ExcludeExportedTypes), + Field(&Storage::Synchronization::Type::exportedTypes, IsEmpty())), + AllOf(IsStorageType("First2.qml", + Storage::Synchronization::ImportedType{"Object2"}, + TypeTraitsKind::Reference, + qmlDocumentSourceId2, + Storage::Synchronization::ChangeLevel::ExcludeExportedTypes), + Field(&Storage::Synchronization::Type::exportedTypes, IsEmpty())))), Field(&SynchronizationPackage::updatedSourceIds, UnorderedElementsAre(qmlDocumentSourceId1, qmlDocumentSourceId2, @@ -3346,13 +3364,13 @@ TEST_F(ProjectStorageUpdater, input_is_reused_next_call_if_an_error_happens_and_ Eq(itemType), AllOf(IsStorageType("First.qml", Storage::Synchronization::ImportedType{"Object"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::ExcludeExportedTypes), Field(&Storage::Synchronization::Type::exportedTypes, IsEmpty())), AllOf(IsStorageType("First2.qml", Storage::Synchronization::ImportedType{"Object2"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId2, Storage::Synchronization::ChangeLevel::ExcludeExportedTypes), Field(&Storage::Synchronization::Type::exportedTypes, IsEmpty())))), @@ -3401,13 +3419,13 @@ TEST_F(ProjectStorageUpdater, input_is_cleared_after_successful_update) UnorderedElementsAre( AllOf(IsStorageType("First.qml", Storage::Synchronization::ImportedType{"Object"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId1, Storage::Synchronization::ChangeLevel::ExcludeExportedTypes), Field(&Storage::Synchronization::Type::exportedTypes, IsEmpty())), AllOf(IsStorageType("First2.qml", Storage::Synchronization::ImportedType{"Object2"}, - TypeTraits::Reference, + TypeTraitsKind::Reference, qmlDocumentSourceId2, Storage::Synchronization::ChangeLevel::ExcludeExportedTypes), Field(&Storage::Synchronization::Type::exportedTypes, IsEmpty())))), diff --git a/tests/unit/tests/unittests/projectstorage/qmltypesparser-test.cpp b/tests/unit/tests/unittests/projectstorage/qmltypesparser-test.cpp index ba4b4865b86..78f8f41d6c4 100644 --- a/tests/unit/tests/unittests/projectstorage/qmltypesparser-test.cpp +++ b/tests/unit/tests/unittests/projectstorage/qmltypesparser-test.cpp @@ -58,6 +58,15 @@ MATCHER_P(HasFlag, flag, std::string(negation ? "hasn't " : "has ") + PrintToStr return bool(arg & flag); } +MATCHER_P(UsesCustomParser, + value, + std::string(negation ? "don't used custom parser " : "uses custom parser")) +{ + const Storage::TypeTraits &traits = arg; + + return traits.usesCustomParser == value; +} + template auto IsTypeTrait(const Matcher &matcher) { @@ -215,12 +224,12 @@ TEST_F(QmlTypesParser, types) UnorderedElementsAre(IsType("QObject", Synchronization::ImportedType{}, Synchronization::ImportedType{}, - Storage::TypeTraits::Reference, + Storage::TypeTraitsKind::Reference, qmltypesFileSourceId), IsType("QQmlComponent", Synchronization::ImportedType{}, Synchronization::ImportedType{}, - Storage::TypeTraits::Reference, + Storage::TypeTraitsKind::Reference, qmltypesFileSourceId))); } @@ -238,12 +247,12 @@ TEST_F(QmlTypesParser, prototype) UnorderedElementsAre(IsType("QObject", Synchronization::ImportedType{}, Synchronization::ImportedType{}, - Storage::TypeTraits::Reference, + Storage::TypeTraitsKind::Reference, qmltypesFileSourceId), IsType("QQmlComponent", Synchronization::ImportedType{"QObject"}, Synchronization::ImportedType{}, - Storage::TypeTraits::Reference, + Storage::TypeTraitsKind::Reference, qmltypesFileSourceId))); } @@ -261,12 +270,12 @@ TEST_F(QmlTypesParser, extension) UnorderedElementsAre(IsType("QObject", Synchronization::ImportedType{}, Synchronization::ImportedType{}, - Storage::TypeTraits::Reference, + Storage::TypeTraitsKind::Reference, qmltypesFileSourceId), IsType("QQmlComponent", Synchronization::ImportedType{}, Synchronization::ImportedType{"QObject"}, - Storage::TypeTraits::Reference, + Storage::TypeTraitsKind::Reference, qmltypesFileSourceId))); } @@ -588,6 +597,8 @@ TEST_F(QmlTypesParser, enumeration_is_exported_as_type) }})"}; parser.parse(source, imports, types, projectData); + QmlDesigner::Storage::TypeTraits traits{QmlDesigner::Storage::TypeTraitsKind::Value}; + traits.isEnum = true; ASSERT_THAT( types, @@ -595,7 +606,7 @@ TEST_F(QmlTypesParser, enumeration_is_exported_as_type) AllOf(IsType("QObject::NamedColorSpace", Synchronization::ImportedType{}, Synchronization::ImportedType{}, - Storage::TypeTraits::Value | Storage::TypeTraits::IsEnum, + traits, qmltypesFileSourceId), Field(&Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQmlNativeModuleId, @@ -604,7 +615,7 @@ TEST_F(QmlTypesParser, enumeration_is_exported_as_type) AllOf(IsType("QObject::VerticalLayoutDirection", Synchronization::ImportedType{}, Synchronization::ImportedType{}, - Storage::TypeTraits::Value | Storage::TypeTraits::IsEnum, + traits, qmltypesFileSourceId), Field(&Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQmlNativeModuleId, @@ -632,13 +643,15 @@ TEST_F(QmlTypesParser, enumeration_is_exported_as_type_with_alias) }})"}; parser.parse(source, imports, types, projectData); + QmlDesigner::Storage::TypeTraits traits{QmlDesigner::Storage::TypeTraitsKind::Value}; + traits.isEnum = true; ASSERT_THAT(types, UnorderedElementsAre( AllOf(IsType("QObject::NamedColorSpaces", Synchronization::ImportedType{}, Synchronization::ImportedType{}, - Storage::TypeTraits::Value | Storage::TypeTraits::IsEnum, + traits, qmltypesFileSourceId), Field(&Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQmlNativeModuleId, @@ -678,13 +691,15 @@ TEST_F(QmlTypesParser, enumeration_is_exported_as_type_with_alias_too) }})"}; parser.parse(source, imports, types, projectData); + QmlDesigner::Storage::TypeTraits traits{QmlDesigner::Storage::TypeTraitsKind::Value}; + traits.isEnum = true; ASSERT_THAT(types, UnorderedElementsAre( AllOf(IsType("QObject::NamedColorSpaces", Synchronization::ImportedType{}, Synchronization::ImportedType{}, - Storage::TypeTraits::Value | Storage::TypeTraits::IsEnum, + traits, qmltypesFileSourceId), Field(&Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQmlNativeModuleId, @@ -760,7 +775,7 @@ TEST_F(QmlTypesParser, access_type_is_reference) parser.parse(source, imports, types, projectData); - ASSERT_THAT(types, ElementsAre(IsTypeTrait(Storage::TypeTraits::Reference))); + ASSERT_THAT(types, ElementsAre(IsTypeTrait(Storage::TypeTraitsKind::Reference))); } TEST_F(QmlTypesParser, access_type_is_value) @@ -772,7 +787,7 @@ TEST_F(QmlTypesParser, access_type_is_value) parser.parse(source, imports, types, projectData); - ASSERT_THAT(types, ElementsAre(IsTypeTrait(Storage::TypeTraits::Value))); + ASSERT_THAT(types, ElementsAre(IsTypeTrait(Storage::TypeTraitsKind::Value))); } TEST_F(QmlTypesParser, access_type_is_sequence) @@ -784,7 +799,7 @@ TEST_F(QmlTypesParser, access_type_is_sequence) parser.parse(source, imports, types, projectData); - ASSERT_THAT(types, ElementsAre(IsTypeTrait(Storage::TypeTraits::Sequence))); + ASSERT_THAT(types, ElementsAre(IsTypeTrait(Storage::TypeTraitsKind::Sequence))); } TEST_F(QmlTypesParser, access_type_is_none) @@ -796,7 +811,7 @@ TEST_F(QmlTypesParser, access_type_is_none) parser.parse(source, imports, types, projectData); - ASSERT_THAT(types, ElementsAre(IsTypeTrait(Storage::TypeTraits::None))); + ASSERT_THAT(types, ElementsAre(IsTypeTrait(Storage::TypeTraitsKind::None))); } TEST_F(QmlTypesParser, uses_custom_parser) @@ -808,7 +823,7 @@ TEST_F(QmlTypesParser, uses_custom_parser) parser.parse(source, imports, types, projectData); - ASSERT_THAT(types, ElementsAre(IsTypeTrait(HasFlag(Storage::TypeTraits::UsesCustomParser)))); + ASSERT_THAT(types, ElementsAre(IsTypeTrait(UsesCustomParser(true)))); } TEST_F(QmlTypesParser, uses_no_custom_parser) @@ -820,7 +835,7 @@ TEST_F(QmlTypesParser, uses_no_custom_parser) parser.parse(source, imports, types, projectData); - ASSERT_THAT(types, ElementsAre(IsTypeTrait(Not(HasFlag(Storage::TypeTraits::UsesCustomParser))))); + ASSERT_THAT(types, ElementsAre(IsTypeTrait(UsesCustomParser(false)))); } } // namespace diff --git a/tests/unit/tests/unittests/projectstorage/typeannotationreader-test.cpp b/tests/unit/tests/unittests/projectstorage/typeannotationreader-test.cpp new file mode 100644 index 00000000000..ed5c1a0778b --- /dev/null +++ b/tests/unit/tests/unittests/projectstorage/typeannotationreader-test.cpp @@ -0,0 +1,758 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "../utils/googletest.h" + +#include + +#include +#include +#include + +namespace { + +template +auto IsTypeAnnotation(QmlDesigner::SourceId sourceId, + Utils::SmallStringView typeName, + QmlDesigner::ModuleId moduleId, + Utils::SmallStringView iconPath, + QmlDesigner::Storage::TypeTraits traits, + HintsJsonMatcher hintsJsonMatcher, + ItemLibraryJsonMatcher itemLibraryJsonMatcher) +{ + using QmlDesigner::Storage::Synchronization::TypeAnnotation; + return AllOf(Field("sourceId", &TypeAnnotation::sourceId, sourceId), + Field("typeName", &TypeAnnotation::typeName, typeName), + Field("moduleId", &TypeAnnotation::moduleId, moduleId), + Field("iconPath", &TypeAnnotation::iconPath, iconPath), + Field("traits", &TypeAnnotation::traits, traits), + Field("hintsJson", &TypeAnnotation::hintsJson, hintsJsonMatcher), + Field("itemLibraryJson", &TypeAnnotation::itemLibraryJson, itemLibraryJsonMatcher)); +} + +class TypeAnnotationReader : public testing::Test +{ +protected: + static void SetUpTestSuite() + { + static_database = std::make_unique(":memory:", Sqlite::JournalMode::Memory); + + static_projectStorage = std::make_unique>( + *static_database, static_database->isInitialized()); + } + + static void TearDownTestSuite() + { + static_projectStorage.reset(); + static_database.reset(); + } + + auto moduleId(Utils::SmallStringView name) const { return storage.moduleId(name); } + +protected: + inline static std::unique_ptr static_database; + Sqlite::Database &database = *static_database; + inline static std::unique_ptr> static_projectStorage; + QmlDesigner::ProjectStorage &storage = *static_projectStorage; + QmlDesigner::Storage::TypeAnnotationReader reader{storage}; + QmlDesigner::SourceId sourceId = QmlDesigner::SourceId::create(33); +}; + +TEST_F(TypeAnnotationReader, parse_type) +{ + auto content = QString{R"xy( + MetaInfo { + Type { + name: "QtQuick.Controls.Frame" + icon: "images/frame-icon16.png" + } + Type { + name: "QtQuick.Item" + icon: "images/item-icon16.png" + } + })xy"}; + QmlDesigner::Storage::TypeTraits traits; + + auto annotations = reader.parseTypeAnnotation(content, "/path", sourceId); + + ASSERT_THAT(annotations, + UnorderedElementsAre(IsTypeAnnotation(sourceId, + "Frame", + moduleId("QtQuick.Controls"), + "/path/images/frame-icon16.png", + traits, + IsEmpty(), + IsEmpty()), + IsTypeAnnotation(sourceId, + "Item", + moduleId("QtQuick"), + "/path/images/item-icon16.png", + traits, + IsEmpty(), + IsEmpty()))); +} + +TEST_F(TypeAnnotationReader, parse_true_canBeContainer) +{ + using QmlDesigner::FlagIs; + auto content = QString{R"xy( + MetaInfo { + Type { + name: "QtQuick.Controls.Frame" + icon: "images/frame-icon16.png" + + Hints { + canBeContainer: true + } + } + })xy"}; + QmlDesigner::Storage::TypeTraits traits; + traits.canBeContainer = FlagIs::True; + + auto annotations = reader.parseTypeAnnotation(content, "/path", sourceId); + + ASSERT_THAT(annotations, + ElementsAre(IsTypeAnnotation(sourceId, + "Frame", + moduleId("QtQuick.Controls"), + "/path/images/frame-icon16.png", + traits, + IsEmpty(), + IsEmpty()))); +} + +TEST_F(TypeAnnotationReader, parse_true_forceClip) +{ + using QmlDesigner::FlagIs; + auto content = QString{R"xy( + MetaInfo { + Type { + name: "QtQuick.Controls.Frame" + icon: "images/frame-icon16.png" + + Hints { + forceClip: true + } + } + })xy"}; + QmlDesigner::Storage::TypeTraits traits; + traits.forceClip = FlagIs::True; + + auto annotations = reader.parseTypeAnnotation(content, "/path", sourceId); + + ASSERT_THAT(annotations, + ElementsAre(IsTypeAnnotation(sourceId, + "Frame", + moduleId("QtQuick.Controls"), + "/path/images/frame-icon16.png", + traits, + IsEmpty(), + IsEmpty()))); +} + +TEST_F(TypeAnnotationReader, parse_true_doesLayoutChildren) +{ + using QmlDesigner::FlagIs; + auto content = QString{R"xy( + MetaInfo { + Type { + name: "QtQuick.Controls.Frame" + icon: "images/frame-icon16.png" + + Hints { + doesLayoutChildren: true + } + } + })xy"}; + QmlDesigner::Storage::TypeTraits traits; + traits.doesLayoutChildren = FlagIs::True; + + auto annotations = reader.parseTypeAnnotation(content, "/path", sourceId); + + ASSERT_THAT(annotations, + ElementsAre(IsTypeAnnotation(sourceId, + "Frame", + moduleId("QtQuick.Controls"), + "/path/images/frame-icon16.png", + traits, + IsEmpty(), + IsEmpty()))); +} + +TEST_F(TypeAnnotationReader, parse_true_canBeDroppedInFormEditor) +{ + using QmlDesigner::FlagIs; + auto content = QString{R"xy( + MetaInfo { + Type { + name: "QtQuick.Controls.Frame" + icon: "images/frame-icon16.png" + + Hints { + canBeDroppedInFormEditor: true + } + } + })xy"}; + QmlDesigner::Storage::TypeTraits traits; + traits.canBeDroppedInFormEditor = FlagIs::True; + + auto annotations = reader.parseTypeAnnotation(content, "/path", sourceId); + + ASSERT_THAT(annotations, + ElementsAre(IsTypeAnnotation(sourceId, + "Frame", + moduleId("QtQuick.Controls"), + "/path/images/frame-icon16.png", + traits, + IsEmpty(), + IsEmpty()))); +} + +TEST_F(TypeAnnotationReader, parse_true_canBeDroppedInNavigator) +{ + using QmlDesigner::FlagIs; + auto content = QString{R"xy( + MetaInfo { + Type { + name: "QtQuick.Controls.Frame" + icon: "images/frame-icon16.png" + + Hints { + canBeDroppedInNavigator: true + } + } + })xy"}; + QmlDesigner::Storage::TypeTraits traits; + traits.canBeDroppedInNavigator = FlagIs::True; + + auto annotations = reader.parseTypeAnnotation(content, "/path", sourceId); + + ASSERT_THAT(annotations, + ElementsAre(IsTypeAnnotation(sourceId, + "Frame", + moduleId("QtQuick.Controls"), + "/path/images/frame-icon16.png", + traits, + IsEmpty(), + IsEmpty()))); +} + +TEST_F(TypeAnnotationReader, parse_true_canBeDroppedInView3D) +{ + using QmlDesigner::FlagIs; + auto content = QString{R"xy( + MetaInfo { + Type { + name: "QtQuick.Controls.Frame" + icon: "images/frame-icon16.png" + + Hints { + canBeDroppedInView3D: true + } + } + })xy"}; + QmlDesigner::Storage::TypeTraits traits; + traits.canBeDroppedInView3D = FlagIs::True; + + auto annotations = reader.parseTypeAnnotation(content, "/path", sourceId); + + ASSERT_THAT(annotations, + ElementsAre(IsTypeAnnotation(sourceId, + "Frame", + moduleId("QtQuick.Controls"), + "/path/images/frame-icon16.png", + traits, + IsEmpty(), + IsEmpty()))); +} + +TEST_F(TypeAnnotationReader, parse_true_isMovable) +{ + using QmlDesigner::FlagIs; + auto content = QString{R"xy( + MetaInfo { + Type { + name: "QtQuick.Controls.Frame" + icon: "images/frame-icon16.png" + + Hints { + isMovable: true + } + } + })xy"}; + QmlDesigner::Storage::TypeTraits traits; + traits.isMovable = FlagIs::True; + + auto annotations = reader.parseTypeAnnotation(content, "/path", sourceId); + + ASSERT_THAT(annotations, + ElementsAre(IsTypeAnnotation(sourceId, + "Frame", + moduleId("QtQuick.Controls"), + "/path/images/frame-icon16.png", + traits, + IsEmpty(), + IsEmpty()))); +} + +TEST_F(TypeAnnotationReader, parse_true_isResizable) +{ + using QmlDesigner::FlagIs; + auto content = QString{R"xy( + MetaInfo { + Type { + name: "QtQuick.Controls.Frame" + icon: "images/frame-icon16.png" + + Hints { + isResizable: true + } + } + })xy"}; + QmlDesigner::Storage::TypeTraits traits; + traits.isResizable = FlagIs::True; + + auto annotations = reader.parseTypeAnnotation(content, "/path", sourceId); + + ASSERT_THAT(annotations, + ElementsAre(IsTypeAnnotation(sourceId, + "Frame", + moduleId("QtQuick.Controls"), + "/path/images/frame-icon16.png", + traits, + IsEmpty(), + IsEmpty()))); +} + +TEST_F(TypeAnnotationReader, parse_true_hasFormEditorItem) +{ + using QmlDesigner::FlagIs; + auto content = QString{R"xy( + MetaInfo { + Type { + name: "QtQuick.Controls.Frame" + icon: "images/frame-icon16.png" + + Hints { + hasFormEditorItem: true + } + } + })xy"}; + QmlDesigner::Storage::TypeTraits traits; + traits.hasFormEditorItem = FlagIs::True; + + auto annotations = reader.parseTypeAnnotation(content, "/path", sourceId); + + ASSERT_THAT(annotations, + ElementsAre(IsTypeAnnotation(sourceId, + "Frame", + moduleId("QtQuick.Controls"), + "/path/images/frame-icon16.png", + traits, + IsEmpty(), + IsEmpty()))); +} + +TEST_F(TypeAnnotationReader, parse_true_isStackedContainer) +{ + using QmlDesigner::FlagIs; + auto content = QString{R"xy( + MetaInfo { + Type { + name: "QtQuick.Controls.Frame" + icon: "images/frame-icon16.png" + + Hints { + isStackedContainer: true + } + } + })xy"}; + QmlDesigner::Storage::TypeTraits traits; + traits.isStackedContainer = FlagIs::True; + + auto annotations = reader.parseTypeAnnotation(content, "/path", sourceId); + + ASSERT_THAT(annotations, + ElementsAre(IsTypeAnnotation(sourceId, + "Frame", + moduleId("QtQuick.Controls"), + "/path/images/frame-icon16.png", + traits, + IsEmpty(), + IsEmpty()))); +} + +TEST_F(TypeAnnotationReader, parse_true_takesOverRenderingOfChildren) +{ + using QmlDesigner::FlagIs; + auto content = QString{R"xy( + MetaInfo { + Type { + name: "QtQuick.Controls.Frame" + icon: "images/frame-icon16.png" + + Hints { + takesOverRenderingOfChildren: true + } + } + })xy"}; + QmlDesigner::Storage::TypeTraits traits; + traits.takesOverRenderingOfChildren = FlagIs::True; + + auto annotations = reader.parseTypeAnnotation(content, "/path", sourceId); + + ASSERT_THAT(annotations, + ElementsAre(IsTypeAnnotation(sourceId, + "Frame", + moduleId("QtQuick.Controls"), + "/path/images/frame-icon16.png", + traits, + IsEmpty(), + IsEmpty()))); +} + +TEST_F(TypeAnnotationReader, parse_true_visibleInNavigator) +{ + using QmlDesigner::FlagIs; + auto content = QString{R"xy( + MetaInfo { + Type { + name: "QtQuick.Controls.Frame" + icon: "images/frame-icon16.png" + + Hints { + visibleInNavigator: true + } + } + })xy"}; + QmlDesigner::Storage::TypeTraits traits; + traits.visibleInNavigator = FlagIs::True; + + auto annotations = reader.parseTypeAnnotation(content, "/path", sourceId); + + ASSERT_THAT(annotations, + ElementsAre(IsTypeAnnotation(sourceId, + "Frame", + moduleId("QtQuick.Controls"), + "/path/images/frame-icon16.png", + traits, + IsEmpty(), + IsEmpty()))); +} + +TEST_F(TypeAnnotationReader, parse_true_visibleInLibrary) +{ + using QmlDesigner::FlagIs; + auto content = QString{R"xy( + MetaInfo { + Type { + name: "QtQuick.Controls.Frame" + icon: "images/frame-icon16.png" + + Hints { + visibleInLibrary: true + } + } + })xy"}; + QmlDesigner::Storage::TypeTraits traits; + traits.visibleInLibrary = FlagIs::True; + + auto annotations = reader.parseTypeAnnotation(content, "/path", sourceId); + + ASSERT_THAT(annotations, + ElementsAre(IsTypeAnnotation(sourceId, + "Frame", + moduleId("QtQuick.Controls"), + "/path/images/frame-icon16.png", + traits, + IsEmpty(), + IsEmpty()))); +} + +TEST_F(TypeAnnotationReader, parse_false) +{ + using QmlDesigner::FlagIs; + auto content = QString{R"xy( + MetaInfo { + Type { + name: "QtQuick.Controls.Frame" + icon: "images/frame-icon16.png" + + Hints { + isMovable: false + } + } + })xy"}; + QmlDesigner::Storage::TypeTraits traits; + + auto annotations = reader.parseTypeAnnotation(content, "/path", sourceId); + + ASSERT_THAT(annotations, + ElementsAre(IsTypeAnnotation(sourceId, + "Frame", + moduleId("QtQuick.Controls"), + "/path/images/frame-icon16.png", + traits, + IsEmpty(), + IsEmpty()))); +} + +TEST_F(TypeAnnotationReader, parse_complex_expression) +{ + using QmlDesigner::FlagIs; + auto content = QString{R"xy( + MetaInfo { + Type { + name: "QtQuick.Controls.Frame" + icon: "images/frame-icon16.png" + + Hints { + isMovable: true || false + visibleNonDefaultProperties: "layer.effect" + } + } + + Type { + name: "QtQuick.Item" + icon: "images/item-icon16.png" + + Hints { + canBeContainer: true + } + } + })xy"}; + QmlDesigner::Storage::TypeTraits frameTraits; + frameTraits.isMovable = QmlDesigner::FlagIs::Set; + QmlDesigner::Storage::TypeTraits itemTraits; + itemTraits.canBeContainer = QmlDesigner::FlagIs::True; + + auto annotations = reader.parseTypeAnnotation(content, "/path", sourceId); + + ASSERT_THAT(annotations, + UnorderedElementsAre(IsTypeAnnotation(sourceId, + "Frame", + moduleId("QtQuick.Controls"), + "/path/images/frame-icon16.png", + frameTraits, + StrippedStringEq(R"xy({"isMovable":"true || false", + "visibleNonDefaultProperties":"layer.effect"})xy"), + IsEmpty()), + IsTypeAnnotation(sourceId, + "Item", + moduleId("QtQuick"), + "/path/images/item-icon16.png", + itemTraits, + IsEmpty(), + IsEmpty()))); +} + +TEST_F(TypeAnnotationReader, parse_item_library_entry) +{ + auto content = QString{R"xy( + MetaInfo { + Type { + name: "QtQuick.Controls.Frame" + icon: "images/frame-icon16.png" + + ItemLibraryEntry { + name: "Frame" + category: "Qt Quick - Controls 2" + libraryIcon: "images/frame-icon.png" + requiredImport: "QtQuick.Controls" + toolTip: qsTr("An untitled container for a group of controls.") + } + + ItemLibraryEntry { + name: "Large Frame" + category: "Qt Quick - Controls 2" + libraryIcon: "images/frame-icon.png" + requiredImport: "QtQuick.Controls" + toolTip: qsTr("An large container for a group of controls.") + } + } + })xy"}; + QmlDesigner::Storage::TypeTraits traits; + + auto annotations = reader.parseTypeAnnotation(content, "/path", sourceId); + + ASSERT_THAT(annotations, + ElementsAre(IsTypeAnnotation(sourceId, + "Frame", + moduleId("QtQuick.Controls"), + "/path/images/frame-icon16.png", + traits, + IsEmpty(), + StrippedStringEq(R"xy([ + {"category":"Qt Quick - Controls 2", + "iconPath":"images/frame-icon.png", + "import":"QtQuick.Controls", + "name":"Frame", + "toolTip":"qsTr(\"An untitled container for a group of controls.\")"}, + {"category":"Qt Quick - Controls 2", + "iconPath":"images/frame-icon.png", + "import":"QtQuick.Controls", + "name":"Large Frame", + "toolTip":"qsTr(\"An large container for a group of controls.\")"}] + )xy")))); +} + +TEST_F(TypeAnnotationReader, parse_item_library_entry_with_properties) +{ + auto content = QString{R"xy( + MetaInfo { + Type { + name: "QtQuick.Controls.Frame" + icon: "images/frame-icon16.png" + + ItemLibraryEntry { + name: "Frame" + category: "Qt Quick - Controls 2" + libraryIcon: "images/frame-icon.png" + requiredImport: "QtQuick.Controls" + toolTip: qsTr("An untitled container for a group of controls.") + + Property { name: "width"; type: "int"; value: 200 } + Property { name: "height"; type: "int"; value: 100 } + } + + ItemLibraryEntry { + name: "Large Frame" + category: "Qt Quick - Controls 2" + libraryIcon: "images/frame-icon.png" + requiredImport: "QtQuick.Controls" + toolTip: qsTr("An large container for a group of controls.") + + Property { name: "width"; type: "int"; value: 2000 } + Property { name: "height"; type: "int"; value: 1000 } + } + } + })xy"}; + QmlDesigner::Storage::TypeTraits traits; + + auto annotations = reader.parseTypeAnnotation(content, "/path", sourceId); + + ASSERT_THAT(annotations, + ElementsAre(IsTypeAnnotation(sourceId, + "Frame", + moduleId("QtQuick.Controls"), + "/path/images/frame-icon16.png", + traits, + IsEmpty(), + StrippedStringEq(R"xy([ + {"category":"Qt Quick - Controls 2", + "iconPath":"images/frame-icon.png", + "import":"QtQuick.Controls", + "name":"Frame", + "properties":[["width","int",200.0],["height","int",100.0]], + "toolTip":"qsTr(\"An untitled container for a group of controls.\")"}, + {"category":"Qt Quick - Controls 2", + "iconPath":"images/frame-icon.png", + "import":"QtQuick.Controls", + "name":"Large Frame", + "properties":[["width","int",2000.0],["height","int",1000.0]], + "toolTip":"qsTr(\"An large container for a group of controls.\")"}] + )xy")))); +} + +TEST_F(TypeAnnotationReader, parse_item_library_entry_template_path) +{ + auto content = QString{R"xy( + MetaInfo { + Type { + name: "QtQuick.Controls.Frame" + + ItemLibraryEntry { + name: "Frame" + + QmlSource{ source: "templates/frame.qml" } + } + } + Type { + name: "QtQuick.Item" + + ItemLibraryEntry { + name: "Item" + + QmlSource{ source: "templates/item.qml" } + } + } + })xy"}; + QmlDesigner::Storage::TypeTraits traits; + + auto annotations = reader.parseTypeAnnotation(content, "/path", sourceId); + + ASSERT_THAT(annotations, + ElementsAre(IsTypeAnnotation(sourceId, + "Frame", + moduleId("QtQuick.Controls"), + {}, + traits, + IsEmpty(), + StrippedStringEq(R"xy([ + {"name":"Frame", + "templatePath":"/path/templates/frame.qml"}] + )xy")), + IsTypeAnnotation(sourceId, + "Item", + moduleId("QtQuick"), + {}, + traits, + IsEmpty(), + StrippedStringEq(R"xy([ + {"name":"Item", + "templatePath":"/path/templates/item.qml"}] + )xy")))); +} + +TEST_F(TypeAnnotationReader, parse_item_library_entry_extra_file_paths) +{ + auto content = QString{R"xy( + MetaInfo { + Type { + name: "QtQuick.Controls.Frame" + + ItemLibraryEntry { + name: "Frame" + + ExtraFile{ source: "templates/frame.png" } + ExtraFile{ source: "templates/frame.frag" } + } + } + Type { + name: "QtQuick.Item" + + ItemLibraryEntry { + name: "Item" + + ExtraFile{ source: "templates/item.png" } + } + } + })xy"}; + QmlDesigner::Storage::TypeTraits traits; + + auto annotations = reader.parseTypeAnnotation(content, "/path", sourceId); + + ASSERT_THAT(annotations, + ElementsAre(IsTypeAnnotation(sourceId, + "Frame", + moduleId("QtQuick.Controls"), + {}, + traits, + IsEmpty(), + StrippedStringEq(R"xy([ + {"extraFilePaths":["/path/templates/frame.png", "/path/templates/frame.frag"], + "name":"Frame"}] + )xy")), + IsTypeAnnotation(sourceId, + "Item", + moduleId("QtQuick"), + {}, + traits, + IsEmpty(), + StrippedStringEq(R"xy([ + {"extraFilePaths":["/path/templates/item.png"], + "name":"Item"}] + )xy")))); +} + +} // namespace diff --git a/tests/unit/tests/unittests/qmlprojectmanager/converters-test.cpp b/tests/unit/tests/unittests/qmlprojectmanager/converters-test.cpp index a1ad263cf7c..adef15462a7 100644 --- a/tests/unit/tests/unittests/qmlprojectmanager/converters-test.cpp +++ b/tests/unit/tests/unittests/qmlprojectmanager/converters-test.cpp @@ -67,7 +67,9 @@ INSTANTIATE_TEST_SUITE_P(QmlProjectItem, QmlProjectConverter, ::testing::Values(QString("test-set-1"), QString("test-set-2"), - QString("test-set-3"))); + QString("test-set-3"), + QString("test-set-mcu-1"), + QString("test-set-mcu-2"))); TEST_P(QmlProjectConverter, qml_project_to_json) { diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/README.md b/tests/unit/tests/unittests/qmlprojectmanager/data/README.md index bffd46b79ee..c73ef285904 100644 --- a/tests/unit/tests/unittests/qmlprojectmanager/data/README.md +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/README.md @@ -30,6 +30,37 @@ Test functions iterate over the "test-set-*" folders and run the tests by using * **purpose**: testing `QDS.` prefixes * **origin**: copy of test-set-1 +### Qt for MCUs test sets + +These tests define the requirements for Qt for MCUs (QUL) projects. They are maintained by the Qt for MCUs +team. Please do not make changes to these test sets before consulting them. These tests help make sure +that QDS and QUL are aligned on the qmlproject format and that new features in QDS does not break +qmlproject files for MCU projects. The test set will be tested in the Qt for MCUs repositories +to make sure both the original and the converted qmlprojects build correctly. + +The qmlproject files in the test set aim to cover all the possible contents of a Qt for MCUs qmlproject, +but since new features are added with every release, it is not guaranteed to be exhaustive. + +Some main points for qmlproject files for MCU projects: + +* Unknown content in the qmlproject file will cause errors in the QUL tooling +* Any node or property with the `QDS`-prefix is ignored by QUL. When adding new properties, + these must either be prefixed or communicated with the MCU team to whitelist them in the tooling. +* Plain `Files` nodes are ignored by QUL, it needs to know the node type. The node contents will be processed + by different tools depending on which type of files it includes. The node types used by QUL are: + + `QmlFiles` + + `ImageFiles` + + `FontFiles` + + `ModuleFiles` + + `InterfaceFiles` + + `TranslationFiles` +* In addition to adding files to the project, any of the `*Files` nodes listed can also contain properties to + apply to the file group added by the node. +* MCU projects may have MCU-specific configuration nodes: `MCU.Config` and `MCU.Module`. +* A new version of Qt for MCUs may add new properties to any node, including `Project`. For this reason + it is not possible to define an exact set of properties to support which is valid across all versions. +* Qt for MCUs developers must still edit and maintain qmlproject files manually. The converted format + of the qmlproject file should be easy to read and edit by hand ## File Filters test data diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-1/testfile.jsontoqml b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-1/testfile.jsontoqml index dbd6e4a91ac..2a67b12d505 100644 --- a/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-1/testfile.jsontoqml +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-1/testfile.jsontoqml @@ -40,10 +40,6 @@ Project { directory: "imports" } - QmlFiles { - directory: "asset_imports" - } - JavaScriptFiles { directory: "content" } @@ -61,38 +57,42 @@ Project { } Files { - directory: "." - filters: "*.conf" - files: [ "qtquickcontrols2.conf" ] + files: [ + "qtquickcontrols2.conf" + ] + } + + Files { + filter: "*.conf" } Files { directory: "." - filters: "*.ttf;*.otf;*.ctf" + filter: "qmldir" + } + + Files { + filter: "*.ttf;*.otf;*.ctf" + } + + Files { + filter: "*.wav;*.mp3" + } + + Files { + filter: "*.mp4" + } + + Files { + filter: "*.glsl;*.glslv;*.glslf;*.vsh;*.fsh;*.vert;*.frag;*.trag" } Files { directory: "asset_imports" - filters: "*.mesh" + filter: "*.mesh" } - Files { - directory: "." - filters: "qmldir" - } - - Files { - directory: "." - filters: "*.glsl;*.glslv;*.glslf;*.vsh;*.fsh;*.vert;*.frag;*.trag" - } - - Files { - directory: "." - filters: "*.mp3;*.wav" - } - - Files { - directory: "." - filters: "*.mp4" + QmlFiles { + directory: "asset_imports" } } diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-1/testfile.qmlproject b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-1/testfile.qmlproject index d3e15d20be2..260938164a7 100644 --- a/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-1/testfile.qmlproject +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-1/testfile.qmlproject @@ -30,10 +30,13 @@ Project { } Files { - filter: "*.conf" files: ["qtquickcontrols2.conf"] } + Files { + filter: "*.conf" + } + Files { filter: "qmldir" directory: "." diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-1/testfile.qmltojson b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-1/testfile.qmltojson index 293b8e96524..c4475af39c4 100644 --- a/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-1/testfile.qmltojson +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-1/testfile.qmltojson @@ -9,37 +9,63 @@ "QT_LOGGING_RULES": "qt.qml.connections=false", "QT_QUICK_CONTROLS_CONF": "qtquickcontrols2.conf" }, - "fileGroups": { - "config": { - "directories": [ - "." - ], - "files": [ - { - "name": "qtquickcontrols2.conf" - } - ], - "filters": [ - "*.conf" - ] - }, - "font": { - "directories": [ - "." - ], + "fileGroups": [ + { + "directory": "content", "files": [ ], "filters": [ - "*.ttf", - "*.otf", - "*.ctf" - ] - }, - "image": { - "directories": [ - "content", - "asset_imports" + "*.qml" ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "Qml" + }, + { + "directory": "imports", + "files": [ + ], + "filters": [ + "*.qml" + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "Qml" + }, + { + "directory": "content", + "files": [ + ], + "filters": [ + "*.js", + "*.ts" + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "JavaScript" + }, + { + "directory": "imports", + "files": [ + ], + "filters": [ + "*.js", + "*.ts" + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "JavaScript" + }, + { + "directory": "content", "files": [ ], "filters": [ @@ -48,57 +74,115 @@ "*.png", "*.svg", "*.hdr", - ".ktx" - ] - }, - "javaScript": { - "directories": [ - "content", - "imports" + "*.ktx" ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "Image" + }, + { + "directory": "asset_imports", "files": [ ], "filters": [ - "*.js", - "*.ts" - ] - }, - "meshes": { - "directories": [ - "asset_imports" + "*.jpeg", + "*.jpg", + "*.png", + "*.svg", + "*.hdr", + "*.ktx" ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "Image" + }, + { + "directory": "", + "files": [ + "qtquickcontrols2.conf" + ], + "filters": [ + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "" + }, + { + "directory": "", "files": [ ], "filters": [ - "*.mesh" - ] + "*.conf" + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "" }, - "qml": { - "directories": [ - "content", - "imports", - "asset_imports" - ], - "files": [ - ], - "filters": [ - "*.qml" - ] - }, - "qmldir": { - "directories": [ - "." - ], + { + "directory": ".", "files": [ ], "filters": [ "qmldir" - ] - }, - "shader": { - "directories": [ - "." ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "" + }, + { + "directory": "", + "files": [ + ], + "filters": [ + "*.ttf", + "*.otf", + "*.ctf" + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "" + }, + { + "directory": "", + "files": [ + ], + "filters": [ + "*.wav", + "*.mp3" + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "" + }, + { + "directory": "", + "files": [ + ], + "filters": [ + "*.mp4" + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "" + }, + { + "directory": "", "files": [ ], "filters": [ @@ -110,30 +194,40 @@ "*.vert", "*.frag", "*.trag" - ] - }, - "sound": { - "directories": [ - "." ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "" + }, + { + "directory": "asset_imports", "files": [ ], "filters": [ - "*.mp3", - "*.wav" - ] - }, - "video": { - "directories": [ - "." + "*.mesh" ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "" + }, + { + "directory": "asset_imports", "files": [ ], "filters": [ - "*.mp4" - ] + "*.qml" + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "" } - }, + ], "fileVersion": 1, "importPaths": [ "imports", @@ -146,8 +240,14 @@ "en" ] }, - "mcuConfig": { - "mcuEnabled": true + "mcu": { + "config": { + }, + "enabled": true, + "module": { + } + }, + "otherProperties": { }, "runConfig": { "fileSelectors": [ diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-2/testfile.jsontoqml b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-2/testfile.jsontoqml index e1ec5b97566..5878bdafc73 100644 --- a/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-2/testfile.jsontoqml +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-2/testfile.jsontoqml @@ -5,20 +5,13 @@ import QmlProject Project { mainFile: "fileSelectors.qml" - mainUiFile: "" targetDirectory: "/opt/fileSelectors" widgetApp: false importPaths: [ "imports" ] - qdsVersion: "" - quickVersion: "" qt6Project: false qtForMCUs: false - multilanguageSupport: false - primaryLanguage: "" - supportedLanguages: [ ] - Environment { QT_AUTO_SCREEN_SCALE_FACTOR: "1" QT_QUICK_CONTROLS_CONF: "qtquickcontrols2.conf" @@ -37,13 +30,17 @@ Project { } Files { - directory: "." - filters: "*.conf" - files: [ "qtquickcontrols2.conf" ] + files: [ + "qtquickcontrols2.conf" + ] + } + + Files { + filter: "*.conf" } Files { directory: "." - filters: "qmldir" + filter: "qmldir" } } diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-2/testfile.qmlproject b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-2/testfile.qmlproject index 409b46bb7ff..3ceeda651af 100644 --- a/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-2/testfile.qmlproject +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-2/testfile.qmlproject @@ -19,10 +19,13 @@ Project { } Files { - filter: "*.conf" files: ["qtquickcontrols2.conf"] } + Files { + filter: "*.conf" + } + Files { filter: "qmldir" directory: "." diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-2/testfile.qmltojson b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-2/testfile.qmltojson index 33a478e2ca4..a26e0fc1607 100644 --- a/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-2/testfile.qmltojson +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-2/testfile.qmltojson @@ -6,24 +6,36 @@ "QT_AUTO_SCREEN_SCALE_FACTOR": "1", "QT_QUICK_CONTROLS_CONF": "qtquickcontrols2.conf" }, - "fileGroups": { - "config": { - "directories": [ - "." - ], + "fileGroups": [ + { + "directory": ".", "files": [ - { - "name": "qtquickcontrols2.conf" - } ], "filters": [ - "*.conf" - ] - }, - "image": { - "directories": [ - "." + "*.qml" ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "Qml" + }, + { + "directory": ".", + "files": [ + ], + "filters": [ + "*.js", + "*.ts" + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "JavaScript" + }, + { + "directory": ".", "files": [ ], "filters": [ @@ -32,48 +44,68 @@ "*.png", "*.svg", "*.hdr", - ".ktx" - ] - }, - "javaScript": { - "directories": [ - "." + "*.ktx" ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "Image" + }, + { + "directory": "", + "files": [ + "qtquickcontrols2.conf" + ], + "filters": [ + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "" + }, + { + "directory": "", "files": [ ], "filters": [ - "*.js", - "*.ts" - ] + "*.conf" + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "" }, - "qml": { - "directories": [ - "." - ], - "files": [ - ], - "filters": [ - "*.qml" - ] - }, - "qmldir": { - "directories": [ - "." - ], + { + "directory": ".", "files": [ ], "filters": [ "qmldir" - ] + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "" } - }, + ], "fileVersion": 1, "importPaths": [ "imports" ], "language": { }, - "mcuConfig": { + "mcu": { + "config": { + }, + "enabled": false, + "module": { + } + }, + "otherProperties": { }, "runConfig": { "fileSelectors": [ diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-3/testfile.jsontoqml b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-3/testfile.jsontoqml index dbd6e4a91ac..2a67b12d505 100644 --- a/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-3/testfile.jsontoqml +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-3/testfile.jsontoqml @@ -40,10 +40,6 @@ Project { directory: "imports" } - QmlFiles { - directory: "asset_imports" - } - JavaScriptFiles { directory: "content" } @@ -61,38 +57,42 @@ Project { } Files { - directory: "." - filters: "*.conf" - files: [ "qtquickcontrols2.conf" ] + files: [ + "qtquickcontrols2.conf" + ] + } + + Files { + filter: "*.conf" } Files { directory: "." - filters: "*.ttf;*.otf;*.ctf" + filter: "qmldir" + } + + Files { + filter: "*.ttf;*.otf;*.ctf" + } + + Files { + filter: "*.wav;*.mp3" + } + + Files { + filter: "*.mp4" + } + + Files { + filter: "*.glsl;*.glslv;*.glslf;*.vsh;*.fsh;*.vert;*.frag;*.trag" } Files { directory: "asset_imports" - filters: "*.mesh" + filter: "*.mesh" } - Files { - directory: "." - filters: "qmldir" - } - - Files { - directory: "." - filters: "*.glsl;*.glslv;*.glslf;*.vsh;*.fsh;*.vert;*.frag;*.trag" - } - - Files { - directory: "." - filters: "*.mp3;*.wav" - } - - Files { - directory: "." - filters: "*.mp4" + QmlFiles { + directory: "asset_imports" } } diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-3/testfile.qmlproject b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-3/testfile.qmlproject index 721dea3d28e..1ec43b95d5a 100644 --- a/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-3/testfile.qmlproject +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-3/testfile.qmlproject @@ -30,10 +30,13 @@ Project { } QDS.Files { - filter: "*.conf" files: ["qtquickcontrols2.conf"] } + QDS.Files { + filter: "*.conf" + } + QDS.Files { filter: "qmldir" directory: "." diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-3/testfile.qmltojson b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-3/testfile.qmltojson index 293b8e96524..c4475af39c4 100644 --- a/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-3/testfile.qmltojson +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-3/testfile.qmltojson @@ -9,37 +9,63 @@ "QT_LOGGING_RULES": "qt.qml.connections=false", "QT_QUICK_CONTROLS_CONF": "qtquickcontrols2.conf" }, - "fileGroups": { - "config": { - "directories": [ - "." - ], - "files": [ - { - "name": "qtquickcontrols2.conf" - } - ], - "filters": [ - "*.conf" - ] - }, - "font": { - "directories": [ - "." - ], + "fileGroups": [ + { + "directory": "content", "files": [ ], "filters": [ - "*.ttf", - "*.otf", - "*.ctf" - ] - }, - "image": { - "directories": [ - "content", - "asset_imports" + "*.qml" ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "Qml" + }, + { + "directory": "imports", + "files": [ + ], + "filters": [ + "*.qml" + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "Qml" + }, + { + "directory": "content", + "files": [ + ], + "filters": [ + "*.js", + "*.ts" + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "JavaScript" + }, + { + "directory": "imports", + "files": [ + ], + "filters": [ + "*.js", + "*.ts" + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "JavaScript" + }, + { + "directory": "content", "files": [ ], "filters": [ @@ -48,57 +74,115 @@ "*.png", "*.svg", "*.hdr", - ".ktx" - ] - }, - "javaScript": { - "directories": [ - "content", - "imports" + "*.ktx" ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "Image" + }, + { + "directory": "asset_imports", "files": [ ], "filters": [ - "*.js", - "*.ts" - ] - }, - "meshes": { - "directories": [ - "asset_imports" + "*.jpeg", + "*.jpg", + "*.png", + "*.svg", + "*.hdr", + "*.ktx" ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "Image" + }, + { + "directory": "", + "files": [ + "qtquickcontrols2.conf" + ], + "filters": [ + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "" + }, + { + "directory": "", "files": [ ], "filters": [ - "*.mesh" - ] + "*.conf" + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "" }, - "qml": { - "directories": [ - "content", - "imports", - "asset_imports" - ], - "files": [ - ], - "filters": [ - "*.qml" - ] - }, - "qmldir": { - "directories": [ - "." - ], + { + "directory": ".", "files": [ ], "filters": [ "qmldir" - ] - }, - "shader": { - "directories": [ - "." ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "" + }, + { + "directory": "", + "files": [ + ], + "filters": [ + "*.ttf", + "*.otf", + "*.ctf" + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "" + }, + { + "directory": "", + "files": [ + ], + "filters": [ + "*.wav", + "*.mp3" + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "" + }, + { + "directory": "", + "files": [ + ], + "filters": [ + "*.mp4" + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "" + }, + { + "directory": "", "files": [ ], "filters": [ @@ -110,30 +194,40 @@ "*.vert", "*.frag", "*.trag" - ] - }, - "sound": { - "directories": [ - "." ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "" + }, + { + "directory": "asset_imports", "files": [ ], "filters": [ - "*.mp3", - "*.wav" - ] - }, - "video": { - "directories": [ - "." + "*.mesh" ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "" + }, + { + "directory": "asset_imports", "files": [ ], "filters": [ - "*.mp4" - ] + "*.qml" + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "" } - }, + ], "fileVersion": 1, "importPaths": [ "imports", @@ -146,8 +240,14 @@ "en" ] }, - "mcuConfig": { - "mcuEnabled": true + "mcu": { + "config": { + }, + "enabled": true, + "module": { + } + }, + "otherProperties": { }, "runConfig": { "fileSelectors": [ diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-mcu-1/testfile.jsontoqml b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-mcu-1/testfile.jsontoqml new file mode 100644 index 00000000000..99e4f60bb34 --- /dev/null +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-mcu-1/testfile.jsontoqml @@ -0,0 +1,111 @@ +// prop: json-converted +// prop: auto-generated + +import QmlProject + +Project { + mainFile: "Main.qml" + targetDirectory: "/opt/UntitledProject13" + widgetApp: true + importPaths: [ "imports","asset_imports" ] + + qdsVersion: "4.0" + quickVersion: "6.2" + qt6Project: false + qtForMCUs: true + + multilanguageSupport: true + primaryLanguage: "en" + supportedLanguages: [ "no" ] + + idBasedTranslations: true + projectRootPath: ".." + + Environment { + QML_COMPAT_RESOLVE_URLS_ON_ASSIGNMENT: "1" + QT_AUTO_SCREEN_SCALE_FACTOR: "1" + QT_ENABLE_HIGHDPI_SCALING: "0" + QT_LOGGING_RULES: "qt.qml.connections=false" + QT_QUICK_CONTROLS_CONF: "qtquickcontrols2.conf" + } + + ShaderTool { + args: "-s --glsl \"100 es,120,150\" --hlsl 50 --msl 12" + files: [ "content/shaders/*" ] + } + + MCU.Config { + defaultFontFamily: "Roboto" + fontEngine: "Spark" + maxResourceCacheSize: [[128,128],[16384,1]] + resourceCompression: true + } + + QmlFiles { + directory: "content" + MCU.copyQmlFiles: true + } + + QmlFiles { + files: [ + "qml/Header.qml", + "qml/Footer.qml" + ] + } + + ImageFiles { + directory: "assets" + } + + ImageFiles { + files: [ + "images/clock.png" + ] + MCU.resourceCompression: false + } + + ImageFiles { + files: [ + "images/weather.png", + "images/navigation.png" + ] + MCU.base: "images" + MCU.prefix: "assets" + } + + ModuleFiles { + files: [ + "qmlproject/module/module.qmlproject" + ] + MCU.qulModules: ["Controls","Timeline"] + } + + FontFiles { + files: [ + "fonts/RobotoFonts.fmp" + ] + } + + InterfaceFiles { + files: [ + "src/BoardInterface.h" + ] + } + + TranslationFiles { + files: [ + "additional_translations/qml_en.ts", + "i18n/qml_no.ts" + ] + MCU.omitSourceLanguage: true + } + + JavaScriptFiles { + directory: "scripts" + } + + Files { + directory: "more_files" + filter: "*.mp3" + } +} diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-mcu-1/testfile.qmlproject b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-mcu-1/testfile.qmlproject new file mode 100644 index 00000000000..1b5b10ec18b --- /dev/null +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-mcu-1/testfile.qmlproject @@ -0,0 +1,156 @@ +import QmlProject 1.3 + +// This file works with Qt for MCUs 2.6, with the testfile of set-set-mcu-2 used as the module. +// Do not make any changes to this or the expected output before communicating with the MCU team + +// The set of properties may not be exhaustive and may not match every supported Qt for MCUs version + +Project { +// These properties are used by Qt for MCUs +// They should never have the QDS-prefix + mainFile: "Main.qml" + supportedLanguages: ["no"] + primaryLanguage: "en" + importPaths: [ "imports", "asset_imports" ] + idBasedTranslations: true + projectRootPath: ".." +// END of common properties + +// The following properties are ignored by Qt for MCUs +// They can have the QDS-prefix, but do not need it + targetDirectory: "/opt/UntitledProject13" + qdsVersion: "4.0" + quickVersion: "6.2" + qtForMCUs: true + widgetApp: true + multilanguageSupport: true +// END of ignored properties + +// The following properties must have the QDS prefix as of Qt for MCUs 2.6 +// empty +// END of QDS prefixed properties + +// These nodes are used by Qt for MCUs as well as Design Studio +// Their object type and content must never change based on conversion +// Any child properties prefixed with MCU must remain unchanged + +// The nodes can have properties set on the file group. +// This test set does not contain an exhaustive set of the available properties + + QmlFiles { + directory: "content" + MCU.copyQmlFiles: true + } + + QmlFiles { + files: [ + "qml/Header.qml", + "qml/Footer.qml" + ] + } + + ImageFiles { + directory: "assets" + } + + ImageFiles { + files: [ + "images/clock.png" + ] + MCU.resourceCompression: false + } + + ImageFiles { + files: [ + "images/weather.png", + "images/navigation.png" + ] + MCU.prefix: "assets" + MCU.base: "images" + } + +// END common nodes + +// These nodes are used by Qt for MCUs but not Design Studio +// Their object type and content must never change based on conversion +// Any child properties prefixed with MCU must remain unchanged + ModuleFiles { + files: [ + "qmlproject/module/module.qmlproject" + ] + MCU.qulModules: [ + "Controls", + "Timeline" + ] + } + + FontFiles { + files: [ + "fonts/RobotoFonts.fmp" + ] + } + + InterfaceFiles { + files: [ + "src/BoardInterface.h" + ] + } + + TranslationFiles { + files: [ + "additional_translations/qml_en.ts", + "i18n/qml_no.ts" + ] + MCU.omitSourceLanguage: true + } +// END MCU nodes + +// This node is prefixed with MCU. Its props are not prefixed +// It must remain unchanged after conversion. Modules may have MCU.Module + MCU.Config { + resourceCompression: true + fontEngine: "Spark" + defaultFontFamily: "Roboto" + + maxResourceCacheSize: [ + [128, 128], + [16384, 1] + ] + + } +//END MCU-prefixed node + +// The following nodes are ignored by Qt for MCUs +// They do not need the QDS-prefix, but may have it + Environment { + QT_QUICK_CONTROLS_CONF: "qtquickcontrols2.conf" + QT_AUTO_SCREEN_SCALE_FACTOR: "1" + QML_COMPAT_RESOLVE_URLS_ON_ASSIGNMENT: "1" + QT_LOGGING_RULES: "qt.qml.connections=false" + QT_ENABLE_HIGHDPI_SCALING: "0" + /* Useful for debugging + QSG_VISUALIZE=batches + QSG_VISUALIZE=clip + QSG_VISUALIZE=changes + QSG_VISUALIZE=overdraw + */ + } + + JavaScriptFiles { + directory: "scripts" + } + + Files { + directory: "more_files" + filter: "*.mp3" + } +// END of ignored nodes + +// The following nodes are unknown to Qt for MCUs (2.6) +// Unknown nodes cause a warning in MCU tooling, but no error as of Qt for MCUs 2.6 + ShaderTool { + args: "-s --glsl \"100 es,120,150\" --hlsl 50 --msl 12" + files: [ "content/shaders/*" ] + } +// END of unknown nodes +} diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-mcu-1/testfile.qmltojson b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-mcu-1/testfile.qmltojson new file mode 100644 index 00000000000..303bfc3899d --- /dev/null +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-mcu-1/testfile.qmltojson @@ -0,0 +1,236 @@ +{ + "deployment": { + "targetDirectory": "/opt/UntitledProject13" + }, + "environment": { + "QML_COMPAT_RESOLVE_URLS_ON_ASSIGNMENT": "1", + "QT_AUTO_SCREEN_SCALE_FACTOR": "1", + "QT_ENABLE_HIGHDPI_SCALING": "0", + "QT_LOGGING_RULES": "qt.qml.connections=false", + "QT_QUICK_CONTROLS_CONF": "qtquickcontrols2.conf" + }, + "fileGroups": [ + { + "directory": "content", + "files": [ + ], + "filters": [ + "*.qml" + ], + "mcuProperties": { + "copyQmlFiles": true + }, + "otherProperties": { + }, + "type": "Qml" + }, + { + "directory": "", + "files": [ + "qml/Header.qml", + "qml/Footer.qml" + ], + "filters": [ + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "Qml" + }, + { + "directory": "assets", + "files": [ + ], + "filters": [ + "*.jpeg", + "*.jpg", + "*.png", + "*.svg", + "*.hdr", + "*.ktx" + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "Image" + }, + { + "directory": "", + "files": [ + "images/clock.png" + ], + "filters": [ + ], + "mcuProperties": { + "resourceCompression": false + }, + "otherProperties": { + }, + "type": "Image" + }, + { + "directory": "", + "files": [ + "images/weather.png", + "images/navigation.png" + ], + "filters": [ + ], + "mcuProperties": { + "base": "images", + "prefix": "assets" + }, + "otherProperties": { + }, + "type": "Image" + }, + { + "directory": "", + "files": [ + "qmlproject/module/module.qmlproject" + ], + "filters": [ + ], + "mcuProperties": { + "qulModules": [ + "Controls", + "Timeline" + ] + }, + "otherProperties": { + }, + "type": "Module" + }, + { + "directory": "", + "files": [ + "fonts/RobotoFonts.fmp" + ], + "filters": [ + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "Font" + }, + { + "directory": "", + "files": [ + "src/BoardInterface.h" + ], + "filters": [ + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "Interface" + }, + { + "directory": "", + "files": [ + "additional_translations/qml_en.ts", + "i18n/qml_no.ts" + ], + "filters": [ + ], + "mcuProperties": { + "omitSourceLanguage": true + }, + "otherProperties": { + }, + "type": "Translation" + }, + { + "directory": "scripts", + "files": [ + ], + "filters": [ + "*.js", + "*.ts" + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "JavaScript" + }, + { + "directory": "more_files", + "files": [ + ], + "filters": [ + "*.mp3" + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "" + } + ], + "fileVersion": 1, + "importPaths": [ + "imports", + "asset_imports" + ], + "language": { + "multiLanguageSupport": true, + "primaryLanguage": "en", + "supportedLanguages": [ + "no" + ] + }, + "mcu": { + "config": { + "defaultFontFamily": "Roboto", + "fontEngine": "Spark", + "maxResourceCacheSize": [ + [ + 128, + 128 + ], + [ + 16384, + 1 + ] + ], + "resourceCompression": true + }, + "enabled": true, + "module": { + } + }, + "otherProperties": { + "idBasedTranslations": true, + "projectRootPath": ".." + }, + "runConfig": { + "fileSelectors": [ + ], + "mainFile": "Main.qml", + "widgetApp": true + }, + "shaderTool": { + "args": [ + "-s", + "--glsl", + "\"100 es,120,150\"", + "--hlsl", + "50", + "--msl", + "12" + ], + "files": [ + "content/shaders/*" + ] + }, + "versions": { + "designStudio": "4.0", + "qt": "5", + "qtQuick": "6.2" + } +} diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-mcu-2/testfile.jsontoqml b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-mcu-2/testfile.jsontoqml new file mode 100644 index 00000000000..2e73146cdaf --- /dev/null +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-mcu-2/testfile.jsontoqml @@ -0,0 +1,32 @@ +// prop: json-converted +// prop: auto-generated + +import QmlProject + +Project { + widgetApp: false + + qt6Project: false + qtForMCUs: true + + projectRootPath: "../.." + + MCU.Module { + generateQmltypes: true + uri: "MyModule" + } + + QmlFiles { + files: [ + "qml/TextIcon.qml" + ] + } + + ImageFiles { + files: [ + "images/qt_logo.png" + ] + MCU.base: "images" + MCU.prefix: "logo" + } +} diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-mcu-2/testfile.qmlproject b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-mcu-2/testfile.qmlproject new file mode 100644 index 00000000000..bb4e83f0338 --- /dev/null +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-mcu-2/testfile.qmlproject @@ -0,0 +1,29 @@ +import QmlProject 1.3 + +// This file represents the module contained in the testfile of test-set-mcu-1 + +// Do not modify without discussing with the MCU team + +Project { + + projectRootPath: "../.." + + MCU.Module { + uri: "MyModule" + generateQmltypes: true + } + + QmlFiles { + files: [ + "qml/TextIcon.qml" + ] + } + + ImageFiles { + files: [ + "images/qt_logo.png" + ] + MCU.base: "images" + MCU.prefix: "logo" + } +} diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-mcu-2/testfile.qmltojson b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-mcu-2/testfile.qmltojson new file mode 100644 index 00000000000..0ef011b615c --- /dev/null +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/converter/test-set-mcu-2/testfile.qmltojson @@ -0,0 +1,56 @@ +{ + "deployment": { + }, + "fileGroups": [ + { + "directory": "", + "files": [ + "qml/TextIcon.qml" + ], + "filters": [ + ], + "mcuProperties": { + }, + "otherProperties": { + }, + "type": "Qml" + }, + { + "directory": "", + "files": [ + "images/qt_logo.png" + ], + "filters": [ + ], + "mcuProperties": { + "base": "images", + "prefix": "logo" + }, + "otherProperties": { + }, + "type": "Image" + } + ], + "fileVersion": 1, + "language": { + }, + "mcu": { + "config": { + }, + "enabled": true, + "module": { + "generateQmltypes": true, + "uri": "MyModule" + } + }, + "otherProperties": { + "projectRootPath": "../.." + }, + "runConfig": { + "fileSelectors": [ + ] + }, + "versions": { + "qt": "5" + } +} diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/file-filters/MaterialBundle.qmlproject b/tests/unit/tests/unittests/qmlprojectmanager/data/file-filters/MaterialBundle.qmlproject index 479c20456be..837e7bbd911 100644 --- a/tests/unit/tests/unittests/qmlprojectmanager/data/file-filters/MaterialBundle.qmlproject +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/file-filters/MaterialBundle.qmlproject @@ -55,7 +55,7 @@ Project { Files { filter: "*.mesh" directory: "asset_imports" - } + } Files { filter: "*.mesh" diff --git a/tests/unit/tests/unittests/qmlprojectmanager/projectitem-test.cpp b/tests/unit/tests/unittests/qmlprojectmanager/projectitem-test.cpp index 976841b541a..49caf960746 100644 --- a/tests/unit/tests/unittests/qmlprojectmanager/projectitem-test.cpp +++ b/tests/unit/tests/unittests/qmlprojectmanager/projectitem-test.cpp @@ -1,6 +1,8 @@ // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// clazy:excludeall=non-pod-global-static + #include "../utils/google-using-declarations.h" #include "../utils/googletest.h" // IWYU pragma: keep @@ -44,15 +46,14 @@ protected: } protected: - static inline std::unique_ptr projectItemEmpty; - static inline std::unique_ptr projectItemWithQdsPrefix; - static inline std::unique_ptr - projectItemWithoutQdsPrefix; + inline static std::unique_ptr projectItemEmpty; + inline static std::unique_ptr projectItemWithQdsPrefix; + inline static std::unique_ptr projectItemWithoutQdsPrefix; std::unique_ptr projectItemSetters = std::make_unique< QmlProjectManager::QmlProjectItem>(Utils::FilePath::fromString( localTestDataDir + "/getter-setter/empty.qmlproject"), true); - static inline std::unique_ptr projectItemFileFilters; + inline static std::unique_ptr projectItemFileFilters; }; auto createAbsoluteFilePaths(const QStringList &fileList) diff --git a/tests/unit/tests/unittests/sqlite/createtablesqlstatementbuilder-test.cpp b/tests/unit/tests/unittests/sqlite/createtablesqlstatementbuilder-test.cpp index f9ae5bcfdbc..8dcf71c2746 100644 --- a/tests/unit/tests/unittests/sqlite/createtablesqlstatementbuilder-test.cpp +++ b/tests/unit/tests/unittests/sqlite/createtablesqlstatementbuilder-test.cpp @@ -400,7 +400,7 @@ TEST_F(CreateTableSqlStatementBuilder, default_value_float) builder.addColumn("id", ColumnType::Real, {Sqlite::DefaultValue{1.1}}); - ASSERT_THAT(builder.sqlStatement(), "CREATE TABLE test(id REAL DEFAULT 1.100000)"); + ASSERT_THAT(builder.sqlStatement(), "CREATE TABLE test(id REAL DEFAULT 1.1)"); } TEST_F(CreateTableSqlStatementBuilder, default_value_string) @@ -928,7 +928,7 @@ TEST_F(CreateStrictTableSqlStatementBuilder, default_value_float) builder.addColumn("id", StrictColumnType::Real, {Sqlite::DefaultValue{1.1}}); - ASSERT_THAT(builder.sqlStatement(), "CREATE TABLE test(id REAL DEFAULT 1.100000) STRICT"); + ASSERT_THAT(builder.sqlStatement(), "CREATE TABLE test(id REAL DEFAULT 1.1) STRICT"); } TEST_F(CreateStrictTableSqlStatementBuilder, default_value_string) diff --git a/tests/unit/tests/unittests/utils/smallstring-test.cpp b/tests/unit/tests/unittests/utils/smallstring-test.cpp index 17124b1373b..bdcdb44019f 100644 --- a/tests/unit/tests/unittests/utils/smallstring-test.cpp +++ b/tests/unit/tests/unittests/utils/smallstring-test.cpp @@ -1837,8 +1837,8 @@ TEST(SmallString, number_to_string) ASSERT_THAT(SmallString::number(std::numeric_limits::min()), "-2147483648"); ASSERT_THAT(SmallString::number(std::numeric_limits::max()), "9223372036854775807"); ASSERT_THAT(SmallString::number(std::numeric_limits::min()), "-9223372036854775808"); - ASSERT_THAT(SmallString::number(1.2), "1.200000"); - ASSERT_THAT(SmallString::number(-1.2), "-1.200000"); + ASSERT_THAT(SmallString::number(1.2), "1.2"); + ASSERT_THAT(SmallString::number(-1.2), "-1.2"); } TEST(SmallString, string_view_plus_operator)