Merge "Merge remote-tracking branch 'origin/8.0' into 9.0" into 9.0

This commit is contained in:
The Qt Project
2022-10-18 09:51:56 +00:00
74 changed files with 1782 additions and 1113 deletions

View File

@@ -299,22 +299,26 @@
users, but always place example values also in the text. users, but always place example values also in the text.
\list \list
\li Use the screen resolution of 1366x768 (available on the most \li Use the screen resolution of 1920x1080 (available on the most
commonly used screens, as of this writing). commonly used screens, as of this writing).
\note Use display scaling 100%.
\li Use the aspect ratio of 16:9.
\li Open the application in the maximum size on full screen.
\li Use your favorite tool to take the screen shot. \li Use your favorite tool to take the screen shot.
\li Include only the part of the screen that you need (you can crop the \li Include only the part of the screen that you need (you can crop the
image also in the screen capture tool). image also in the screen capture tool). In \QDS, close all
unnecessary views to avoid clutter.
\li Do not scale or resize the images in the tool because it causes
reduced visual quality and bigger file size. Also, the CSS we use
online scales down images if needed (their width is larger than 800
pixels).
\li To highlight parts of the screen shot, use the images of numbers \li To highlight parts of the screen shot, use the images of numbers
that are stored in \c{doc/images/numbers} in the \QC repository. that are stored in \c{qtcreator/doc/qtcreator/images/numbers} in
the \QC repository.
\li Before you submit the images to the repository, optimize them to \li Before you submit PNG images to the repository, optimize them to
save space. save space.
\endlist \endlist
@@ -322,9 +326,9 @@
You can use number icons in screenshots to highlight parts of the screenshot You can use number icons in screenshots to highlight parts of the screenshot
(instead of using red arrows or borders, or something similar). You can then (instead of using red arrows or borders, or something similar). You can then
refer to the numbers in text. For and example, see the refer to the numbers in text. For an example, see the
\l{https://doc.qt.io/qt/topics-app-development.html}{Development Tools} \l{https://doc.qt.io/qtcreator/creator-quick-tour.html}{User Interface}
topic in the Qt reference documentation. topic in the \QC Manual.
This improves the consistency of the look and feel of Qt documentation, This improves the consistency of the look and feel of Qt documentation,
and eliminates the need to describe parts of the UI in the text because and eliminates the need to describe parts of the UI in the text because
@@ -332,8 +336,8 @@
brackets. brackets.
You can find a set of images that show the numbers from 1 to 10 in the You can find a set of images that show the numbers from 1 to 10 in the
\c doc/images/numbers directory (or in the \c qtdoc module sources in \c qtcreator/doc/qtcreator/images/numbers directory (or in the \c qtdoc
\c doc/images/numbers). module sources in \c doc/images/numbers).
To use the numbers, copy-paste the number images on the screenshot to the To use the numbers, copy-paste the number images on the screenshot to the
places that you want to refer to from text. places that you want to refer to from text.
@@ -358,6 +362,13 @@
recolors icons in \c qtcreator/doc/qtcreator/images/icons. Use the recolors icons in \c qtcreator/doc/qtcreator/images/icons. Use the
\c -docspath option to specify the path to another icon source directory. \c -docspath option to specify the path to another icon source directory.
For example, if you saved the new icons in \c {C:\iconconversions}, switch to
the \c {qtcreator\src\tools\icons} folder and enter:
\badcode
recolordocsicons.py -docspath C:\iconconversions
\endcode
To run the script, you will need to install the following tools and add them To run the script, you will need to install the following tools and add them
to the PATH: to the PATH:
@@ -367,20 +378,26 @@
\li optipng \li optipng
\endlist \endlist
\section2 Saving Images
Save images in PNG or WebP format in the \QC project folder in the
\c doc/qtcreator/images or \c doc/qtdesignstudio/images folder. Binary
images can easily add megabytes to the Git
history. To keep the history as small as possible, the Git post-commit hooks
remind you to try to keep image file size below 50 kilobytes. To achieve this
goal, crop images so that only relevant information is visible in them.
If your screenshot contains lots of colorful content or a photo, for example,
consider saving it in WebP format for a smaller image file size.
\section2 Optimizing Images \section2 Optimizing Images
Save images in the PNG format in the \QC project folder in the Before committing PNG images, optimize them by using an image optimization
\c {doc/images} folder. Binary images can easily add megabytes to the Git tool. Optimization should not visibly reduce image quality. If it does, try
history. To keep the history as small as possible, the Git post-commit hooks saving the image as WebP instead.
remind you to try to keep image size below 50 kilobytes. To achieve this
goal, crop images so that only relevant information is visible in them.
Before committing images, optimize them by using an image optimization tool.
Optimization should not visibly reduce image quality. If it does, do not do
it.
You can use a web service, such as \l{https://tinypng.com}, or an image You can use a web service, such as \l{https://tinypng.com}, or an image
optimization tool to shrink the images. For example, you can use the Radical optimization tool to shrink PNG images. For example, you can use the Radical
Image Optimization Tool (RIOT) or OptiPNG on Windows, ImageOptim on Image Optimization Tool (RIOT) or OptiPNG on Windows, ImageOptim on
\macos, or some other tool available on Linux. \macos, or some other tool available on Linux.
@@ -425,6 +442,16 @@
optipng -o 7 -strip all doc/images/<screenshot_name> optipng -o 7 -strip all doc/images/<screenshot_name>
\endcode \endcode
\section2 Creating GIF Videos
Sometimes it is easier to explain how something works by recording
a short GIF video. You can use any tool you like, for example
\l {https://www.screentogif.com/}{ScreenToGif}. GIF videos are typically
bigger than screenshots, so try to make them as short and to the point as
you can.
Use the \c {\image} command to add GIF files to the documentation.
\section2 Linking to Youtube Videos \section2 Linking to Youtube Videos
You can use the \c {\youtube} macro to link to a video on Youtube. The HTML You can use the \c {\youtube} macro to link to a video on Youtube. The HTML
@@ -477,11 +504,7 @@
\section2 Setting Up Documentation Builds \section2 Setting Up Documentation Builds
For more information about setting up the build environment with a You can use an installed Qt version to build the documentation.
self-built Qt if you do not want to build the whole Qt, see
\l{https://wiki.qt.io/Building_Qt_Documentation}{Building Qt Documentation}
on the Qt wiki.
The content and formatting of documentation are separated in QDoc. The content and formatting of documentation are separated in QDoc.
The documentation configuration, style sheets, and templates have The documentation configuration, style sheets, and templates have
changed over time, so they differ between Qt and \QC versions. changed over time, so they differ between Qt and \QC versions.
@@ -532,7 +555,7 @@
For example (all on one line): For example (all on one line):
\badcode \badcode
C:\dev\qtc-doc-build>cmake -DWITH_DOCS=ON C:\dev\qtc-doc-build>cmake -DWITH_DOCS=ON
"-DCMAKE_PREFIX_PATH=C:\Qt\5.15.1\msvc2019_64" "-DCMAKE_PREFIX_PATH=C:\Qt\6.4.0\msvc2019_64"
C:\dev\qtc-super\qtcreator C:\dev\qtc-super\qtcreator
\endcode \endcode
\li To also build Extending \QC Manual, add the following option: \li To also build Extending \QC Manual, add the following option:
@@ -544,7 +567,7 @@
\badcode \badcode
C:\dev\qtc-doc-build>cmake -DWITH_DOCS=ON -DBUILD_DEVELOPER_DOCS=ON C:\dev\qtc-doc-build>cmake -DWITH_DOCS=ON -DBUILD_DEVELOPER_DOCS=ON
"-DCMAKE_MODULE_PATH=C:\dev\tqtc-plugin-qtquickdesigner\studiodata\branding" "-DCMAKE_MODULE_PATH=C:\dev\tqtc-plugin-qtquickdesigner\studiodata\branding"
"-DCMAKE_PREFIX_PATH=C:\Qt\5.15.1\msvc2019_64" "-DCMAKE_PREFIX_PATH=C:\Qt\6.4.0\msvc2019_64"
C:\dev\qtc-super\qtcreator C:\dev\qtc-super\qtcreator
\endcode \endcode
\li To build the docs using the online style, use the following option \li To build the docs using the online style, use the following option
@@ -556,7 +579,7 @@
C:\dev\qtc-doc-build>cmake -DWITH_ONLINE_DOCS=ON C:\dev\qtc-doc-build>cmake -DWITH_ONLINE_DOCS=ON
-DBUILD_DEVELOPER_DOCS=ON -DBUILD_DEVELOPER_DOCS=ON
"-DCMAKE_MODULE_PATH=C:\dev\tqtc-plugin-qtquickdesigner\studiodata\branding" "-DCMAKE_MODULE_PATH=C:\dev\tqtc-plugin-qtquickdesigner\studiodata\branding"
"-DCMAKE_PREFIX_PATH=C:\Qt\5.15.1\msvc2019_64" "-DCMAKE_PREFIX_PATH=C:\Qt\6.4.0\msvc2019_64"
C:\dev\qtc-super\qtcreator C:\dev\qtc-super\qtcreator
\endcode \endcode
\note If you already ran CMake \c {-DWITH_DOCS=ON} in a folder and \note If you already ran CMake \c {-DWITH_DOCS=ON} in a folder and

View File

@@ -4,7 +4,7 @@
/*! /*!
\page state-transition-animations.html \page state-transition-animations.html
\ingroup gstutorials \ingroup gstutorials
\sa States, {Transitions}, {Adding States} \sa States, {Transitions}, {Working with States}
\title Animated State Transitions \title Animated State Transitions
\brief Illustrates how to create animated state transitions. \brief Illustrates how to create animated state transitions.
@@ -12,7 +12,7 @@
\image animated-state-transitions.jpg \image animated-state-transitions.jpg
The \e{Animated State Transitions} tutorial illustrates how you can animate The \e{Animated State Transitions} tutorial illustrates how you can animate
the transition between \l{Adding States}{states}. the transition between \l{Working with States}{states}.
The starting point of this tutorial is the Car Demo project, you can The starting point of this tutorial is the Car Demo project, you can
download it from download it from

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -8,23 +8,18 @@
\title Log In UI - Components \title Log In UI - Components
\brief Illustrates how to use wizard templates to create a simple UI \brief Illustrates how to use wizard templates to create a simple UI
wireframe that contains a text label, images, and push buttons. that contains a text label, images, and push buttons.
\image loginui1.jpg \image loginui1.jpg
\e{Log In UI - Components} is the first in a series of tutorials that build \e{Log In UI - Components} is the first tutorial in a series of tutorials
on each other to illustrate how to use \QDS to create a simple UI with some that describes how to use the \QDS wizard templates to create a project and
basic UI components, such as pages, buttons, and fields. The first tutorial a button UI control, and how to modify the files generated by the wizard
in the series describes how to use the \QDS wizard templates to create a templates to design the UI.
project and a button UI control, and how to modify the files generated by
the wizard templates to wireframe the UI.
You can donwnload the completed project from You can donwnload the completed project from
\l{https://git.qt.io/public-demos/qtdesign-studio/-/tree/master/tutorial%20projects/Loginui1}{here}. \l{https://git.qt.io/public-demos/qtdesign-studio/-/tree/master/tutorial%20projects/Loginui1}{here}.
The \e {Learn More} sections provide additional information about the
tasks performed by the wizards and about other basic tasks and concepts.
\section1 Creating the UI Project \section1 Creating the UI Project
For the purposes of this tutorial, you will use the empty wizard template. For the purposes of this tutorial, you will use the empty wizard template.
@@ -47,6 +42,7 @@
\li Set \uicontrol Width to 720 and \uicontrol Height to 1280. \li Set \uicontrol Width to 720 and \uicontrol Height to 1280.
You can change the screen size later in \l Properties. You can change the screen size later in \l Properties.
\endlist \endlist
\li Select \uicontrol Create to create the project. \li Select \uicontrol Create to create the project.
\endlist \endlist
@@ -57,7 +53,7 @@
The wizard constructs the \e Screen01 \l{glossary-component}{component} The wizard constructs the \e Screen01 \l{glossary-component}{component}
using instances of a \l{basic-rectangle}{Rectangle} component that forms using instances of a \l{basic-rectangle}{Rectangle} component that forms
the background and a \l Text component that displays some text. the background, a \l Button, and a \l Text component that displays some text.
\note The visibility of views depends on the selected workspace, \note The visibility of views depends on the selected workspace,
so your \QDS might look somewhat different from the above image. so your \QDS might look somewhat different from the above image.
@@ -65,53 +61,14 @@
in the \uicontrol Design mode. For more information about moving in the \uicontrol Design mode. For more information about moving
views around, see \l {Managing Workspaces}. views around, see \l {Managing Workspaces}.
\section2 Learn More - Projects and Files You should remove this \l Button for now from the UI
to have a clean workspace. You'll add this later in the course of the tutorial.
Then you shall know how to modify and adjust it as you need.
\QDS creates a set of boilerplate files and folders that you need to create To remove this Button, just select it and press \key {Backspace}.
a UI. The files are listed in the \l{File System} view.
\image loginui1-project-files.png
\list
\li The \e {loginui1.qmlproject} project file defines that all
component, JavaScript, and image files in the project folder belong
to the project. Therefore, you do not need to individually list new
files when you add them to the project.
\li The \e {loginui1.qml} file defines the functionality of
the UI. For the time being, it does not do anything.
\li The \e {Screen01.ui.qml} file is a custom component created by
the wizard template. For more information, see \l {UI Files}.
By default, this is the main file in the project, but you can
change that in the .qmlproject file. While the custom component
is a good starting point for new users, you don't have to use it.
Specifically, if you export and import designs using \QB, your main
file is most likely called something else. For more information,
see \l {Exporting from Design Tools}.
\li The \e CMakeLists.txt project configuration file allowing you to
share your project as a fully working C++ application with
developers.
\li The \e {qtquickcontrols2.conf} file specifies the selected
\l {Styling Qt Quick Controls}{UI style} and some style-specific
arguments.
\li The \e imports folder contains \e {Constants.qml} and
\e {DirectoryFontLoader.qml} files that specify a font loader
and a \e qmldir module definition file that declares the Constant
component. For more information, see
\l {Module Definition qmldir Files}. The \e EventListModel.qml and
\e EventListSimulator.qml files are not used in this example, so
you can ignore them for now.
\endlist
\l{UI Files}{UI files} define a hierarchy of components with a
highly-readable, structured layout. Every UI file consists of two parts:
an imports section and an component declaration section. The components and
functionality most common to UIs are provided in the \c QtQuick import. You
can view the code of a \e .ui.qml file in the \l{Code} view.
Next, you will edit the values of the properties of the component instances Next, you will edit the values of the properties of the component instances
to create the main page of the UI. to create the main page of the UI.
\section1 Creating the Main Page \section1 Creating the Main Page
You will now change the values of the properties of the \l Text component You will now change the values of the properties of the \l Text component
@@ -123,9 +80,9 @@
You can download the logo and the background image from here: You can download the logo and the background image from here:
\list \list
\li \l{https://doc.qt.io/qtdesignstudio/images/used-in-examples/loginui1/images/qt_logo_green_128x128px.png} \li \l{https://git.qt.io/public-demos/qtdesign-studio/-/blob/master/tutorial%20projects/Loginui1/content/images/qt_logo_green_128x128px.png}
{qt_logo_green_128x128px.png} {qt_logo_green_128x128px.png}
\li \l{https://doc.qt.io/qtdesignstudio/images/used-in-examples/loginui1/images/adventurePage.jpg} \li \l{https://git.qt.io/public-demos/qtdesign-studio/-/blob/master/tutorial%20projects/Loginui1/content/images/adventurePage.jpg}
{Background image} (\e adventurePage.jpg) {Background image} (\e adventurePage.jpg)
Photo by \l{https://unsplash.com/photos/a2MgJdG6UvE} Photo by \l{https://unsplash.com/photos/a2MgJdG6UvE}
@@ -139,7 +96,8 @@
To add the assets: To add the assets:
\list 1 \list 1
\li Select \uicontrol Assets > \inlineimage icons/plus.png \li Select \uicontrol Assets > \inlineimage icons/plus.png
. (Select \uicontrol View > \uicontrol Views > \uicontrol Assets to enable it,
if you can't find it).
\li Select the asset files, and then select \uicontrol Open. \li Select the asset files, and then select \uicontrol Open.
\li Select the location where the files will be saved in the \li Select the location where the files will be saved in the
\uicontrol {Add Resources} dialog. \uicontrol {Add Resources} dialog.
@@ -158,8 +116,8 @@
To modify the \e Screen01 component in the \uicontrol {2D} view: To modify the \e Screen01 component in the \uicontrol {2D} view:
\list 1 \list 1
\li Drag-and-drop the background image from \uicontrol Assets to the \li Drag-and-drop the background image (1) from \uicontrol Assets to the
rectangle in \l Navigator. \l{basic-rectangle}{Rectangle} (2) in \l Navigator.
\image loginui1-library-assets.jpg "Assets view" \image loginui1-library-assets.jpg "Assets view"
\li \QDS automatically creates an instance of the \l{Images}{Image} \li \QDS automatically creates an instance of the \l{Images}{Image}
component for you with the path to the image file set as the component for you with the path to the image file set as the
@@ -167,7 +125,7 @@
\image loginui1-image-properties.png "Image properties" \image loginui1-image-properties.png "Image properties"
\li Drag-and-drop the Qt logo from \uicontrol Assets to the rectangle \li Drag-and-drop the Qt logo from \uicontrol Assets to the rectangle
in \uicontrol Navigator and move it to the top-center of the in \uicontrol Navigator and move it to the top-center of the
background image in \uicontrol the {2D} view. background image in the \uicontrol {2D} view.
\li Select \e Text in \uicontrol Navigator and drag it below the logo \li Select \e Text in \uicontrol Navigator and drag it below the logo
in the \uicontrol {2D} view. If the text is hidden behind the in the \uicontrol {2D} view. If the text is hidden behind the
background, select \inlineimage icons/navigator-arrowdown.png background, select \inlineimage icons/navigator-arrowdown.png
@@ -182,8 +140,8 @@
line: \e {Are you ready to explore?}. line: \e {Are you ready to explore?}.
\image loginui1-text-properties.png "Text properties" \image loginui1-text-properties.png "Text properties"
\li In \uicontrol Font, select \e {Titillium Web ExtraLight}. \li In \uicontrol Font, select \e {Titillium Web ExtraLight}.
\li In \uicontrol Size, set the font size of the tag line to \li In \uicontrol Size, first select the scale to pixels (\uicontrol px),
\e 50 pixels (\uicontrol px). then set font size of the tag line to \e 50 (\uicontrol px).
\li In \uicontrol {Text color}, set the text color to white \li In \uicontrol {Text color}, set the text color to white
(\e #ffffff). (\e #ffffff).
\endlist \endlist
@@ -197,74 +155,6 @@
\image loginui1-main-page.jpg "Modified UI in the Design mode" \image loginui1-main-page.jpg "Modified UI in the Design mode"
You can resize the preview dialog to display the whole screen. You can resize the preview dialog to display the whole screen.
\section2 Learn More - Components
\QDS provides preset \l{glossary-component}{components} for creating
UIs, including components for creating and animating visual components,
receiving user input, and creating data models and views.
To be able to use the functionality of preset components, the wizard template
adds the following \e import statements to the UI files (.ui.qml) that it
creates:
\quotefromfile Loginui1/content/Screen01.ui.qml
\skipto import
\printuntil Controls
You can view the import statements in the \uicontrol {Code} view.
The \l Components view lists the components in each module that are
supported by \QDS. You can use the basic components to create your own
components, and they will be listed in \uicontrol {My Components}.
This section is only visible if you have created custom components.
The \l {basic-rectangle}{Rectangle}, \l Text, and \l {Images}{Image}
components used in this tutorial are based on the \l Item component.
It is the base component for all visual elements, with implementation
of basic functions and properties, such as component type, ID, position,
size, and visibility.
For more information, see \l{Use Case - Visual Elements In QML}. For
descriptions of all components, see \l{All QML Types} in the Qt reference
documentation.
\section3 Regtangle Properties
The default \l {basic-rectangle}{Rectangle} component is used for drawing
shapes with four sides and four corners. You can fill rectangles either with
a solid fill color or a gradient. You can specify the border color separately.
By setting the value of the radius property, you can create shapes with
rounded corners.
If you want to specify the radius of each corner separately, you can use the
\l{studio-rectangle}{Rectangle} component from the
\uicontrol {Qt Quick Studio Components} module instead of the basic rectangle
component. It is available in \uicontrol Components
> \uicontrol {Qt Quick Studio Components}.
\section3 Text Properties
The \l Text component is used for adding static text to the UI, such as
titles and labels. You can select the font to use and specify extensive
properties for each text component, such as size in points or pixels,
weight, style, and spacing.
If you want to create a label with a background, use the \l Label component
from the \uicontrol {Qt Quick Controls} module instead of the Text component.
\section3 Image Properties
The \l {Images}{Image} component is used for adding images to the UI in several
supported formats, including bitmap formats such as PNG and JPEG and vector
graphics formats such as SVG. To add an image to \uicontrol Assets, select
\inlineimage icons/plus.png
, and then select the image file.
If you need to display animated images, use the \l {Animated Image}
component, also available in \uicontrol Components >
\uicontrol {Default Components} > \uicontrol Basic.
\section1 Creating a Push Button \section1 Creating a Push Button
You can use another wizard template to create a push button and to add it to You can use another wizard template to create a push button and to add it to
@@ -298,21 +188,9 @@
\image loginui1-button.png "Button in the Design mode." \image loginui1-button.png "Button in the Design mode."
\section2 Learn More - UI Controls \note To open the \uicontrol States view, select it from
\uicontrol View > \uicontrol Views > \uicontrol States, if
The \e {Custom Button} wizard template creates a button component it is not available by default.
based on the \l {Button} control in the \l {Qt Quick Controls} module. It
is a push-button control that can be pushed or clicked by the user. Buttons
are normally used to perform an action or to answer a question. The
properties and functionality inherited from the Button component enable
you to set text, display an icon, react to mouse clicks, and so on.
To be able to use the functionality of the Button control, the wizard template
adds the following \e import statements to the \e EntryField.ui.qml file:
\quotefromfile Loginui1/content/EntryField.ui.qml
\skipto import
\printuntil Controls
Next, you will change the appearance of the EntryField component by Next, you will change the appearance of the EntryField component by
modifying its properties. modifying its properties.
@@ -353,8 +231,8 @@
properties in \uicontrol Properties. properties in \uicontrol Properties.
\li In \uicontrol Character > \uicontrol Font, select \li In \uicontrol Character > \uicontrol Font, select
\e {Titillium Web ExtraLight}. \e {Titillium Web ExtraLight}.
\li In \uicontrol Size, set the font size to \e 34 pixels \li In \uicontrol Size, first select the scale to pixels (\uicontrol px),
(\uicontrol px). then set font size to \e 34 (\uicontrol px).
\li In \uicontrol {Text color}, set the text color to white \li In \uicontrol {Text color}, set the text color to white
(\e #ffffff). (\e #ffffff).
\li In \uicontrol {Alignment H}, select the \uicontrol Left button to \li In \uicontrol {Alignment H}, select the \uicontrol Left button to
@@ -374,6 +252,10 @@
\image loginui1-entry-field-styled.jpg "Modified button in the 2D view" \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
property, because this will break the connection, and later you won't be able
to change the text in \uicontrol {Button Content} > \uicontrol Text.
Next, you will add instances of the \e EntryField component to the Next, you will add instances of the \e EntryField component to the
\e Screen01 component and modify their properties. \e Screen01 component and modify their properties.
@@ -437,7 +319,8 @@
its properties in \uicontrol Properties. its properties in \uicontrol Properties.
\li In \uicontrol Character > \uicontrol Font, select \li In \uicontrol Character > \uicontrol Font, select
\e {Titillium Web ExtraLight}. \e {Titillium Web ExtraLight}.
\li In \uicontrol Size, set the font size to \e 34 pixels. \li In \uicontrol Size, first select the scale to pixels (\uicontrol px),
then set font size to \e 34 (\uicontrol px).
\li In \uicontrol {Text color}, set the text color to \e #41cd52. \li In \uicontrol {Text color}, set the text color to \e #41cd52.
\li In the \uicontrol States view, select the \e normal state and repeat \li In the \uicontrol States view, select the \e normal state and repeat
the changes, as necessary. the changes, as necessary.
@@ -481,7 +364,136 @@
\image loginui1-ready.jpg "The finished UI in the 2D view" \image loginui1-ready.jpg "The finished UI in the 2D view"
\section2 Learn More - Component IDs \section1 Learn More
The \e {Learn More} sections provide additional information about the
tasks performed by the wizards and about other basic tasks and concepts.
\section2 Projects and Files
\QDS creates a set of files and folders that you need to create
a UI. The files are listed in the \l{File System} view.
\image loginui1-project-files.png
\list
\li The \e {loginui1.qmlproject} project file defines that all
component, JavaScript, and image files in the project folder belong
to the project. Therefore, you do not need to individually list new
files when you add them to the project.
\li The \e {loginui1.qml} file defines the functionality of
the UI. For the time being, it does not do anything.
\li The \e {Screen01.ui.qml} file is a custom component created by
the wizard template. For more information, see \l {UI Files}.
By default, this is the main file in the project, but you can
change that in the .qmlproject file. While the custom component
is a good starting point for new users, you don't have to use it.
Specifically, if you export and import designs using \QB, your main
file is most likely called something else. For more information,
see \l {Exporting from Design Tools}.
\li The \e CMakeLists.txt project configuration file allowing you to
share your project as a fully working C++ application with
developers.
\li The \e {qtquickcontrols2.conf} file specifies the selected
\l {Styling Qt Quick Controls}{UI style} and some style-specific
arguments.
\li The \e imports folder contains \e {Constants.qml} and
\e {DirectoryFontLoader.qml} files that specify a font loader
and a \e qmldir module definition file that declares the Constant
component. For more information, see
\l {Module Definition qmldir Files}. The \e EventListModel.qml and
\e EventListSimulator.qml files are not used in this example, so
you can ignore them for now.
\endlist
\l{UI Files}{UI files} define a hierarchy of components with a
highly-readable, structured layout. Every UI file consists of two parts:
an imports section and an component declaration section. The components and
functionality most common to UIs are provided in the \c QtQuick import. You
can view the code of a \e .ui.qml file in the \l{Code} view.
\section2 Components
\QDS provides preset \l{glossary-component}{components} for creating
UIs, including components for creating and animating visual components,
receiving user input, and creating data models and views.
To be able to use the functionality of preset components, the wizard template
adds the following \e import statements to the UI files (.ui.qml) that it
creates:
\quotefromfile Loginui1/content/Screen01.ui.qml
\skipto import
\printuntil Controls
You can view the import statements in the \uicontrol {Code} view.
The \l Components view lists the components in each module that are
supported by \QDS. You can use the basic components to create your own
components, and they will be listed in \uicontrol {My Components}.
This section is only visible if you have created custom components.
The \l {basic-rectangle}{Rectangle}, \l Text, and \l {Images}{Image}
components used in this tutorial are based on the \l Item component.
It is the base component for all visual elements, with implementation
of basic functions and properties, such as component type, ID, position,
size, and visibility.
For more information, see \l{Use Case - Visual Elements In QML}. For
descriptions of all components, see \l{All QML Types} in the Qt reference
documentation.
\section3 Regtangle Properties
The default \l {basic-rectangle}{Rectangle} component is used for drawing
shapes with four sides and four corners. You can fill rectangles either with
a solid fill color or a gradient. You can specify the border color separately.
By setting the value of the radius property, you can create shapes with
rounded corners.
If you want to specify the radius of each corner separately, you can use the
\l{studio-rectangle}{Rectangle} component from the
\uicontrol {Qt Quick Studio Components} module instead of the basic rectangle
component. It is available in \uicontrol Components
> \uicontrol {Qt Quick Studio Components}.
\section3 Text Properties
The \l Text component is used for adding static text to the UI, such as
titles and labels. You can select the font to use and specify extensive
properties for each text component, such as size in points or pixels,
weight, style, and spacing.
If you want to create a label with a background, use the \l Label component
from the \uicontrol {Qt Quick Controls} module instead of the Text component.
\section3 Image Properties
The \l {Images}{Image} component is used for adding images to the UI in several
supported formats, including bitmap formats such as PNG and JPEG and vector
graphics formats such as SVG. To add an image to \uicontrol Assets, select
\inlineimage icons/plus.png
, and then select the image file.
If you need to display animated images, use the \l {Animated Image}
component, also available in \uicontrol Components >
\uicontrol {Default Components} > \uicontrol Basic.
\section2 UI Controls
The \e {Custom Button} wizard template creates a button component
based on the \l {Button} control in the \l {Qt Quick Controls} module. It
is a push-button control that can be pushed or clicked by the user. Buttons
are normally used to perform an action or to answer a question. The
properties and functionality inherited from the Button component enable
you to set text, display an icon, react to mouse clicks, and so on.
To be able to use the functionality of the Button control, the wizard template
adds the following \e import statements to the \e EntryField.ui.qml file:
\quotefromfile Loginui1/content/EntryField.ui.qml
\skipto import
\printuntil Controls
\section2 Component IDs
Each component and each instance of a component has an \e ID that uniquely Each component and each instance of a component has an \e ID that uniquely
identifies it and enables other components' properties to be bound to it. identifies it and enables other components' properties to be bound to it.

View File

@@ -76,13 +76,11 @@
rectangle at the top, while keeping its horizontal center aligned rectangle at the top, while keeping its horizontal center aligned
with that of the rectangle. with that of the rectangle.
\li Select \e tagLine in \uicontrol Navigator. \li Select \e tagLine in \uicontrol Navigator.
\li In \uicontrol Properties > \uicontrol Layout, deselect the \li In \uicontrol Properties > \uicontrol Layout,
\inlineimage icons/anchor-center-vertical.png select the \inlineimage icons/anchor-top.png
button to remove the vertical center anchor, and then select the button and then select \e qt_logo_green_128x128px
\inlineimage icons/anchor-top.png as \uicontrol Target to anchor \e tagLine with a 40-pixel margin.
button to anchor the tag line to This attaches the top of the tag line to the
\e qt_logo_green_128x128px in the \uicontrol Target field with a
40-pixel margin. This attaches the top of the tag line to the
bottom of the logo, while keeping its horizontal center aligned bottom of the logo, while keeping its horizontal center aligned
with that of the rectangle. with that of the rectangle.
\image loginui2-layout-text.png "Text Layout properties" \image loginui2-layout-text.png "Text Layout properties"
@@ -101,20 +99,6 @@
\image loginui2-loginpage.jpg "Login page in the Design mode and live preview" \image loginui2-loginpage.jpg "Login page in the Design mode and live preview"
\section2 Learn More - Anchors
In an anchor-based layout, each component instance can be thought of as
having a set of invisible \e anchor lines: top, bottom, left, right, fill,
horizontal center, vertical center, and baseline.
Anchors enable placing a component instance either adjacent to or inside of
another component instance, by attaching one or more of the instance's
anchor lines to the anchor lines of the other component instance. If a
component instance changes, the instances that are anchored to it will
adjust automatically to maintain the anchoring.
For more information, see \l{Positioning with Anchors}.
\section1 Using Column Positioners \section1 Using Column Positioners
You will now position the entry fields and buttons in columns You will now position the entry fields and buttons in columns
@@ -150,7 +134,7 @@
\li Select \e fields in \uicontrol Navigator. \li Select \e fields in \uicontrol Navigator.
\li In \uicontrol Properties > \uicontrol Layout, select the \li In \uicontrol Properties > \uicontrol Layout, select the
\inlineimage icons/anchor-top.png \inlineimage icons/anchor-top.png
button to anchor the top of the field column to button to anchor the top of the fields column to
the bottom of \e tagLine with a 170-pixel margin. the bottom of \e tagLine with a 170-pixel margin.
\li Select the \inlineimage icons/anchor-center-horizontal.png \li Select the \inlineimage icons/anchor-center-horizontal.png
button to anchor the column horizontally to its parent. button to anchor the column horizontally to its parent.
@@ -170,7 +154,20 @@
\image loginui2-loginpage-ready.jpg "Login page in the Design mode and live preview" \image loginui2-loginpage-ready.jpg "Login page in the Design mode and live preview"
\section1 Learn More - Positioners \section1 Learn More
\section2 Anchors
In an anchor-based layout, each component instance can be thought of as
having a set of invisible \e anchor lines: top, bottom, left, right, fill,
horizontal center, vertical center, and baseline.
Anchors enable placing a component instance either adjacent to or inside of
another component instance, by attaching one or more of the instance's
anchor lines to the anchor lines of the other component instance. If a
component instance changes, the instances that are anchored to it will
adjust automatically to maintain the anchoring.
For more information, see \l{Positioning with Anchors}.
\section2 Positioners
For many use cases, the best positioner to use is a simple grid, row, or For many use cases, the best positioner to use is a simple grid, row, or
column, and \QDS provides components that will position children in these column, and \QDS provides components that will position children in these

View File

@@ -77,7 +77,7 @@
\section1 Using States to Simulate Page Changes \section1 Using States to Simulate Page Changes
You will now add \l{Adding States}{states} to the UI to show and hide UI You will now add \l{Working with States}{states} to the UI to show and hide UI
components in the \uicontrol {2D} view, depending on the current page: components in the \uicontrol {2D} view, depending on the current page:
\list 1 \list 1
@@ -115,29 +115,6 @@
\image loginui3-login-state-preview.jpg "Preview of the login state" \image loginui3-login-state-preview.jpg "Preview of the login state"
\section2 Learn More - States
The \l{Adding States}{state} of a particular visual component is the set of
information which describes how and where the individual parts of the visual
component are displayed within it, and all the data associated with that
state. Most visual components in a UI will have a limited number of states,
each with well-defined properties.
For example, a list item may be either selected or not, and if
selected, it may either be the currently active single selection or it
may be part of a selection group. Each of those states may have certain
associated visual appearance (neutral, highlighted, expanded, and so on).
Youn can apply states to trigger behavior or animations. UI components
typically have a default state that contains all of a component's initial
property values and is therefore useful for managing property values before
state changes.
You can specify additional states by adding new states. Each state within a
component has a unique name. To change the current state of an component,
the state property is set to the name of the state. State changes can be
bound to conditions by using the \c when property.
Next, you will create connections to specify that clicking the Next, you will create connections to specify that clicking the
\uicontrol {Create Account} button on the login page triggers a \uicontrol {Create Account} button on the login page triggers a
transition to the account creation page. transition to the account creation page.
@@ -162,9 +139,13 @@
\e createAccount should apply. \e createAccount should apply.
\li Double-click the value \uicontrol Action column and select \li Double-click the value \uicontrol Action column and select
\uicontrol {Change state to createAccount} in the drop-down menu. \uicontrol {Change state to createAccount} in the drop-down menu.
\image loginui3-connections.png "Connections tab" \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"
\li Select \uicontrol File > \uicontrol Save or press \key {Ctrl+S} \li Select \uicontrol File > \uicontrol Save or press \key {Ctrl+S}
to save your changes. to save your changes.
\endlist \endlist
In the live preview, you can now click the \uicontrol {Create Account} In the live preview, you can now click the \uicontrol {Create Account}
@@ -172,8 +153,30 @@
\image loginui3.gif "Moving between login page and account creation page" \image loginui3.gif "Moving between login page and account creation page"
\section1 Learn More
\section2 States
The \l{Working with States}{state} of a particular visual component is the set of
information which describes how and where the individual parts of the visual
component are displayed within it, and all the data associated with that
state. Most visual components in a UI will have a limited number of states,
each with well-defined properties.
\section2 Learn More - Signal and Event Handlers For example, a list item may be either selected or not, and if
selected, it may either be the currently active single selection or it
may be part of a selection group. Each of those states may have certain
associated visual appearance (neutral, highlighted, expanded, and so on).
Youn can apply states to trigger behavior or animations. UI components
typically have a default state that contains all of a component's initial
property values and is therefore useful for managing property values before
state changes.
You can specify additional states by adding new states. Each state within a
component has a unique name. To change the current state of an component,
the state property is set to the name of the state. State changes can be
bound to conditions by using the \c when property.
\section2 Signal and Event Handlers
UI components need to communicate with each other. For example, a button UI components need to communicate with each other. For example, a button
needs to know that the user has clicked on it. In response, the button may needs to know that the user has clicked on it. In response, the button may

View File

@@ -240,7 +240,7 @@
\section1 Binding Animation to States \section1 Binding Animation to States
You will now bring back the \l{Adding States}{states} in the You will now bring back the \l{Working with States}{states} in the
\uicontrol States view and bind them to the animation settings \uicontrol States view and bind them to the animation settings
in \uicontrol Timeline: in \uicontrol Timeline:

View File

@@ -31,7 +31,7 @@
The button can have the following states: checked, hover, pressed, and The button can have the following states: checked, hover, pressed, and
normal. We construct the button using different images for the button normal. We construct the button using different images for the button
background, frame, and front. We then add \l{Adding States}{states} in background, frame, and front. We then add \l{Working with States}{states} in
the \l States view for each of the button states. In each state, we turn the \l States view for each of the button states. In each state, we turn
the visibility of the appropriate images on or off in the button properties, the visibility of the appropriate images on or off in the button properties,
to change the appearance of the button. to change the appearance of the button.

View File

@@ -166,7 +166,7 @@
in the top-left corner of the root component. in the top-left corner of the root component.
Then, we open the \uicontrol States view to create the \e start, Then, we open the \uicontrol States view to create the \e start,
\e settings, \e presets, and \e running \l{Adding States}{states} for \e settings, \e presets, and \e running \l{Working with States}{states} for
displaying a particular screen by selecting \uicontrol {Create New State}. displaying a particular screen by selecting \uicontrol {Create New State}.
\image washingmachineui-states.png "States view" \image washingmachineui-states.png "States view"

View File

@@ -117,7 +117,7 @@
PNG file, as a border and a background. PNG file, as a border and a background.
Use two border images and suitable graphics to change the appearance of Use two border images and suitable graphics to change the appearance of
a button when it is clicked. You can use use \l{Adding States}{states} a button when it is clicked. You can use use \l{Working with States}{states}
to determine which image is visible depending on whether the mouse to determine which image is visible depending on whether the mouse
button is pressed down. You could add more images and states to button is pressed down. You could add more images and states to
change the appearance of the button depending on other mouse events, change the appearance of the button depending on other mouse events,

View File

@@ -43,7 +43,7 @@
\image qmldesigner-bindings.png "Connections view Bindings tab" \image qmldesigner-bindings.png "Connections view Bindings tab"
\li Add states to apply sets of changes to the property values of one \li Add states to apply sets of changes to the property values of one
or several component instances in the \uicontrol States view. or several component instances in the \uicontrol States view.
For more information, see \l{Adding States}. For more information, see \l{Working with States}.
\li Animate the properties of component instances in the \li Animate the properties of component instances in the
\uicontrol Timeline view. For more information, see \uicontrol Timeline view. For more information, see
\l{Creating Timeline Animations}. \l{Creating Timeline Animations}.

View File

@@ -63,7 +63,7 @@
your UI. your UI.
\li Use as few components as necessary. To minimize the number of \li Use as few components as necessary. To minimize the number of
components, use \l{Adding Property Aliases}{alias properties} and components, use \l{Adding Property Aliases}{alias properties} and
\l{Adding States}{states} to create the differences in your \l{Working with States}{states} to create the differences in your
component instances. We recommend reusing components component instances. We recommend reusing components
instead of duplicating them, so the components do not need to be instead of duplicating them, so the components do not need to be
processed as completely new component types. This reduces loading processed as completely new component types. This reduces loading

View File

@@ -32,7 +32,7 @@
exported as a public property of the relevant component. For example, exported as a public property of the relevant component. For example,
a speedometer should have a property for speed to which the UI is bound. a speedometer should have a property for speed to which the UI is bound.
You can declare various \l{Adding States}{UI states} that describe how You can declare various \l{Working with States}{UI states} that describe how
property values change from a base state. States can be a useful way of property values change from a base state. States can be a useful way of
organizing your UI logic. You can associate transitions with components organizing your UI logic. You can associate transitions with components
to define how their properties will animate when they change due to a to define how their properties will animate when they change due to a
@@ -100,7 +100,7 @@
\li \l{Adding Property Aliases} \li \l{Adding Property Aliases}
\row \row
\li Referencing a state from within a specific component \li Referencing a state from within a specific component
\li \l{Adding States} \li \l{Working with States}
\row \row
\li Switching to a state when a particular property changes \li Switching to a state when a particular property changes
\li \l{Applying States} \li \l{Applying States}

View File

@@ -49,7 +49,7 @@
\uicontrol {Flow Decision} components from \uicontrol {Flow Decision} components from
\uicontrol Components > \uicontrol {Flow View}, as described in \uicontrol Components > \uicontrol {Flow View}, as described in
\l{Simulating Conditions}. \l{Simulating Conditions}.
\li Use \l{Adding States}{states} in flows to modify the appearance \li Use \l{Working with States}{states} in flows to modify the appearance
of components on screens in response to user interaction, as of components on screens in response to user interaction, as
described in \l{Applying States in Flows}. described in \l{Applying States in Flows}.
\li Use \uicontrol {Flow Wildcard} components from \li Use \uicontrol {Flow Wildcard} components from
@@ -620,7 +620,7 @@
\title Applying States in Flows \title Applying States in Flows
You can use \l{Adding States}{states} in flows to modify the appearance You can use \l{Working with States}{states} in flows to modify the appearance
of \l{glossary-component}{components} in flow items in response to user of \l{glossary-component}{components} in flow items in response to user
interaction, for example. For this purpose, you use the interaction, for example. For this purpose, you use the
\uicontrol {Flow Item} components available in \uicontrol {Flow Item} components available in

View File

@@ -28,7 +28,7 @@
\endtable \endtable
You can edit the properties of the controls in all the preset You can edit the properties of the controls in all the preset
\l{Adding States}{states} to apply your own style to them. \l{Working with States}{states} to apply your own style to them.
\note For buttons and check boxes, you can disable the misbehaving hover \note For buttons and check boxes, you can disable the misbehaving hover
effects by selecting \l Properties > \uicontrol Control, and then disabling effects by selecting \l Properties > \uicontrol Control, and then disabling

View File

@@ -121,7 +121,7 @@
\list \list
\li \l{Connections} \li \l{Connections}
\li \l{Adding Connections} \li \l{Working with Connections}
\endlist \endlist
\section1 Device \section1 Device
@@ -249,7 +249,7 @@
\list \list
\li \l{States} \li \l{States}
\li \l{Adding States} \li \l{Working with States}
\endlist \endlist
\section1 Transition \section1 Transition

View File

@@ -118,13 +118,13 @@
\endlist \endlist
\li \l{Dynamic Behaviors} \li \l{Dynamic Behaviors}
\list \list
\li \l{Adding Connections} \li \l{Working with Connections}
\list \list
\li\l{Connecting Components to Signals} \li\l{Connecting Components to Signals}
\li\l{Adding Bindings Between Properties} \li\l{Adding Bindings Between Properties}
\li\l{Specifying Custom Properties} \li\l{Specifying Custom Properties}
\endlist \endlist
\li \l{Adding States} \li \l{Working with States}
\endlist \endlist
\li \l{Validating with Target Hardware} \li \l{Validating with Target Hardware}
\list \list

View File

@@ -13,7 +13,7 @@
when the values of other components or the UI state change. when the values of other components or the UI state change.
\list \list
\li \l {Adding Connections} \li \l {Working with Connections}
You can create connections between the UI components and You can create connections between the UI components and
the application to enable them to communicate with each other. For the application to enable them to communicate with each other. For
@@ -25,7 +25,7 @@
binding their properties together. This way, when the value of a binding their properties together. This way, when the value of a
property changes in a parent component, it can be automatically property changes in a parent component, it can be automatically
changed in all the child components, for example. changed in all the child components, for example.
\li \l {Adding States} \li \l {Working with States}
You can declare various UI states that describe how component You can declare various UI states that describe how component
properties change from a base state. Therefore, states can be properties change from a base state. Therefore, states can be

View File

@@ -6,7 +6,7 @@
\previouspage qtquick-adding-dynamics.html \previouspage qtquick-adding-dynamics.html
\nextpage quick-signals.html \nextpage quick-signals.html
\title Adding Connections \title Working with Connections
\list \list
\li \l{Connecting Components to Signals} \li \l{Connecting Components to Signals}

View File

@@ -75,14 +75,14 @@
\li \l{Connections} \li \l{Connections}
\li Enables you to add functionality to the UI by creating \li Enables you to add functionality to the UI by creating
connections between components, signals, and component properties. connections between components, signals, and component properties.
\li \l{Adding Connections} \li \l{Working with Connections}
\row \row
\li \l States \li \l States
\li Displays the different states that can be applied to a component. \li Displays the different states that can be applied to a component.
Typically, states describe UI configurations, such as the Typically, states describe UI configurations, such as the
visibility and behavior of components and the available user visibility and behavior of components and the available user
actions. actions.
\li \l{Adding States} \li \l{Working with States}
\row \row
\li \l{Transitions} \li \l{Transitions}
\li Enables you to make movement between states smooth by animating \li Enables you to make movement between states smooth by animating

View File

@@ -124,7 +124,7 @@
the \l {3D} view nor access their properties in the \l {3D} view nor access their properties in
\uicontrol Properties. \uicontrol Properties.
If you attempt to \l{Adding States}{remove a state} that changes the If you attempt to \l{Working with States}{remove a state} that changes the
properties of a locked component, you are prompted to confirm the removal. properties of a locked component, you are prompted to confirm the removal.
If you have \l{Editing Animation Curves}{added easing curves} to keyframe If you have \l{Editing Animation Curves}{added easing curves} to keyframe
@@ -212,7 +212,7 @@
\image qmldesigner-export-item.png \image qmldesigner-export-item.png
You can then use the property alias in other components to You can then use the property alias in other components to
\l{Adding Connections}{create connections} to this component. \l{Working with Connections}{create connections} to this component.
\section1 Moving Within Components \section1 Moving Within Components

View File

@@ -86,7 +86,7 @@
The default values of properties are displayed in white color, while the The default values of properties are displayed in white color, while the
values that you specify explicitly are highlighted with blue color. In values that you specify explicitly are highlighted with blue color. In
addition, property changes in \l{Adding States}{states} are highlighted addition, property changes in \l{Working with States}{states} are highlighted
with blue. with blue.
This allows you to easily see which values are set in the component This allows you to easily see which values are set in the component

View File

@@ -153,7 +153,7 @@
If the \uicontrol Clip check box is selected, the component and its children If the \uicontrol Clip check box is selected, the component and its children
are clipped to the bounding rectangle of the component. are clipped to the bounding rectangle of the component.
in the \uicontrol State field, select the \l{Adding States}{state} to in the \uicontrol State field, select the \l{Working with States}{state} to
change the value of a property in that state. change the value of a property in that state.
\section1 Picking Colors \section1 Picking Colors

View File

@@ -8,8 +8,8 @@
\title States \title States
The \uicontrol States view displays the different \l{Adding States}{states} The \uicontrol States view displays the different
of a UI. \l{Working with States}{states} of a UI.
\image qmldesigner-transitions.png "States view" \image qmldesigner-transitions.png "States view"

View File

@@ -11,7 +11,7 @@
\nextpage exporting-3d-assets.html \nextpage exporting-3d-assets.html
\endif \endif
\title Adding States \title Working with States
You can define states for components and component instances in the You can define states for components and component instances in the
\l States view by selecting \inlineimage icons/plus.png \l States view by selecting \inlineimage icons/plus.png

View File

@@ -12,7 +12,7 @@
\uicontrol {Transitions} to animate the changes between \uicontrol {Transitions} to animate the changes between
states. states.
First, you need to \l{Adding States}{add states} in the \l States view First, you need to \l{Working with States}{add states} in the \l States view
and \l{Specifying Component Properties}{edit some properties} that can be and \l{Specifying Component Properties}{edit some properties} that can be
animated, such as colors or numbers, in the \l Properties view. For example, animated, such as colors or numbers, in the \l Properties view. For example,
you can animate the changes in the position of a component. you can animate the changes in the position of a component.

View File

@@ -24,6 +24,10 @@ Item {
// Array of supported externally dropped files that trigger custom import process // Array of supported externally dropped files that trigger custom import process
property var dropComplexExtFiles: [] property var dropComplexExtFiles: []
AssetsContextMenu {
id: contextMenu
}
function clearSearchFilter() function clearSearchFilter()
{ {
searchBox.clear(); searchBox.clear();
@@ -103,307 +107,11 @@ Item {
root.selectedAssetsChanged() root.selectedAssetsChanged()
} }
StudioControls.Menu {
id: contextMenu
closePolicy: Popup.CloseOnPressOutside | Popup.CloseOnEscape
onOpened: {
var numSelected = Object.values(root.selectedAssets).filter(p => p).length
deleteFileItem.text = numSelected > 1 ? qsTr("Delete Files") : qsTr("Delete File")
}
StudioControls.MenuItem {
text: qsTr("Expand All")
enabled: root.allExpandedState !== 1
visible: root.isDirContextMenu
height: visible ? implicitHeight : 0
onTriggered: assetsModel.toggleExpandAll(true)
}
StudioControls.MenuItem {
text: qsTr("Collapse All")
enabled: root.allExpandedState !== 2
visible: root.isDirContextMenu
height: visible ? implicitHeight : 0
onTriggered: assetsModel.toggleExpandAll(false)
}
StudioControls.MenuSeparator {
visible: root.isDirContextMenu
height: visible ? StudioTheme.Values.border : 0
}
StudioControls.MenuItem {
id: deleteFileItem
text: qsTr("Delete File")
visible: root.contextFilePath
height: deleteFileItem.visible ? deleteFileItem.implicitHeight : 0
onTriggered: {
assetsModel.deleteFiles(Object.keys(root.selectedAssets).filter(p => root.selectedAssets[p]))
}
}
StudioControls.MenuSeparator {
visible: root.contextFilePath
height: visible ? StudioTheme.Values.border : 0
}
StudioControls.MenuItem {
text: qsTr("Rename Folder")
visible: root.isDirContextMenu
height: visible ? implicitHeight : 0
onTriggered: renameFolderDialog.open()
}
StudioControls.MenuItem {
text: qsTr("New Folder")
onTriggered: newFolderDialog.open()
}
StudioControls.MenuItem {
text: qsTr("Delete Folder")
visible: root.isDirContextMenu
height: visible ? implicitHeight : 0
onTriggered: {
var dirEmpty = !(root.contextDir.dirsModel && root.contextDir.dirsModel.rowCount() > 0)
&& !(root.contextDir.filesModel && root.contextDir.filesModel.rowCount() > 0);
if (dirEmpty)
assetsModel.deleteFolder(root.contextDir.dirPath)
else
confirmDeleteFolderDialog.open()
}
}
}
RegExpValidator { RegExpValidator {
id: folderNameValidator id: folderNameValidator
regExp: /^(\w[^*/><?\\|:]*)$/ regExp: /^(\w[^*/><?\\|:]*)$/
} }
Dialog {
id: renameFolderDialog
title: qsTr("Rename Folder")
anchors.centerIn: parent
closePolicy: Popup.CloseOnEscape
implicitWidth: 280
modal: true
property bool renameError: false
contentItem: Column {
spacing: 2
StudioControls.TextField {
id: folderRename
actionIndicator.visible: false
translationIndicator.visible: false
width: renameFolderDialog.width - 12
validator: folderNameValidator
onEditChanged: renameFolderDialog.renameError = false
Keys.onEnterPressed: btnRename.onClicked()
Keys.onReturnPressed: btnRename.onClicked()
}
Text {
text: qsTr("Folder name cannot be empty.")
color: "#ff0000"
visible: folderRename.text === "" && !renameFolderDialog.renameError
}
Text {
text: qsTr("Could not rename folder. Make sure no folder with the same name exists.")
wrapMode: Text.WordWrap
width: renameFolderDialog.width - 12
color: "#ff0000"
visible: renameFolderDialog.renameError
}
Item { // spacer
width: 1
height: 10
}
Text {
text: qsTr("If the folder has assets in use, renaming it might cause the project to not work correctly.")
color: StudioTheme.Values.themeTextColor
wrapMode: Text.WordWrap
width: renameFolderDialog.width
leftPadding: 10
rightPadding: 10
}
Item { // spacer
width: 1
height: 20
}
Row {
anchors.right: parent.right
Button {
id: btnRename
text: qsTr("Rename")
enabled: folderRename.text !== ""
onClicked: {
var success = assetsModel.renameFolder(root.contextDir.dirPath, folderRename.text)
if (success)
renameFolderDialog.accept()
renameFolderDialog.renameError = !success
}
}
Button {
text: qsTr("Cancel")
onClicked: renameFolderDialog.reject()
}
}
}
onOpened: {
folderRename.text = root.contextDir.dirName
folderRename.selectAll()
folderRename.forceActiveFocus()
renameFolderDialog.renameError = false
}
}
Dialog {
id: newFolderDialog
title: qsTr("Create New Folder")
anchors.centerIn: parent
closePolicy: Popup.CloseOnEscape
modal: true
contentItem: Column {
spacing: 2
Row {
Text {
text: qsTr("Folder name: ")
anchors.verticalCenter: parent.verticalCenter
color: StudioTheme.Values.themeTextColor
}
StudioControls.TextField {
id: folderName
actionIndicator.visible: false
translationIndicator.visible: false
validator: folderNameValidator
Keys.onEnterPressed: btnCreate.onClicked()
Keys.onReturnPressed: btnCreate.onClicked()
}
}
Text {
text: qsTr("Folder name cannot be empty.")
color: "#ff0000"
anchors.right: parent.right
visible: folderName.text === ""
}
Item { // spacer
width: 1
height: 20
}
Row {
anchors.right: parent.right
Button {
id: btnCreate
text: qsTr("Create")
enabled: folderName.text !== ""
onClicked: {
assetsModel.addNewFolder(root.contextDir.dirPath + '/' + folderName.text)
newFolderDialog.accept()
}
}
Button {
text: qsTr("Cancel")
onClicked: newFolderDialog.reject()
}
}
}
onOpened: {
folderName.text = "New folder"
folderName.selectAll()
folderName.forceActiveFocus()
}
}
Dialog {
id: confirmDeleteFolderDialog
title: qsTr("Folder Not Empty")
anchors.centerIn: parent
closePolicy: Popup.CloseOnEscape
implicitWidth: 300
modal: true
contentItem: Column {
spacing: 20
width: parent.width
Text {
id: folderNotEmpty
text: qsTr("Folder \"%1\" is not empty. Delete it anyway?")
.arg(root.contextDir ? root.contextDir.dirName : "")
color: StudioTheme.Values.themeTextColor
wrapMode: Text.WordWrap
width: confirmDeleteFolderDialog.width
leftPadding: 10
rightPadding: 10
Keys.onEnterPressed: btnDelete.onClicked()
Keys.onReturnPressed: btnDelete.onClicked()
}
Text {
text: qsTr("If the folder has assets in use, deleting it might cause the project to not work correctly.")
color: StudioTheme.Values.themeTextColor
wrapMode: Text.WordWrap
width: confirmDeleteFolderDialog.width
leftPadding: 10
rightPadding: 10
}
Row {
anchors.right: parent.right
Button {
id: btnDelete
text: qsTr("Delete")
onClicked: {
assetsModel.deleteFolder(root.contextDir.dirPath)
confirmDeleteFolderDialog.accept()
}
}
Button {
text: qsTr("Cancel")
onClicked: confirmDeleteFolderDialog.reject()
}
}
}
onOpened: folderNotEmpty.forceActiveFocus()
}
Column { Column {
anchors.fill: parent anchors.fill: parent
anchors.topMargin: 5 anchors.topMargin: 5
@@ -507,220 +215,10 @@ Item {
} }
} }
ScrollView { // TODO: experiment using ListView instead of ScrollView + Column AssetsView {
id: assetsView id: assetsView
width: parent.width width: parent.width
height: parent.height - y height: parent.height - y
clip: true
interactive: assetsView.verticalScrollBarVisible && !contextMenu.opened
Column {
Repeater {
model: assetsModel // context property
delegate: dirSection
}
Component {
id: dirSection
Section {
id: section
width: assetsView.width -
(assetsView.verticalScrollBarVisible ? assetsView.verticalThickness : 0) - 5
caption: dirName
sectionHeight: 30
sectionFontSize: 15
leftPadding: 0
topPadding: dirDepth > 0 ? 5 : 0
bottomPadding: 0
hideHeader: dirDepth === 0
showLeftBorder: dirDepth > 0
expanded: dirExpanded
visible: dirVisible
expandOnClick: false
useDefaulContextMenu: false
dropEnabled: true
onToggleExpand: {
dirExpanded = !dirExpanded
}
onDropEnter: (drag)=> {
root.updateDropExtFiles(drag)
section.highlight = drag.accepted && root.dropSimpleExtFiles.length > 0
}
onDropExit: {
section.highlight = false
}
onDrop: {
section.highlight = false
rootView.handleExtFilesDrop(root.dropSimpleExtFiles,
root.dropComplexExtFiles,
dirPath)
}
onShowContextMenu: {
root.contextFilePath = ""
root.contextDir = model
root.isDirContextMenu = true
root.allExpandedState = assetsModel.getAllExpandedState()
contextMenu.popup()
}
Column {
spacing: 5
leftPadding: 5
Repeater {
model: dirsModel
delegate: dirSection
}
Repeater {
model: filesModel
delegate: fileSection
}
Text {
text: qsTr("Empty folder")
color: StudioTheme.Values.themeTextColorDisabled
font.pixelSize: 12
visible: !(dirsModel && dirsModel.rowCount() > 0)
&& !(filesModel && filesModel.rowCount() > 0)
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: {
root.contextFilePath = ""
root.contextDir = model
root.isDirContextMenu = true
contextMenu.popup()
}
}
}
}
}
}
Component {
id: fileSection
Rectangle {
width: assetsView.width -
(assetsView.verticalScrollBarVisible ? assetsView.verticalThickness : 0)
height: img.height
color: root.selectedAssets[filePath]
? StudioTheme.Values.themeInteraction
: (mouseArea.containsMouse ? StudioTheme.Values.themeSectionHeadBackground
: "transparent")
Row {
spacing: 5
Image {
id: img
asynchronous: true
fillMode: Image.PreserveAspectFit
width: 48
height: 48
source: "image://qmldesigner_assets/" + filePath
}
Text {
text: fileName
color: StudioTheme.Values.themeTextColor
font.pixelSize: 14
anchors.verticalCenter: parent.verticalCenter
}
}
readonly property string suffix: fileName.substr(-4)
readonly property bool isFont: suffix === ".ttf" || suffix === ".otf"
property bool currFileSelected: false
MouseArea {
id: mouseArea
property bool allowTooltip: true
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
onExited: tooltipBackend.hideTooltip()
onEntered: allowTooltip = true
onCanceled: {
tooltipBackend.hideTooltip()
allowTooltip = true
}
onPositionChanged: tooltipBackend.reposition()
onPressed: (mouse)=> {
forceActiveFocus()
allowTooltip = false
tooltipBackend.hideTooltip()
var ctrlDown = mouse.modifiers & Qt.ControlModifier
if (mouse.button === Qt.LeftButton) {
if (!root.selectedAssets[filePath] && !ctrlDown)
root.selectedAssets = {}
currFileSelected = ctrlDown ? !root.selectedAssets[filePath] : true
root.selectedAssets[filePath] = currFileSelected
root.selectedAssetsChanged()
if (currFileSelected) {
rootView.startDragAsset(
Object.keys(root.selectedAssets).filter(p => root.selectedAssets[p]),
mapToGlobal(mouse.x, mouse.y))
}
} else {
if (!root.selectedAssets[filePath] && !ctrlDown)
root.selectedAssets = {}
currFileSelected = root.selectedAssets[filePath] || !ctrlDown
root.selectedAssets[filePath] = currFileSelected
root.selectedAssetsChanged()
root.contextFilePath = filePath
root.contextDir = model.fileDir
root.isDirContextMenu = false
contextMenu.popup()
}
}
onReleased: (mouse)=> {
allowTooltip = true
if (mouse.button === Qt.LeftButton) {
if (!(mouse.modifiers & Qt.ControlModifier))
root.selectedAssets = {}
root.selectedAssets[filePath] = currFileSelected
root.selectedAssetsChanged()
}
}
ToolTip {
visible: !isFont && mouseArea.containsMouse && !contextMenu.visible
text: filePath
delay: 1000
}
Timer {
interval: 1000
running: mouseArea.containsMouse && mouseArea.allowTooltip
onTriggered: {
if (suffix === ".ttf" || suffix === ".otf") {
tooltipBackend.name = fileName
tooltipBackend.path = filePath
tooltipBackend.showTooltip()
}
}
}
}
}
}
}
} }
} }
} }

View File

@@ -0,0 +1,120 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuickDesignerTheme
import HelperWidgets as HelperWidgets
import StudioControls as StudioControls
import StudioTheme as StudioTheme
StudioControls.Menu {
id: contextMenu
closePolicy: Popup.CloseOnPressOutside | Popup.CloseOnEscape
onOpened: {
var numSelected = Object.values(root.selectedAssets).filter(p => p).length
deleteFileItem.text = numSelected > 1 ? qsTr("Delete Files") : qsTr("Delete File")
}
StudioControls.MenuItem {
text: qsTr("Expand All")
enabled: root.allExpandedState !== 1
visible: root.isDirContextMenu
height: visible ? implicitHeight : 0
onTriggered: assetsModel.toggleExpandAll(true)
}
StudioControls.MenuItem {
text: qsTr("Collapse All")
enabled: root.allExpandedState !== 2
visible: root.isDirContextMenu
height: visible ? implicitHeight : 0
onTriggered: assetsModel.toggleExpandAll(false)
}
StudioControls.MenuSeparator {
visible: root.isDirContextMenu
height: visible ? StudioTheme.Values.border : 0
}
StudioControls.MenuItem {
id: deleteFileItem
text: qsTr("Delete File")
visible: root.contextFilePath
height: deleteFileItem.visible ? deleteFileItem.implicitHeight : 0
onTriggered: {
assetsModel.deleteFiles(Object.keys(root.selectedAssets).filter(p => root.selectedAssets[p]))
}
}
StudioControls.MenuSeparator {
visible: root.contextFilePath
height: visible ? StudioTheme.Values.border : 0
}
StudioControls.MenuItem {
text: qsTr("Rename Folder")
visible: root.isDirContextMenu
height: visible ? implicitHeight : 0
onTriggered: renameFolderDialog.open()
RenameFolderDialog {
id: renameFolderDialog
}
}
StudioControls.MenuItem {
text: qsTr("New Folder")
NewFolderDialog {
id: newFolderDialog
}
onTriggered: newFolderDialog.open()
}
StudioControls.MenuItem {
text: qsTr("Delete Folder")
visible: root.isDirContextMenu
height: visible ? implicitHeight : 0
ConfirmDeleteFolderDialog {
id: confirmDeleteFolderDialog
}
onTriggered: {
var dirEmpty = !(root.contextDir.dirsModel && root.contextDir.dirsModel.rowCount() > 0)
&& !(root.contextDir.filesModel && root.contextDir.filesModel.rowCount() > 0);
if (dirEmpty)
assetsModel.deleteFolder(root.contextDir.dirPath)
else
confirmDeleteFolderDialog.open()
}
}
}

View File

@@ -0,0 +1,246 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuickDesignerTheme
import HelperWidgets
import StudioControls as StudioControls
import StudioTheme as StudioTheme
ScrollView { // TODO: experiment using ListView instead of ScrollView + Column
id: assetsView
clip: true
interactive: assetsView.verticalScrollBarVisible && !contextMenu.opened
Column {
Repeater {
model: assetsModel // context property
delegate: dirSection
}
Component {
id: dirSection
Section {
id: section
width: assetsView.width -
(assetsView.verticalScrollBarVisible ? assetsView.verticalThickness : 0) - 5
caption: dirName
sectionHeight: 30
sectionFontSize: 15
leftPadding: 0
topPadding: dirDepth > 0 ? 5 : 0
bottomPadding: 0
hideHeader: dirDepth === 0
showLeftBorder: dirDepth > 0
expanded: dirExpanded
visible: dirVisible
expandOnClick: false
useDefaulContextMenu: false
dropEnabled: true
onToggleExpand: {
dirExpanded = !dirExpanded
}
onDropEnter: (drag)=> {
root.updateDropExtFiles(drag)
section.highlight = drag.accepted && root.dropSimpleExtFiles.length > 0
}
onDropExit: {
section.highlight = false
}
onDrop: {
section.highlight = false
rootView.handleExtFilesDrop(root.dropSimpleExtFiles,
root.dropComplexExtFiles,
dirPath)
}
onShowContextMenu: {
root.contextFilePath = ""
root.contextDir = model
root.isDirContextMenu = true
root.allExpandedState = assetsModel.getAllExpandedState()
contextMenu.popup()
}
Column {
spacing: 5
leftPadding: 5
Repeater {
model: dirsModel
delegate: dirSection
}
Repeater {
model: filesModel
delegate: fileSection
}
Text {
text: qsTr("Empty folder")
color: StudioTheme.Values.themeTextColorDisabled
font.pixelSize: 12
visible: !(dirsModel && dirsModel.rowCount() > 0)
&& !(filesModel && filesModel.rowCount() > 0)
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: {
root.contextFilePath = ""
root.contextDir = model
root.isDirContextMenu = true
contextMenu.popup()
}
}
}
}
}
}
Component {
id: fileSection
Rectangle {
width: assetsView.width -
(assetsView.verticalScrollBarVisible ? assetsView.verticalThickness : 0)
height: img.height
color: root.selectedAssets[filePath]
? StudioTheme.Values.themeInteraction
: (mouseArea.containsMouse ? StudioTheme.Values.themeSectionHeadBackground
: "transparent")
Row {
spacing: 5
Image {
id: img
asynchronous: true
fillMode: Image.PreserveAspectFit
width: 48
height: 48
source: "image://qmldesigner_assets/" + filePath
}
Text {
text: fileName
color: StudioTheme.Values.themeTextColor
font.pixelSize: 14
anchors.verticalCenter: parent.verticalCenter
}
}
readonly property string suffix: fileName.substr(-4)
readonly property bool isFont: suffix === ".ttf" || suffix === ".otf"
property bool currFileSelected: false
MouseArea {
id: mouseArea
property bool allowTooltip: true
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
onExited: tooltipBackend.hideTooltip()
onEntered: allowTooltip = true
onCanceled: {
tooltipBackend.hideTooltip()
allowTooltip = true
}
onPositionChanged: tooltipBackend.reposition()
onPressed: (mouse)=> {
forceActiveFocus()
allowTooltip = false
tooltipBackend.hideTooltip()
var ctrlDown = mouse.modifiers & Qt.ControlModifier
if (mouse.button === Qt.LeftButton) {
if (!root.selectedAssets[filePath] && !ctrlDown)
root.selectedAssets = {}
currFileSelected = ctrlDown ? !root.selectedAssets[filePath] : true
root.selectedAssets[filePath] = currFileSelected
root.selectedAssetsChanged()
if (currFileSelected) {
rootView.startDragAsset(
Object.keys(root.selectedAssets).filter(p => root.selectedAssets[p]),
mapToGlobal(mouse.x, mouse.y))
}
} else {
if (!root.selectedAssets[filePath] && !ctrlDown)
root.selectedAssets = {}
currFileSelected = root.selectedAssets[filePath] || !ctrlDown
root.selectedAssets[filePath] = currFileSelected
root.selectedAssetsChanged()
root.contextFilePath = filePath
root.contextDir = model.fileDir
root.isDirContextMenu = false
contextMenu.popup()
}
}
onReleased: (mouse)=> {
allowTooltip = true
if (mouse.button === Qt.LeftButton) {
if (!(mouse.modifiers & Qt.ControlModifier))
root.selectedAssets = {}
root.selectedAssets[filePath] = currFileSelected
root.selectedAssetsChanged()
}
}
ToolTip {
visible: !isFont && mouseArea.containsMouse && !contextMenu.visible
text: filePath
delay: 1000
}
Timer {
interval: 1000
running: mouseArea.containsMouse && mouseArea.allowTooltip
onTriggered: {
if (suffix === ".ttf" || suffix === ".otf") {
tooltipBackend.name = fileName
tooltipBackend.path = filePath
tooltipBackend.showTooltip()
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,92 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuickDesignerTheme
import HelperWidgets
import StudioControls as StudioControls
import StudioTheme as StudioTheme
Dialog {
id: confirmDeleteFolderDialog
title: qsTr("Folder Not Empty")
anchors.centerIn: parent
closePolicy: Popup.CloseOnEscape
implicitWidth: 300
modal: true
contentItem: Column {
spacing: 20
width: parent.width
Text {
id: folderNotEmpty
text: qsTr("Folder \"%1\" is not empty. Delete it anyway?")
.arg(root.contextDir ? root.contextDir.dirName : "")
color: StudioTheme.Values.themeTextColor
wrapMode: Text.WordWrap
width: confirmDeleteFolderDialog.width
leftPadding: 10
rightPadding: 10
Keys.onEnterPressed: btnDelete.onClicked()
Keys.onReturnPressed: btnDelete.onClicked()
}
Text {
text: qsTr("If the folder has assets in use, deleting it might cause the project to not work correctly.")
color: StudioTheme.Values.themeTextColor
wrapMode: Text.WordWrap
width: confirmDeleteFolderDialog.width
leftPadding: 10
rightPadding: 10
}
Row {
anchors.right: parent.right
Button {
id: btnDelete
text: qsTr("Delete")
onClicked: {
assetsModel.deleteFolder(root.contextDir.dirPath)
confirmDeleteFolderDialog.accept()
}
}
Button {
text: qsTr("Cancel")
onClicked: confirmDeleteFolderDialog.reject()
}
}
}
onOpened: folderNotEmpty.forceActiveFocus()
}

View File

@@ -0,0 +1,102 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuickDesignerTheme
import HelperWidgets
import StudioControls as StudioControls
import StudioTheme as StudioTheme
Dialog {
id: newFolderDialog
title: qsTr("Create New Folder")
anchors.centerIn: parent
closePolicy: Popup.CloseOnEscape
modal: true
contentItem: Column {
spacing: 2
Row {
Text {
text: qsTr("Folder name: ")
anchors.verticalCenter: parent.verticalCenter
color: StudioTheme.Values.themeTextColor
}
StudioControls.TextField {
id: folderName
actionIndicator.visible: false
translationIndicator.visible: false
validator: folderNameValidator
Keys.onEnterPressed: btnCreate.onClicked()
Keys.onReturnPressed: btnCreate.onClicked()
}
}
Text {
text: qsTr("Folder name cannot be empty.")
color: "#ff0000"
anchors.right: parent.right
visible: folderName.text === ""
}
Item { // spacer
width: 1
height: 20
}
Row {
anchors.right: parent.right
Button {
id: btnCreate
text: qsTr("Create")
enabled: folderName.text !== ""
onClicked: {
assetsModel.addNewFolder(root.contextDir.dirPath + '/' + folderName.text)
newFolderDialog.accept()
}
}
Button {
text: qsTr("Cancel")
onClicked: newFolderDialog.reject()
}
}
}
onOpened: {
folderName.text = qsTr("New folder")
folderName.selectAll()
folderName.forceActiveFocus()
}
}

View File

@@ -0,0 +1,124 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuickDesignerTheme
import HelperWidgets
import StudioControls as StudioControls
import StudioTheme as StudioTheme
Dialog {
id: renameFolderDialog
title: qsTr("Rename Folder")
anchors.centerIn: parent
closePolicy: Popup.CloseOnEscape
implicitWidth: 280
modal: true
property bool renameError: false
contentItem: Column {
spacing: 2
StudioControls.TextField {
id: folderRename
actionIndicator.visible: false
translationIndicator.visible: false
width: renameFolderDialog.width - 12
validator: folderNameValidator
onEditChanged: renameFolderDialog.renameError = false
Keys.onEnterPressed: btnRename.onClicked()
Keys.onReturnPressed: btnRename.onClicked()
}
Text {
text: qsTr("Folder name cannot be empty.")
color: "#ff0000"
visible: folderRename.text === "" && !renameFolderDialog.renameError
}
Text {
text: qsTr("Could not rename folder. Make sure no folder with the same name exists.")
wrapMode: Text.WordWrap
width: renameFolderDialog.width - 12
color: "#ff0000"
visible: renameFolderDialog.renameError
}
Item { // spacer
width: 1
height: 10
}
Text {
text: qsTr("If the folder has assets in use, renaming it might cause the project to not work correctly.")
color: StudioTheme.Values.themeTextColor
wrapMode: Text.WordWrap
width: renameFolderDialog.width
leftPadding: 10
rightPadding: 10
}
Item { // spacer
width: 1
height: 20
}
Row {
anchors.right: parent.right
Button {
id: btnRename
text: qsTr("Rename")
enabled: folderRename.text !== ""
onClicked: {
var success = assetsModel.renameFolder(root.contextDir.dirPath, folderRename.text)
if (success)
renameFolderDialog.accept()
renameFolderDialog.renameError = !success
}
}
Button {
text: qsTr("Cancel")
onClicked: renameFolderDialog.reject()
}
}
}
onOpened: {
folderRename.text = root.contextDir.dirName
folderRename.selectAll()
folderRename.forceActiveFocus()
renameFolderDialog.renameError = false
}
}

View File

@@ -27,6 +27,8 @@ import QtQuick 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import QtQuickDesignerTheme 1.0 import QtQuickDesignerTheme 1.0
import HelperWidgets 2.0 import HelperWidgets 2.0
import QtQuick.Controls
import StudioTheme 1.0 as StudioTheme import StudioTheme 1.0 as StudioTheme
Item { Item {
@@ -43,7 +45,7 @@ Item {
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
onPressed: (mouse) => { onPressed: (mouse) => {
if (mouse.button === Qt.LeftButton) if (mouse.button === Qt.LeftButton && !materialBrowserBundleModel.importerRunning)
rootView.startDragBundleMaterial(modelData, mapToGlobal(mouse.x, mouse.y)) rootView.startDragBundleMaterial(modelData, mapToGlobal(mouse.x, mouse.y))
else if (mouse.button === Qt.RightButton) else if (mouse.button === Qt.RightButton)
root.showContextMenu() root.showContextMenu()
@@ -64,6 +66,48 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
source: modelData.bundleMaterialIcon source: modelData.bundleMaterialIcon
cache: false cache: false
Rectangle { // circular indicator for imported bundle materials
width: 10
height: 10
radius: 5
anchors.right: img.right
anchors.top: img.top
anchors.margins: 5
color: "#00ff00"
border.color: "#555555"
border.width: 1
visible: modelData.bundleMaterialImported
ToolTip {
visible: indicatorMouseArea.containsMouse
text: qsTr("Material is imported to project")
delay: 1000
}
MouseArea {
id: indicatorMouseArea
anchors.fill: parent
hoverEnabled: true
}
}
IconButton {
icon: StudioTheme.Constants.plus
tooltip: qsTr("Add an instance to project")
buttonSize: 22
property color c: StudioTheme.Values.themeIconColor
normalColor: Qt.hsla(c.hslHue, c.hslSaturation, c.hslLightness, .2)
hoverColor: Qt.hsla(c.hslHue, c.hslSaturation, c.hslLightness, .3)
pressColor: Qt.hsla(c.hslHue, c.hslSaturation, c.hslLightness, .4)
anchors.right: img.right
anchors.bottom: img.bottom
enabled: !materialBrowserBundleModel.importerRunning
onClicked: {
materialBrowserBundleModel.addToProject(modelData)
}
}
} }
TextInput { TextInput {

View File

@@ -24,8 +24,8 @@ Item {
// Called also from C++ to close context menu on focus out // Called also from C++ to close context menu on focus out
function closeContextMenu() function closeContextMenu()
{ {
cxtMenu.close() ctxMenu.close()
cxtMenuBundle.close() ctxMenuBundle.close()
} }
// Called from C++ to refresh a preview material after it changes // Called from C++ to refresh a preview material after it changes
@@ -57,7 +57,7 @@ Item {
if (!materialBrowserModel.hasMaterialRoot && (!materialBrowserBundleModel.matBundleExists if (!materialBrowserModel.hasMaterialRoot && (!materialBrowserBundleModel.matBundleExists
|| mouse.y < userMatsSecBottom)) { || mouse.y < userMatsSecBottom)) {
root.currentMaterial = null root.currentMaterial = null
cxtMenu.popup() ctxMenu.popup()
} }
} }
} }
@@ -75,8 +75,12 @@ Item {
} }
} }
UnimportBundleMaterialDialog {
id: unimportBundleMaterialDialog
}
StudioControls.Menu { StudioControls.Menu {
id: cxtMenu id: ctxMenu
closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside
@@ -103,7 +107,10 @@ Item {
width: parent.width width: parent.width
onAboutToShow: { onAboutToShow: {
root.matSectionsModel = ["All"]; if (root.currentMaterial.hasDynamicProperties)
root.matSectionsModel = ["All", "Custom"];
else
root.matSectionsModel = ["All"];
switch (root.currentMaterial.materialType) { switch (root.currentMaterial.materialType) {
case "DefaultMaterial": case "DefaultMaterial":
@@ -180,7 +187,7 @@ Item {
} }
StudioControls.Menu { StudioControls.Menu {
id: cxtMenuBundle id: ctxMenuBundle
closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside
@@ -199,9 +206,22 @@ Item {
StudioControls.MenuSeparator {} StudioControls.MenuSeparator {}
StudioControls.MenuItem { StudioControls.MenuItem {
text: qsTr("Add to project") enabled: !materialBrowserBundleModel.importerRunning
text: qsTr("Add an instance to project")
onTriggered: materialBrowserBundleModel.addMaterial(root.currentBundleMaterial) onTriggered: {
materialBrowserBundleModel.addToProject(root.currentBundleMaterial)
}
}
StudioControls.MenuItem {
enabled: !materialBrowserBundleModel.importerRunning && root.currentBundleMaterial.bundleMaterialImported
text: qsTr("Remove from project")
onTriggered: {
unimportBundleMaterialDialog.targetBundleMaterial = root.currentBundleMaterial
unimportBundleMaterialDialog.open()
}
} }
} }
@@ -276,13 +296,14 @@ Item {
height: root.height - searchBox.height height: root.height - searchBox.height
clip: true clip: true
visible: materialBrowserModel.hasQuick3DImport && !materialBrowserModel.hasMaterialRoot visible: materialBrowserModel.hasQuick3DImport && !materialBrowserModel.hasMaterialRoot
interactive: !ctxMenu.opened && !ctxMenuBundle.opened
Column { Column {
Section { Section {
id: userMaterialsSection id: userMaterialsSection
width: root.width width: root.width
caption: qsTr("User materials") caption: qsTr("Materials")
hideHeader: !materialBrowserBundleModel.matBundleExists hideHeader: !materialBrowserBundleModel.matBundleExists
Grid { Grid {
@@ -304,7 +325,7 @@ Item {
onShowContextMenu: { onShowContextMenu: {
root.currentMaterial = model root.currentMaterial = model
cxtMenu.popup() ctxMenu.popup()
} }
} }
} }
@@ -367,7 +388,7 @@ Item {
onShowContextMenu: { onShowContextMenu: {
root.currentBundleMaterial = modelData root.currentBundleMaterial = modelData
cxtMenuBundle.popup() ctxMenuBundle.popup()
} }
} }
} }

View File

@@ -0,0 +1,85 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuickDesignerTheme
import HelperWidgets
import StudioControls as StudioControls
import StudioTheme as StudioTheme
Dialog {
id: root
title: qsTr("Bundle material might be in use")
anchors.centerIn: parent
closePolicy: Popup.CloseOnEscape
implicitWidth: 300
modal: true
property var targetBundleMaterial
contentItem: Column {
spacing: 20
width: parent.width
Text {
id: folderNotEmpty
text: qsTr("If the material you are removing is in use, it might cause the project to malfunction.\n\nAre you sure you want to remove the material?")
color: StudioTheme.Values.themeTextColor
wrapMode: Text.WordWrap
anchors.right: parent.right
anchors.left: parent.left
leftPadding: 10
rightPadding: 10
Keys.onEnterPressed: btnRemove.onClicked()
Keys.onReturnPressed: btnRemove.onClicked()
}
Row {
anchors.right: parent.right
Button {
id: btnRemove
text: qsTr("Remove")
onClicked: {
materialBrowserBundleModel.removeFromProject(root.targetBundleMaterial)
root.accept()
}
}
Button {
text: qsTr("Cancel")
onClicked: root.reject()
}
}
}
onOpened: folderNotEmpty.forceActiveFocus()
}

View File

@@ -175,6 +175,7 @@ Column {
// Section with hidden header is used so properties are aligned with the other sections' properties // Section with hidden header is used so properties are aligned with the other sections' properties
hideHeader: true hideHeader: true
width: parent.width width: parent.width
collapsible: false
SectionLayout { SectionLayout {
PropertyLabel { text: qsTr("Name") } PropertyLabel { text: qsTr("Name") }

View File

@@ -0,0 +1,29 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
import QtQuick 2.15
import "../QtQuick" as Original
Original.QtObjectPane {}

View File

@@ -29,6 +29,7 @@ Item {
property int level: 0 property int level: 0
property int levelShift: 10 property int levelShift: 10
property bool hideHeader: false property bool hideHeader: false
property bool collapsible: true
property bool expandOnClick: true // if false, toggleExpand signal will be emitted instead property bool expandOnClick: true // if false, toggleExpand signal will be emitted instead
property bool addTopPadding: true property bool addTopPadding: true
property bool addBottomPadding: true property bool addBottomPadding: true
@@ -48,7 +49,10 @@ Item {
Connections { Connections {
target: Controller target: Controller
function onCollapseAll() { section.expanded = false } function onCollapseAll() {
if (collapsible)
section.expanded = false
}
function onExpandAll() { section.expanded = true } function onExpandAll() { section.expanded = true }
} }
@@ -120,6 +124,9 @@ Item {
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: function(mouse) { onClicked: function(mouse) {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
if (!section.collapsible && section.expanded)
return
transition.enabled = true transition.enabled = true
if (section.expandOnClick) if (section.expandOnClick)
section.expanded = !section.expanded section.expanded = !section.expanded

View File

@@ -521,20 +521,27 @@ Item {
] ]
Text { Text {
visible: root.autocompleteString !== "" id: tmpSelectionName
visible: root.autocompleteString !== "" && root.open
text: root.autocompleteString text: root.autocompleteString
x: textInput.leftPadding + textMetrics.advanceWidth x: textInput.leftPadding + textMetrics.advanceWidth
y: (textInput.height - Math.ceil(textMetrics.height)) / 2 y: (textInput.height - Math.ceil(tmpSelectionTextMetrics.height)) / 2
color: "gray" // TODO proper color value color: "gray" // TODO proper color value
font: textInput.font font: textInput.font
renderType: textInput.renderType renderType: textInput.renderType
TextMetrics {
id: textMetrics
font: textInput.font
text: textInput.text
}
TextMetrics {
id: tmpSelectionTextMetrics
font: tmpSelectionName.font
text: "Xq"
}
} }
TextMetrics {
id: textMetrics
font: textInput.font
text: textInput.text
}
Rectangle { Rectangle {
id: checkIndicator id: checkIndicator

View File

@@ -48,6 +48,17 @@ T.MenuItem {
} }
} }
arrow: T.Label {
id: arrow
x: parent.width - (StudioTheme.Values.height + arrow.width) / 2
y: (parent.height - arrow.height) / 2
visible: control.subMenu
text: StudioTheme.Constants.startNode
color: StudioTheme.Values.themeTextColor
font.pixelSize: 8
font.family: StudioTheme.Constants.iconFont.family
}
background: Rectangle { background: Rectangle {
implicitWidth: textLabel.implicitWidth + control.labelSpacing + shortcutLabel.implicitWidth implicitWidth: textLabel.implicitWidth + control.labelSpacing + shortcutLabel.implicitWidth
+ control.leftPadding + control.rightPadding + control.leftPadding + control.rightPadding

View File

@@ -49,128 +49,126 @@ QtObject {
readonly property string centerHorizontal: "\u0042" readonly property string centerHorizontal: "\u0042"
readonly property string centerVertical: "\u0043" readonly property string centerVertical: "\u0043"
readonly property string closeCross: "\u0044" readonly property string closeCross: "\u0044"
readonly property string closeLink: "\u0045" readonly property string colorPopupClose: "\u0045"
readonly property string colorPopupClose: "\u0046" readonly property string columnsAndRows: "\u0046"
readonly property string columnsAndRows: "\u0047" readonly property string copyStyle: "\u0047"
readonly property string copyLink: "\u0048" readonly property string cornerA: "\u0048"
readonly property string copyStyle: "\u0049" readonly property string cornerB: "\u0049"
readonly property string cornerA: "\u004A" readonly property string cornersAll: "\u004A"
readonly property string cornerB: "\u004B" readonly property string curveDesigner: "\u004B"
readonly property string cornersAll: "\u004C" readonly property string curveEditor: "\u004C"
readonly property string curveDesigner: "\u004D" readonly property string customMaterialEditor: "\u004D"
readonly property string curveEditor: "\u004E" readonly property string decisionNode: "\u004E"
readonly property string customMaterialEditor: "\u004F" readonly property string deleteColumn: "\u004F"
readonly property string decisionNode: "\u0050" readonly property string deleteMaterial: "\u0050"
readonly property string deleteColumn: "\u0051" readonly property string deleteRow: "\u0051"
readonly property string deleteMaterial: "\u0052" readonly property string deleteTable: "\u0052"
readonly property string deleteRow: "\u0053" readonly property string detach: "\u0053"
readonly property string deleteTable: "\u0054" readonly property string distributeBottom: "\u0054"
readonly property string detach: "\u0055" readonly property string distributeCenterHorizontal: "\u0055"
readonly property string distributeBottom: "\u0056" readonly property string distributeCenterVertical: "\u0056"
readonly property string distributeCenterHorizontal: "\u0057" readonly property string distributeLeft: "\u0057"
readonly property string distributeCenterVertical: "\u0058" readonly property string distributeOriginBottomRight: "\u0058"
readonly property string distributeLeft: "\u0059" readonly property string distributeOriginCenter: "\u0059"
readonly property string distributeOriginBottomRight: "\u005A" readonly property string distributeOriginNone: "\u005A"
readonly property string distributeOriginCenter: "\u005B" readonly property string distributeOriginTopLeft: "\u005B"
readonly property string distributeOriginNone: "\u005C" readonly property string distributeRight: "\u005C"
readonly property string distributeOriginTopLeft: "\u005D" readonly property string distributeSpacingHorizontal: "\u005D"
readonly property string distributeRight: "\u005E" readonly property string distributeSpacingVertical: "\u005E"
readonly property string distributeSpacingHorizontal: "\u005F" readonly property string distributeTop: "\u005F"
readonly property string distributeSpacingVertical: "\u0060" readonly property string download: "\u0060"
readonly property string distributeTop: "\u0061" readonly property string downloadUnavailable: "\u0061"
readonly property string download: "\u0062" readonly property string downloadUpdate: "\u0062"
readonly property string downloadUnavailable: "\u0063" readonly property string downloaded: "\u0063"
readonly property string downloadUpdate: "\u0064" readonly property string edit: "\u0064"
readonly property string downloaded: "\u0065" readonly property string eyeDropper: "\u0065"
readonly property string edit: "\u0066" readonly property string favorite: "\u0066"
readonly property string eyeDropper: "\u0067" readonly property string flowAction: "\u0067"
readonly property string favorite: "\u0068" readonly property string flowTransition: "\u0068"
readonly property string flowAction: "\u0069" readonly property string fontStyleBold: "\u0069"
readonly property string flowTransition: "\u006A" readonly property string fontStyleItalic: "\u006A"
readonly property string fontStyleBold: "\u006B" readonly property string fontStyleStrikethrough: "\u006B"
readonly property string fontStyleItalic: "\u006C" readonly property string fontStyleUnderline: "\u006C"
readonly property string fontStyleStrikethrough: "\u006D" readonly property string gradient: "\u006D"
readonly property string fontStyleUnderline: "\u006E" readonly property string gridView: "\u006E"
readonly property string gradient: "\u006F" readonly property string idAliasOff: "\u006F"
readonly property string gridView: "\u0070" readonly property string idAliasOn: "\u0070"
readonly property string idAliasOff: "\u0071" readonly property string imported: "\u0071"
readonly property string idAliasOn: "\u0072" readonly property string infinity: "\u0072"
readonly property string infinity: "\u0073" readonly property string keyframe: "\u0073"
readonly property string keyframe: "\u0074" readonly property string linkTriangle: "\u0074"
readonly property string linkTriangle: "\u0075" readonly property string linked: "\u0075"
readonly property string linked: "\u0076" readonly property string listView: "\u0076"
readonly property string listView: "\u0077" readonly property string lockOff: "\u0077"
readonly property string lockOff: "\u0078" readonly property string lockOn: "\u0078"
readonly property string lockOn: "\u0079" readonly property string materialPreviewEnvironment: "\u0079"
readonly property string materialPreviewEnvironment: "\u007A" readonly property string materialPreviewModel: "\u007A"
readonly property string materialPreviewModel: "\u007B" readonly property string mergeCells: "\u007B"
readonly property string mergeCells: "\u007C" readonly property string minus: "\u007C"
readonly property string minus: "\u007D" readonly property string mirror: "\u007D"
readonly property string mirror: "\u007E" readonly property string newMaterial: "\u007E"
readonly property string newMaterial: "\u007F" readonly property string openMaterialBrowser: "\u007F"
readonly property string openLink: "\u0080" readonly property string orientation: "\u0080"
readonly property string openMaterialBrowser: "\u0081" readonly property string paddingEdge: "\u0081"
readonly property string orientation: "\u0082" readonly property string paddingFrame: "\u0082"
readonly property string paddingEdge: "\u0083" readonly property string pasteStyle: "\u0083"
readonly property string paddingFrame: "\u0084" readonly property string pause: "\u0084"
readonly property string pasteStyle: "\u0085" readonly property string pin: "\u0085"
readonly property string pause: "\u0086" readonly property string play: "\u0086"
readonly property string pin: "\u0087" readonly property string plus: "\u0087"
readonly property string play: "\u0088" readonly property string promote: "\u0088"
readonly property string plus: "\u0089" readonly property string readOnly: "\u0089"
readonly property string promote: "\u008A" readonly property string redo: "\u008A"
readonly property string readOnly: "\u008B" readonly property string rotationFill: "\u008B"
readonly property string redo: "\u008C" readonly property string rotationOutline: "\u008C"
readonly property string rotationFill: "\u008D" readonly property string search: "\u008D"
readonly property string rotationOutline: "\u008E" readonly property string sectionToggle: "\u008E"
readonly property string search: "\u008F" readonly property string splitColumns: "\u008F"
readonly property string sectionToggle: "\u0090" readonly property string splitRows: "\u0090"
readonly property string splitColumns: "\u0091" readonly property string startNode: "\u0091"
readonly property string splitRows: "\u0092" readonly property string testIcon: "\u0092"
readonly property string startNode: "\u0093" readonly property string textAlignBottom: "\u0093"
readonly property string testIcon: "\u0094" readonly property string textAlignCenter: "\u0094"
readonly property string textAlignBottom: "\u0095" readonly property string textAlignJustified: "\u0095"
readonly property string textAlignCenter: "\u0096" readonly property string textAlignLeft: "\u0096"
readonly property string textAlignJustified: "\u0097" readonly property string textAlignMiddle: "\u0097"
readonly property string textAlignLeft: "\u0098" readonly property string textAlignRight: "\u0098"
readonly property string textAlignMiddle: "\u0099" readonly property string textAlignTop: "\u0099"
readonly property string textAlignRight: "\u009A" readonly property string textBulletList: "\u009A"
readonly property string textAlignTop: "\u009B" readonly property string textFullJustification: "\u009B"
readonly property string textBulletList: "\u009D" readonly property string textNumberedList: "\u009D"
readonly property string textFullJustification: "\u009E" readonly property string tickIcon: "\u009E"
readonly property string textNumberedList: "\u009F" readonly property string translationCreateFiles: "\u009F"
readonly property string tickIcon: "\u00A0" readonly property string translationCreateReport: "\u00A0"
readonly property string translationCreateFiles: "\u00A1" readonly property string translationExport: "\u00A1"
readonly property string translationCreateReport: "\u00A2" readonly property string translationImport: "\u00A2"
readonly property string translationExport: "\u00A3" readonly property string translationSelectLanguages: "\u00A3"
readonly property string translationImport: "\u00A4" readonly property string translationTest: "\u00A4"
readonly property string translationSelectLanguages: "\u00A5" readonly property string transparent: "\u00A5"
readonly property string translationTest: "\u00A6" readonly property string triState: "\u00A6"
readonly property string transparent: "\u00A7" readonly property string triangleArcA: "\u00A7"
readonly property string triState: "\u00A8" readonly property string triangleArcB: "\u00A8"
readonly property string triangleArcA: "\u00A9" readonly property string triangleCornerA: "\u00A9"
readonly property string triangleArcB: "\u00AA" readonly property string triangleCornerB: "\u00AA"
readonly property string triangleCornerA: "\u00AB" readonly property string unLinked: "\u00AB"
readonly property string triangleCornerB: "\u00AC" readonly property string undo: "\u00AC"
readonly property string unLinked: "\u00AE" readonly property string unpin: "\u00AE"
readonly property string undo: "\u00AF" readonly property string upDownIcon: "\u00AF"
readonly property string unpin: "\u00B0" readonly property string upDownSquare2: "\u00B0"
readonly property string upDownIcon: "\u00B1" readonly property string visibilityOff: "\u00B1"
readonly property string upDownSquare2: "\u00B2" readonly property string visibilityOn: "\u00B2"
readonly property string visibilityOff: "\u00B3" readonly property string wildcard: "\u00B3"
readonly property string visibilityOn: "\u00B4" readonly property string wizardsAutomotive: "\u00B4"
readonly property string wildcard: "\u00B5" readonly property string wizardsDesktop: "\u00B5"
readonly property string wizardsAutomotive: "\u00B6" readonly property string wizardsGeneric: "\u00B6"
readonly property string wizardsDesktop: "\u00B7" readonly property string wizardsMcuEmpty: "\u00B7"
readonly property string wizardsGeneric: "\u00B8" readonly property string wizardsMcuGraph: "\u00B8"
readonly property string wizardsMcuEmpty: "\u00B9" readonly property string wizardsMobile: "\u00B9"
readonly property string wizardsMcuGraph: "\u00BA" readonly property string wizardsUnknown: "\u00BA"
readonly property string wizardsMobile: "\u00BB" readonly property string zoomAll: "\u00BB"
readonly property string wizardsUnknown: "\u00BC" readonly property string zoomIn: "\u00BC"
readonly property string zoomAll: "\u00BD" readonly property string zoomOut: "\u00BD"
readonly property string zoomIn: "\u00BE" readonly property string zoomSelection: "\u00BE"
readonly property string zoomOut: "\u00BF"
readonly property string zoomSelection: "\u00C0"
readonly property font iconFont: Qt.font({ readonly property font iconFont: Qt.font({
"family": controlIcons.name, "family": controlIcons.name,

View File

@@ -328,6 +328,10 @@
"source": "../common/fonts.txt", "source": "../common/fonts.txt",
"target": "%{ProjectDirectory}/content/fonts/fonts.txt" "target": "%{ProjectDirectory}/content/fonts/fonts.txt"
}, },
{
"source": "../common/asset_imports.txt",
"target": "%{ProjectDirectory}/asset_imports/asset_imports.txt"
},
{ {
"source": "../common/CMakeLists.imports.txt.tpl", "source": "../common/CMakeLists.imports.txt.tpl",
"target": "%{ProjectDirectory}/imports/CMakeLists.txt" "target": "%{ProjectDirectory}/imports/CMakeLists.txt"

View File

@@ -324,6 +324,10 @@
"source": "../common/fonts.txt", "source": "../common/fonts.txt",
"target": "%{ProjectDirectory}/content/fonts/fonts.txt" "target": "%{ProjectDirectory}/content/fonts/fonts.txt"
}, },
{
"source": "../common/asset_imports.txt",
"target": "%{ProjectDirectory}/asset_imports/asset_imports.txt"
},
{ {
"source": "../common/CMakeLists.imports.txt.tpl", "source": "../common/CMakeLists.imports.txt.tpl",
"target": "%{ProjectDirectory}/imports/CMakeLists.txt" "target": "%{ProjectDirectory}/imports/CMakeLists.txt"

View File

@@ -0,0 +1 @@
Imported 3D assets and components imported from bundles will be created in this folder.

View File

@@ -323,6 +323,10 @@
"source": "../common/fonts.txt", "source": "../common/fonts.txt",
"target": "%{ProjectDirectory}/content/fonts/fonts.txt" "target": "%{ProjectDirectory}/content/fonts/fonts.txt"
}, },
{
"source": "../common/asset_imports.txt",
"target": "%{ProjectDirectory}/asset_imports/asset_imports.txt"
},
{ {
"source": "../common/CMakeLists.imports.txt.tpl", "source": "../common/CMakeLists.imports.txt.tpl",
"target": "%{ProjectDirectory}/imports/CMakeLists.txt" "target": "%{ProjectDirectory}/imports/CMakeLists.txt"

View File

@@ -280,6 +280,10 @@
"source": "../common/fonts.txt", "source": "../common/fonts.txt",
"target": "%{ProjectDirectory}/content/fonts/fonts.txt" "target": "%{ProjectDirectory}/content/fonts/fonts.txt"
}, },
{
"source": "../common/asset_imports.txt",
"target": "%{ProjectDirectory}/asset_imports/asset_imports.txt"
},
{ {
"source": "../common/CMakeLists.imports.txt.tpl", "source": "../common/CMakeLists.imports.txt.tpl",
"target": "%{ProjectDirectory}/imports/CMakeLists.txt" "target": "%{ProjectDirectory}/imports/CMakeLists.txt"

View File

@@ -284,6 +284,10 @@
"source": "../common/fonts.txt", "source": "../common/fonts.txt",
"target": "%{ProjectDirectory}/content/fonts/fonts.txt" "target": "%{ProjectDirectory}/content/fonts/fonts.txt"
}, },
{
"source": "../common/asset_imports.txt",
"target": "%{ProjectDirectory}/asset_imports/asset_imports.txt"
},
{ {
"source": "../common/CMakeLists.imports.txt.tpl", "source": "../common/CMakeLists.imports.txt.tpl",
"target": "%{ProjectDirectory}/imports/CMakeLists.txt" "target": "%{ProjectDirectory}/imports/CMakeLists.txt"

View File

@@ -284,6 +284,10 @@
"source": "../common/fonts.txt", "source": "../common/fonts.txt",
"target": "%{ProjectDirectory}/content/fonts/fonts.txt" "target": "%{ProjectDirectory}/content/fonts/fonts.txt"
}, },
{
"source": "../common/asset_imports.txt",
"target": "%{ProjectDirectory}/asset_imports/asset_imports.txt"
},
{ {
"source": "../common/CMakeLists.imports.txt.tpl", "source": "../common/CMakeLists.imports.txt.tpl",
"target": "%{ProjectDirectory}/imports/CMakeLists.txt" "target": "%{ProjectDirectory}/imports/CMakeLists.txt"

View File

@@ -220,22 +220,35 @@ static void setupInstallSettings(QString &installSettingspath)
QLatin1String(Core::Constants::IDE_SETTINGSVARIANT_STR), QLatin1String(Core::Constants::IDE_CASED_ID))); QLatin1String(Core::Constants::IDE_SETTINGSVARIANT_STR), QLatin1String(Core::Constants::IDE_CASED_ID)));
installSettingspath.clear(); installSettingspath.clear();
} }
// Check if the default install settings contain a setting for the actual install settings.
// This can be an absolute path, or a path relative to applicationDirPath().
// The result is interpreted like -settingspath, but for SystemScope
static const char kInstallSettingsKey[] = "Settings/InstallSettings"; static const char kInstallSettingsKey[] = "Settings/InstallSettings";
QSettings::setPath(QSettings::IniFormat, QSettings::SystemScope, QSettings::setPath(QSettings::IniFormat, QSettings::SystemScope,
installSettingspath.isEmpty() ? resourcePath() : installSettingspath); installSettingspath.isEmpty() ? resourcePath() : installSettingspath);
QSettings installSettings(QSettings::IniFormat, QSettings::UserScope, // Check if the default install settings contain a setting for the actual install settings.
QLatin1String(Core::Constants::IDE_SETTINGSVARIANT_STR), // This can be an absolute path, or a path relative to applicationDirPath().
QLatin1String(Core::Constants::IDE_CASED_ID)); // The result is interpreted like -settingspath, but for SystemScope.
if (installSettings.contains(kInstallSettingsKey)) { //
QString installSettingsPath = installSettings.value(kInstallSettingsKey).toString(); // Through the sdktool split that is upcoming, the new install settings might redirect
if (QDir::isRelativePath(installSettingsPath)) // yet a second time. So try this a few times.
installSettingsPath = applicationDirPath() + '/' + installSettingsPath; // (Only the first time with QSettings::UserScope, to allow setting the install settings path
QSettings::setPath(QSettings::IniFormat, QSettings::SystemScope, installSettingsPath); // in the user settings.)
} QSettings::Scope scope = QSettings::UserScope;
int count = 0;
bool containsInstallSettingsKey = false;
do {
QSettings installSettings(QSettings::IniFormat, scope,
QLatin1String(Core::Constants::IDE_SETTINGSVARIANT_STR),
QLatin1String(Core::Constants::IDE_CASED_ID));
containsInstallSettingsKey = installSettings.contains(kInstallSettingsKey);
if (containsInstallSettingsKey) {
QString newInstallSettingsPath = installSettings.value(kInstallSettingsKey).toString();
if (QDir::isRelativePath(newInstallSettingsPath))
newInstallSettingsPath = applicationDirPath() + '/' + newInstallSettingsPath;
QSettings::setPath(QSettings::IniFormat, QSettings::SystemScope, newInstallSettingsPath);
}
scope = QSettings::SystemScope; // UserScope only the first time we check
++count;
} while (containsInstallSettingsKey && count < 3);
} }
static Utils::QtcSettings *createUserSettings() static Utils::QtcSettings *createUserSettings()

View File

@@ -538,7 +538,7 @@ static McuAbstractTargetFactory::Ptr createFactory(bool isLegacy,
{"arm-greenhills", {"arm-greenhills",
McuPackagePtr{new McuPackage{settingsHandler, McuPackagePtr{new McuPackage{settingsHandler,
{}, {},
toolchainFilePrefix / "arm-ghs.cmake", toolchainFilePrefix / "ghs-arm.cmake",
{}, {},
{}, {},
Legacy::Constants::TOOLCHAIN_FILE_CMAKE_VARIABLE, Legacy::Constants::TOOLCHAIN_FILE_CMAKE_VARIABLE,

View File

@@ -35,6 +35,25 @@ static Q_LOGGING_CATEGORY(assetsLibraryBenchmark, "qtc.assetsLibrary.setRoot", Q
namespace QmlDesigner { namespace QmlDesigner {
AssetsLibraryModel::AssetsLibraryModel(Utils::FileSystemWatcher *fileSystemWatcher, QObject *parent)
: QAbstractListModel(parent)
, m_fileSystemWatcher(fileSystemWatcher)
{
// add role names
int role = 0;
const QMetaObject meta = AssetsLibraryDir::staticMetaObject;
for (int i = meta.propertyOffset(); i < meta.propertyCount(); ++i)
m_roleNames.insert(role++, meta.property(i).name());
}
void AssetsLibraryModel::setSearchText(const QString &searchText)
{
if (m_searchText != searchText) {
m_searchText = searchText;
refresh();
}
}
void AssetsLibraryModel::saveExpandedState(bool expanded, const QString &assetPath) void AssetsLibraryModel::saveExpandedState(bool expanded, const QString &assetPath)
{ {
m_expandedStateHash.insert(assetPath, expanded); m_expandedStateHash.insert(assetPath, expanded);
@@ -186,66 +205,18 @@ QObject *AssetsLibraryModel::rootDir() const
return m_assetsDir; return m_assetsDir;
} }
const QStringList &AssetsLibraryModel::supportedImageSuffixes() bool AssetsLibraryModel::isEmpty() const
{ {
static QStringList retList; return m_isEmpty;
if (retList.isEmpty()) { };
const QList<QByteArray> suffixes = QImageReader::supportedImageFormats();
for (const QByteArray &suffix : suffixes) void AssetsLibraryModel::setIsEmpty(bool empty)
retList.append("*." + QString::fromUtf8(suffix)); {
if (m_isEmpty != empty) {
m_isEmpty = empty;
emit isEmptyChanged();
} }
return retList; };
}
const QStringList &AssetsLibraryModel::supportedFragmentShaderSuffixes()
{
static const QStringList retList {"*.frag", "*.glsl", "*.glslf", "*.fsh"};
return retList;
}
const QStringList &AssetsLibraryModel::supportedShaderSuffixes()
{
static const QStringList retList {"*.frag", "*.vert",
"*.glsl", "*.glslv", "*.glslf",
"*.vsh", "*.fsh"};
return retList;
}
const QStringList &AssetsLibraryModel::supportedFontSuffixes()
{
static const QStringList retList {"*.ttf", "*.otf"};
return retList;
}
const QStringList &AssetsLibraryModel::supportedAudioSuffixes()
{
static const QStringList retList {"*.wav", "*.mp3"};
return retList;
}
const QStringList &AssetsLibraryModel::supportedVideoSuffixes()
{
static const QStringList retList {"*.mp4"};
return retList;
}
const QStringList &AssetsLibraryModel::supportedTexture3DSuffixes()
{
// These are file types only supported by 3D textures
static QStringList retList {"*.hdr", "*.ktx"};
return retList;
}
AssetsLibraryModel::AssetsLibraryModel(Utils::FileSystemWatcher *fileSystemWatcher, QObject *parent)
: QAbstractListModel(parent)
, m_fileSystemWatcher(fileSystemWatcher)
{
// add role names
int role = 0;
const QMetaObject meta = AssetsLibraryDir::staticMetaObject;
for (int i = meta.propertyOffset(); i < meta.propertyCount(); ++i)
m_roleNames.insert(role++, meta.property(i).name());
}
QVariant AssetsLibraryModel::data(const QModelIndex &index, int role) const QVariant AssetsLibraryModel::data(const QModelIndex &index, int role) const
{ {
@@ -353,12 +324,54 @@ void AssetsLibraryModel::setRootPath(const QString &path)
qCInfo(assetsLibraryBenchmark) << "model reset:" << time.elapsed(); qCInfo(assetsLibraryBenchmark) << "model reset:" << time.elapsed();
} }
void AssetsLibraryModel::setSearchText(const QString &searchText) const QStringList &AssetsLibraryModel::supportedImageSuffixes()
{ {
if (m_searchText != searchText) { static QStringList retList;
m_searchText = searchText; if (retList.isEmpty()) {
refresh(); const QList<QByteArray> suffixes = QImageReader::supportedImageFormats();
for (const QByteArray &suffix : suffixes)
retList.append("*." + QString::fromUtf8(suffix));
} }
return retList;
}
const QStringList &AssetsLibraryModel::supportedFragmentShaderSuffixes()
{
static const QStringList retList {"*.frag", "*.glsl", "*.glslf", "*.fsh"};
return retList;
}
const QStringList &AssetsLibraryModel::supportedShaderSuffixes()
{
static const QStringList retList {"*.frag", "*.vert",
"*.glsl", "*.glslv", "*.glslf",
"*.vsh", "*.fsh"};
return retList;
}
const QStringList &AssetsLibraryModel::supportedFontSuffixes()
{
static const QStringList retList {"*.ttf", "*.otf"};
return retList;
}
const QStringList &AssetsLibraryModel::supportedAudioSuffixes()
{
static const QStringList retList {"*.wav", "*.mp3"};
return retList;
}
const QStringList &AssetsLibraryModel::supportedVideoSuffixes()
{
static const QStringList retList {"*.mp4"};
return retList;
}
const QStringList &AssetsLibraryModel::supportedTexture3DSuffixes()
{
// These are file types only supported by 3D textures
static QStringList retList {"*.hdr", "*.ktx"};
return retList;
} }
const QSet<QString> &AssetsLibraryModel::supportedSuffixes() const QSet<QString> &AssetsLibraryModel::supportedSuffixes()
@@ -379,19 +392,6 @@ const QSet<QString> &AssetsLibraryModel::supportedSuffixes()
return allSuffixes; return allSuffixes;
} }
bool AssetsLibraryModel::isEmpty() const
{
return m_isEmpty;
};
void AssetsLibraryModel::setIsEmpty(bool empty)
{
if (m_isEmpty != empty) {
m_isEmpty = empty;
emit isEmptyChanged();
}
};
const QSet<QString> &AssetsLibraryModel::previewableSuffixes() const const QSet<QString> &AssetsLibraryModel::previewableSuffixes() const
{ {
static QSet<QString> previewableSuffixes; static QSet<QString> previewableSuffixes;

View File

@@ -69,6 +69,8 @@ bool AssetsLibraryWidget::eventFilter(QObject *obj, QEvent *event)
m_assetsToDrag.clear(); m_assetsToDrag.clear();
} }
} }
} else if (event->type() == QMouseEvent::MouseButtonRelease) {
m_assetsToDrag.clear();
} }
return QObject::eventFilter(obj, event); return QObject::eventFilter(obj, event);

View File

@@ -57,10 +57,8 @@ public:
centerHorizontal, centerHorizontal,
centerVertical, centerVertical,
closeCross, closeCross,
closeLink,
colorPopupClose, colorPopupClose,
columnsAndRows, columnsAndRows,
copyLink,
copyStyle, copyStyle,
cornerA, cornerA,
cornerB, cornerB,
@@ -103,6 +101,7 @@ public:
gridView, gridView,
idAliasOff, idAliasOff,
idAliasOn, idAliasOn,
imported,
infinity, infinity,
keyframe, keyframe,
linkTriangle, linkTriangle,
@@ -116,7 +115,6 @@ public:
minus, minus,
mirror, mirror,
newMaterial, newMaterial,
openLink,
openMaterialBrowser, openMaterialBrowser,
orientation, orientation,
paddingEdge, paddingEdge,

View File

@@ -14,6 +14,7 @@
#include "viewmanager.h" #include "viewmanager.h"
#include <seekerslider.h> #include <seekerslider.h>
#include <nodeinstanceview.h> #include <nodeinstanceview.h>
#include <import.h>
#include <coreplugin/actionmanager/actionmanager.h> #include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/command.h> #include <coreplugin/actionmanager/command.h>
@@ -208,14 +209,21 @@ void Edit3DWidget::updateCreateSubMenu(const QStringList &keys,
void Edit3DWidget::onCreateAction() void Edit3DWidget::onCreateAction()
{ {
// QAction *action = qobject_cast<QAction *>(sender()); // QAction *action = qobject_cast<QAction *>(sender());
// if (!action) // if (!action || !m_view || !m_view->model())
// return; // return;
// m_view->executeInTransaction(__FUNCTION__, [&] { // m_view->executeInTransaction(__FUNCTION__, [&] {
// int activeScene = m_view->rootModelNode().auxiliaryData("active3dScene@Internal").toInt(); // ItemLibraryEntry entry = m_nameToEntry.value(action->data().toString());
// Import import = Import::createLibraryImport(entry.requiredImport(),
// QString::number(entry.majorVersion())
// + QLatin1Char('.')
// + QString::number(entry.minorVersion()));
// if (!m_view->model()->hasImport(import))
// m_view->model()->changeImports({import}, {});
// auto modelNode = QmlVisualNode::createQml3DNode(m_view, m_nameToEntry.value(action->data().toString()), // int activeScene = m_view->rootModelNode().auxiliaryData("active3dScene@Internal").toInt();
// activeScene, m_contextMenuPos3d).modelNode(); // auto modelNode = QmlVisualNode::createQml3DNode(m_view, entry,
// activeScene, m_contextMenuPos3d).modelNode();
// QTC_ASSERT(modelNode.isValid(), return); // QTC_ASSERT(modelNode.isValid(), return);
// m_view->setSelectedModelNode(modelNode); // m_view->setSelectedModelNode(modelNode);

View File

@@ -273,8 +273,10 @@ void ItemLibraryWidget::delayedUpdateModel()
void ItemLibraryWidget::setModel(Model *model) void ItemLibraryWidget::setModel(Model *model)
{ {
m_model = model; m_model = model;
if (!model) if (!model) {
m_itemToDrag = {};
return; return;
}
setItemLibraryInfo(model->metaInfo().itemLibraryInfo()); setItemLibraryInfo(model->metaInfo().itemLibraryInfo());

View File

@@ -97,9 +97,10 @@ QString BundleImporter::importComponent(const QString &qmlFile,
FilePath qmlSourceFile = bundleImportPath.resolvePath(FilePath::fromString(qmlFile)); FilePath qmlSourceFile = bundleImportPath.resolvePath(FilePath::fromString(qmlFile));
const bool qmlFileExists = qmlSourceFile.exists(); const bool qmlFileExists = qmlSourceFile.exists();
const QString qmlType = qmlSourceFile.baseName(); const QString qmlType = qmlSourceFile.baseName();
m_pendingTypes.append(QStringLiteral("%1.%2.%3") const QString fullTypeName = QStringLiteral("%1.%2.%3")
.arg(QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER).mid(1), .arg(QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER).mid(1), m_bundleId, qmlType);
m_bundleId, qmlType)); if (m_pendingTypes.contains(fullTypeName) && !m_pendingTypes[fullTypeName])
return QStringLiteral("Unable to import while unimporting the same type: '%1'").arg(fullTypeName);
if (!qmldirContent.contains(qmlFile)) { if (!qmldirContent.contains(qmlFile)) {
qmldirContent.append(qmlType); qmldirContent.append(qmlType);
qmldirContent.append(" 1.0 "); qmldirContent.append(" 1.0 ");
@@ -162,6 +163,7 @@ QString BundleImporter::importComponent(const QString &qmlFile,
m_importAddPending = true; m_importAddPending = true;
} }
} }
m_pendingTypes.insert(fullTypeName, true);
m_importTimerCount = 0; m_importTimerCount = 0;
m_importTimer.start(); m_importTimer.start();
@@ -175,8 +177,16 @@ void BundleImporter::handleImportTimer()
m_fullReset = false; m_fullReset = false;
m_importAddPending = false; m_importAddPending = false;
m_importTimerCount = 0; m_importTimerCount = 0;
m_pendingTypes.clear();
emit importFinished({}); // Emit dummy finished signals for all pending types
const QStringList pendingTypes = m_pendingTypes.keys();
for (const QString &pendingType : pendingTypes) {
m_pendingTypes.remove(pendingType);
if (m_pendingTypes[pendingType])
emit importFinished({});
else
emit unimportFinished({});
}
}; };
auto doc = QmlDesignerPlugin::instance()->currentDesignDocument(); auto doc = QmlDesignerPlugin::instance()->currentDesignDocument();
@@ -210,12 +220,17 @@ void BundleImporter::handleImportTimer()
} }
// Detect when the code model has the new material(s) fully available // Detect when the code model has the new material(s) fully available
const QStringList pendingTypes = m_pendingTypes; const QStringList pendingTypes = m_pendingTypes.keys();
for (const QString &pendingType : pendingTypes) { for (const QString &pendingType : pendingTypes) {
NodeMetaInfo metaInfo = model->metaInfo(pendingType.toUtf8()); NodeMetaInfo metaInfo = model->metaInfo(pendingType.toUtf8());
if (metaInfo.isValid() && !metaInfo.superClasses().empty()) { const bool isImport = m_pendingTypes[pendingType];
m_pendingTypes.removeAll(pendingType); const bool typeComplete = metaInfo.isValid() && !metaInfo.superClasses().empty();
emit importFinished(metaInfo); if (isImport == typeComplete) {
m_pendingTypes.remove(pendingType);
if (isImport)
emit importFinished(metaInfo);
else
emit unimportFinished(metaInfo);
} }
} }
@@ -257,14 +272,14 @@ QString BundleImporter::unimportComponent(const QString &qmlFile)
{ {
FilePath bundleImportPath = resolveBundleImportPath(); FilePath bundleImportPath = resolveBundleImportPath();
if (bundleImportPath.isEmpty()) if (bundleImportPath.isEmpty())
return "Failed to resolve bundle import folder"; return QStringLiteral("Failed to resolve bundle import folder for: '%1'").arg(qmlFile);
if (!bundleImportPath.exists()) if (!bundleImportPath.exists())
return {}; return QStringLiteral("Unable to find bundle path: '%1'").arg(bundleImportPath.toString());
FilePath qmlFilePath = bundleImportPath.resolvePath(qmlFile); FilePath qmlFilePath = bundleImportPath.resolvePath(qmlFile);
if (!qmlFilePath.exists()) if (!qmlFilePath.exists())
return {}; return QStringLiteral("Unable to find specified file: '%1'").arg(qmlFilePath.toString());
QStringList removedFiles; QStringList removedFiles;
removedFiles.append(qmlFile); removedFiles.append(qmlFile);
@@ -272,9 +287,15 @@ QString BundleImporter::unimportComponent(const QString &qmlFile)
FilePath qmldirPath = bundleImportPath.resolvePath(QStringLiteral("qmldir")); FilePath qmldirPath = bundleImportPath.resolvePath(QStringLiteral("qmldir"));
const std::optional<QByteArray> qmldirContent = qmldirPath.fileContents(); const std::optional<QByteArray> qmldirContent = qmldirPath.fileContents();
QByteArray newContent; QByteArray newContent;
QString qmlType = qmlFilePath.baseName();
const QString fullTypeName = QStringLiteral("%1.%2.%3")
.arg(QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER).mid(1), m_bundleId, qmlType);
if (m_pendingTypes.contains(fullTypeName) && m_pendingTypes[fullTypeName])
return QStringLiteral("Unable to unimport while importing the same type: '%1'").arg(fullTypeName);
if (qmldirContent) { if (qmldirContent) {
QByteArray qmlType = qmlFilePath.baseName().toUtf8(); int typeIndex = qmldirContent->indexOf(qmlType.toUtf8());
int typeIndex = qmldirContent->indexOf(qmlType);
if (typeIndex != -1) { if (typeIndex != -1) {
int newLineIndex = qmldirContent->indexOf('\n', typeIndex); int newLineIndex = qmldirContent->indexOf('\n', typeIndex);
newContent = qmldirContent->left(typeIndex); newContent = qmldirContent->left(typeIndex);
@@ -287,6 +308,8 @@ QString BundleImporter::unimportComponent(const QString &qmlFile)
} }
} }
m_pendingTypes.insert(fullTypeName, false);
QVariantHash assetRefMap = loadAssetRefMap(bundleImportPath); QVariantHash assetRefMap = loadAssetRefMap(bundleImportPath);
bool writeAssetRefs = false; bool writeAssetRefs = false;
const auto keys = assetRefMap.keys(); const auto keys = assetRefMap.keys();

View File

@@ -51,18 +51,19 @@ public:
QString importComponent(const QString &qmlFile, QString importComponent(const QString &qmlFile,
const QStringList &files); const QStringList &files);
QString unimportComponent(const QString &qmlFile); QString unimportComponent(const QString &qmlFile);
Utils::FilePath resolveBundleImportPath();
signals: signals:
// The metaInfo parameter will be invalid if an error was encountered during // The metaInfo parameter will be invalid if an error was encountered during
// asynchronous part of the import. In this case all remaining pending imports have been // asynchronous part of the import. In this case all remaining pending imports have been
// terminated, and will not receive separate importFinished notifications. // terminated, and will not receive separate importFinished notifications.
void importFinished(const QmlDesigner::NodeMetaInfo &metaInfo); void importFinished(const QmlDesigner::NodeMetaInfo &metaInfo);
void unimportFinished(const QmlDesigner::NodeMetaInfo &metaInfo);
private: private:
void handleImportTimer(); void handleImportTimer();
QVariantHash loadAssetRefMap(const Utils::FilePath &bundlePath); QVariantHash loadAssetRefMap(const Utils::FilePath &bundlePath);
void writeAssetRefMap(const Utils::FilePath &bundlePath, const QVariantHash &assetRefMap); void writeAssetRefMap(const Utils::FilePath &bundlePath, const QVariantHash &assetRefMap);
Utils::FilePath resolveBundleImportPath();
Utils::FilePath m_bundleDir; Utils::FilePath m_bundleDir;
QString m_bundleId; QString m_bundleId;
@@ -72,7 +73,7 @@ private:
int m_importTimerCount = 0; int m_importTimerCount = 0;
bool m_importAddPending = false; bool m_importAddPending = false;
bool m_fullReset = false; bool m_fullReset = false;
QStringList m_pendingTypes; QHash<QString, bool> m_pendingTypes; // <type, isImport>
}; };
} // namespace QmlDesigner::Internal } // namespace QmlDesigner::Internal

View File

@@ -70,4 +70,20 @@ bool BundleMaterial::visible() const
return m_visible; return m_visible;
} }
bool BundleMaterial::setImported(bool imported)
{
if (m_imported != imported) {
m_imported = imported;
emit materialImportedChanged();
return true;
}
return false;
}
bool BundleMaterial::imported() const
{
return m_imported;
}
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -40,6 +40,7 @@ class BundleMaterial : public QObject
Q_PROPERTY(QString bundleMaterialName MEMBER m_name CONSTANT) Q_PROPERTY(QString bundleMaterialName MEMBER m_name CONSTANT)
Q_PROPERTY(QUrl bundleMaterialIcon MEMBER m_icon CONSTANT) Q_PROPERTY(QUrl bundleMaterialIcon MEMBER m_icon CONSTANT)
Q_PROPERTY(bool bundleMaterialVisible MEMBER m_visible NOTIFY materialVisibleChanged) Q_PROPERTY(bool bundleMaterialVisible MEMBER m_visible NOTIFY materialVisibleChanged)
Q_PROPERTY(bool bundleMaterialImported READ imported WRITE setImported NOTIFY materialImportedChanged)
public: public:
BundleMaterial(QObject *parent, BundleMaterial(QObject *parent,
@@ -57,8 +58,12 @@ public:
QStringList files() const; QStringList files() const;
bool visible() const; bool visible() const;
bool setImported(bool imported);
bool imported() const;
signals: signals:
void materialVisibleChanged(); void materialVisibleChanged();
void materialImportedChanged();
private: private:
QString m_name; QString m_name;
@@ -68,6 +73,7 @@ private:
QStringList m_files; QStringList m_files;
bool m_visible = true; bool m_visible = true;
bool m_imported = false;
}; };
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -37,6 +37,16 @@ void BundleMaterialCategory::addBundleMaterial(BundleMaterial *bundleMat)
m_categoryMaterials.append(bundleMat); m_categoryMaterials.append(bundleMat);
} }
bool BundleMaterialCategory::updateImportedState(const QStringList &importedMats)
{
bool changed = false;
for (BundleMaterial *mat : std::as_const(m_categoryMaterials))
changed |= mat->setImported(importedMats.contains(mat->qml().chopped(4)));
return changed;
}
bool BundleMaterialCategory::filter(const QString &searchText) bool BundleMaterialCategory::filter(const QString &searchText)
{ {
bool visible = false; bool visible = false;

View File

@@ -42,6 +42,7 @@ public:
BundleMaterialCategory(QObject *parent, const QString &name); BundleMaterialCategory(QObject *parent, const QString &name);
void addBundleMaterial(BundleMaterial *bundleMat); void addBundleMaterial(BundleMaterial *bundleMat);
bool updateImportedState(const QStringList &importedMats);
bool filter(const QString &searchText); bool filter(const QString &searchText);
QString name() const; QString name() const;

View File

@@ -122,7 +122,7 @@ void MaterialBrowserBundleModel::loadMaterialBundle()
m_matBundleExists = true; m_matBundleExists = true;
const QString bundleId = m_matBundleObj.value("id").toString(); QString bundleId = m_matBundleObj.value("id").toString();
const QJsonObject catsObj = m_matBundleObj.value("categories").toObject(); const QJsonObject catsObj = m_matBundleObj.value("categories").toObject();
const QStringList categories = catsObj.keys(); const QStringList categories = catsObj.keys();
@@ -160,8 +160,17 @@ void MaterialBrowserBundleModel::loadMaterialBundle()
m_importer = new Internal::BundleImporter(matBundleDir.path(), bundleId, sharedFiles); m_importer = new Internal::BundleImporter(matBundleDir.path(), bundleId, sharedFiles);
connect(m_importer, &Internal::BundleImporter::importFinished, this, [&](const QmlDesigner::NodeMetaInfo &metaInfo) { connect(m_importer, &Internal::BundleImporter::importFinished, this, [&](const QmlDesigner::NodeMetaInfo &metaInfo) {
m_importerRunning = false;
emit importerRunningChanged();
if (metaInfo.isValid()) if (metaInfo.isValid())
emit addBundleMaterialToProjectRequested(metaInfo); emit bundleMaterialImported(metaInfo);
});
connect(m_importer, &Internal::BundleImporter::unimportFinished, this, [&](const QmlDesigner::NodeMetaInfo &metaInfo) {
Q_UNUSED(metaInfo)
m_importerRunning = false;
emit importerRunningChanged();
emit bundleMaterialUnimported(metaInfo);
}); });
} }
@@ -193,6 +202,11 @@ void MaterialBrowserBundleModel::setHasMaterialRoot(bool b)
emit hasMaterialRootChanged(); emit hasMaterialRootChanged();
} }
Internal::BundleImporter *MaterialBrowserBundleModel::bundleImporter() const
{
return m_importer;
}
void MaterialBrowserBundleModel::setSearchText(const QString &searchText) void MaterialBrowserBundleModel::setSearchText(const QString &searchText)
{ {
QString lowerSearchText = searchText.toLower(); QString lowerSearchText = searchText.toLower();
@@ -219,6 +233,16 @@ void MaterialBrowserBundleModel::setSearchText(const QString &searchText)
resetModel(); resetModel();
} }
void MaterialBrowserBundleModel::updateImportedState(const QStringList &importedMats)
{
bool changed = false;
for (BundleMaterialCategory *cat : std::as_const(m_bundleCategories))
changed |= cat->updateImportedState(importedMats);
if (changed)
resetModel();
}
void MaterialBrowserBundleModel::resetModel() void MaterialBrowserBundleModel::resetModel()
{ {
beginResetModel(); beginResetModel();
@@ -230,12 +254,30 @@ void MaterialBrowserBundleModel::applyToSelected(BundleMaterial *mat, bool add)
emit applyToSelectedTriggered(mat, add); emit applyToSelectedTriggered(mat, add);
} }
void MaterialBrowserBundleModel::addMaterial(BundleMaterial *mat) void MaterialBrowserBundleModel::addToProject(BundleMaterial *mat)
{ {
QString err = m_importer->importComponent(mat->qml(), mat->files()); QString err = m_importer->importComponent(mat->qml(), mat->files());
if (!err.isEmpty()) if (err.isEmpty()) {
m_importerRunning = true;
emit importerRunningChanged();
} else {
qWarning() << __FUNCTION__ << err; qWarning() << __FUNCTION__ << err;
}
}
void MaterialBrowserBundleModel::removeFromProject(BundleMaterial *mat)
{
emit bundleMaterialAboutToUnimport(mat->type());
QString err = m_importer->unimportComponent(mat->qml());
if (err.isEmpty()) {
m_importerRunning = true;
emit importerRunningChanged();
} else {
qWarning() << __FUNCTION__ << err;
}
} }
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -50,6 +50,7 @@ class MaterialBrowserBundleModel : public QAbstractListModel
Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged)
Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged) Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged)
Q_PROPERTY(bool hasMaterialRoot READ hasMaterialRoot WRITE setHasMaterialRoot NOTIFY hasMaterialRootChanged) Q_PROPERTY(bool hasMaterialRoot READ hasMaterialRoot WRITE setHasMaterialRoot NOTIFY hasMaterialRootChanged)
Q_PROPERTY(bool importerRunning MEMBER m_importerRunning NOTIFY importerRunningChanged)
public: public:
MaterialBrowserBundleModel(QObject *parent = nullptr); MaterialBrowserBundleModel(QObject *parent = nullptr);
@@ -59,6 +60,7 @@ public:
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
void setSearchText(const QString &searchText); void setSearchText(const QString &searchText);
void updateImportedState(const QStringList &importedMats);
bool hasQuick3DImport() const; bool hasQuick3DImport() const;
void setHasQuick3DImport(bool b); void setHasQuick3DImport(bool b);
@@ -66,10 +68,13 @@ public:
bool hasMaterialRoot() const; bool hasMaterialRoot() const;
void setHasMaterialRoot(bool b); void setHasMaterialRoot(bool b);
Internal::BundleImporter *bundleImporter() const;
void resetModel(); void resetModel();
Q_INVOKABLE void applyToSelected(QmlDesigner::BundleMaterial *mat, bool add = false); Q_INVOKABLE void applyToSelected(QmlDesigner::BundleMaterial *mat, bool add = false);
Q_INVOKABLE void addMaterial(QmlDesigner::BundleMaterial *mat); Q_INVOKABLE void addToProject(QmlDesigner::BundleMaterial *mat);
Q_INVOKABLE void removeFromProject(QmlDesigner::BundleMaterial *mat);
signals: signals:
void isEmptyChanged(); void isEmptyChanged();
@@ -77,7 +82,10 @@ signals:
void hasMaterialRootChanged(); void hasMaterialRootChanged();
void materialVisibleChanged(); void materialVisibleChanged();
void applyToSelectedTriggered(QmlDesigner::BundleMaterial *mat, bool add = false); void applyToSelectedTriggered(QmlDesigner::BundleMaterial *mat, bool add = false);
void addBundleMaterialToProjectRequested(const QmlDesigner::NodeMetaInfo &metaInfo); void bundleMaterialImported(const QmlDesigner::NodeMetaInfo &metaInfo);
void bundleMaterialAboutToUnimport(const QmlDesigner::TypeName &type);
void bundleMaterialUnimported(const QmlDesigner::NodeMetaInfo &metaInfo);
void importerRunningChanged();
private: private:
void loadMaterialBundle(); void loadMaterialBundle();
@@ -93,6 +101,7 @@ private:
bool m_hasMaterialRoot = false; bool m_hasMaterialRoot = false;
bool m_matBundleExists = false; bool m_matBundleExists = false;
bool m_probeMatBundleDir = false; bool m_probeMatBundleDir = false;
bool m_importerRunning = false;
}; };
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -51,6 +51,9 @@ QVariant MaterialBrowserModel::data(const QModelIndex &index, int role) const
return matType; return matType;
} }
if (roleName == "hasDynamicProperties")
return !m_materialList.at(index.row()).dynamicProperties().isEmpty();
return {}; return {};
} }
@@ -121,7 +124,8 @@ QHash<int, QByteArray> MaterialBrowserModel::roleNames() const
{Qt::UserRole + 1, "materialName"}, {Qt::UserRole + 1, "materialName"},
{Qt::UserRole + 2, "materialInternalId"}, {Qt::UserRole + 2, "materialInternalId"},
{Qt::UserRole + 3, "materialVisible"}, {Qt::UserRole + 3, "materialVisible"},
{Qt::UserRole + 4, "materialType"} {Qt::UserRole + 4, "materialType"},
{Qt::UserRole + 5, "hasDynamicProperties"}
}; };
return roles; return roles;
} }
@@ -338,33 +342,47 @@ void MaterialBrowserModel::copyMaterialProperties(int idx, const QString &sectio
setCopiedMaterialType(matType); setCopiedMaterialType(matType);
m_allPropsCopied = section == "All"; m_allPropsCopied = section == "All";
bool dynamicPropsCopied = section == "Custom";
QmlObjectNode mat(m_copiedMaterial); QmlObjectNode mat(m_copiedMaterial);
QSet<PropertyName> validProps; QSet<PropertyName> validProps;
QHash<PropertyName, TypeName> dynamicProps;
PropertyNameList copiedProps; PropertyNameList copiedProps;
// Base state properties are always valid if (dynamicPropsCopied || m_allPropsCopied) {
const auto baseProps = m_copiedMaterial.propertyNames(); // Dynamic properties must always be set in base state
for (const auto &baseProp : baseProps) const QList<AbstractProperty> dynProps = m_copiedMaterial.dynamicProperties();
validProps.insert(baseProp); for (const auto &prop : dynProps) {
dynamicProps.insert(prop.name(), prop.dynamicTypeName());
if (!mat.isInBaseState()) { validProps.insert(prop.name());
QmlPropertyChanges changes = mat.propertyChangeForCurrentState();
if (changes.isValid()) {
const QList<AbstractProperty> changedProps = changes.targetProperties();
for (const auto &changedProp : changedProps)
validProps.insert(changedProp.name());
} }
} }
if (mat.timelineIsActive()) { if (!dynamicPropsCopied) {
const QList<QmlTimelineKeyframeGroup> keyframeGroups // Base state properties are always valid
= mat.currentTimeline().keyframeGroupsForTarget(m_copiedMaterial); const auto baseProps = m_copiedMaterial.propertyNames();
for (const auto &kfg : keyframeGroups) for (const auto &baseProp : baseProps)
validProps.insert(kfg.propertyName()); validProps.insert(baseProp);
}
if (m_allPropsCopied || m_propertyGroupsObj.empty()) { if (!mat.isInBaseState()) {
QmlPropertyChanges changes = mat.propertyChangeForCurrentState();
if (changes.isValid()) {
const QList<AbstractProperty> changedProps = changes.targetProperties();
for (const auto &changedProp : changedProps)
validProps.insert(changedProp.name());
}
}
if (mat.timelineIsActive()) {
const QList<QmlTimelineKeyframeGroup> keyframeGroups
= mat.currentTimeline().keyframeGroupsForTarget(m_copiedMaterial);
for (const auto &kfg : keyframeGroups)
validProps.insert(kfg.propertyName());
}
}
validProps.remove("objectName");
if (m_allPropsCopied || dynamicPropsCopied || m_propertyGroupsObj.empty()) {
copiedProps = validProps.values(); copiedProps = validProps.values();
} else { } else {
QJsonObject propsSpecObj = m_propertyGroupsObj.value(m_copiedMaterialType).toObject(); QJsonObject propsSpecObj = m_propertyGroupsObj.value(m_copiedMaterialType).toObject();
@@ -389,8 +407,10 @@ void MaterialBrowserModel::copyMaterialProperties(int idx, const QString &sectio
PropertyCopyData data; PropertyCopyData data;
data.name = propName; data.name = propName;
data.isValid = m_allPropsCopied || validProps.contains(propName); data.isValid = m_allPropsCopied || validProps.contains(propName);
data.isBinding = mat.hasBindingProperty(propName);
if (data.isValid) { if (data.isValid) {
if (dynamicProps.contains(propName))
data.dynamicTypeName = dynamicProps[propName];
data.isBinding = mat.hasBindingProperty(propName);
if (data.isBinding) if (data.isBinding)
data.value = mat.expression(propName); data.value = mat.expression(propName);
else else

View File

@@ -75,6 +75,7 @@ public:
struct PropertyCopyData struct PropertyCopyData
{ {
PropertyName name; PropertyName name;
TypeName dynamicTypeName;
QVariant value; QVariant value;
bool isBinding = false; bool isBinding = false;
bool isValid = false; bool isValid = false;

View File

@@ -2,8 +2,10 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "materialbrowserview.h" #include "materialbrowserview.h"
#include "bindingproperty.h" #include "bindingproperty.h"
#include "bundlematerial.h" #include "bundlematerial.h"
#include "bundleimporter.h"
#include "materialbrowsermodel.h" #include "materialbrowsermodel.h"
#include "materialbrowserwidget.h" #include "materialbrowserwidget.h"
#include "materialbrowserbundlemodel.h" #include "materialbrowserbundlemodel.h"
@@ -18,6 +20,7 @@
#include <nodemetainfo.h> #include <nodemetainfo.h>
#include <nodelistproperty.h> #include <nodelistproperty.h>
#include <qmldesignerconstants.h> #include <qmldesignerconstants.h>
#include <utils/algorithm.h>
#include <QQuickItem> #include <QQuickItem>
#include <QRegularExpression> #include <QRegularExpression>
@@ -81,13 +84,19 @@ WidgetInfo MaterialBrowserView::widgetInfo()
// remove current properties // remove current properties
PropertyNameList propNames; PropertyNameList propNames;
if (mat.isInBaseState()) { if (mat.isInBaseState()) {
propNames = material.propertyNames(); const QList<AbstractProperty> baseProps = material.properties();
for (const auto &baseProp : baseProps) {
if (!baseProp.isDynamic())
propNames.append(baseProp.name());
}
} else { } else {
QmlPropertyChanges changes = mat.propertyChangeForCurrentState(); QmlPropertyChanges changes = mat.propertyChangeForCurrentState();
if (changes.isValid()) { if (changes.isValid()) {
const QList<AbstractProperty> changedProps = changes.targetProperties(); const QList<AbstractProperty> changedProps = changes.targetProperties();
for (const auto &changedProp : changedProps) for (const auto &changedProp : changedProps) {
propNames.append(changedProp.name()); if (!changedProp.isDynamic())
propNames.append(changedProp.name());
}
} }
} }
for (const PropertyName &propName : std::as_const(propNames)) { for (const PropertyName &propName : std::as_const(propNames)) {
@@ -98,14 +107,29 @@ WidgetInfo MaterialBrowserView::widgetInfo()
// apply pasted properties // apply pasted properties
for (const QmlDesigner::MaterialBrowserModel::PropertyCopyData &propData : propDatas) { for (const QmlDesigner::MaterialBrowserModel::PropertyCopyData &propData : propDatas) {
if (propData.name == "objectName")
continue;
if (propData.isValid) { if (propData.isValid) {
if (propData.isBinding) const bool isDynamic = !propData.dynamicTypeName.isEmpty();
const bool isBaseState = currentState().isBaseState();
const bool hasProperty = mat.hasProperty(propData.name);
if (propData.isBinding) {
if (isDynamic && (!hasProperty || isBaseState)) {
mat.modelNode().bindingProperty(propData.name)
.setDynamicTypeNameAndExpression(
propData.dynamicTypeName, propData.value.toString());
continue;
}
mat.setBindingProperty(propData.name, propData.value.toString()); mat.setBindingProperty(propData.name, propData.value.toString());
else } else {
const bool isRecording = mat.timelineIsActive()
&& mat.currentTimeline().isRecording();
if (isDynamic && (!hasProperty || (isBaseState && !isRecording))) {
mat.modelNode().variantProperty(propData.name)
.setDynamicTypeNameAndValue(
propData.dynamicTypeName, propData.value);
continue;
}
mat.setVariantProperty(propData.name, propData.value); mat.setVariantProperty(propData.name, propData.value);
}
} else { } else {
mat.removeProperty(propData.name); mat.removeProperty(propData.name);
} }
@@ -122,23 +146,38 @@ WidgetInfo MaterialBrowserView::widgetInfo()
connect(matBrowserBundleModel, &MaterialBrowserBundleModel::applyToSelectedTriggered, this, connect(matBrowserBundleModel, &MaterialBrowserBundleModel::applyToSelectedTriggered, this,
[&] (BundleMaterial *bundleMat, bool add) { [&] (BundleMaterial *bundleMat, bool add) {
if (!m_selectedModel.isValid()) if (m_selectedModels.isEmpty())
return; return;
m_bundleMaterialDropTarget = m_selectedModel; m_bundleMaterialTargets = m_selectedModels;
m_bundleMaterialAddToSelected = add; m_bundleMaterialAddToSelected = add;
ModelNode defaultMat = getBundleMaterialDefaultInstance(bundleMat->type()); ModelNode defaultMat = getBundleMaterialDefaultInstance(bundleMat->type());
if (defaultMat.isValid()) if (defaultMat.isValid())
applyBundleMaterialToDropTarget(defaultMat); applyBundleMaterialToDropTarget(defaultMat);
else else
m_widget->materialBrowserBundleModel()->addMaterial(bundleMat); m_widget->materialBrowserBundleModel()->addToProject(bundleMat);
}); });
connect(matBrowserBundleModel, &MaterialBrowserBundleModel::addBundleMaterialToProjectRequested, this, connect(matBrowserBundleModel, &MaterialBrowserBundleModel::bundleMaterialImported, this,
[&] (const QmlDesigner::NodeMetaInfo &metaInfo) { [&] (const QmlDesigner::NodeMetaInfo &metaInfo) {
applyBundleMaterialToDropTarget({}, metaInfo); applyBundleMaterialToDropTarget({}, metaInfo);
updateBundleMaterialsImportedState();
}); });
connect(matBrowserBundleModel, &MaterialBrowserBundleModel::bundleMaterialAboutToUnimport, this,
[&] (const QmlDesigner::TypeName &type) {
// delete instances of the bundle material that is about to be unimported
executeInTransaction("MaterialBrowserView::widgetInfo", [&] {
Utils::reverseForeach(m_widget->materialBrowserModel()->materials(), [&](const ModelNode &mat) {
if (mat.isValid() && mat.type() == type)
QmlObjectNode(mat).destroy();
});
});
});
connect(matBrowserBundleModel, &MaterialBrowserBundleModel::bundleMaterialUnimported, this,
&MaterialBrowserView::updateBundleMaterialsImportedState);
} }
return createWidgetInfo(m_widget.data(), return createWidgetInfo(m_widget.data(),
@@ -178,40 +217,42 @@ void MaterialBrowserView::applyBundleMaterialToDropTarget(const ModelNode &bundl
newMatNode = bundleMat; newMatNode = bundleMat;
} }
if (m_bundleMaterialDropTarget.isValid() // TODO: unify this logic as it exist elsewhere also
&& m_bundleMaterialDropTarget.metaInfo().isQtQuick3DModel()) { auto expToList = [](const QString &exp) {
QmlObjectNode qmlObjNode(m_bundleMaterialDropTarget); QString copy = exp;
if (m_bundleMaterialAddToSelected) { copy = copy.remove("[").remove("]");
// TODO: unify this logic as it exist elsewhere also
auto expToList = [](const QString &exp) {
QString copy = exp;
copy = copy.remove("[").remove("]");
QStringList tmp = copy.split(',', Qt::SkipEmptyParts); QStringList tmp = copy.split(',', Qt::SkipEmptyParts);
for (QString &str : tmp) for (QString &str : tmp)
str = str.trimmed(); str = str.trimmed();
return tmp; return tmp;
}; };
auto listToExp = [](QStringList &stringList) { auto listToExp = [](QStringList &stringList) {
if (stringList.size() > 1) if (stringList.size() > 1)
return QString("[" + stringList.join(",") + "]"); return QString("[" + stringList.join(",") + "]");
if (stringList.size() == 1) if (stringList.size() == 1)
return stringList.first(); return stringList.first();
return QString(); return QString();
}; };
QStringList matList = expToList(qmlObjNode.expression("materials"));
matList.append(newMatNode.id()); for (const ModelNode &target : std::as_const(m_bundleMaterialTargets)) {
QString updatedExp = listToExp(matList); if (target.isValid() && target.metaInfo().isQtQuick3DModel()) {
qmlObjNode.setBindingProperty("materials", updatedExp); QmlObjectNode qmlObjNode(target);
} else { if (m_bundleMaterialAddToSelected) {
qmlObjNode.setBindingProperty("materials", newMatNode.id()); QStringList matList = expToList(qmlObjNode.expression("materials"));
matList.append(newMatNode.id());
QString updatedExp = listToExp(matList);
qmlObjNode.setBindingProperty("materials", updatedExp);
} else {
qmlObjNode.setBindingProperty("materials", newMatNode.id());
}
} }
m_bundleMaterialDropTarget = {}; m_bundleMaterialTargets = {};
m_bundleMaterialAddToSelected = false; m_bundleMaterialAddToSelected = false;
} }
}); });
@@ -226,6 +267,7 @@ void MaterialBrowserView::modelAttached(Model *model)
rootModelNode().metaInfo().isQtQuick3DMaterial()); rootModelNode().metaInfo().isQtQuick3DMaterial());
m_hasQuick3DImport = model->hasImport("QtQuick3D"); m_hasQuick3DImport = model->hasImport("QtQuick3D");
updateBundleMaterialsImportedState();
// Project load is already very busy and may even trigger puppet reset, so let's wait a moment // Project load is already very busy and may even trigger puppet reset, so let's wait a moment
// before refreshing the model // before refreshing the model
@@ -274,24 +316,20 @@ void MaterialBrowserView::modelAboutToBeDetached(Model *model)
void MaterialBrowserView::selectedNodesChanged(const QList<ModelNode> &selectedNodeList, void MaterialBrowserView::selectedNodesChanged(const QList<ModelNode> &selectedNodeList,
[[maybe_unused]] const QList<ModelNode> &lastSelectedNodeList) [[maybe_unused]] const QList<ModelNode> &lastSelectedNodeList)
{ {
m_selectedModel = {}; m_selectedModels = Utils::filtered(selectedNodeList, [](const ModelNode &node) {
return node.metaInfo().isQtQuick3DModel();
});
for (const ModelNode &node : selectedNodeList) { m_widget->materialBrowserModel()->setHasModelSelection(!m_selectedModels.isEmpty());
if (node.metaInfo().isQtQuick3DModel()) {
m_selectedModel = node;
break;
}
}
m_widget->materialBrowserModel()->setHasModelSelection(m_selectedModel.isValid());
// the logic below selects the material of the first selected model if auto selection is on
if (!m_autoSelectModelMaterial) if (!m_autoSelectModelMaterial)
return; return;
if (selectedNodeList.size() > 1 || !m_selectedModel.isValid()) if (selectedNodeList.size() > 1 || m_selectedModels.isEmpty())
return; return;
QmlObjectNode qmlObjNode(m_selectedModel); QmlObjectNode qmlObjNode(m_selectedModels.at(0));
QString matExp = qmlObjNode.expression("materials"); QString matExp = qmlObjNode.expression("materials");
if (matExp.isEmpty()) if (matExp.isEmpty())
return; return;
@@ -385,6 +423,25 @@ void QmlDesigner::MaterialBrowserView::loadPropertyGroups()
m_propertyGroupsLoaded = m_widget->materialBrowserModel()->loadPropertyGroups(matPropsPath); m_propertyGroupsLoaded = m_widget->materialBrowserModel()->loadPropertyGroups(matPropsPath);
} }
void MaterialBrowserView::updateBundleMaterialsImportedState()
{
using namespace Utils;
if (!m_widget->materialBrowserBundleModel()->bundleImporter())
return;
QStringList importedBundleMats;
FilePath materialBundlePath = m_widget->materialBrowserBundleModel()->bundleImporter()->resolveBundleImportPath();
if (materialBundlePath.exists()) {
importedBundleMats = transform(materialBundlePath.dirEntries({{"*.qml"}, QDir::Files}),
[](const FilePath &f) { return f.fileName().chopped(4); });
}
m_widget->materialBrowserBundleModel()->updateImportedState(importedBundleMats);
}
ModelNode MaterialBrowserView::getBundleMaterialDefaultInstance(const TypeName &type) ModelNode MaterialBrowserView::getBundleMaterialDefaultInstance(const TypeName &type)
{ {
const QList<ModelNode> materials = m_widget->materialBrowserModel()->materials(); const QList<ModelNode> materials = m_widget->materialBrowserModel()->materials();
@@ -442,13 +499,13 @@ void MaterialBrowserView::customNotification(const AbstractView *view,
} else if (identifier == "delete_selected_material") { } else if (identifier == "delete_selected_material") {
m_widget->materialBrowserModel()->deleteSelectedMaterial(); m_widget->materialBrowserModel()->deleteSelectedMaterial();
} else if (identifier == "drop_bundle_material") { } else if (identifier == "drop_bundle_material") {
m_bundleMaterialDropTarget = nodeList.first(); m_bundleMaterialTargets = nodeList;
ModelNode defaultMat = getBundleMaterialDefaultInstance(m_draggedBundleMaterial->type()); ModelNode defaultMat = getBundleMaterialDefaultInstance(m_draggedBundleMaterial->type());
if (defaultMat.isValid()) if (defaultMat.isValid())
applyBundleMaterialToDropTarget(defaultMat); applyBundleMaterialToDropTarget(defaultMat);
else else
m_widget->materialBrowserBundleModel()->addMaterial(m_draggedBundleMaterial); m_widget->materialBrowserBundleModel()->addToProject(m_draggedBundleMaterial);
m_draggedBundleMaterial = nullptr; m_draggedBundleMaterial = nullptr;
} }

View File

@@ -46,13 +46,15 @@ private:
void refreshModel(bool updateImages); void refreshModel(bool updateImages);
bool isMaterial(const ModelNode &node) const; bool isMaterial(const ModelNode &node) const;
void loadPropertyGroups(); void loadPropertyGroups();
void updateBundleMaterialsImportedState();
void applyBundleMaterialToDropTarget(const ModelNode &bundleMat, const NodeMetaInfo &metaInfo = {}); void applyBundleMaterialToDropTarget(const ModelNode &bundleMat, const NodeMetaInfo &metaInfo = {});
ModelNode getBundleMaterialDefaultInstance(const TypeName &type); ModelNode getBundleMaterialDefaultInstance(const TypeName &type);
QPointer<MaterialBrowserWidget> m_widget; QPointer<MaterialBrowserWidget> m_widget;
ModelNode m_bundleMaterialDropTarget; QList<ModelNode> m_bundleMaterialTargets;
ModelNode m_selectedModel; // first selected 3D model node QList<ModelNode> m_selectedModels; // selected 3D model nodes
BundleMaterial *m_draggedBundleMaterial = nullptr; BundleMaterial *m_draggedBundleMaterial = nullptr;
bool m_bundleMaterialAddToSelected = false; bool m_bundleMaterialAddToSelected = false;
bool m_hasQuick3DImport = false; bool m_hasQuick3DImport = false;
bool m_autoSelectModelMaterial = false; // TODO: wire this to some action bool m_autoSelectModelMaterial = false; // TODO: wire this to some action

View File

@@ -129,6 +129,7 @@ public:
QList<NodeListProperty> nodeListProperties() const; QList<NodeListProperty> nodeListProperties() const;
QList<BindingProperty> bindingProperties() const; QList<BindingProperty> bindingProperties() const;
QList<SignalHandlerProperty> signalProperties() const; QList<SignalHandlerProperty> signalProperties() const;
QList<AbstractProperty> dynamicProperties() const;
PropertyNameList propertyNames() const; PropertyNameList propertyNames() const;
bool hasProperties() const; bool hasProperties() const;

View File

@@ -623,6 +623,18 @@ QList<SignalHandlerProperty> ModelNode::signalProperties() const
return propertyList; return propertyList;
} }
QList<AbstractProperty> ModelNode::dynamicProperties() const
{
QList<AbstractProperty> propertyList;
const QList<AbstractProperty> abstractProperties = properties();
for (const AbstractProperty &abstractProperty : abstractProperties) {
if (abstractProperty.isDynamic())
propertyList.append(abstractProperty);
}
return propertyList;
}
/*! /*!
\brief removes a property from this node \brief removes a property from this node
\param name name of the property \param name name of the property

View File

@@ -912,9 +912,9 @@ void QtOptionsPageWidget::apply()
&QtOptionsPageWidget::updateQtVersions); &QtOptionsPageWidget::updateQtVersions);
} }
// TODO whenever we move the output of sdktool to a different location in the installer,
// this needs to be adapted accordingly
const QStringList kSubdirsToCheck = {"", const QStringList kSubdirsToCheck = {"",
"Tools/sdktool", // macOS
"Tools/sdktool/share/qtcreator", // Windows/Linux
"Qt Creator.app/Contents/Resources", "Qt Creator.app/Contents/Resources",
"Contents/Resources", "Contents/Resources",
"Tools/QtCreator/share/qtcreator", "Tools/QtCreator/share/qtcreator",

View File

@@ -5706,8 +5706,8 @@ void TextEditorWidget::mouseDoubleClickEvent(QMouseEvent *e)
} }
} }
QTextCursor oldCursor = multiTextCursor().mainCursor(); QTextCursor eventCursor = cursorForPosition(QPoint(e->pos().x(), e->pos().y()));
const int oldPosition = oldCursor.position(); const int eventDocumentPosition = eventCursor.position();
QPlainTextEdit::mouseDoubleClickEvent(e); QPlainTextEdit::mouseDoubleClickEvent(e);
@@ -5715,19 +5715,19 @@ void TextEditorWidget::mouseDoubleClickEvent(QMouseEvent *e)
// event is triggered on a position that is inbetween two whitespaces this event selects the // event is triggered on a position that is inbetween two whitespaces this event selects the
// previous word or nothing if the whitespaces are at the block start. Replace this behavior // previous word or nothing if the whitespaces are at the block start. Replace this behavior
// with selecting the whitespaces starting from the previous word end to the next word. // with selecting the whitespaces starting from the previous word end to the next word.
const QChar character = characterAt(oldPosition); const QChar character = characterAt(eventDocumentPosition);
const QChar prevCharacter = characterAt(oldPosition - 1); const QChar prevCharacter = characterAt(eventDocumentPosition - 1);
if (character.isSpace() && prevCharacter.isSpace()) { if (character.isSpace() && prevCharacter.isSpace()) {
if (prevCharacter != QChar::ParagraphSeparator) { if (prevCharacter != QChar::ParagraphSeparator) {
oldCursor.movePosition(QTextCursor::PreviousWord); eventCursor.movePosition(QTextCursor::PreviousWord);
oldCursor.movePosition(QTextCursor::EndOfWord); eventCursor.movePosition(QTextCursor::EndOfWord);
} else if (character == QChar::ParagraphSeparator) { } else if (character == QChar::ParagraphSeparator) {
return; // no special handling for empty lines return; // no special handling for empty lines
} }
oldCursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor); eventCursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor);
MultiTextCursor cursor = multiTextCursor(); MultiTextCursor cursor = multiTextCursor();
cursor.replaceMainCursor(oldCursor); cursor.replaceMainCursor(eventCursor);
setMultiTextCursor(cursor); setMultiTextCursor(cursor);
} }
} }

View File

@@ -47,8 +47,8 @@ def verifyVersionControlView(targetDir, canceled):
"Searching for target directory in clone log") "Searching for target directory in clone log")
test.verify(" ".join(["clone", "--progress", cloneUrl, cloneDir]) in vcsLog, test.verify(" ".join(["clone", "--progress", cloneUrl, cloneDir]) in vcsLog,
"Searching for git parameters in clone log") "Searching for git parameters in clone log")
test.verify(canceled == (" terminated abnormally" in vcsLog), test.compare(canceled, " terminated abnormally" in vcsLog,
"Searching for result in clone log") "Searching for result in clone log")
clickButton(waitForObject(":*Qt Creator.Clear_QToolButton")) clickButton(waitForObject(":*Qt Creator.Clear_QToolButton"))
def verifyFiles(targetDir): def verifyFiles(targetDir):
@@ -117,5 +117,5 @@ def main():
test.fail("The checked out project was not being opened.", test.fail("The checked out project was not being opened.",
str(waitForObject(":Cannot Open Project_QTextEdit").plainText)) str(waitForObject(":Cannot Open Project_QTextEdit").plainText))
clickButton(waitForObject(":Cannot Open Project.OK_QPushButton")) clickButton(waitForObject(":Cannot Open Project.OK_QPushButton"))
verifyVersionControlView(targetDir, button != ":Git Repository Clone.Finish_QPushButton") verifyVersionControlView(targetDir, button == "Cancel immediately")
invokeMenuItem("File", "Exit") invokeMenuItem("File", "Exit")