Merge "Merge remote-tracking branch 'origin/qds/dev'"

This commit is contained in:
The Qt Project
2025-01-15 09:02:54 +00:00
106 changed files with 1429 additions and 673 deletions

View File

@@ -19,7 +19,7 @@
The editor shows found issues as inline annotations if the project matches The editor shows found issues as inline annotations if the project matches
the currently open one and the respective file is part of the project. the currently open one and the respective file is part of the project.
Hover the mouse over an annotation to bring up a tooltip with a short hover over an annotation to bring up a tooltip with a short
description of the issue. description of the issue.
\image qtcreator-axivion-annotation.webp {Annotation popup} \image qtcreator-axivion-annotation.webp {Annotation popup}

View File

@@ -140,7 +140,7 @@
\li Select \uicontrol Projects > \uicontrol {Project Settings} > \li Select \uicontrol Projects > \uicontrol {Project Settings} >
\uicontrol {Clang Tools}. \uicontrol {Clang Tools}.
\image qtcreator-clang-tools-settings.webp {Clang Tools customized settings} \image qtcreator-clang-tools-settings.webp {Clang Tools customized settings}
\li Deselect \uicontrol {Use global settings}. \li Clear \uicontrol {Use global settings}.
\li Specify \uicontrol preferences for the project. \li Specify \uicontrol preferences for the project.
\li In \uicontrol {Suppressed diagnostics}, you can view the suppression \li In \uicontrol {Suppressed diagnostics}, you can view the suppression
list for a project and to remove diagnostics from it. list for a project and to remove diagnostics from it.
@@ -196,7 +196,7 @@
generated during the build. For big projects, not building the generated during the build. For big projects, not building the
project might save some time. project might save some time.
\li To disable automatic analysis of open documents, deselect the \li To disable automatic analysis of open documents, clear the
\uicontrol {Analyze open files} check box. \uicontrol {Analyze open files} check box.
\li In the \uicontrol {Parallel jobs} field, select the number of jobs \li In the \uicontrol {Parallel jobs} field, select the number of jobs

View File

@@ -122,7 +122,7 @@
Memcheck also reports uses of uninitialised values, most commonly with the Memcheck also reports uses of uninitialised values, most commonly with the
message \uicontrol {Conditional jump or move depends on uninitialised value(s).} message \uicontrol {Conditional jump or move depends on uninitialised value(s).}
To determine the root cause of these errors, the \uicontrol {Track origins of To determine the root cause of these errors, the \uicontrol {Track origins of
uninitialized memory} check box is selected by default. You can deselect it uninitialized memory} check box is selected by default. You can clear it
to make Memcheck run faster. to make Memcheck run faster.
\section1 Viewing a Summary \section1 Viewing a Summary

View File

@@ -94,7 +94,7 @@
source files are located. To hide the subfolder names and arrange the files source files are located. To hide the subfolder names and arrange the files
only according to their source group, select \preferences > only according to their source group, select \preferences >
\uicontrol CMake > \uicontrol General, and then \uicontrol CMake > \uicontrol General, and then
deselect the \uicontrol {Show subfolders inside source group folders} check clear the \uicontrol {Show subfolders inside source group folders} check
box. The change takes effect after you select \uicontrol Build > box. The change takes effect after you select \uicontrol Build >
\uicontrol {Run CMake}. \uicontrol {Run CMake}.

View File

@@ -1109,7 +1109,7 @@
If you cannot debug Qt objects because their data is corrupted, you can If you cannot debug Qt objects because their data is corrupted, you can
switch off the debugging helpers to make low-level structures visible. switch off the debugging helpers to make low-level structures visible.
To switch off the debugging helpers, deselect To switch off the debugging helpers, clear
\uicontrol {Use Debugging Helpers} in \preferences > \uicontrol {Use Debugging Helpers} in \preferences >
\uicontrol Debugger > \uicontrol {Locals & Expressions}. \uicontrol Debugger > \uicontrol {Locals & Expressions}.
@@ -2166,7 +2166,7 @@
\section1 Structure Members Are Not Sorted According to Structure Layout \section1 Structure Members Are Not Sorted According to Structure Layout
By default, structure members are displayed in alphabetic order. To inspect By default, structure members are displayed in alphabetic order. To inspect
the real layout in memory, deselect the real layout in memory, clear
\uicontrol {Sort Members of Classes and Structs Alphabetically} in the \uicontrol {Sort Members of Classes and Structs Alphabetically} in the
context menu in the \uicontrol Locals and \uicontrol Expressions views. context menu in the \uicontrol Locals and \uicontrol Expressions views.

View File

@@ -166,7 +166,7 @@
When completion is invoked manually, \QC completes the common prefix of the When completion is invoked manually, \QC completes the common prefix of the
list of suggestions. This is especially useful for classes with several list of suggestions. This is especially useful for classes with several
similarly named members. To disable this functionality, deselect the similarly named members. To disable this functionality, clear the
\uicontrol {Autocomplete common prefix} check box. \uicontrol {Autocomplete common prefix} check box.
Select the \uicontrol {Automatically split strings} check box to split Select the \uicontrol {Automatically split strings} check box to split

View File

@@ -68,7 +68,7 @@
To help you keep line length at a particular number of characters, set the To help you keep line length at a particular number of characters, set the
number of characters in the \uicontrol {Display right margin at column} number of characters in the \uicontrol {Display right margin at column}
field. To use a different color for the margin area, select the field. To use a different color for the margin area, select the
\uicontrol {Tint whole margin area} check box. Deselect the check box to show \uicontrol {Tint whole margin area} check box. Clear the check box to show
the margin as a vertical line. the margin as a vertical line.
To use a context-specific margin when available, select the To use a context-specific margin when available, select the

View File

@@ -34,14 +34,14 @@
percentage for viewing the text. You can also zoom in or out by pressing percentage for viewing the text. You can also zoom in or out by pressing
\key {Ctrl++} or \key {Ctrl+-}, or by pressing \key Ctrl and rolling \key {Ctrl++} or \key {Ctrl+-}, or by pressing \key Ctrl and rolling
the mouse button up or down. To disable the mouse wheel function, select the mouse button up or down. To disable the mouse wheel function, select
\preferences > \uicontrol {Text Editor} > \uicontrol Behavior and deselect \preferences > \uicontrol {Text Editor} > \uicontrol Behavior and clear
the \uicontrol {Enable scroll wheel zooming} check box. the \uicontrol {Enable scroll wheel zooming} check box.
To improve the readability of text in the editor, adjust the line spacing in To improve the readability of text in the editor, adjust the line spacing in
the \uicontrol {Line spacing} field. the \uicontrol {Line spacing} field.
Antialiasing is used by default to make text look smoother and more readable Antialiasing is used by default to make text look smoother and more readable
on the screen. Deselect the \uicontrol Antialias check box to turn off on the screen. Clear the \uicontrol Antialias check box to turn off
antialiasing. antialiasing.
\sa {Behavior}, {Change editor colors} \sa {Behavior}, {Change editor colors}

View File

@@ -88,7 +88,7 @@
\endlist \endlist
To use the built-in code model instead, select \preferences > To use the built-in code model instead, select \preferences >
\uicontrol C++ > \uicontrol clangd, and deselect the \uicontrol {Use clangd} check box. \uicontrol C++ > \uicontrol clangd, and clear the \uicontrol {Use clangd} check box.
This setting also exists on the project level, so that you can have the Clang-based This setting also exists on the project level, so that you can have the Clang-based
services generally enabled, but switch them off for certain projects, or vice versa. services generally enabled, but switch them off for certain projects, or vice versa.
@@ -337,7 +337,7 @@
\li Select \uicontrol Projects > \uicontrol {Project Settings} > \li Select \uicontrol Projects > \uicontrol {Project Settings} >
\uicontrol {Clangd}. \uicontrol {Clangd}.
\image qtcreator-projects-settings-clangd.webp {Clangd preferences for a project} \image qtcreator-projects-settings-clangd.webp {Clangd preferences for a project}
\li Deselect \uicontrol {Use global settings}. \li Clear \uicontrol {Use global settings}.
\li Select \uicontrol {Use clangd}. \li Select \uicontrol {Use clangd}.
\li Specify \uicontrol Clangd preferences for the project. \li Specify \uicontrol Clangd preferences for the project.
\endlist \endlist

View File

@@ -230,7 +230,7 @@
filter to locate the files. filter to locate the files.
To use the sorting from the selected tool instead of from \QC, To use the sorting from the selected tool instead of from \QC,
deselect the \uicontrol {Sort results} check box in the \c md clear the \uicontrol {Sort results} check box in the \c md
locator filter configuration. locator filter configuration.
\image qtcreator-locator-filter-edit-md.webp {Filter Configuration dialog} \image qtcreator-locator-filter-edit-md.webp {Filter Configuration dialog}

View File

@@ -81,8 +81,7 @@
\uicontrol {Request Copilot Suggestion} in the \l{Navigate with locator} \uicontrol {Request Copilot Suggestion} in the \l{Navigate with locator}
{locator}. {locator}.
Hover the mouse over a suggestion to show a toolbar with Hover over a suggestion to show a toolbar with \inlineimage icons/prev.png
\inlineimage icons/prev.png
and \inlineimage icons/next.png and \inlineimage icons/next.png
buttons for cycling between Copilot suggestions. buttons for cycling between Copilot suggestions.

View File

@@ -47,8 +47,8 @@
To use a locator filter: To use a locator filter:
\list \list
\li Type the locator filter prefix followed by \key Space. The prefix \li Enter the locator filter prefix followed by \key Space. The prefix
is usually short, from one to three characters. Then type the search is usually short, from one to three characters. Then enter the search
string (for example, a filename or class name) or the command to string (for example, a filename or class name) or the command to
execute. execute.
@@ -58,7 +58,7 @@
selected filter. selected filter.
\endlist \endlist
As you type a search string, As you enter a search string,
the locator shows the occurrences of that string regardless of where in the the locator shows the occurrences of that string regardless of where in the
name of an object it appears. Some locator filters, such as colon, \c m, name of an object it appears. Some locator filters, such as colon, \c m,
and \c t, support \e fuzzy matching, which means that you can enter the and \c t, support \e fuzzy matching, which means that you can enter the

View File

@@ -68,7 +68,7 @@
select \preferences > \uicontrol {Text Editor} > select \preferences > \uicontrol {Text Editor} >
\uicontrol Behavior > \uicontrol Typing. \uicontrol Behavior > \uicontrol Typing.
To disable automatic indentation, deselect the To disable automatic indentation, clear the
\uicontrol {Enable automatic indentation} check box. \uicontrol {Enable automatic indentation} check box.
You can specify how the indentation is decreased when you press You can specify how the indentation is decreased when you press

View File

@@ -368,7 +368,7 @@
If a Qt Quick test case does not have a name, it is marked If a Qt Quick test case does not have a name, it is marked
\uicontrol Unnamed in the list. \uicontrol {Run All Tests} executes \uicontrol Unnamed in the list. \uicontrol {Run All Tests} executes
unnamed test cases. You cannot select or deselect them. unnamed test cases. You cannot select or clear them.
\QC scans the project for tests when you open the project and updates the \QC scans the project for tests when you open the project and updates the
test list for the currently active test frameworks when you edit tests. test list for the currently active test frameworks when you edit tests.
@@ -848,7 +848,7 @@
To show all messages, select \uicontrol {Check All Filters}. To show all messages, select \uicontrol {Check All Filters}.
To deselect all message types, select \uicontrol {Uncheck All Filters}. To clear all message types, select \uicontrol {Uncheck All Filters}.
\section1 Blacklisting Tests \section1 Blacklisting Tests

View File

@@ -15,7 +15,7 @@
To set preferences for documentation comments for a particular project, To set preferences for documentation comments for a particular project,
select \uicontrol Projects > \uicontrol {Project Settings} > select \uicontrol Projects > \uicontrol {Project Settings} >
\uicontrol {Documentation Comments}, and deselect the \uicontrol {Documentation Comments}, and clear the
\uicontrol {Use global settings} check box. \uicontrol {Use global settings} check box.
\image qtcreator-preferences-documentation-comments.webp {Documentation Comments settings} \image qtcreator-preferences-documentation-comments.webp {Documentation Comments settings}

View File

@@ -137,7 +137,7 @@
Increase the limit if frames are dropped during the recording. Increase the limit if frames are dropped during the recording.
\row \row
\li \uicontrol {Export animated images as infinite loop} \li \uicontrol {Export animated images as infinite loop}
\li Whether to export animated images as inifite loops. Deselect \li Whether to export animated images as inifite loops. Clear
this check box to only play the animation once. this check box to only play the animation once.
\row \row
\li \uicontrol {Write command line of FFmpeg calls to General Messages} \li \uicontrol {Write command line of FFmpeg calls to General Messages}

View File

@@ -149,7 +149,7 @@
To trigger the GDB command that generates a core file while debugging, To trigger the GDB command that generates a core file while debugging,
go to \uicontrol View > \uicontrol Views > \l {Debugger Log}. go to \uicontrol View > \uicontrol Views > \l {Debugger Log}.
In the \uicontrol Command field, type \c gcore and select \key Enter. The In the \uicontrol Command field, enter \c gcore and select \key Enter. The
core file is created in the current working directory. You can specify core file is created in the current working directory. You can specify
another location for the file, including a relative or absolute path, as an another location for the file, including a relative or absolute path, as an
argument of the command. argument of the command.
@@ -232,7 +232,7 @@
When you run a console application, you can view the output in the console When you run a console application, you can view the output in the console
window of the calling application. If the window of the calling application. If the
calling application is a GUI application (for example, a release-built calling application is a GUI application (for example, a release-built
version of \QC), a new console window is opened. For this version of \QC), a new console window is opened. For this
type of application, \c qDebug() and related functions use standard output type of application, \c qDebug() and related functions use standard output
and error output. and error output.
@@ -317,8 +317,8 @@
in the tab bar, find our that the function is inline, fix the problem, and in the tab bar, find our that the function is inline, fix the problem, and
forget where they came from. forget where they came from.
With \QC, developers can type \c {Ctrl+K m AFun} to find the function. With \QC, developers can enter \c {Ctrl+K m AFun} to find the function.
Typically, they only need to type 3 to 4 characters of the function name. Typically, they only need to enter 3 to 4 characters of the function name.
They can then fix the problem and select \key Alt+Back to go back to where They can then fix the problem and select \key Alt+Back to go back to where
they were. they were.

View File

@@ -100,7 +100,7 @@
deployment time and only copies files that have changed since the last deployment time and only copies files that have changed since the last
deployment. However, when you make major changes on the device, such as deployment. However, when you make major changes on the device, such as
removing files from the device manually or flashing a new disk image, or removing files from the device manually or flashing a new disk image, or
when you use another device with the same IP address, deselect the check box when you use another device with the same IP address, clear the check box
once, to have \QC deploy all files again. once, to have \QC deploy all files again.
\section2 Creating a Tarball \section2 Creating a Tarball

View File

@@ -22,7 +22,7 @@
By default, \QC builds qmake projects (that have .pro files) in a separate By default, \QC builds qmake projects (that have .pro files) in a separate
directory from the source directory, as \l{glossary-shadow-build} directory from the source directory, as \l{glossary-shadow-build}
{shadow builds}. This keeps the files generated for each kit separate. If {shadow builds}. This keeps the files generated for each kit separate. If
you only build and run with a single kit, you can deselect the you only build and run with a single kit, you can clear the
\uicontrol {Shadow build} checkbox. \uicontrol {Shadow build} checkbox.
Select the build directory in the \uicontrol {Build Directory} field. You Select the build directory in the \uicontrol {Build Directory} field. You
@@ -38,7 +38,7 @@
\section1 Tooltips in Kit Selector \section1 Tooltips in Kit Selector
In the \uicontrol {Tooltip in target selector} field, you can enter text In the \uicontrol {Tooltip in target selector} field, you can enter text
that is displayed as a tooltip when you hover the mouse over the build that is displayed as a tooltip when you hover over the build
configuration in the \l{Build for many platforms}{kit selector}. configuration in the \l{Build for many platforms}{kit selector}.
You can create separate versions of project files to keep platform-dependent You can create separate versions of project files to keep platform-dependent

View File

@@ -60,7 +60,7 @@
\li Enter code to see the resulting assembly code. \li Enter code to see the resulting assembly code.
\endlist \endlist
Hover the mouse over the assembly code, to have the matching source lines hover over the assembly code, to have the matching source lines
highlighted. highlighted.
You can also see the application status and output. You can also see the application status and output.

View File

@@ -85,7 +85,7 @@
debug version. For example, if the release version is called example.lib, debug version. For example, if the release version is called example.lib,
the debug version is called exampled.lib. You can specify that the letter the debug version is called exampled.lib. You can specify that the letter
is added for the debug version and removed for the release version. is added for the debug version and removed for the release version.
If the library name ends in \e d, deselect the \uicontrol {Remove "d" suffix If the library name ends in \e d, clear the \uicontrol {Remove "d" suffix
for release version} option. for release version} option.
For more information about the project file settings, see For more information about the project file settings, see

View File

@@ -66,7 +66,7 @@
\list 1 \list 1
\li Select \preferences > \uicontrol Qbs. \li Select \preferences > \uicontrol Qbs.
\image qtcreator-options-qbs.png "Qbs preferences" \image qtcreator-options-qbs.png "Qbs preferences"
\li Deselect the \uicontrol {Use \QC settings directory for Qbs} check \li Clear the \uicontrol {Use \QC settings directory for Qbs} check
box to store Qbs profiles in the Qbs settings directory. box to store Qbs profiles in the Qbs settings directory.
\li In the \uicontrol {Path to qbs executable} field, you can view \li In the \uicontrol {Path to qbs executable} field, you can view
and change the path to the Qbs executable. and change the path to the Qbs executable.

View File

@@ -21,7 +21,7 @@
\uicontrol {Build Directory} field. \uicontrol {Build Directory} field.
In the \uicontrol {Tooltip in target selector} field, you can enter text In the \uicontrol {Tooltip in target selector} field, you can enter text
that is displayed as a tooltip when you hover the mouse over the build that is displayed as a tooltip when you hover over the build
configuration in the \l{Build for many platforms}{kit selector}. configuration in the \l{Build for many platforms}{kit selector}.
You can enter a name for the build configuration in the You can enter a name for the build configuration in the
@@ -98,7 +98,7 @@
\note On Windows, the build will fail if the application \note On Windows, the build will fail if the application
is running because the executable file cannot be is running because the executable file cannot be
overwritten. To avoid this issue, you can deselect this overwritten. To avoid this issue, you can clear this
check box and add a \uicontrol {Qbs Install} deployment step check box and add a \uicontrol {Qbs Install} deployment step
in the run settings that will be performed just before in the run settings that will be performed just before
running the application. running the application.
@@ -108,7 +108,7 @@
starts. starts.
\li Select \uicontrol {Use default location} to install the \li Select \uicontrol {Use default location} to install the
artifacts to the default location. Deselect the check box to artifacts to the default location. Clear the check box to
specify another location in the specify another location in the
\uicontrol {Installation directory} field. \uicontrol {Installation directory} field.

View File

@@ -26,7 +26,7 @@
\li Select \uicontrol Projects > \uicontrol {Project Settings} > \li Select \uicontrol Projects > \uicontrol {Project Settings} >
\uicontrol Editor. \uicontrol Editor.
\li Deselect \uicontrol {Use global settings}. \li Clear \uicontrol {Use global settings}.
\li Specify text editor settings for the project. \li Specify text editor settings for the project.

View File

@@ -72,7 +72,7 @@
\image qtcreator-settings-run-desktop.webp {Run Settings} \image qtcreator-settings-run-desktop.webp {Run Settings}
To prevent \QC from automatically creating run configurations, select To prevent \QC from automatically creating run configurations, select
\preferences > \uicontrol {Build & Run}, and then deselect the \preferences > \uicontrol {Build & Run}, and then clear the
\uicontrol {Create suitable run configurations automatically} check box. \uicontrol {Create suitable run configurations automatically} check box.
\section1 Overriding Global Preferences \section1 Overriding Global Preferences

View File

@@ -41,7 +41,7 @@
\li Select \uicontrol{Next} (on Windows and Linux) or \uicontrol Continue \li Select \uicontrol{Next} (on Windows and Linux) or \uicontrol Continue
(on \macos) to open the \uicontrol {Define Class} dialog. (on \macos) to open the \uicontrol {Define Class} dialog.
\image qtcreator-new-qt-for-python-app-widgets-define-class.webp {Define Class dialog} \image qtcreator-new-qt-for-python-app-widgets-define-class.webp {Define Class dialog}
\li In \uicontrol {Class name}, type \b {MyWidget} as the class \li In \uicontrol {Class name}, enter \b {MyWidget} as the class
name. name.
\li In \uicontrol {Base class}, select \b {QWidget} as the base class. \li In \uicontrol {Base class}, select \b {QWidget} as the base class.
\note The \uicontrol {Source file} field is automatically updated to \note The \uicontrol {Source file} field is automatically updated to

View File

@@ -24,7 +24,7 @@
\endlist \endlist
To show the title bars of views, select \uicontrol View > \uicontrol Views, To show the title bars of views, select \uicontrol View > \uicontrol Views,
and deselect the \uicontrol {Automatically Hide Title Bars} check box. and clear the \uicontrol {Automatically Hide Title Bars} check box.
\section1 Attach views \section1 Attach views

View File

@@ -10,7 +10,7 @@
\title Show and hide the main menu \title Show and hide the main menu
On Linux and Windows, you can hide the main menu bar to save space on the On Linux and Windows, you can hide the main menu bar to save space on the
screen. Select \uicontrol View, and deselect the \uicontrol {Show Menu Bar} screen. Select \uicontrol View, and clear the \uicontrol {Show Menu Bar}
check box. check box.
\image qtcreator-without-menubar.webp {Qt Creator without the main menu} \image qtcreator-without-menubar.webp {Qt Creator without the main menu}

View File

@@ -24,7 +24,7 @@
\inlineimage icons/sort_alphabetically.png \inlineimage icons/sort_alphabetically.png
(\uicontrol {Sort Alphabetically}). (\uicontrol {Sort Alphabetically}).
\li To stop the synchronization with the type or symbol selected in the \li To stop the synchronization with the type or symbol selected in the
editor, deselect \inlineimage icons/linkicon.png editor, clear \inlineimage icons/linkicon.png
(\uicontrol {Synchronize with Editor}). (\uicontrol {Synchronize with Editor}).
\endlist \endlist
*/ */

View File

@@ -42,7 +42,7 @@
in the context menu or select \key {Ctrl+A}. in the context menu or select \key {Ctrl+A}.
\li To open links in a browser, files in the editor, or folders in the \li To open links in a browser, files in the editor, or folders in the
\l Projects view, hover the mouse over them, and select \key Ctrl. \l Projects view, hover over them, and select \key Ctrl.
\li To \l{Search in current file}{search} through the output, press \li To \l{Search in current file}{search} through the output, press
\key {Ctrl+F}. \key {Ctrl+F}.

View File

@@ -108,7 +108,7 @@
\li Close all files in a project. \li Close all files in a project.
\li Close the selected project or all projects except the selected \li Close the selected project or all projects except the selected
one. By default, this closes all files in the projects. To keep one. By default, this closes all files in the projects. To keep
them open, deselect the \preferences > \uicontrol {Build & Run} > them open, clear the \preferences > \uicontrol {Build & Run} >
\uicontrol General > \uicontrol {Close source files along with project} \uicontrol General > \uicontrol {Close source files along with project}
check box. check box.
\endlist \endlist
@@ -139,7 +139,7 @@
\endlist \endlist
To stop synchronizing the position in the project tree with the file To stop synchronizing the position in the project tree with the file
currently opened in the editor, deselect \inlineimage icons/linkicon.png currently opened in the editor, clear \inlineimage icons/linkicon.png
(\uicontrol {Synchronize with Editor}). (\uicontrol {Synchronize with Editor}).
Some build systems support adding and removing files to a project in \QC Some build systems support adding and removing files to a project in \QC

View File

@@ -57,7 +57,7 @@
or select \key F6 and \key Shift+F6. or select \key F6 and \key Shift+F6.
By default, a new build clears the \uicontrol Issues view. To keep By default, a new build clears the \uicontrol Issues view. To keep
the issues from the previous build rounds, deselect \preferences > the issues from the previous build rounds, clear \preferences >
\uicontrol {Build & Run} > \uicontrol General > \uicontrol {Build & Run} > \uicontrol General >
\uicontrol {Clear issues list on new build}. \uicontrol {Clear issues list on new build}.

View File

@@ -54,7 +54,7 @@
Unified Change Management (UCM) view, they are added to the change set of Unified Change Management (UCM) view, they are added to the change set of
a UCM activity. By default, the activities are automatically assigned names. a UCM activity. By default, the activities are automatically assigned names.
To disable this functionality, select \preferences > To disable this functionality, select \preferences >
\uicontrol {Version Control} > \uicontrol ClearCase, and then deselect the \uicontrol {Version Control} > \uicontrol ClearCase, and then clear the
\uicontrol {Auto assign activity names} check box. \uicontrol {Auto assign activity names} check box.
To automatically check out files when you edit them, select the To automatically check out files when you edit them, select the

View File

@@ -64,7 +64,7 @@
within the client workspace. By default, files are automatically opened for within the client workspace. By default, files are automatically opened for
editing. To disable this feature, select \preferences > editing. To disable this feature, select \preferences >
\uicontrol {Version Control} > \uicontrol Perforce, \uicontrol {Version Control} > \uicontrol Perforce,
and then deselect the \uicontrol {Automatically open files when editing} and then clear the \uicontrol {Automatically open files when editing}
check box. check box.
To list files that are open for editing, select \uicontrol Tools > To list files that are open for editing, select \uicontrol Tools >

View File

@@ -145,7 +145,7 @@
\row \row
\li Check box - \uicontrol {Clean whitespace} \li Check box - \uicontrol {Clean whitespace}
\li Removes trailing whitespace upon saving. \li Removes trailing whitespace upon saving.
\li Enable this check box to remove trailing whitespace upon saving. \li Select this check box to remove trailing whitespace upon saving.
\row \row
\li Combo box - \uicontrol Size \li Combo box - \uicontrol Size
\li The font size used in the terminal (in points). \li The font size used in the terminal (in points).

View File

@@ -202,7 +202,7 @@
select \imageplus select \imageplus
. .
\li In the \uicontrol {Property Type} field, enter \e {Item}. \li In the \uicontrol {Property Type} field, enter \e {Item}.
This field is a drop-down list, but you can also type text. This field is a drop-down list, but you can also enter text.
\li In the \uicontrol {Property Value} field, enter \e {null}. \li In the \uicontrol {Property Value} field, enter \e {null}.
\endlist \endlist
\image animation-tutorial-property.png \image animation-tutorial-property.png

View File

@@ -116,7 +116,7 @@
\image coffee-machine-properties.png \image coffee-machine-properties.png
When we deselect the record button to stop recording the timeline, the When we clear the record button to stop recording the timeline, the
new timeline appears in the view. new timeline appears in the view.
For more information about using the timeline, see For more information about using the timeline, see

View File

@@ -54,7 +54,7 @@
to return to the Standard screen. Finally, at frame 4000, we set the X to return to the Standard screen. Finally, at frame 4000, we set the X
coordinate to -19 to return to the Trip screen. coordinate to -19 to return to the Trip screen.
When we deselect the record button to stop recording the timeline, the When we clear the record button to stop recording the timeline, the
new timeline appears in the view. new timeline appears in the view.
When we select \e tripScreen in the \uicontrol Navigator, we can see the When we select \e tripScreen in the \uicontrol Navigator, we can see the

View File

@@ -158,7 +158,7 @@
(\uicontrol {Per Property Recording}) button for the (\uicontrol {Per Property Recording}) button for the
\uicontrol opacity property of \e repeatPassword to start \uicontrol opacity property of \e repeatPassword to start
recording property changes. recording property changes.
\li In \uicontrol Visibility > \uicontrol Opacity, type \e 0 to hide the button, and press \li In \uicontrol Visibility > \uicontrol Opacity, enter \e 0 to hide the button, and press
\key Enter to save the value. \key Enter to save the value.
\li Move the playhead to frame \e 1000 and change the opacity value to \e 1 \li Move the playhead to frame \e 1000 and change the opacity value to \e 1
to show the button. to show the button.

View File

@@ -23,7 +23,7 @@
\l {progress-bar-control}{Progress Bar} component available in \l {progress-bar-control}{Progress Bar} component available in
\uicontrol Components > \uicontrol {Qt Quick Controls}. \uicontrol Components > \uicontrol {Qt Quick Controls}.
In the \uicontrol Design mode, we drag-and-drop a \uicontrol Rectangle from In the \uicontrol Design mode, we drag a \uicontrol Rectangle from
\uicontrol Components > \uicontrol {Default Components} > \uicontrol Components > \uicontrol {Default Components} >
\uicontrol Basic to the \l {2D} view and modify its size to create the \uicontrol Basic to the \l {2D} view and modify its size to create the
background for the progress bar. We change its ID to \e pb_back in background for the progress bar. We change its ID to \e pb_back in
@@ -31,12 +31,12 @@
We want to be able to control the background rectangle and the text label We want to be able to control the background rectangle and the text label
that was added by the project wizard, so we will use an \uicontrol Item that was added by the project wizard, so we will use an \uicontrol Item
component for that. We drag-and-drop the Item from \uicontrol Components > component for that. We drag the Item from \uicontrol Components >
\uicontrol {Default Components} > \uicontrol Basic \uicontrol {Default Components} > \uicontrol Basic
to the \uicontrol {2D} view and change its ID to \e root in to the \uicontrol {2D} view and change its ID to \e root in
\uicontrol Properties. \uicontrol Properties.
To make the background and text children of the Item, we drag-and-drop them To make the background and text children of the Item, we drag them
to the Item in \l Navigator. This enables us to use the anchor to the Item in \l Navigator. This enables us to use the anchor
buttons in \uicontrol Properties > \uicontrol Layout to anchor them to their buttons in \uicontrol Properties > \uicontrol Layout to anchor them to their
parent. We anchor the background to its parent on all edges, with a 30-pixel parent. We anchor the background to its parent on all edges, with a 30-pixel
@@ -45,7 +45,7 @@
\image progressbar-rectangle.png "Progress bar background in the 2D view" \image progressbar-rectangle.png "Progress bar background in the 2D view"
We now drag-and-drop another rectangle on top of the background rectangle in We now drag another rectangle on top of the background rectangle in
\uicontrol Navigator and change its ID to \e pb_front in \uicontrol Navigator and change its ID to \e pb_front in
\uicontrol Properties. \uicontrol Properties.
We then anchor the left, top, and bottom of the indicator to its parent with We then anchor the left, top, and bottom of the indicator to its parent with
@@ -125,7 +125,7 @@
We want the progress bar to be reusable, so we'll move it to a separate We want the progress bar to be reusable, so we'll move it to a separate
component file. To make sure that the component will contain the timeline, component file. To make sure that the component will contain the timeline,
we select \uicontrol {Filter Tree} in \uicontrol Navigator and then we select \uicontrol {Filter Tree} in \uicontrol Navigator and then
deselect the \uicontrol {Show Only Visible Items} check box to show the clear the \uicontrol {Show Only Visible Items} check box to show the
timeline component in \uicontrol Navigator. We then move the timeline timeline component in \uicontrol Navigator. We then move the timeline
component to \e root to have it moved as a part of the root component. component to \e root to have it moved as a part of the root component.

View File

@@ -13,7 +13,7 @@
\e {Side Menu} displays a menu bar and a side menu that slides open when \e {Side Menu} displays a menu bar and a side menu that slides open when
users click the menu icon. The appearance of the menu bar buttons changes users click the menu icon. The appearance of the menu bar buttons changes
when users hover the cursor over them or select them. when users hover over them or select them.
Each button opens an image file. The side menu can be used to apply Each button opens an image file. The side menu can be used to apply
\l {2D effects}{graphical effects}, such as hue, saturation, \l {2D effects}{graphical effects}, such as hue, saturation,
@@ -82,7 +82,7 @@
We construct the menu bar in the \e {MainFile.ui.qml} file using the We construct the menu bar in the \e {MainFile.ui.qml} file using the
\l {2D} view. The CustomButton component is listed in \l {2D} view. The CustomButton component is listed in
\uicontrol Components > \uicontrol {My Components}. \uicontrol Components > \uicontrol {My Components}.
We drag-and-drop several instances of the component to \uicontrol Navigator We drag several instances of the component to \uicontrol Navigator
or the \uicontrol {2D} view and enclose them in a \uicontrol {Row Layout} or the \uicontrol {2D} view and enclose them in a \uicontrol {Row Layout}
component instance to lay them out as a menu bar. component instance to lay them out as a menu bar.
@@ -90,8 +90,8 @@
We can change the properties of each CustomButton instance separately in We can change the properties of each CustomButton instance separately in
the \uicontrol Properties view. We want only one of the menu bar buttons the \uicontrol Properties view. We want only one of the menu bar buttons
to be checked at any time, so that checking another button automatically to be selected at any time, so that selecting another button automatically
unchecks the previously checked one. Therefore, we set the clears the previously selected one. Therefore, we set the
\l {AbstractButton::}{autoExclusive} property to \c true for all \l {AbstractButton::}{autoExclusive} property to \c true for all
button instances. button instances.
@@ -104,7 +104,7 @@
\section1 Creating a side menu \section1 Creating a side menu
We can now continue to create a side menu that slides open when users We can now continue to create a side menu that slides open when users
click the burger menu. We drag-and-drop a \l Text component from click the burger menu. We drag a \l Text component from
\uicontrol Components > \uicontrol {Default Components} \uicontrol Components > \uicontrol {Default Components}
> \uicontrol Basic and a \l {slider-control}{Slider} component from > \uicontrol Basic and a \l {slider-control}{Slider} component from
\uicontrol {Qt Quick Controls} to \uicontrol Navigator to create separate \uicontrol {Qt Quick Controls} to \uicontrol Navigator to create separate
@@ -209,7 +209,7 @@
in the \uicontrol Settings menu of the \uicontrol Value property in in the \uicontrol Settings menu of the \uicontrol Value property in
\uicontrol Properties > \uicontrol Slider. \uicontrol Properties > \uicontrol Slider.
We open the \e {EffectStack.qml} file, and drag-and-drop components from We open the \e {EffectStack.qml} file, and drag components from
\uicontrol Components > \uicontrol {Qt Quick Studio Effects} to \uicontrol Components > \uicontrol {Qt Quick Studio Effects} to
\uicontrol Navigator to create the effect stack. \uicontrol Navigator to create the effect stack.

View File

@@ -167,7 +167,7 @@
In our UI, we use connections and states to move between screens. First, In our UI, we use connections and states to move between screens. First,
we specify the application workflow in \e ApplicationFlow.qml. When the we specify the application workflow in \e ApplicationFlow.qml. When the
file is open in the \uicontrol {2D} view, we drag-and-drop the components file is open in the \uicontrol {2D} view, we drag the components
that define the screens in the application from \uicontrol Components to that define the screens in the application from \uicontrol Components to
\uicontrol Navigator or the \uicontrol {2D} view: \e StartScreen, \uicontrol Navigator or the \uicontrol {2D} view: \e StartScreen,
\e SettingsScreen, \e PresetsScreen, and \e RunningScreen. \e SettingsScreen, \e PresetsScreen, and \e RunningScreen.

View File

@@ -149,7 +149,7 @@
the UI we will create. We use the imported components to create the the UI we will create. We use the imported components to create the
UI in the \e {MainApp.ui.qml} file. The imported components are UI in the \e {MainApp.ui.qml} file. The imported components are
listed in \uicontrol Components > \uicontrol {My Components}, listed in \uicontrol Components > \uicontrol {My Components},
and we can drag-and-drop them to the \l {2D} view. and we can drag them to the \l {2D} view.
\image webinardemo-mainappui.png "Main app UI in Design mode" \image webinardemo-mainappui.png "Main app UI in Design mode"

View File

@@ -13,7 +13,7 @@
that is to be animated, and apply the animation depending on the type of that is to be animated, and apply the animation depending on the type of
behavior that is required. behavior that is required.
You can drag-and-drop animation components from You can drag animation components from
\uicontrol Components > \uicontrol {Default Components} > \uicontrol Components > \uicontrol {Default Components} >
\uicontrol Animation to the \l Navigator or \l {2D} view to \uicontrol Animation to the \l Navigator or \l {2D} view to
create instances of them. create instances of them.
@@ -132,7 +132,7 @@
You can create several animations that can run in parallel or in sequence. You can create several animations that can run in parallel or in sequence.
To manage a group of animations that will play at the same time, create an To manage a group of animations that will play at the same time, create an
instance of a \uicontrol {Parallel Animation} component and drag-and-drop instance of a \uicontrol {Parallel Animation} component and drag
the other animations to it. To play the animations in the specified order, the other animations to it. To play the animations in the specified order,
one after the other, create an instance of a one after the other, create an instance of a
\uicontrol {Sequential Animation} instead. \uicontrol {Sequential Animation} instead.

View File

@@ -33,7 +33,7 @@
(\uicontrol W) and height (\uicontrol H) of the button in (\uicontrol W) and height (\uicontrol H) of the button in
\l Properties. \l Properties.
\li Drag-and-drop a \uicontrol Rectangle from \uicontrol Components > \li Drag a \uicontrol Rectangle from \uicontrol Components >
\uicontrol {Default Components} > \uicontrol Basic to the component \uicontrol {Default Components} > \uicontrol Basic to the component
in \uicontrol Navigator. This creates a nested component where the in \uicontrol Navigator. This creates a nested component where the
Item is the parent of the Rectangle. Components are positioned Item is the parent of the Rectangle. Components are positioned
@@ -57,7 +57,7 @@
\endlist \endlist
\li Drag-and-drop a \uicontrol {Text} component to the Item in \li Drag a \uicontrol {Text} component to the Item in
\uicontrol Navigator. \uicontrol Navigator.
\li In the \uicontrol Properties view, edit the properties of the \li In the \uicontrol Properties view, edit the properties of the
@@ -150,11 +150,11 @@
\l Properties view to match the size of the images \l Properties view to match the size of the images
you plan to use. This specifies the initial size of the button you plan to use. This specifies the initial size of the button
component. component.
\li Drag-and-drop two \uicontrol {Border Image} components from \li Drag two \uicontrol {Border Image} components from
\uicontrol Components > \uicontrol {Default Components} > \uicontrol Components > \uicontrol {Default Components} >
\uicontrol Basic to the root component in \uicontrol Navigator. \uicontrol Basic to the root component in \uicontrol Navigator.
\li Drag-and-drop a \uicontrol Text component to the root component. \li Drag a \uicontrol Text component to the root component.
\li Drag-and-drop a \uicontrol {Mouse Area} to the root component. \li Drag a \uicontrol {Mouse Area} to the root component.
\li Select a border image to edit the values of its properties: \li Select a border image to edit the values of its properties:
\list 1 \list 1
\li In the \uicontrol Id field, enter an ID for the border \li In the \uicontrol Id field, enter an ID for the border
@@ -238,7 +238,7 @@
When you work on other files in the project to create screens When you work on other files in the project to create screens
or other components for the UI, the button component appears in or other components for the UI, the button component appears in
\uicontrol Components > \uicontrol {My Components}. \uicontrol Components > \uicontrol {My Components}.
You can drag-and-drop it to the \uicontrol {2D} or You can drag it to the \uicontrol {2D} or
\uicontrol Navigator view to create button instances and modify the values \uicontrol Navigator view to create button instances and modify the values
of their properties to assign them useful IDs, change their appearance, of their properties to assign them useful IDs, change their appearance,
and set the button text for each button instance, for example. and set the button text for each button instance, for example.

View File

@@ -16,7 +16,7 @@
To create component instances and edit their properties: To create component instances and edit their properties:
\list 1 \list 1
\li Drag-and-drop components from \uicontrol Components (1) to the \li Drag components from \uicontrol Components (1) to the
\l Navigator (2), \l {2D} (3), or \l {3D} view (4). \l Navigator (2), \l {2D} (3), or \l {3D} view (4).
This creates instances of the components in the current This creates instances of the components in the current
component file. component file.

View File

@@ -33,7 +33,7 @@
capital letter. capital letter.
\li Click \uicontrol Design to open the file in the \li Click \uicontrol Design to open the file in the
\uicontrol {2D} view. \uicontrol {2D} view.
\li Drag-and-drop a component from \uicontrol Components to \li Drag a component from \uicontrol Components to
\uicontrol Navigator or the \uicontrol {2D} view. \uicontrol Navigator or the \uicontrol {2D} view.
\li Edit component properties in the \uicontrol Properties view. \li Edit component properties in the \uicontrol Properties view.
The available properties depend on the component type. You can The available properties depend on the component type. You can

View File

@@ -124,7 +124,7 @@
\section3 Flat buttons \section3 Flat buttons
A flat button typically does not draw a background unless it is pressed or A flat button typically does not draw a background unless it is pressed or
checked. To create a flat button, select the \uicontrol Flat check box in selected. To create a flat button, select the \uicontrol Flat check box in
the \uicontrol Button section. the \uicontrol Button section.
The following image shows an example of a flat button: The following image shows an example of a flat button:
@@ -134,7 +134,7 @@
\section3 Icon buttons \section3 Icon buttons
To create a button that contains an icon, use the wizard template to To create a button that contains an icon, use the wizard template to
\l{Creating Custom Controls}{create a custom button} and drag-and-drop \l{Creating Custom Controls}{create a custom button} and drag
the icon to the button background component. For an example of using the the icon to the button background component. For an example of using the
wizard template, see \l{Creating a Push Button}. wizard template, see \l{Creating a Push Button}.

View File

@@ -27,7 +27,7 @@
\image qtquick-designer-image-type.png "Image component in different views" \image qtquick-designer-image-type.png "Image component in different views"
When you drag-and-drop an image file from \uicontrol Assets to \l Navigator When you drag an image file from \uicontrol Assets to \l Navigator
or the \l {2D} view, \QDS automatically or the \l {2D} view, \QDS automatically
creates an instance of the Image component for you with the path to the creates an instance of the Image component for you with the path to the
image file set as the value of the \uicontrol Source field in image file set as the value of the \uicontrol Source field in
@@ -181,7 +181,7 @@
(\uicontrol Paused) check box. (\uicontrol Paused) check box.
When the \uicontrol Cache check box is selected, every frame of the When the \uicontrol Cache check box is selected, every frame of the
animation is cached. Deselect the check box if you are playing a long animation is cached. Clear the check box if you are playing a long
or large animation and you want to conserve memory. or large animation and you want to conserve memory.
If the image data comes from a sequential device (such as a socket), If the image data comes from a sequential device (such as a socket),

View File

@@ -26,7 +26,7 @@
By default, the path is closed, which means that its start and end points By default, the path is closed, which means that its start and end points
are identical. To create separate start and end points for it, right-click are identical. To create separate start and end points for it, right-click
an edit point to open a context menu, and deselect \uicontrol {Closed Path}. an edit point to open a context menu, and clear \uicontrol {Closed Path}.
To add intermediary points to a curve segment, select \uicontrol {Split Segment} To add intermediary points to a curve segment, select \uicontrol {Split Segment}
in the context menu. in the context menu.

View File

@@ -79,7 +79,7 @@
pressed. pressed.
Even though \uicontrol {Mouse Area} is an invisible component, it has a Even though \uicontrol {Mouse Area} is an invisible component, it has a
\uicontrol Visible property. Deselect the \uicontrol Visible check box in \uicontrol Visible property. Clear the \uicontrol Visible check box in
the \uicontrol Visibility section to make the mouse area transparent to the \uicontrol Visibility section to make the mouse area transparent to
mouse events. mouse events.

View File

@@ -45,7 +45,7 @@
\list 1 \list 1
\li In the \uicontrol {Export path} field, specify the path where \li In the \uicontrol {Export path} field, specify the path where
the metadata file and assets are exported. the metadata file and assets are exported.
\li Deselect the \uicontrol {Export assets} check box to disable \li Clear the \uicontrol {Export assets} check box to disable
exporting assets and only generate the metadata file. exporting assets and only generate the metadata file.
\li Select the \uicontrol {Export components separately} check box to \li Select the \uicontrol {Export components separately} check box to
generate separate metadata files for each component. generate separate metadata files for each component.

View File

@@ -30,7 +30,7 @@
set of predefined properties, some of which control things that are set of predefined properties, some of which control things that are
visible to users, while others are used behind the scene. visible to users, while others are used behind the scene.
You drag-and-drop the preset components from the \uicontrol Components view You drag the preset components from the \uicontrol Components view
to the \l {2D}, \l {3D}, or \l Navigator view to create to the \l {2D}, \l {3D}, or \l Navigator view to create
instances of them. You then change the instances to your liking by modifying instances of them. You then change the instances to your liking by modifying
their properties in the \l Properties view. The application code is their properties in the \l Properties view. The application code is

View File

@@ -11,12 +11,12 @@
You can import 2D assets, such as images, fonts, and sound files, to \QDS to You can import 2D assets, such as images, fonts, and sound files, to \QDS to
use them in your projects. use them in your projects.
To import an asset, drag-and-drop the external file containing the asset from, To import an asset, drag the external file containing the asset from,
for example, File Explorer on Windows, to the \uicontrol {2D}, for example, File Explorer on Windows, to the \uicontrol {2D},
\uicontrol Navigator, or \uicontrol {Code} view. Alternatively, select \uicontrol Navigator, or \uicontrol {Code} view. Alternatively, select
\uicontrol Assets > \imageplus \uicontrol Assets > \imageplus
and follow the instructions in the \uicontrol {Asset Import} dialog. You can and follow the instructions in the \uicontrol {Asset Import} dialog. You can
also multiselect several external asset files to drag-and-drop them to also multiselect several external asset files to drag them to
\QDS simultaneously. \QDS simultaneously.
The imported images will appear in \uicontrol Assets. The imported images will appear in \uicontrol Assets.
@@ -26,7 +26,7 @@
external font file to the \uicontrol {2D} view, it will be added to your external font file to the \uicontrol {2D} view, it will be added to your
project as a text component. Other imported assets, such as sound files, project as a text component. Other imported assets, such as sound files,
will only appear in \uicontrol Assets, and you can then will only appear in \uicontrol Assets, and you can then
drag-and-drop them to a suitable view. drag them to a suitable view.
\section1 Importing designs from other design tools \section1 Importing designs from other design tools
@@ -74,9 +74,9 @@
the image files to. the image files to.
\li Select the \uicontrol {Create sub directory} check box to import the \li Select the \uicontrol {Create sub directory} check box to import the
assets in a sub directory inside \uicontrol {Export Paths}. assets in a sub directory inside \uicontrol {Export Paths}.
\li Deselect the \uicontrol {Import assets} check box if you only want \li Clear the \uicontrol {Import assets} check box if you only want
to create QML files. to create QML files.
\li Deselect the \uicontrol {Generate QML} check box if you only \li Clear the \uicontrol {Generate QML} check box if you only
want to import assets. want to import assets.
\li Select the \uicontrol {Merge QML} check box if you have imported the \li Select the \uicontrol {Merge QML} check box if you have imported the
assets before and want to merge the changes into existing QML files assets before and want to merge the changes into existing QML files
@@ -93,7 +93,7 @@
The components that you specified in the design tool are displayed in The components that you specified in the design tool are displayed in
\uicontrol Components > \uicontrol {My Components} as well as in the \uicontrol Components > \uicontrol {My Components} as well as in the
\uicontrol Projects view as separate QML files. To use them, \uicontrol Projects view as separate QML files. To use them,
drag-and-drop them from \uicontrol Components to the \uicontrol {2D} or drag them from \uicontrol Components to the \uicontrol {2D} or
\l Navigator view. \l Navigator view.
If asset importer conflicts, warnings, and errors are displayed in the If asset importer conflicts, warnings, and errors are displayed in the

View File

@@ -111,7 +111,7 @@
Double-click the \uicontrol {Qt/SML Send} or \uicontrol {Qt/QML Receive} Double-click the \uicontrol {Qt/SML Send} or \uicontrol {Qt/QML Receive}
block in Simulink to specify a property name. A pop-up for \uicontrol block in Simulink to specify a property name. A pop-up for \uicontrol
{Block Parameters} appears. Type the name of the property in the \uicontrol {Block Parameters} appears. Enter the name of the property in the \uicontrol
{Qt Signal/Property Name} field and click \uicontrol OK. The name, for {Qt Signal/Property Name} field and click \uicontrol OK. The name, for
example speedProp, needs to match a \uicontrol signal or a \uicontrol example speedProp, needs to match a \uicontrol signal or a \uicontrol
property in \QDS. property in \QDS.
@@ -165,7 +165,7 @@
bind the root item property. Select the \imageactionicon bind the root item property. Select the \imageactionicon
(\uicontrol Actions) menu next to a property, and then select (\uicontrol Actions) menu next to a property, and then select
\uicontrol {Set Binding}. In the \uicontrol {Binding Editor}, select the \uicontrol {Set Binding}. In the \uicontrol {Binding Editor}, select the
text field and type in \c {<id>.<property name>}, for example text field and enter \c {<id>.<property name>}, for example
\c rectangle.speedProp. For more information, see \l {Setting bindings}. \c rectangle.speedProp. For more information, see \l {Setting bindings}.
\image studio-binding-editor.png "The Binding Editor window" \image studio-binding-editor.png "The Binding Editor window"

View File

@@ -268,7 +268,7 @@
component now appears in \uicontrol {My 3D Components}. component now appears in \uicontrol {My 3D Components}.
\image exporting-from-qt3ds/33-see-qml-stream-component-in-myqmlcomponents.png "QML stream in My QML Components" \image exporting-from-qt3ds/33-see-qml-stream-component-in-myqmlcomponents.png "QML stream in My QML Components"
\li Drag-and-drop the QML stream component to MyOwnCluster in \uicontrol \li Drag the QML stream component to MyOwnCluster in \uicontrol
Navigator. Navigator.
\image exporting-from-qt3ds/34-drag-to-myowncluster-in-navigator.png "Drag the QML stream component to MyOwnCluster" \image exporting-from-qt3ds/34-drag-to-myowncluster-in-navigator.png "Drag the QML stream component to MyOwnCluster"

View File

@@ -9,7 +9,7 @@
\title 3D effects \title 3D effects
\QDS provides a set of 3D effects, which are visible in the \l {2D} view. \QDS provides a set of 3D effects, which are visible in the \l {2D} view.
To apply a visual effect to a scene, drag-and-drop an effect from To apply a visual effect to a scene, drag an effect from
\uicontrol Components > \uicontrol {Qt Quick 3D} > \uicontrol Components > \uicontrol {Qt Quick 3D} >
\uicontrol {Qt Quick 3D Effects} to a \uicontrol SceneEnvironment component \uicontrol {Qt Quick 3D Effects} to a \uicontrol SceneEnvironment component
in \l Navigator. in \l Navigator.

View File

@@ -14,7 +14,7 @@
the \l Properties view simultaneously. the \l Properties view simultaneously.
To add a \uicontrol Group component To add a \uicontrol Group component
to your scene, drag-and-drop it from \uicontrol Components > to your scene, drag it from \uicontrol Components >
\uicontrol {Qt Quick 3D} > \uicontrol {Qt Quick 3D} to the \l {3D} \uicontrol {Qt Quick 3D} > \uicontrol {Qt Quick 3D} to the \l {3D}
view or to \l Navigator > \uicontrol View3D > \uicontrol {Scene Environment} view or to \l Navigator > \uicontrol View3D > \uicontrol {Scene Environment}
> \uicontrol Scene. > \uicontrol Scene.

View File

@@ -47,7 +47,7 @@
\l{https://doc.qt.io/qt/qml-qtquick3d-fileinstancing.html}{FileInstancing} \l{https://doc.qt.io/qt/qml-qtquick3d-fileinstancing.html}{FileInstancing}
QML type. QML type.
To use the \uicontrol Instancing component, drag-and-drop it from To use the \uicontrol Instancing component, drag it from
\uicontrol Components to \uicontrol Scene in \uicontrol Navigator. \uicontrol Components to \uicontrol Scene in \uicontrol Navigator.
\section2 Instancing properties \section2 Instancing properties
@@ -84,11 +84,11 @@
To build an instance table: To build an instance table:
\list 1 \list 1
\li Drag-and-drop an \uicontrol {Instance List} component from \li Drag an \uicontrol {Instance List} component from
\uicontrol Components > \uicontrol {Qt Quick 3D} \uicontrol Components > \uicontrol {Qt Quick 3D}
> \uicontrol {Qt Quick 3D} to \uicontrol Scene in > \uicontrol {Qt Quick 3D} to \uicontrol Scene in
\uicontrol Navigator. \uicontrol Navigator.
\li Drag-and-drop \uicontrol {Instance List Entry} components to the \li Drag \uicontrol {Instance List Entry} components to the
\uicontrol {Instance List} component to create list items. \uicontrol {Instance List} component to create list items.
\image studio-3d-instancing-instance-list.png "Instance List and Instance Entries in Navigator" \image studio-3d-instancing-instance-list.png "Instance List and Instance Entries in Navigator"

View File

@@ -23,7 +23,7 @@
\uicontrol {Target Qt Version} when \l {Creating projects}{creating your project}. \uicontrol {Target Qt Version} when \l {Creating projects}{creating your project}.
To apply a 3D material to a component, you should first delete the default To apply a 3D material to a component, you should first delete the default
material and then drag-and-drop a new material from material and then drag a new material from
\uicontrol Components > \uicontrol {Qt Quick 3D Materials} > \uicontrol Components > \uicontrol {Qt Quick 3D Materials} >
\uicontrol {Qt Quick 3D Materials} to a model component in \l Navigator. \uicontrol {Qt Quick 3D Materials} to a model component in \l Navigator.
The materials you add to the model are listed in the model component's The materials you add to the model are listed in the model component's

View File

@@ -18,7 +18,7 @@
The normal workflow is to use an external content creation tool to create The normal workflow is to use an external content creation tool to create
a mesh, which also contains morph targets, and import it to \QDS. a mesh, which also contains morph targets, and import it to \QDS.
To add a morph target for a model in \QDS, drag-and-drop a To add a morph target for a model in \QDS, drag a
\uicontrol {Morph Target} component from \uicontrol Components \uicontrol {Morph Target} component from \uicontrol Components
> \uicontrol {Qt Quick 3D} > \uicontrol {Qt Quick 3D} to \uicontrol Scene in > \uicontrol {Qt Quick 3D} > \uicontrol {Qt Quick 3D} to \uicontrol Scene in
\l Navigator. Then select the model in \uicontrol Navigator, and in \l Navigator. Then select the model in \uicontrol Navigator, and in

View File

@@ -386,7 +386,7 @@
when you select it again. when you select it again.
To temporarily stop the simulation, select \uicontrol Paused. Particles To temporarily stop the simulation, select \uicontrol Paused. Particles
are not destroyed, and when you deselect the check box, the simulation are not destroyed, and when you clear the check box, the simulation
resumes from the point where you paused it. resumes from the point where you paused it.
Select \uicontrol Logging to collect particle system statistics, such as Select \uicontrol Logging to collect particle system statistics, such as
@@ -1131,7 +1131,7 @@
them in \uicontrol Particles. Select \imageplus them in \uicontrol Particles. Select \imageplus
to add logical particles to the list. to add logical particles to the list.
Deselect \uicontrol Enabled to turn the affector off. Usually, this Clear \uicontrol Enabled to turn the affector off. Usually, this
property is used in code to conditionally turn affectors off and on. property is used in code to conditionally turn affectors off and on.
*/ */

View File

@@ -22,7 +22,7 @@
Logic helpers are invisible components that you can use in connection with Logic helpers are invisible components that you can use in connection with
controls, such as a \l {slider-control}{Slider} or \l {Check Box}. controls, such as a \l {slider-control}{Slider} or \l {Check Box}.
To use a logic helper, drag-and-drop it from \uicontrol Components > To use a logic helper, drag it from \uicontrol Components >
\uicontrol {Qt Quick Studio Logic Helper} to \l Navigator. If you cannot \uicontrol {Qt Quick Studio Logic Helper} to \l Navigator. If you cannot
find the logic helpers in \uicontrol {Components}, you need to add the find the logic helpers in \uicontrol {Components}, you need to add the
\uicontrol {Qt Quick Studio Logic Helper} module to your project, \uicontrol {Qt Quick Studio Logic Helper} module to your project,
@@ -42,7 +42,7 @@
The output is evaluated as \c true if both inputs are \c true. The output is evaluated as \c true if both inputs are \c true.
For example, we could use the checked state of two check boxes to determine For example, we could use the checked state of two check boxes to determine
the checked state of a third one. First, we drag-and-drop three instances of the checked state of a third one. First, we drag three instances of
the \uicontrol {Check Box} components and one instance of the the \uicontrol {Check Box} components and one instance of the
\uicontrol {And Operator} component to \uicontrol Navigator (1). Then, we \uicontrol {And Operator} component to \uicontrol Navigator (1). Then, we
select the \uicontrol {And Operator} component instance (2) and set its select the \uicontrol {And Operator} component instance (2) and set its
@@ -81,7 +81,7 @@
condition is not met. condition is not met.
For example, we could specify that if one check box is checked, another For example, we could specify that if one check box is checked, another
one cannot be checked. First, we drag-and-drop two instances of the one cannot be checked. First, we drag two instances of the
\uicontrol {Check Box} component and one instance of the \uicontrol {Check Box} component and one instance of the
\uicontrol {Not Operator} component to \uicontrol Navigator. Then, we select \uicontrol {Not Operator} component to \uicontrol Navigator. Then, we select
the \uicontrol {Not Operator} component instance and set its properties in the \uicontrol {Not Operator} component instance and set its properties in
@@ -97,7 +97,7 @@
\image studio-logic-helper-not-check-box.png "Check box checked property bound to NOT operator output" \image studio-logic-helper-not-check-box.png "Check box checked property bound to NOT operator output"
When we preview our UI, the second check box is initially checked. However, When we preview our UI, the second check box is initially selected. However,
when we select the first check box, the second one is automatically cleared. when we select the first check box, the second one is automatically cleared.
\image studio-logic-helper-not-operator.gif "Previewing two check boxes bound with a NOT operator" \image studio-logic-helper-not-operator.gif "Previewing two check boxes bound with a NOT operator"
@@ -110,7 +110,7 @@
a slider and checkbox. Typically, it is used to bind a backend value a slider and checkbox. Typically, it is used to bind a backend value
to a control, such as a slider. to a control, such as a slider.
For example, to synchronize the values of two sliders, we drag-and-drop two For example, to synchronize the values of two sliders, we drag two
instances of the \uicontrol Slider component and one instance of the instances of the \uicontrol Slider component and one instance of the
\uicontrol {Bi Direct. Binding} component to the same parent component in \uicontrol {Bi Direct. Binding} component to the same parent component in
\uicontrol Navigator. Then, we select the bi-directional binding instance \uicontrol Navigator. Then, we select the bi-directional binding instance
@@ -140,7 +140,7 @@
the string. the string.
For example, to use a \l Text component to display the value of a For example, to use a \l Text component to display the value of a
slider, we drag-and-drop \uicontrol Text, \uicontrol Slider, and slider, we drag \uicontrol Text, \uicontrol Slider, and
\uicontrol {String Mapper} components to the same parent component. Then, \uicontrol {String Mapper} components to the same parent component. Then,
we select the \uicontrol {String Mapper} instance in \uicontrol Navigator we select the \uicontrol {String Mapper} instance in \uicontrol Navigator
to display its properties in \uicontrol Properties. There we bind the value to display its properties in \uicontrol Properties. There we bind the value
@@ -175,7 +175,7 @@
For example, to restrict the maximum value of a slider to 0.60, For example, to restrict the maximum value of a slider to 0.60,
regardless of the maximum value set in the slider properties, regardless of the maximum value set in the slider properties,
we drag-and-drop a \uicontrol {Min Max Mapper} to our example we drag a \uicontrol {Min Max Mapper} to our example
above. We select it to display its properties in \uicontrol Properties. above. We select it to display its properties in \uicontrol Properties.
Then, we bind the value of the \uicontrol Input property of the mapper to Then, we bind the value of the \uicontrol Input property of the mapper to
the value of the \c value property of the slider and set the value the value of the \c value property of the slider and set the value

View File

@@ -47,11 +47,11 @@
in \l Navigator, you can select \imagelockon in \l Navigator, you can select \imagelockon
to unlock it. You can also lock individual easing curves for editing. to unlock it. You can also lock individual easing curves for editing.
To lock an animation curve, hover the mouse over the property in the list, To lock an animation curve, hover over the property in the list,
and then select \imagelockoff and then select \imagelockoff
. .
To pin an animation curve, hover the mouse over the property in the list, To pin an animation curve, hover over the property in the list,
and then select \imagepin and then select \imagepin
. .

View File

@@ -125,7 +125,7 @@
\image qtquick-properties-visibility.png "Visibility properties" \image qtquick-properties-visibility.png "Visibility properties"
Deselect the \uicontrol Visible check box to hide a component and all Clear the \uicontrol Visible check box to hide a component and all
its child components, unless they have explicitly been set to be visible. its child components, unless they have explicitly been set to be visible.
This might have surprise effects when using property bindings. In such This might have surprise effects when using property bindings. In such
cases, it may be better to use the \uicontrol Opacity property instead. cases, it may be better to use the \uicontrol Opacity property instead.

View File

@@ -147,7 +147,7 @@
not part of a view. not part of a view.
\li In \uicontrol States, select the \uicontrol + symbol to create \li In \uicontrol States, select the \uicontrol + symbol to create
a new state and give it a name. For example, \c Normal. a new state and give it a name. For example, \c Normal.
\li In \l Properties (2), deselect the \uicontrol Visibility \li In \l Properties (2), clear the \uicontrol Visibility
check box or set \uicontrol Opacity to 0 for each component that check box or set \uicontrol Opacity to 0 for each component that
is not needed in this view. If you specify the setting for the is not needed in this view. If you specify the setting for the
parent component, all child components inherit it and are also parent component, all child components inherit it and are also

View File

@@ -121,13 +121,14 @@ Rectangle {
} }
component Cell: Rectangle { component Cell: Rectangle {
required property string display required property var display
required property int row required property int row
required property int column required property int column
required property bool editing required property bool editing
required property bool isBinding required property bool isBinding
required property var propertyValue
color: root.backgroundColor color: root.backgroundColor
implicitWidth: root.cellWidth implicitWidth: root.cellWidth
@@ -227,7 +228,7 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
leftPadding: root.leftPadding leftPadding: root.leftPadding
value: parseInt(numberDelegate.display) value: numberDelegate.display
from: -1000 // TODO define min/max from: -1000 // TODO define min/max
to: 1000 to: 1000
editable: true editable: true
@@ -261,7 +262,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: root.leftPadding anchors.leftMargin: root.leftPadding
checked: flagDelegate.display === "true" checked: flagDelegate.display
text: flagDelegate.display text: flagDelegate.display
onToggled: { onToggled: {
@@ -328,7 +329,7 @@ Rectangle {
height: parent.height height: parent.height
verticalAlignment: Qt.AlignVCenter verticalAlignment: Qt.AlignVCenter
color: StudioTheme.Values.themeTextColor color: StudioTheme.Values.themeTextColor
text: colorDelegate.display text: colorDelegate.propertyValue
} }
} }

View File

@@ -748,6 +748,10 @@ Item {
enabled: !root.propNameError && !root.uniNameError enabled: !root.propNameError && !root.uniNameError
onClicked: { onClicked: {
// Remove the focus from the editing control. It fixes a mac bug where a
// control's value doesn't get applied when the Apply button is clicked
acceptButton.forceActiveFocus()
root.accepted() root.accepted()
root.visible = false root.visible = false
} }

View File

@@ -15,6 +15,7 @@ Section {
anchors.right: parent.right anchors.right: parent.right
readonly property string disabledTooltip: qsTr("This property is defined by an anchor or a layout.") readonly property string disabledTooltip: qsTr("This property is defined by an anchor or a layout.")
readonly property string disabledAnchoredTooltip: qsTr("Adjust this property manually from the 2D view or by changing margins from Layout.")
function positionDisabled() { function positionDisabled() {
return anchorBackend.isFilled || anchorBackend.isInLayout return anchorBackend.isFilled || anchorBackend.isInLayout
@@ -67,7 +68,7 @@ Section {
ControlLabel { ControlLabel {
text: "X" text: "X"
tooltip: xSpinBox.enabled ? qsTr("X-coordinate") : root.disabledTooltip tooltip: xSpinBox.enabled ? qsTr("X-coordinate") : root.disabledAnchoredTooltip
enabled: xSpinBox.enabled enabled: xSpinBox.enabled
} }
@@ -88,7 +89,7 @@ Section {
ControlLabel { ControlLabel {
text: "Y" text: "Y"
tooltip: xSpinBox.enabled ? qsTr("Y-coordinate") : root.disabledTooltip tooltip: ySpinBox.enabled ? qsTr("Y-coordinate") : root.disabledAnchoredTooltip
enabled: ySpinBox.enabled enabled: ySpinBox.enabled
} }
/* /*
@@ -123,7 +124,7 @@ Section {
ControlLabel { ControlLabel {
//: The width of the object //: The width of the object
text: qsTr("W", "width") text: qsTr("W", "width")
tooltip: widthSpinBox.enabled ? qsTr("Width") : root.disabledTooltip tooltip: widthSpinBox.enabled ? qsTr("Width") : root.disabledAnchoredTooltip
enabled: widthSpinBox.enabled enabled: widthSpinBox.enabled
} }
@@ -145,7 +146,7 @@ Section {
ControlLabel { ControlLabel {
//: The height of the object //: The height of the object
text: qsTr("H", "height") text: qsTr("H", "height")
tooltip: heightSpinBox.enabled ? qsTr("Height") : root.disabledTooltip tooltip: heightSpinBox.enabled ? qsTr("Height") : root.disabledAnchoredTooltip
enabled: heightSpinBox.enabled enabled: heightSpinBox.enabled
} }
/* /*

View File

@@ -118,12 +118,15 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.left: home.right anchors.left: home.right
anchors.leftMargin: 10 anchors.leftMargin: 10
width: 160 width: 170
runTarget: backend?.runTargetIndex runTarget: backend?.runTargetIndex
runManagerState: backend?.runManagerState runManagerState: backend?.runManagerState
runManagerProgress: backend?.runManagerProgress
runManagerError: backend?.runManagerError
onClicked: backend.toggleRunning() onClicked: backend.toggleRunning()
onCancelClicked: backend.cancelRunning()
onRunTargetSelected: function(targetName) { backend.selectRunTarget(targetName) } onRunTargetSelected: function(targetName) { backend.selectRunTarget(targetName) }
onOpenRunTargets: backend.openDeviceManager() onOpenRunTargets: backend.openDeviceManager()
} }

View File

@@ -2,6 +2,8 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick import QtQuick
import QtQuick.Shapes
import QtQuick.Layouts
import QtQuick.Templates as T import QtQuick.Templates as T
import StudioTheme as StudioTheme import StudioTheme as StudioTheme
@@ -17,11 +19,15 @@ Item {
property bool hover: primaryButton.hover || menuButton.hover property bool hover: primaryButton.hover || menuButton.hover
signal clicked() signal clicked()
signal cancelClicked()
signal runTargetSelected(targetId: string) signal runTargetSelected(targetId: string)
signal openRunTargets() signal openRunTargets()
property int runTarget: 0 property int runTarget: 0 // index
property string runTargetName
property int runManagerState: RunManager.NotRunning property int runManagerState: RunManager.NotRunning
property int runManagerProgress: 0
property string runManagerError
property int menuWidth: Math.max(160, root.width) property int menuWidth: Math.max(160, root.width)
@@ -33,6 +39,11 @@ Item {
return runManagerModel.data(modelIndex, RunManagerModel.Enabled) return runManagerModel.data(modelIndex, RunManagerModel.Enabled)
} }
function getRunTargetName(index: int): string {
let modelIndex = runManagerModel.index(index, 0)
return runManagerModel.data(modelIndex, "targetName")
}
ToolTipArea { ToolTipArea {
id: toolTipArea id: toolTipArea
anchors.fill: parent anchors.fill: parent
@@ -42,16 +53,85 @@ Item {
} }
onRunTargetChanged: { onRunTargetChanged: {
root.runTargetName = root.getRunTargetName(root.runTarget)
primaryButton.enabled = root.isRunTargetEnabled(root.runTarget) primaryButton.enabled = root.isRunTargetEnabled(root.runTarget)
} }
component ProgressCircle: Shape {
id: shape
property real from: 0
property real to: 100
property bool indeterminate: false
property real value: 0.5
property int strokeWidth: 4
property int radiusX: (shape.width - shape.strokeWidth) / 2
property int radiusY: (shape.height - shape.strokeWidth) / 2
width: 20
height: 20
preferredRendererType: Shape.CurveRenderer
ShapePath {
id: background
strokeColor: "gray"
strokeWidth: shape.strokeWidth
fillColor: "transparent"
capStyle: ShapePath.FlatCap
PathAngleArc {
radiusX: shape.radiusX
radiusY: shape.radiusY
centerX: shape.width / 2
centerY: shape.height / 2
startAngle: 0
sweepAngle: 360
}
}
ShapePath {
id: foreground
strokeColor: StudioTheme.Values.themeInteraction
strokeWidth: shape.strokeWidth
fillColor: "transparent"
capStyle: ShapePath.FlatCap
PathAngleArc {
radiusX: shape.radiusX
radiusY: shape.radiusY
centerX: shape.width / 2
centerY: shape.height / 2
startAngle: -90
sweepAngle: shape.indeterminate ? 90 : 360 * shape.value
}
}
// Indeterminate rotation animation
RotationAnimation on rotation {
loops: Animation.Infinite
from: 0
to: 360
running: shape.indeterminate
duration: 2000
onStopped: shape.rotation = 0
}
}
Connections { Connections {
target: runManagerModel target: runManagerModel
function onModelReset() { function onModelReset() {
root.runTargetName = root.getRunTargetName(root.runTarget)
primaryButton.enabled = root.isRunTargetEnabled(root.runTarget) primaryButton.enabled = root.isRunTargetEnabled(root.runTarget)
} }
} }
readonly property bool showProgress: root.runManagerState === RunManager.Packing
|| root.runManagerState === RunManager.Sending
|| root.runManagerState === RunManager.Starting
Row { Row {
T.AbstractButton { T.AbstractButton {
id: primaryButton id: primaryButton
@@ -94,47 +174,95 @@ Item {
width: primaryButton.width width: primaryButton.width
height: primaryButton.height height: primaryButton.height
Row { RowLayout {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 8 anchors.leftMargin: 8
anchors.rightMargin: 8
anchors.verticalCenter: parent.verticalCenter
spacing: 8 spacing: 8
T.Label { Item {
width: 20
height: primaryButton.height height: primaryButton.height
font.family: StudioTheme.Constants.iconFont.family
font.pixelSize: primaryButton.style.baseIconFontSize ProgressCircle {
verticalAlignment: Text.AlignVCenter anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter visible: root.showProgress
color: { indeterminate: root.runManagerState === RunManager.Packing
if (root.runManagerState === RunManager.NotRunning) || root.runManagerState === RunManager.Starting
return primaryButton.press ? primaryButton.style.text.idle value: root.runManagerProgress / 100
: primaryButton.hover ? "#2eff68" // running green
: "#649a5d" // idle green
else
return primaryButton.press ? primaryButton.style.text.idle
: primaryButton.hover ? "#cc3c34" // recording red
: "#6a4242" // idle red
} }
text: { T.Label {
if (root.runManagerState === RunManager.NotRunning) visible: !root.showProgress
return StudioTheme.Constants.playOutline_medium height: primaryButton.height
else
return StudioTheme.Constants.stop_medium font.family: StudioTheme.Constants.iconFont.family
font.pixelSize: primaryButton.style.baseIconFontSize
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: {
if (root.runManagerState === RunManager.NotRunning)
return primaryButton.press ? primaryButton.style.text.idle
: primaryButton.hover ? "#2eff68" // running green
: "#649a5d" // idle green
else
return primaryButton.press ? primaryButton.style.text.idle
: primaryButton.hover ? "#cc3c34" // recording red
: "#6a4242" // idle red
}
text: {
if (root.runManagerState === RunManager.NotRunning)
return StudioTheme.Constants.playOutline_medium
else
return StudioTheme.Constants.stop_medium
}
} }
} }
T.Label { T.Label {
Layout.fillWidth: true
height: primaryButton.height height: primaryButton.height
color: primaryButton.enabled ? primaryButton.style.text.idle color: primaryButton.enabled ? primaryButton.style.text.idle
: primaryButton.style.text.disabled : primaryButton.style.text.disabled
font.pixelSize: primaryButton.style.baseFontSize font.pixelSize: primaryButton.style.baseFontSize
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignLeft
elide: Text.ElideMiddle
text: { text: {
let index = runManagerModel.index(root.runTarget, 0) if (root.runManagerState === RunManager.Packing)
return runManagerModel.data(index, "targetName") return qsTr("Packing")
else if (root.runManagerState === RunManager.Sending)
return qsTr("Sending")
else if (root.runManagerState === RunManager.Starting)
return qsTr("Starting")
else
return root.runTargetName
}
}
T.Label {
visible: root.showProgress || root.runManagerError
height: primaryButton.height
font.family: StudioTheme.Constants.iconFont.family
font.pixelSize: StudioTheme.Values.mediumFontSize
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: root.runManagerError ? StudioTheme.Values.themeError
: primaryButton.style.text.idle
text: root.runManagerError ? StudioTheme.Constants.error_medium
: StudioTheme.Constants.close_small
ToolTipArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
tooltip: root.runManagerError
onClicked: root.cancelClicked()
} }
} }
} }
@@ -256,6 +384,7 @@ Item {
checkable: false checkable: false
checked: window.visible checked: window.visible
enabled: root.runManagerState === RunManager.NotRunning
onClicked: { onClicked: {
if (window.visible) { if (window.visible) {

View File

@@ -77,10 +77,7 @@ public:
template<typename String> template<typename String>
friend void convertToString(String &string, BasicId id) friend void convertToString(String &string, BasicId id)
{ {
if (id.isNull()) NanotraceHR::convertToString(string, id.id);
NanotraceHR::convertToString(string, "invalid null");
else
NanotraceHR::convertToString(string, id.internalId());
} }
friend bool compareId(BasicId first, BasicId second) { return first.id == second.id; } friend bool compareId(BasicId first, BasicId second) { return first.id == second.id; }
@@ -146,8 +143,10 @@ public:
template<typename String> template<typename String>
friend void convertToString(String &string, CompoundBasicId id) friend void convertToString(String &string, CompoundBasicId id)
{ {
convertToString(string, id.mainId()); int mainId = id;
convertToString(string, id.contextId()); int contextId = id >> 32;
convertToString(string, mainId);
convertToString(string, contextId);
} }
friend bool compareId(CompoundBasicId first, CompoundBasicId second) friend bool compareId(CompoundBasicId first, CompoundBasicId second)

View File

@@ -748,15 +748,22 @@ extend_qtc_plugin(QmlDesigner
extend_qtc_plugin(QmlDesigner extend_qtc_plugin(QmlDesigner
CONDITION TARGET Qt::WebSockets CONDITION TARGET Qt::WebSockets
DEFINES QT_WEBSOCKET_ENABLED
DEPENDS
Qt::WebSockets
)
extend_qtc_plugin(QmlDesigner
SOURCES_PREFIX components/devicesharing SOURCES_PREFIX components/devicesharing
DEPENDS DEPENDS
QtCreator::QrCodeGenerator Qt::WebSockets QtCreator::QrCodeGenerator
SOURCES SOURCES
device.cpp device.h device.cpp device.h
deviceinfo.cpp deviceinfo.h deviceinfo.cpp deviceinfo.h
devicemanager.cpp devicemanager.h devicemanager.cpp devicemanager.h
devicemanagermodel.cpp devicemanagermodel.h devicemanagermodel.cpp devicemanagermodel.h
devicemanagerwidget.cpp devicemanagerwidget.h devicemanagerwidget.cpp devicemanagerwidget.h
websocketmock.h
) )
extend_qtc_plugin(QmlDesigner extend_qtc_plugin(QmlDesigner
@@ -774,7 +781,6 @@ extend_qtc_plugin(QmlDesigner
PUBLIC_DEFINES DVCONNECTOR_ENABLED PUBLIC_DEFINES DVCONNECTOR_ENABLED
SOURCES SOURCES
dvconnector.cpp dvconnector.h dvconnector.cpp dvconnector.h
resourcegeneratorproxy.cpp resourcegeneratorproxy.h
) )
add_qtc_plugin(assetexporterplugin add_qtc_plugin(assetexporterplugin

View File

@@ -19,127 +19,13 @@
#include <qtsupport/qtkitaspect.h> #include <qtsupport/qtkitaspect.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
#include <QMessageBox> #include <QMessageBox>
#include <QProgressDialog> #include <QProgressDialog>
#include <QTemporaryFile>
#include <QXmlStreamWriter>
#include <QtConcurrent> #include <QtConcurrent>
using namespace Utils; using namespace Utils;
namespace QmlDesigner::ResourceGenerator { namespace QmlDesigner {
void generateMenuEntry(QObject *parent)
{
const Core::Context projectContext(QmlProjectManager::Constants::QML_PROJECT_ID);
// ToDo: move this to QtCreator and add tr to the string then
auto action = new QAction(Tr::tr("QmlDesigner::GenerateResource", "Generate QRC Resource File..."),
parent);
action->setEnabled(ProjectExplorer::ProjectManager::startupProject() != nullptr);
// todo make it more intelligent when it gets enabled
QObject::connect(ProjectExplorer::ProjectManager::instance(),
&ProjectExplorer::ProjectManager::startupProjectChanged,
[action]() {
if (auto buildSystem = QmlProjectManager::QmlBuildSystem::getStartupBuildSystem())
action->setEnabled(!buildSystem->qtForMCUs());
});
Core::Command *cmd = Core::ActionManager::registerAction(action, "QmlProject.CreateResource");
QObject::connect(action, &QAction::triggered, []() {
auto project = ProjectExplorer::ProjectManager::startupProject();
QTC_ASSERT(project, return);
const FilePath projectPath = project->projectFilePath().parentDir();
auto qrcFilePath = Core::DocumentManager::getSaveFileNameWithExtension(
Tr::tr("QmlDesigner::GenerateResource", "Save Project as QRC File"),
projectPath.pathAppended(project->displayName() + ".qrc"),
Tr::tr("QmlDesigner::GenerateResource", "QML Resource File (*.qrc)"));
if (qrcFilePath.toUrlishString().isEmpty())
return;
createQrcFile(qrcFilePath);
Core::AsynchronousMessageBox::information(
Tr::tr("QmlDesigner::GenerateResource", "Success"),
Tr::tr("QmlDesigner::GenerateResource", "Successfully generated QRC resource file\n %1")
.arg(qrcFilePath.toUrlishString()));
});
// ToDo: move this to QtCreator and add tr to the string then
auto rccAction = new QAction(Tr::tr("QmlDesigner::GenerateResource",
"Generate Deployable Package..."),
parent);
rccAction->setEnabled(ProjectExplorer::ProjectManager::startupProject() != nullptr);
QObject::connect(ProjectExplorer::ProjectManager::instance(),
&ProjectExplorer::ProjectManager::startupProjectChanged,
[rccAction]() {
rccAction->setEnabled(ProjectExplorer::ProjectManager::startupProject());
});
Core::Command *cmd2 = Core::ActionManager::registerAction(rccAction,
"QmlProject.CreateRCCResource");
QObject::connect(rccAction, &QAction::triggered, []() {
auto project = ProjectExplorer::ProjectManager::startupProject();
QTC_ASSERT(project, return);
const FilePath projectPath = project->projectFilePath().parentDir();
const FilePath qmlrcFilePath = Core::DocumentManager::getSaveFileNameWithExtension(
Tr::tr("QmlDesigner::GenerateResource", "Save Project as Resource"),
projectPath.pathAppended(project->displayName() + ".qmlrc"),
"QML Resource File (*.qmlrc);;Resource File (*.rcc)");
if (qmlrcFilePath.toUrlishString().isEmpty())
return;
QProgressDialog progress;
progress.setLabelText(Tr::tr("QmlDesigner::GenerateResource",
"Generating deployable package. Please wait..."));
progress.setRange(0, 0);
progress.setWindowModality(Qt::WindowModal);
progress.setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
progress.setCancelButton(nullptr);
progress.show();
QFuture<bool> future = QtConcurrent::run(
[qmlrcFilePath]() { return createQmlrcFile(qmlrcFilePath); });
while (!future.isFinished())
QCoreApplication::processEvents();
progress.close();
if (future.isCanceled()) {
qDebug() << "Operation canceled by user";
return;
}
if (!future.result()) {
Core::MessageManager::writeDisrupting(
Tr::tr("QmlDesigner::GenerateResource", "Failed to generate deployable package!"));
QMessageBox msgBox;
msgBox.setWindowTitle(Tr::tr("QmlDesigner::GenerateResource", "Error"));
msgBox.setText(Tr::tr("QmlDesigner::GenerateResource",
"Failed to generate deployable package!\n\nPlease check "
"the output pane for more information."));
msgBox.exec();
return;
}
QMessageBox msgBox;
msgBox.setWindowTitle(Tr::tr("QmlDesigner::GenerateResource", "Success"));
msgBox.setText(
Tr::tr("QmlDesigner::GenerateResource", "Successfully generated deployable package"));
msgBox.exec();
});
Core::ActionContainer *exportMenu = Core::ActionManager::actionContainer(
QmlProjectManager::Constants::EXPORT_MENU);
exportMenu->addAction(cmd, QmlProjectManager::Constants::G_EXPORT_GENERATE);
exportMenu->addAction(cmd2, QmlProjectManager::Constants::G_EXPORT_GENERATE);
}
QStringList getProjectFileList() QStringList getProjectFileList()
{ {
const ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); const ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
@@ -156,12 +42,163 @@ QStringList getProjectFileList()
return selectedFileList; return selectedFileList;
} }
bool createQrcFile(const FilePath &qrcFilePath) ResourceGenerator::ResourceGenerator(QObject *parent)
: QObject(parent)
{
connect(&m_rccProcess, &Utils::Process::done, this, [this]() {
const int exitCode = m_rccProcess.exitCode();
if (exitCode != 0) {
Core::MessageManager::writeDisrupting(Tr::tr("\"%1\" failed (exit code %2).")
.arg(m_rccProcess.commandLine().toUserOutput())
.arg(m_rccProcess.exitCode()));
emit errorOccurred(Tr::tr("Failed to generate deployable package!"));
return;
}
if (m_rccProcess.exitStatus() != QProcess::NormalExit) {
Core::MessageManager::writeDisrupting(
Tr::tr("\"%1\" crashed.").arg(m_rccProcess.commandLine().toUserOutput()));
emit errorOccurred(Tr::tr("Failed to generate deployable package!"));
return;
}
emit qmlrcCreated(m_qmlrcFilePath);
});
connect(&m_rccProcess, &Utils::Process::textOnStandardError, this, [](const QString &text) {
Core::MessageManager::writeFlashing(QString::fromLocal8Bit(text.toLocal8Bit()));
});
connect(&m_rccProcess, &Utils::Process::textOnStandardOutput, this, [](const QString &text) {
Core::MessageManager::writeFlashing(QString::fromLocal8Bit(text.toLocal8Bit()));
});
}
void ResourceGenerator::generateMenuEntry(QObject *parent)
{
const Core::Context projectContext(QmlProjectManager::Constants::QML_PROJECT_ID);
// ToDo: move this to QtCreator and add tr to the string then
auto action = new QAction(Tr::tr("Generate QRC Resource File..."), parent);
action->setEnabled(ProjectExplorer::ProjectManager::startupProject() != nullptr);
// todo make it more intelligent when it gets enabled
QObject::connect(ProjectExplorer::ProjectManager::instance(),
&ProjectExplorer::ProjectManager::startupProjectChanged,
[action]() {
if (auto buildSystem = QmlProjectManager::QmlBuildSystem::getStartupBuildSystem())
action->setEnabled(!buildSystem->qtForMCUs());
});
Core::Command *cmd = Core::ActionManager::registerAction(action, "QmlProject.CreateResource");
QObject::connect(action, &QAction::triggered, []() {
auto project = ProjectExplorer::ProjectManager::startupProject();
QTC_ASSERT(project, return);
const FilePath projectPath = project->projectFilePath().parentDir();
auto qrcFilePath = Core::DocumentManager::getSaveFileNameWithExtension(
Tr::tr("Save Project as QRC File"),
projectPath.pathAppended(project->displayName() + ".qrc"),
Tr::tr("QML Resource File (*.qrc)"));
if (qrcFilePath.toUrlishString().isEmpty())
return;
ResourceGenerator resourceGenerator;
resourceGenerator.createQrc(qrcFilePath);
Core::AsynchronousMessageBox::information(
Tr::tr("QmlDesigner::GenerateResource", "Success"),
Tr::tr("QmlDesigner::GenerateResource", "Successfully generated QRC resource file\n %1")
.arg(qrcFilePath.toUrlishString()));
});
// ToDo: move this to QtCreator and add tr to the string then
auto rccAction = new QAction(Tr::tr("Generate Deployable Package..."), parent);
rccAction->setEnabled(ProjectExplorer::ProjectManager::startupProject() != nullptr);
QObject::connect(ProjectExplorer::ProjectManager::instance(),
&ProjectExplorer::ProjectManager::startupProjectChanged,
[rccAction]() {
rccAction->setEnabled(ProjectExplorer::ProjectManager::startupProject());
});
Core::Command *cmd2 = Core::ActionManager::registerAction(rccAction,
"QmlProject.CreateRCCResource");
QObject::connect(rccAction, &QAction::triggered, []() {
auto project = ProjectExplorer::ProjectManager::startupProject();
QTC_ASSERT(project, return);
const FilePath projectPath = project->projectFilePath().parentDir();
const FilePath qmlrcFilePath = Core::DocumentManager::getSaveFileNameWithExtension(
Tr::tr("Save Project as Resource"),
projectPath.pathAppended(project->displayName() + ".qmlrc"),
"QML Resource File (*.qmlrc);;Resource File (*.rcc)");
if (qmlrcFilePath.toUrlishString().isEmpty())
return;
QProgressDialog progress;
progress.setLabelText(Tr::tr("Generating deployable package. Please wait..."));
progress.setRange(0, 0);
progress.setWindowModality(Qt::WindowModal);
progress.setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
progress.setCancelButton(nullptr);
progress.show();
QFuture<bool> future = QtConcurrent::run([qmlrcFilePath]() {
ResourceGenerator resourceGenerator;
return resourceGenerator.createQmlrcWithPath(qmlrcFilePath);
});
while (!future.isFinished())
QCoreApplication::processEvents();
progress.close();
if (future.isCanceled()) {
qDebug() << "Operation canceled by user";
return;
}
if (!future.result()) {
Core::MessageManager::writeDisrupting(Tr::tr("Failed to generate deployable package!"));
QMessageBox msgBox;
msgBox.setWindowTitle(Tr::tr("Error"));
msgBox.setText(Tr::tr("Failed to generate deployable package!\n\nPlease check "
"the output pane for more information."));
msgBox.exec();
return;
}
QMessageBox msgBox;
msgBox.setWindowTitle(Tr::tr("Success"));
msgBox.setText(Tr::tr("Successfully generated deployable package"));
msgBox.exec();
});
Core::ActionContainer *exportMenu = Core::ActionManager::actionContainer(
QmlProjectManager::Constants::EXPORT_MENU);
exportMenu->addAction(cmd, QmlProjectManager::Constants::G_EXPORT_GENERATE);
exportMenu->addAction(cmd2, QmlProjectManager::Constants::G_EXPORT_GENERATE);
}
std::optional<Utils::FilePath> ResourceGenerator::createQrc(const QString &projectName)
{
const ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
const FilePath projectPath = project->projectFilePath().parentDir();
const FilePath qrcFilePath = projectPath.pathAppended(projectName + ".qrc");
if (!createQrc(qrcFilePath))
return {};
return qrcFilePath;
}
bool ResourceGenerator::createQrc(const Utils::FilePath &qrcFilePath)
{ {
QFile qrcFile(qrcFilePath.toUrlishString()); QFile qrcFile(qrcFilePath.toUrlishString());
if (!qrcFile.open(QIODeviceBase::WriteOnly | QIODevice::Truncate)) if (!qrcFile.open(QIODeviceBase::WriteOnly | QIODevice::Truncate)) {
Core::MessageManager::writeDisrupting(
Tr::tr("Failed to open file to write QRC XML: %1").arg(qrcFilePath.toString()));
return false; return false;
}
QXmlStreamWriter writer(&qrcFile); QXmlStreamWriter writer(&qrcFile);
writer.setAutoFormatting(true); writer.setAutoFormatting(true);
@@ -180,12 +217,74 @@ bool createQrcFile(const FilePath &qrcFilePath)
return true; return true;
} }
bool createQmlrcFile(const FilePath &qmlrcFilePath) void ResourceGenerator::createQmlrcAsyncWithName(const QString &projectName)
{ {
const FilePath tempQrcFile = qmlrcFilePath.parentDir().pathAppended("temp.qrc"); if (m_rccProcess.state() != QProcess::NotRunning) {
if (!createQrcFile(tempQrcFile)) Core::MessageManager::writeDisrupting(Tr::tr("Resource generator is already running."));
return;
}
const ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
const FilePath projectPath = project->projectFilePath().parentDir();
const FilePath qmlrcFilePath = projectPath.pathAppended(projectName + ".qmlrc");
createQmlrcAsyncWithPath(qmlrcFilePath);
}
void ResourceGenerator::createQmlrcAsyncWithPath(const FilePath &qmlrcFilePath)
{
if (m_rccProcess.state() != QProcess::NotRunning) {
Core::MessageManager::writeDisrupting(Tr::tr("Resource generator is already running."));
return;
}
m_qmlrcFilePath = qmlrcFilePath;
const FilePath tempQrcFile = m_qmlrcFilePath.parentDir().pathAppended("temp.qrc");
if (!createQrc(tempQrcFile))
return;
runRcc(qmlrcFilePath, tempQrcFile, true);
}
std::optional<Utils::FilePath> ResourceGenerator::createQmlrcWithName(const QString &projectName)
{
const ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
const FilePath projectPath = project->projectFilePath().parentDir();
const FilePath qmlrcFilePath = projectPath.pathAppended(projectName + ".qmlrc");
if (!createQmlrcWithPath(qmlrcFilePath))
return {};
return qmlrcFilePath;
}
bool ResourceGenerator::createQmlrcWithPath(const FilePath &qmlrcFilePath)
{
if (m_rccProcess.state() != QProcess::NotRunning) {
Core::MessageManager::writeDisrupting(Tr::tr("Resource generator is already running."));
return false;
}
m_qmlrcFilePath = qmlrcFilePath;
const FilePath tempQrcFile = m_qmlrcFilePath.parentDir().pathAppended("temp.qrc");
if (!createQrc(tempQrcFile))
return false; return false;
bool retVal = true;
if (!runRcc(qmlrcFilePath, tempQrcFile)) {
retVal = false;
if (qmlrcFilePath.exists())
qmlrcFilePath.removeFile();
}
tempQrcFile.removeFile();
return retVal;
}
bool ResourceGenerator::runRcc(const FilePath &qmlrcFilePath,
const Utils::FilePath &qrcFilePath,
const bool runAsync)
{
const ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); const ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
const QtSupport::QtVersion *qtVersion = QtSupport::QtKitAspect::qtVersion( const QtSupport::QtVersion *qtVersion = QtSupport::QtKitAspect::qtVersion(
project->activeTarget()->kit()); project->activeTarget()->kit());
@@ -193,8 +292,7 @@ bool createQmlrcFile(const FilePath &qmlrcFilePath)
const FilePath rccBinary = qtVersion->rccFilePath(); const FilePath rccBinary = qtVersion->rccFilePath();
Utils::Process rccProcess; m_rccProcess.setWorkingDirectory(project->projectDirectory());
rccProcess.setWorkingDirectory(project->projectDirectory());
const QStringList arguments = {"--binary", const QStringList arguments = {"--binary",
"--no-zstd", "--no-zstd",
@@ -204,46 +302,36 @@ bool createQmlrcFile(const FilePath &qmlrcFilePath)
"30", "30",
"--output", "--output",
qmlrcFilePath.toUrlishString(), qmlrcFilePath.toUrlishString(),
tempQrcFile.toUrlishString()}; qrcFilePath.toUrlishString()};
rccProcess.setCommand({rccBinary, arguments}); m_rccProcess.setCommand({rccBinary, arguments});
rccProcess.start(); m_rccProcess.start();
if (!rccProcess.waitForStarted()) { if (!m_rccProcess.waitForStarted()) {
Core::MessageManager::writeDisrupting( Core::MessageManager::writeDisrupting(
Tr::tr("QmlDesigner::GenerateResource", "Unable to generate resource file: %1") Tr::tr("QmlDesigner::GenerateResource", "Unable to generate resource file: %1")
.arg(qmlrcFilePath.toUrlishString())); .arg(qmlrcFilePath.toUrlishString()));
return false; return false;
} }
QByteArray stdOut; if (!runAsync) {
QByteArray stdErr; QByteArray stdOut;
if (!rccProcess.readDataFromProcess(&stdOut, &stdErr)) { QByteArray stdErr;
rccProcess.stop(); if (!m_rccProcess.readDataFromProcess(&stdOut, &stdErr)) {
Core::MessageManager::writeDisrupting( m_rccProcess.stop();
Tr::tr("QmlDesigner::GenerateResource", "A timeout occurred running \"%1\".") Core::MessageManager::writeDisrupting(Tr::tr("A timeout occurred running \"%1\".")
.arg(rccProcess.commandLine().toUserOutput())); .arg(m_rccProcess.commandLine().toUserOutput()));
return false; return false;
}
if (m_rccProcess.exitStatus() != QProcess::NormalExit || m_rccProcess.exitCode() != 0)
return false;
} }
if (!stdOut.trimmed().isEmpty())
Core::MessageManager::writeFlashing(QString::fromLocal8Bit(stdOut));
if (!stdErr.trimmed().isEmpty())
Core::MessageManager::writeFlashing(QString::fromLocal8Bit(stdErr));
if (rccProcess.exitStatus() != QProcess::NormalExit) {
Core::MessageManager::writeDisrupting(Tr::tr("QmlDesigner::GenerateResource", "\"%1\" crashed.")
.arg(rccProcess.commandLine().toUserOutput()));
return false;
}
if (rccProcess.exitCode() != 0) {
Core::MessageManager::writeDisrupting(
Tr::tr("QmlDesigner::GenerateResource", "\"%1\" failed (exit code %2).")
.arg(rccProcess.commandLine().toUserOutput())
.arg(rccProcess.exitCode()));
return false;
}
return true; return true;
} }
} // namespace QmlDesigner::ResourceGenerator void ResourceGenerator::cancel()
{
m_rccProcess.kill();
}
} // namespace QmlDesigner

View File

@@ -3,15 +3,41 @@
#pragma once #pragma once
#include <utils/filepath.h>
#include <qmldesignercomponents_global.h> #include <qmldesignercomponents_global.h>
#include <utils/filepath.h>
#include <utils/qtcprocess.h>
namespace QmlDesigner::ResourceGenerator { namespace QmlDesigner {
class ResourceGenerator : public QObject
{
Q_OBJECT
public:
ResourceGenerator(QObject *parent = nullptr);
static void generateMenuEntry(QObject *parent);
QMLDESIGNERCOMPONENTS_EXPORT void generateMenuEntry(QObject *parent); Q_INVOKABLE bool createQrc(const Utils::FilePath &qrcFilePath);
QMLDESIGNERCOMPONENTS_EXPORT QStringList getProjectFileList(); Q_INVOKABLE std::optional<Utils::FilePath> createQrc(const QString &projectName = "share");
QMLDESIGNERCOMPONENTS_EXPORT bool createQrcFile(const Utils::FilePath &qrcFilePath);
QMLDESIGNERCOMPONENTS_EXPORT bool createQmlrcFile(const Utils::FilePath &qmlrcFilePath);
} // namespace QmlDesigner::ResourceGenerator Q_INVOKABLE bool createQmlrcWithPath(const Utils::FilePath &qmlrcFilePath);
Q_INVOKABLE std::optional<Utils::FilePath> createQmlrcWithName(
const QString &projectName = "share");
Q_INVOKABLE void createQmlrcAsyncWithPath(const Utils::FilePath &qmlrcFilePath);
Q_INVOKABLE void createQmlrcAsyncWithName(const QString &projectName = "share");
Q_INVOKABLE void cancel();
private:
Utils::Process m_rccProcess;
Utils::FilePath m_qmlrcFilePath;
private:
bool runRcc(const Utils::FilePath &qmlrcFilePath,
const Utils::FilePath &qrcFilePath,
const bool runAsync = false);
signals:
void errorOccurred(const QString &error);
void qmlrcCreated(const Utils::FilePath &filePath);
};
} // namespace QmlDesigner

View File

@@ -2,6 +2,7 @@
// 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 "collectionmodel.h" #include "collectionmodel.h"
#include <designsystem/dsstore.h>
#include <designsystem/dsthemegroup.h> #include <designsystem/dsthemegroup.h>
#include <designsystem/dsthememanager.h> #include <designsystem/dsthememanager.h>
@@ -9,12 +10,28 @@
namespace QmlDesigner { namespace QmlDesigner {
CollectionModel::CollectionModel(DSThemeManager *collection) CollectionModel::CollectionModel(DSThemeManager *collection, const DSStore *store)
: m_collection(collection) : m_collection(collection)
, m_store(store)
{ {
updateCache(); updateCache();
} }
QStringList CollectionModel::themeNameList() const
{
QStringList themeNames(m_themeIdList.size());
std::transform(m_themeIdList.begin(), m_themeIdList.end(), themeNames.begin(), [this](ThemeId id) {
return QString::fromLatin1(m_collection->themeName(id));
});
return themeNames;
}
void CollectionModel::setActiveTheme(const QString &themeName)
{
if (const auto themeId = m_collection->themeId(themeName.toLatin1()))
m_collection->setActiveTheme(*themeId);
}
int CollectionModel::columnCount(const QModelIndex &parent) const int CollectionModel::columnCount(const QModelIndex &parent) const
{ {
return parent.isValid() ? 0 : static_cast<int>(m_collection->themeCount()); return parent.isValid() ? 0 : static_cast<int>(m_collection->themeCount());
@@ -32,13 +49,23 @@ QVariant CollectionModel::data(const QModelIndex &index, int role) const
if (!property) if (!property)
return {}; return {};
const QVariant propertyValue = property->value.toString();
const QVariant displayValue = property->isBinding
? m_store->resolvedDSBinding(propertyValue.toString()).value
: property->value;
switch (role) { switch (role) {
case Qt::DisplayRole: case Qt::DisplayRole:
return property->value.toString(); case Roles::ResolvedValueRole:
case static_cast<int>(Roles::GroupRole): return displayValue;
case Roles::PropertyValueRole:
return propertyValue;
case Roles::GroupRole:
return QVariant::fromValue<GroupType>(groupType); return QVariant::fromValue<GroupType>(groupType);
case static_cast<int>(Roles::BindingRole): case Roles::BindingRole:
return property->isBinding; return property->isBinding;
case Roles::ActiveThemeRole:
return m_collection->activeTheme() == themeId;
} }
return {}; return {};
@@ -64,18 +91,26 @@ int CollectionModel::rowCount(const QModelIndex &parent) const
QVariant CollectionModel::headerData(int section, Qt::Orientation orientation, int role) const QVariant CollectionModel::headerData(int section, Qt::Orientation orientation, int role) const
{ {
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) if (orientation == Qt::Horizontal) {
return QString::fromLatin1(m_collection->themeName(findThemeId(section))); auto themeId = findThemeId(section);
switch (role) {
case Qt::DisplayRole:
return QString::fromLatin1(m_collection->themeName(themeId));
case Roles::ActiveThemeRole:
return m_collection->activeTheme() == themeId;
default:
break;
}
}
if (orientation == Qt::Vertical) { if (orientation == Qt::Vertical) {
if (auto propInfo = findPropertyName(section)) { if (auto propInfo = findPropertyName(section)) {
if (role == Qt::DisplayRole) if (role == Qt::DisplayRole)
return QString::fromLatin1(propInfo->second); return QString::fromLatin1(propInfo->second);
if (role == static_cast<int>(Roles::GroupRole)) if (role == Roles::GroupRole)
return QVariant::fromValue<GroupType>(propInfo->first); return QVariant::fromValue<GroupType>(propInfo->first);
} }
} }
return {}; return {};
} }
@@ -87,8 +122,11 @@ Qt::ItemFlags CollectionModel::flags(const QModelIndex &index) const
QHash<int, QByteArray> CollectionModel::roleNames() const QHash<int, QByteArray> CollectionModel::roleNames() const
{ {
auto roles = QAbstractItemModel::roleNames(); auto roles = QAbstractItemModel::roleNames();
roles.insert(static_cast<int>(Roles::GroupRole), "group"); roles.insert(Roles::ResolvedValueRole, "resolvedValue");
roles.insert(static_cast<int>(Roles::BindingRole), "isBinding"); roles.insert(Roles::GroupRole, "group");
roles.insert(Roles::BindingRole, "isBinding");
roles.insert(Roles::ActiveThemeRole, "isActive");
roles.insert(Roles::PropertyValueRole, "propertyValue");
return roles; return roles;
} }
@@ -106,6 +144,7 @@ bool CollectionModel::insertColumns([[maybe_unused]] int column, int count, cons
beginResetModel(); beginResetModel();
updateCache(); updateCache();
endResetModel(); endResetModel();
emit themeNameChanged();
} }
return true; return true;
} }
@@ -122,6 +161,7 @@ bool CollectionModel::removeColumns(int column, int count, const QModelIndex &pa
updateCache(); updateCache();
endResetModel(); endResetModel();
emit themeNameChanged();
return true; return true;
} }
@@ -190,25 +230,26 @@ bool CollectionModel::setHeaderData(int section,
const QVariant &value, const QVariant &value,
int role) int role)
{ {
if (role != Qt::EditRole) if (role != Qt::EditRole || section < 0
return false; || (orientation == Qt::Horizontal && section >= columnCount())
if (section < 0 || (orientation == Qt::Horizontal && section >= columnCount())
|| (orientation == Qt::Vertical && section >= rowCount())) { || (orientation == Qt::Vertical && section >= rowCount())) {
return false; // Out of bounds return false; // Out of bounds
} }
const auto &newName = value.toString().toUtf8(); const auto &newName = value.toString().toUtf8();
bool success = false; bool success = false;
if (orientation == Qt::Horizontal) { if (orientation == Qt::Vertical) {
// Theme
success = m_collection->renameTheme(findThemeId(section), newName);
} else {
// Property Name // Property Name
if (auto propInfo = findPropertyName(section)) { if (auto propInfo = findPropertyName(section)) {
auto [groupType, propName] = *propInfo; auto [groupType, propName] = *propInfo;
success = m_collection->renameProperty(groupType, propName, newName); success = m_collection->renameProperty(groupType, propName, newName);
} }
} else {
// Theme
const auto themeId = findThemeId(section);
success = m_collection->renameTheme(themeId, newName);
if (success)
emit themeNameChanged();
} }
if (success) { if (success) {

View File

@@ -10,6 +10,7 @@
namespace QmlDesigner { namespace QmlDesigner {
class DSThemeManager; class DSThemeManager;
class DSStore;
using PropInfo = std::pair<GroupType, PropertyName>; using PropInfo = std::pair<GroupType, PropertyName>;
class CollectionModel : public QAbstractItemModel class CollectionModel : public QAbstractItemModel
@@ -17,9 +18,20 @@ class CollectionModel : public QAbstractItemModel
Q_OBJECT Q_OBJECT
public: public:
enum class Roles { GroupRole = Qt::UserRole + 1, BindingRole }; enum Roles {
GroupRole = Qt::UserRole + 1,
BindingRole,
ActiveThemeRole,
ResolvedValueRole,
PropertyValueRole
};
CollectionModel(DSThemeManager *collection); Q_PROPERTY(QStringList themeNames READ themeNameList NOTIFY themeNameChanged FINAL)
CollectionModel(DSThemeManager *collection, const DSStore *store);
QStringList themeNameList() const;
Q_INVOKABLE void setActiveTheme(const QString &themeName);
// QAbstractItemModel Interface // QAbstractItemModel Interface
int columnCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override;
@@ -52,12 +64,16 @@ public:
const QVariant &value, const QVariant &value,
int role = Qt::EditRole) override; int role = Qt::EditRole) override;
signals:
void themeNameChanged();
private: private:
ThemeId findThemeId(int column) const; ThemeId findThemeId(int column) const;
std::optional<PropInfo> findPropertyName(int row) const; std::optional<PropInfo> findPropertyName(int row) const;
private: private:
DSThemeManager *m_collection = nullptr; DSThemeManager *m_collection = nullptr;
const DSStore *m_store;
// cache // cache
std::vector<ThemeId> m_themeIdList; std::vector<ThemeId> m_themeIdList;

View File

@@ -74,7 +74,8 @@ QStringList DesignSystemInterface::collections() const
CollectionModel *DesignSystemInterface::createModel(const QString &typeName, DSThemeManager *collection) CollectionModel *DesignSystemInterface::createModel(const QString &typeName, DSThemeManager *collection)
{ {
auto [newItr, success] = m_models.try_emplace(typeName, auto [newItr, success] = m_models.try_emplace(typeName,
std::make_unique<CollectionModel>(collection)); std::make_unique<CollectionModel>(collection,
m_store));
if (success) { if (success) {
// Otherwise the model will be deleted by the QML engine. // Otherwise the model will be deleted by the QML engine.
QQmlEngine::setObjectOwnership(newItr->second.get(), QQmlEngine::CppOwnership); QQmlEngine::setObjectOwnership(newItr->second.get(), QQmlEngine::CppOwnership);

View File

@@ -154,7 +154,7 @@ DVConnector::DVConnector(QObject *parent)
}); });
connect(&m_resourceGenerator, connect(&m_resourceGenerator,
&ResourceGeneratorProxy::resourceFileCreated, &ResourceGenerator::qmlrcCreated,
this, this,
[this](const std::optional<Utils::FilePath> &resourcePath) { [this](const std::optional<Utils::FilePath> &resourcePath) {
emit projectIsUploading(); emit projectIsUploading();
@@ -162,12 +162,10 @@ DVConnector::DVConnector(QObject *parent)
uploadProject(projectName, resourcePath->toString()); uploadProject(projectName, resourcePath->toString());
}); });
connect(&m_resourceGenerator, connect(&m_resourceGenerator, &ResourceGenerator::errorOccurred, [this](const QString &errorString) {
&ResourceGeneratorProxy::errorOccurred, qCWarning(deploymentPluginLog) << "Error occurred while packing the project";
[this](const QString &errorString) { emit projectPackingFailed(errorString);
qCWarning(deploymentPluginLog) << "Error occurred while packing the project"; });
emit projectPackingFailed(errorString);
});
fetchUserInfo(); fetchUserInfo();
} }
@@ -220,7 +218,7 @@ void DVConnector::projectList()
void DVConnector::uploadCurrentProject() void DVConnector::uploadCurrentProject()
{ {
QString projectName = ProjectExplorer::ProjectManager::startupProject()->displayName(); QString projectName = ProjectExplorer::ProjectManager::startupProject()->displayName();
m_resourceGenerator.createResourceFileAsync(projectName); m_resourceGenerator.createQmlrcAsyncWithName(projectName);
emit projectIsPacking(); emit projectIsPacking();
} }

View File

@@ -9,7 +9,7 @@
#include <QWebEngineProfile> #include <QWebEngineProfile>
#include <QWebEngineView> #include <QWebEngineView>
#include "resourcegeneratorproxy.h" #include <qmldesigner/components/componentcore/resourcegenerator.h>
namespace QmlDesigner::DesignViewer { namespace QmlDesigner::DesignViewer {
@@ -99,7 +99,7 @@ private:
QByteArray m_userInfo; QByteArray m_userInfo;
// other internals // other internals
ResourceGeneratorProxy m_resourceGenerator; ResourceGenerator m_resourceGenerator;
struct ReplyEvaluatorData struct ReplyEvaluatorData
{ {

View File

@@ -1,65 +0,0 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "resourcegeneratorproxy.h"
#include <QStandardPaths>
#include <QTemporaryFile>
#include <QXmlStreamReader>
#include <QtConcurrent>
#include <qtsupport/baseqtversion.h>
#include <qtsupport/qtkitaspect.h>
#include <coreplugin/messagemanager.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <utils/qtcprocess.h>
#include <qmldesigner/components/componentcore/resourcegenerator.h>
namespace QmlDesigner::DesignViewer {
ResourceGeneratorProxy::~ResourceGeneratorProxy()
{
if (m_future.isRunning()) {
m_future.cancel();
m_future.waitForFinished();
}
}
void ResourceGeneratorProxy::createResourceFileAsync(const QString &projectName)
{
m_future = QtConcurrent::run([&]() {
const std::optional<Utils::FilePath> filePath = createResourceFileSync(projectName);
if (!filePath || filePath->isEmpty()) {
emit errorOccurred("Failed to create resource file");
return;
}
emit resourceFileCreated(filePath.value());
});
}
std::optional<Utils::FilePath> ResourceGeneratorProxy::createResourceFileSync(const QString &projectName)
{
const auto project = ProjectExplorer::ProjectManager::startupProject();
std::optional<Utils::FilePath> resourcePath = project->projectDirectory().pathAppended(
projectName + ".qmlrc");
const bool retVal = ResourceGenerator::createQmlrcFile(resourcePath.value());
if (!retVal || resourcePath->fileSize() == 0) {
Core::MessageManager::writeDisrupting(tr("Failed to create resource file"));
resourcePath.reset();
}
return resourcePath;
}
} // namespace QmlDesigner::DesignViewer

View File

@@ -1,28 +0,0 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <utils/filepath.h>
#include <QFuture>
namespace QmlDesigner::DesignViewer {
class ResourceGeneratorProxy : public QObject
{
Q_OBJECT
public:
~ResourceGeneratorProxy();
Q_INVOKABLE void createResourceFileAsync(const QString &projectName = "share");
Q_INVOKABLE std::optional<Utils::FilePath> createResourceFileSync(const QString &projectName = "share");
private:
QFuture<void> m_future;
signals:
void errorOccurred(const QString &error);
void resourceFileCreated(const Utils::FilePath &filePath);
};
} // namespace QmlDesigner::DesignViewer

View File

@@ -5,7 +5,8 @@
#include <QJsonDocument> #include <QJsonDocument>
#include <QLatin1String> #include <QLatin1String>
#include <QThreadPool>
#include "websocketmock.h"
namespace QmlDesigner::DeviceShare { namespace QmlDesigner::DeviceShare {
@@ -20,17 +21,24 @@ constexpr auto stopRunningProject = "stopRunningProject"_L1;
namespace PackageFromDevice { namespace PackageFromDevice {
using namespace Qt::Literals; using namespace Qt::Literals;
constexpr auto deviceInfo = "deviceInfo"_L1; constexpr auto deviceInfo = "deviceInfo"_L1;
constexpr auto projectReceivingProgress = "projectReceivingProgress"_L1;
constexpr auto projectStarting = "projectStarting"_L1;
constexpr auto projectRunning = "projectRunning"_L1; constexpr auto projectRunning = "projectRunning"_L1;
constexpr auto projectStopped = "projectStopped"_L1; constexpr auto projectStopped = "projectStopped"_L1;
constexpr auto projectLogs = "projectLogs"_L1; constexpr auto projectLogs = "projectLogs"_L1;
}; // namespace PackageFromDevice }; // namespace PackageFromDevice
Device::Device(const DeviceInfo &deviceInfo, const DeviceSettings &deviceSettings, QObject *parent) Device::Device(const QString designStudioId,
const DeviceInfo &deviceInfo,
const DeviceSettings &deviceSettings,
QObject *parent)
: QObject(parent) : QObject(parent)
, m_deviceInfo(deviceInfo) , m_deviceInfo(deviceInfo)
, m_deviceSettings(deviceSettings) , m_deviceSettings(deviceSettings)
, m_socket(nullptr) , m_socket(nullptr)
, m_socketWasConnected(false) , m_socketWasConnected(false)
, m_socketManuallyClosed(false)
, m_designStudioId(designStudioId)
{ {
qCDebug(deviceSharePluginLog) << "initial device info:" << m_deviceInfo; qCDebug(deviceSharePluginLog) << "initial device info:" << m_deviceInfo;
@@ -38,40 +46,33 @@ Device::Device(const DeviceInfo &deviceInfo, const DeviceSettings &deviceSetting
m_socket->setOutgoingFrameSize(128000); m_socket->setOutgoingFrameSize(128000);
connect(m_socket.data(), &QWebSocket::textMessageReceived, this, &Device::processTextMessage); connect(m_socket.data(), &QWebSocket::textMessageReceived, this, &Device::processTextMessage);
connect(m_socket.data(), &QWebSocket::disconnected, this, [this]() { connect(m_socket.data(), &QWebSocket::disconnected, this, [this]() {
m_reconnectTimer.start(); if (m_socketManuallyClosed) {
m_socketManuallyClosed = false;
return;
}
m_reconnectTimer.start(m_reconnectTimeout);
if (!m_socketWasConnected) if (!m_socketWasConnected)
return; return;
m_socketWasConnected = false; m_socketWasConnected = false;
m_pingTimer.stop(); stopPingPong();
m_pongTimer.stop();
emit disconnected(m_deviceSettings.deviceId()); emit disconnected(m_deviceSettings.deviceId());
}); });
connect(m_socket.data(), &QWebSocket::connected, this, [this]() { connect(m_socket.data(), &QWebSocket::connected, this, [this]() {
m_socketWasConnected = true; m_socketWasConnected = true;
m_reconnectTimer.stop(); m_reconnectTimer.stop();
m_pingTimer.start(); restartPingPong();
sendDesignStudioReady(m_deviceSettings.deviceId()); sendDesignStudioReady();
emit connected(m_deviceSettings.deviceId()); emit connected(m_deviceSettings.deviceId());
}); });
connect(m_socket.data(), &QWebSocket::bytesWritten, this, [this](qint64 bytes) {
if (m_lastProjectSize == 0)
return;
m_lastProjectSentSize += bytes;
const float percentage = ((float) m_lastProjectSentSize * 100.0) / (float) m_lastProjectSize;
if (percentage != 100.0)
emit projectSendingProgress(m_deviceSettings.deviceId(), percentage);
if (m_lastProjectSentSize >= m_lastProjectSize)
m_lastProjectSize = 0;
});
m_reconnectTimer.setSingleShot(true); m_reconnectTimer.setSingleShot(true);
m_reconnectTimer.setInterval(m_reconnectTimeout); connect(&m_reconnectTimer, &QTimer::timeout, this, [this]() { reconnect(); });
connect(&m_reconnectTimer, &QTimer::timeout, this, &Device::reconnect);
m_sendTimer.setSingleShot(true);
m_sendTimer.setInterval(10);
connect(&m_sendTimer, &QTimer::timeout, this, &Device::sendProjectDataInternal, Qt::UniqueConnection);
initPingPong(); initPingPong();
reconnect(); reconnect();
@@ -114,10 +115,23 @@ void Device::initPingPong()
}); });
} }
void Device::reconnect() void Device::stopPingPong()
{ {
if (m_socket->state() == QAbstractSocket::ConnectedState) m_pingTimer.stop();
m_socket->close(); m_pongTimer.stop();
}
void Device::restartPingPong()
{
m_pingTimer.start();
}
void Device::reconnect(const QString &closeMessage)
{
if (m_socket && m_socket->isValid() && m_socket->state() == QAbstractSocket::ConnectedState) {
m_socket->close(QWebSocketProtocol::CloseCodeNormal, closeMessage);
m_socketManuallyClosed = true;
}
QUrl url(QStringLiteral("ws://%1:%2").arg(m_deviceSettings.ipAddress()).arg(40000)); QUrl url(QStringLiteral("ws://%1:%2").arg(m_deviceSettings.ipAddress()).arg(40000));
m_socket->open(url); m_socket->open(url);
@@ -146,19 +160,63 @@ void Device::setDeviceSettings(const DeviceSettings &deviceSettings)
reconnect(); reconnect();
} }
bool Device::sendDesignStudioReady(const QString &uuid) bool Device::sendDesignStudioReady()
{ {
return sendTextMessage(PackageToDevice::designStudioReady, uuid); QJsonObject data;
data["designStudioID"] = m_designStudioId;
data["commVersion"] = 1;
return sendTextMessage(PackageToDevice::designStudioReady, data);
} }
bool Device::sendProjectNotification(const int &projectSize) bool Device::sendProjectNotification(const int &projectSize, const QString &qtVersion)
{ {
return sendTextMessage(PackageToDevice::projectData, projectSize); QJsonObject projectInfo;
projectInfo["projectSize"] = projectSize;
projectInfo["qtVersion"] = qtVersion;
return sendTextMessage(PackageToDevice::projectData, projectInfo);
} }
bool Device::sendProjectData(const QByteArray &data) bool Device::sendProjectData(const QByteArray &data, const QString &qtVersion)
{ {
return sendBinaryMessage(data); if (!isConnected())
return false;
sendProjectNotification(data.size(), qtVersion);
m_sendProject = true;
m_projectData = data;
m_totalSentSize = 0;
m_lastSentProgress = 0;
m_sendTimer.start();
stopPingPong();
return true;
}
void Device::sendProjectDataInternal()
{
if (!isConnected())
return;
if (!m_sendProject) {
sendTextMessage(PackageToDevice::stopRunningProject);
restartPingPong();
return;
}
const int chunkSize = 1024 * 50; // 50KB
const QByteArray chunk = m_projectData.mid(m_totalSentSize, chunkSize);
m_socket->sendBinaryMessage(chunk);
m_socket->flush();
m_totalSentSize += chunk.size();
if (m_totalSentSize < m_projectData.size())
m_sendTimer.start();
else
restartPingPong();
} }
bool Device::sendProjectStopped() bool Device::sendProjectStopped()
@@ -186,16 +244,12 @@ bool Device::sendTextMessage(const QLatin1String &dataType, const QJsonValue &da
return true; return true;
} }
bool Device::sendBinaryMessage(const QByteArray &data) void Device::abortProjectTransmission()
{ {
if (!isConnected()) if (!isConnected())
return false; return;
m_lastProjectSize = data.size(); m_sendProject = false;
m_lastProjectSentSize = 0;
sendProjectNotification(m_lastProjectSize);
m_socket->sendBinaryMessage(data);
return true;
} }
void Device::processTextMessage(const QString &data) void Device::processTextMessage(const QString &data)
@@ -213,7 +267,7 @@ void Device::processTextMessage(const QString &data)
if (dataType == PackageFromDevice::deviceInfo) { if (dataType == PackageFromDevice::deviceInfo) {
QJsonObject deviceInfo = jsonObj.value("data").toObject(); QJsonObject deviceInfo = jsonObj.value("data").toObject();
m_deviceInfo.setJsonObject(deviceInfo); m_deviceInfo.setJsonObject(deviceInfo);
emit deviceInfoReady(m_deviceSettings.ipAddress(), m_deviceSettings.deviceId()); emit deviceInfoReady(m_deviceSettings.deviceId());
} else if (dataType == PackageFromDevice::projectRunning) { } else if (dataType == PackageFromDevice::projectRunning) {
qCDebug(deviceSharePluginLog) << "Project started on device" << m_deviceSettings.deviceId(); qCDebug(deviceSharePluginLog) << "Project started on device" << m_deviceSettings.deviceId();
emit projectStarted(m_deviceSettings.deviceId()); emit projectStarted(m_deviceSettings.deviceId());
@@ -221,7 +275,15 @@ void Device::processTextMessage(const QString &data)
qCDebug(deviceSharePluginLog) << "Project stopped on device" << m_deviceSettings.deviceId(); qCDebug(deviceSharePluginLog) << "Project stopped on device" << m_deviceSettings.deviceId();
emit projectStopped(m_deviceSettings.deviceId()); emit projectStopped(m_deviceSettings.deviceId());
} else if (dataType == PackageFromDevice::projectLogs) { } else if (dataType == PackageFromDevice::projectLogs) {
emit projectLogsReceived(m_deviceSettings.deviceId(), jsonObj.value("data").toString()); const QString logs = jsonObj.value("data").toString();
qCDebug(deviceSharePluginLog) << "Device Log:" << m_deviceSettings.deviceId() << logs;
emit projectLogsReceived(m_deviceSettings.deviceId(), logs);
} else if (dataType == PackageFromDevice::projectStarting) {
qCDebug(deviceSharePluginLog) << "Project starting on device" << m_deviceSettings.deviceId();
emit projectStarting(m_deviceSettings.deviceId());
} else if (dataType == PackageFromDevice::projectReceivingProgress) {
const int progress = jsonObj.value("data").toInt();
emit projectSendingProgress(m_deviceSettings.deviceId(), progress);
} else { } else {
qCDebug(deviceSharePluginLog) << "Invalid JSON message:" << jsonObj; qCDebug(deviceSharePluginLog) << "Invalid JSON message:" << jsonObj;
} }

View File

@@ -4,17 +4,20 @@
#pragma once #pragma once
#include <QTimer> #include <QTimer>
#include <QWebSocket>
#include <atomic>
#include "deviceinfo.h" #include "deviceinfo.h"
class QWebSocket;
namespace QmlDesigner::DeviceShare { namespace QmlDesigner::DeviceShare {
class Device : public QObject class Device : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
Device(const DeviceInfo &deviceInfo = {}, Device(const QString designStudioId,
const DeviceInfo &deviceInfo = {},
const DeviceSettings &deviceSettings = {}, const DeviceSettings &deviceSettings = {},
QObject *parent = nullptr); QObject *parent = nullptr);
~Device(); ~Device();
@@ -26,13 +29,13 @@ public:
void setDeviceSettings(const DeviceSettings &deviceSettings); void setDeviceSettings(const DeviceSettings &deviceSettings);
// device communication // device communication
bool sendDesignStudioReady(const QString &uuid); bool sendProjectData(const QByteArray &data, const QString &qtVersion);
bool sendProjectData(const QByteArray &data);
bool sendProjectStopped(); bool sendProjectStopped();
void abortProjectTransmission();
// socket // socket
bool isConnected() const; bool isConnected() const;
void reconnect(); void reconnect(const QString &closeMessage = QString());
private slots: private slots:
void processTextMessage(const QString &data); void processTextMessage(const QString &data);
@@ -43,28 +46,39 @@ private:
QScopedPointer<QWebSocket> m_socket; QScopedPointer<QWebSocket> m_socket;
bool m_socketWasConnected; bool m_socketWasConnected;
bool m_socketManuallyClosed;
QTimer m_reconnectTimer; QTimer m_reconnectTimer;
QTimer m_pingTimer; QTimer m_pingTimer;
QTimer m_pongTimer; QTimer m_pongTimer;
QTimer m_sendTimer;
int m_lastProjectSize; std::atomic<bool> m_sendProject;
int m_lastProjectSentSize; QByteArray m_projectData;
int m_totalSentSize;
int m_lastSentProgress;
const QString m_designStudioId;
static constexpr int m_reconnectTimeout = 5000; static constexpr int m_reconnectTimeout = 5000;
static constexpr int m_pingTimeout = 10000; static constexpr int m_pingTimeout = 10000;
static constexpr int m_pongTimeout = 30000; static constexpr int m_pongTimeout = 30000;
void initPingPong(); void initPingPong();
bool sendProjectNotification(const int &projectSize); void stopPingPong();
void restartPingPong();
void sendProjectDataInternal();
bool sendDesignStudioReady();
bool sendProjectNotification(const int &projectSize, const QString &qtVersion);
bool sendTextMessage(const QLatin1String &dataType, const QJsonValue &data = QJsonValue()); bool sendTextMessage(const QLatin1String &dataType, const QJsonValue &data = QJsonValue());
bool sendBinaryMessage(const QByteArray &data); bool sendBinaryMessage(const QByteArray &data);
signals: signals:
void connected(const QString &deviceId); void connected(const QString &deviceId);
void disconnected(const QString &deviceId); void disconnected(const QString &deviceId);
void deviceInfoReady(const QString &deviceIp, const QString &deviceId); void deviceInfoReady(const QString &deviceId);
void projectSendingProgress(const QString &deviceId, const int progress); void projectSendingProgress(const QString &deviceId, const int progress);
void projectStarting(const QString &deviceId);
void projectStarted(const QString &deviceId); void projectStarted(const QString &deviceId);
void projectStopped(const QString &deviceId); void projectStopped(const QString &deviceId);
void projectLogsReceived(const QString &deviceId, const QString &logs); void projectLogsReceived(const QString &deviceId, const QString &logs);

View File

@@ -127,6 +127,11 @@ void DeviceInfo::setScreenHeight(const int &screenHeight)
m_data[keyScreenHeight] = screenHeight; m_data[keyScreenHeight] = screenHeight;
} }
void DeviceInfo::setSelfId(const QString &selfId)
{
m_data[keySelfId] = selfId;
}
void DeviceInfo::setAppVersion(const QString &appVersion) void DeviceInfo::setAppVersion(const QString &appVersion)
{ {
m_data[keyAppVersion] = appVersion; m_data[keyAppVersion] = appVersion;

View File

@@ -27,7 +27,7 @@ protected:
class DeviceSettings : public IDeviceData class DeviceSettings : public IDeviceData
{ {
public: public:
DeviceSettings() = default; using IDeviceData::IDeviceData;
// Getters // Getters
bool active() const; bool active() const;
@@ -51,7 +51,7 @@ private:
class DeviceInfo : public IDeviceData class DeviceInfo : public IDeviceData
{ {
public: public:
DeviceInfo() = default; using IDeviceData::IDeviceData;
// Getters // Getters
QString os() const; QString os() const;
@@ -76,7 +76,7 @@ private:
static constexpr char keyOsVersion[] = "osVersion"; static constexpr char keyOsVersion[] = "osVersion";
static constexpr char keyScreenWidth[] = "screenWidth"; static constexpr char keyScreenWidth[] = "screenWidth";
static constexpr char keyScreenHeight[] = "screenHeight"; static constexpr char keyScreenHeight[] = "screenHeight";
static constexpr char keySelfId[] = "selfId"; static constexpr char keySelfId[] = "deviceId";
static constexpr char keyArchitecture[] = "architecture"; static constexpr char keyArchitecture[] = "architecture";
static constexpr char keyAppVersion[] = "appVersion"; static constexpr char keyAppVersion[] = "appVersion";
}; };

View File

@@ -2,65 +2,73 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "devicemanager.h" #include "devicemanager.h"
#include "device.h"
#include "devicemanagerwidget.h" #include "devicemanagerwidget.h"
#include <QFile> #include <QFile>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonDocument> #include <QJsonDocument>
#include <QLatin1String>
#include <QNetworkDatagram> #include <QNetworkDatagram>
#include <QNetworkInterface> #include <QNetworkInterface>
#include <QUdpSocket>
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <projectexplorer/kitaspect.h>
#include <projectexplorer/target.h>
#include <qmldesigner/qmldesignerplugin.h>
#include <qtsupport/qtkitaspect.h>
namespace QmlDesigner::DeviceShare { namespace QmlDesigner::DeviceShare {
namespace JsonKeys {
using namespace Qt::Literals;
constexpr auto devices = "devices"_L1;
constexpr auto designStudioId = "designStudioId"_L1;
constexpr auto deviceInfo = "deviceInfo"_L1;
constexpr auto deviceSettings = "deviceSettings"_L1;
} // namespace JsonKeys
DeviceManager::DeviceManager(QObject *parent) DeviceManager::DeviceManager(QObject *parent)
: QObject(parent) : QObject(parent)
, m_currentState(OpTypes::Stopped)
, m_processInterrupted(false)
{ {
QFileInfo fileInfo(Core::ICore::settings()->fileName()); QFileInfo fileInfo(Core::ICore::settings()->fileName());
m_settingsPath = fileInfo.absolutePath() + "/device_manager.json"; m_settingsPath = fileInfo.absolutePath() + "/device_manager.json";
readSettings(); readSettings();
if (m_uuid.isEmpty()) {
m_uuid = QUuid::createUuid().toString(QUuid::WithoutBraces);
writeSettings();
}
initUdpDiscovery(); initUdpDiscovery();
connect(&m_resourceGenerator,
&QmlDesigner::ResourceGenerator::errorOccurred,
this,
[this](const QString &error) {
qCDebug(deviceSharePluginLog) << "ResourceGenerator error:" << error;
handleError(ErrTypes::ProjectPackingError, m_currentDeviceId, error);
});
connect(&m_resourceGenerator,
&QmlDesigner::ResourceGenerator::qmlrcCreated,
this,
&DeviceManager::projectPacked);
} }
DeviceManager::~DeviceManager() = default; DeviceManager::~DeviceManager() = default;
void DeviceManager::initUdpDiscovery() void DeviceManager::initUdpDiscovery()
{ {
const QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces(); QSharedPointer<QUdpSocket> udpSocket = QSharedPointer<QUdpSocket>::create();
for (const QNetworkInterface &interface : interfaces) { bool retVal = udpSocket->bind(QHostAddress::AnyIPv4, 53452, QUdpSocket::ShareAddress);
if (interface.flags().testFlag(QNetworkInterface::IsUp) if (!retVal) {
&& interface.flags().testFlag(QNetworkInterface::IsRunning)) { qCWarning(deviceSharePluginLog) << "UDP:: Failed to bind to UDP port 53452 on AnyIPv4"
for (const QNetworkAddressEntry &entry : interface.addressEntries()) { << ". Error:" << udpSocket->errorString();
if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol) { return;
QSharedPointer<QUdpSocket> udpSocket = QSharedPointer<QUdpSocket>::create();
connect(udpSocket.data(),
&QUdpSocket::readyRead,
this,
&DeviceManager::incomingDatagram);
bool retVal = udpSocket->bind(entry.ip(), 53452, QUdpSocket::ShareAddress);
if (!retVal) {
qCWarning(deviceSharePluginLog)
<< "UDP:: Failed to bind to UDP port 53452 on" << entry.ip().toString()
<< "on interface" << interface.name()
<< ". Error:" << udpSocket->errorString();
continue;
}
qCDebug(deviceSharePluginLog) << "UDP:: Listening on" << entry.ip().toString()
<< "port" << udpSocket->localPort();
m_udpSockets.append(udpSocket);
}
}
}
} }
connect(udpSocket.data(), &QUdpSocket::readyRead, this, &DeviceManager::incomingDatagram);
qCDebug(deviceSharePluginLog) << "UDP:: Listening on AnyIPv4 port" << udpSocket->localPort();
m_udpSockets.append(udpSocket);
} }
void DeviceManager::incomingDatagram() void DeviceManager::incomingDatagram()
@@ -80,16 +88,20 @@ void DeviceManager::incomingDatagram()
const QString id = message["id"].toString(); const QString id = message["id"].toString();
const QString ip = datagram.senderAddress().toString(); const QString ip = datagram.senderAddress().toString();
qCDebug(deviceSharePluginLog) << "Qt UI VIewer found at" << ip << "with id" << id;
bool found = false;
for (const auto &device : m_devices) { for (const auto &device : m_devices) {
if (device->deviceInfo().selfId() == id) { if (device->deviceInfo().selfId() == id) {
found = true;
if (device->deviceSettings().ipAddress() != ip) { if (device->deviceSettings().ipAddress() != ip) {
qCDebug(deviceSharePluginLog) << "Updating IP address for device" << id; qCDebug(deviceSharePluginLog) << "Updating IP address for device" << id;
setDeviceIP(id, ip); setDeviceIP(device->deviceSettings().deviceId(), ip);
} }
} }
} }
if (!found)
qCDebug(deviceSharePluginLog) << "Qt UI VIewer discovered at" << ip << "with id" << id;
} }
} }
@@ -99,13 +111,13 @@ void DeviceManager::writeSettings()
QJsonArray devices; QJsonArray devices;
for (const auto &device : m_devices) { for (const auto &device : m_devices) {
QJsonObject deviceInfo; QJsonObject deviceInfo;
deviceInfo.insert("deviceInfo", device->deviceInfo().jsonObject()); deviceInfo.insert(JsonKeys::deviceInfo, device->deviceInfo().jsonObject());
deviceInfo.insert("deviceSettings", device->deviceSettings().jsonObject()); deviceInfo.insert(JsonKeys::deviceSettings, device->deviceSettings().jsonObject());
devices.append(deviceInfo); devices.append(deviceInfo);
} }
root.insert("devices", devices); root.insert(JsonKeys::devices, devices);
root.insert("uuid", m_uuid); root.insert(JsonKeys::designStudioId, m_designStudioId);
QJsonDocument doc(root); QJsonDocument doc(root);
QFile file(m_settingsPath); QFile file(m_settingsPath);
@@ -127,15 +139,17 @@ void DeviceManager::readSettings()
} }
QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
m_uuid = doc.object()["uuid"].toString(); m_designStudioId = doc.object()[JsonKeys::designStudioId].toString();
QJsonArray devices = doc.object()["devices"].toArray(); if (m_designStudioId.isEmpty()) {
m_designStudioId = QUuid::createUuid().toString(QUuid::WithoutBraces);
writeSettings();
}
QJsonArray devices = doc.object()[JsonKeys::devices].toArray();
for (const QJsonValue &deviceInfoJson : devices) { for (const QJsonValue &deviceInfoJson : devices) {
DeviceInfo deviceInfo; DeviceInfo deviceInfo{deviceInfoJson.toObject()[JsonKeys::deviceInfo].toObject()};
DeviceSettings deviceSettings; DeviceSettings deviceSettings{deviceInfoJson.toObject()[JsonKeys::deviceSettings].toObject()};
deviceInfo.setJsonObject(deviceInfoJson.toObject()["deviceInfo"].toObject()); initDevice(deviceInfo, deviceSettings);
deviceSettings.setJsonObject(deviceInfoJson.toObject()["deviceSettings"].toObject());
auto device = initDevice(deviceInfo, deviceSettings);
m_devices.append(device);
} }
} }
@@ -267,60 +281,68 @@ bool DeviceManager::addDevice(const QString &ip)
deviceSettings.setAlias(generateDeviceAlias()); deviceSettings.setAlias(generateDeviceAlias());
deviceSettings.setDeviceId(QUuid::createUuid().toString(QUuid::WithoutBraces)); deviceSettings.setDeviceId(QUuid::createUuid().toString(QUuid::WithoutBraces));
auto device = initDevice({}, deviceSettings); initDevice({}, deviceSettings);
m_devices.append(device);
writeSettings(); writeSettings();
emit deviceAdded(deviceSettings.deviceId()); emit deviceAdded(deviceSettings.deviceId());
return true; return true;
} }
QSharedPointer<Device> DeviceManager::initDevice(const DeviceInfo &deviceInfo, void DeviceManager::initDevice(const DeviceInfo &deviceInfo, const DeviceSettings &deviceSettings)
const DeviceSettings &deviceSettings)
{ {
QSharedPointer<Device> device = QSharedPointer<Device>(new Device{deviceInfo, deviceSettings}, QSharedPointer<Device> device = QSharedPointer<Device>(new Device{m_designStudioId,
deviceInfo,
deviceSettings},
&QObject::deleteLater); &QObject::deleteLater);
QString deviceId = device->deviceSettings().deviceId();
connect(device.data(), &Device::deviceInfoReady, this, &DeviceManager::deviceInfoReceived); connect(device.data(), &Device::deviceInfoReady, this, &DeviceManager::deviceInfoReceived);
connect(device.data(), &Device::disconnected, this, &DeviceManager::deviceDisconnected); connect(device.data(), &Device::disconnected, this, [this](const QString &deviceId) {
qCDebug(deviceSharePluginLog) << "Device" << deviceId << "disconnected";
emit deviceOffline(deviceId);
handleError(ErrTypes::NoError, deviceId);
});
connect(device.data(), &Device::projectSendingProgress, this, &DeviceManager::projectSendingProgress); connect(device.data(), &Device::projectSendingProgress, this, &DeviceManager::projectSendingProgress);
connect(device.data(), &Device::projectStarted, this, &DeviceManager::projectStarted);
connect(device.data(), &Device::projectStopped, this, &DeviceManager::projectStopped);
connect(device.data(), connect(device.data(), &Device::projectStarting, this, [this](const QString &deviceId) {
&Device::projectLogsReceived, m_currentState = OpTypes::Starting;
this, emit projectStarting(deviceId);
[this](const QString deviceId, const QString &logs) { });
qCDebug(deviceSharePluginLog) << "Log:" << deviceId << logs;
emit projectLogsReceived(deviceId, logs);
});
return device; connect(device.data(), &Device::projectStarted, this, [this](const QString &deviceId) {
m_currentState = OpTypes::Running;
emit projectStarted(deviceId);
});
connect(device.data(), &Device::projectStopped, this, [this](const QString &deviceId) {
handleError(ErrTypes::NoError, deviceId);
});
connect(device.data(), &Device::projectLogsReceived, this, &DeviceManager::projectLogsReceived);
m_devices.append(device);
} }
void DeviceManager::deviceInfoReceived(const QString &deviceIp, const QString &deviceId) void DeviceManager::deviceInfoReceived(const QString &deviceId)
{ {
auto newDevIt = std::find_if(m_devices.begin(), auto newDevice = findDevice(deviceId);
m_devices.end(), const QString selfId = newDevice->deviceInfo().selfId();
[deviceId, deviceIp](const auto &device) { const QString deviceIp = newDevice->deviceSettings().ipAddress();
return device->deviceSettings().deviceId() == deviceId
&& device->deviceSettings().ipAddress() == deviceIp;
});
auto oldDevIt = std::find_if(m_devices.begin(), auto oldDevIt = std::find_if(m_devices.begin(),
m_devices.end(), m_devices.end(),
[deviceId, deviceIp](const auto &device) { [selfId, deviceIp](const auto &device) {
return device->deviceSettings().deviceId() == deviceId return device->deviceInfo().selfId() == selfId
&& device->deviceSettings().ipAddress() != deviceIp; && device->deviceSettings().ipAddress() != deviceIp;
}); });
// if there are 2 devices with the same ID but different IPs, remove the old one // if there are 2 devices with the same ID but different IPs, remove the old one
// aka: merge devices with the same ID // aka: merge devices with the same ID
if (oldDevIt != m_devices.end()) { if (oldDevIt != m_devices.end()) {
QSharedPointer<Device> oldDevice = *oldDevIt; auto oldDevice = *oldDevIt;
QSharedPointer<Device> newDevice = *newDevIt; const QString alias = oldDevice->deviceSettings().alias();
DeviceSettings deviceSettings = oldDevice->deviceSettings(); m_devices.removeOne(*oldDevIt);
deviceSettings.setIpAddress(newDevice->deviceSettings().ipAddress()); DeviceSettings settings = newDevice->deviceSettings();
newDevice->setDeviceSettings(deviceSettings); settings.setAlias(alias);
m_devices.removeOne(oldDevice); newDevice->setDeviceSettings(settings);
} }
writeSettings(); writeSettings();
@@ -328,16 +350,6 @@ void DeviceManager::deviceInfoReceived(const QString &deviceIp, const QString &d
emit deviceOnline(deviceId); emit deviceOnline(deviceId);
} }
void DeviceManager::deviceDisconnected(const QString &deviceId)
{
auto device = findDevice(deviceId);
if (!device)
return;
qCDebug(deviceSharePluginLog) << "Device" << deviceId << "disconnected";
emit deviceOffline(deviceId);
}
void DeviceManager::removeDevice(const QString &deviceId) void DeviceManager::removeDevice(const QString &deviceId)
{ {
auto device = findDevice(deviceId); auto device = findDevice(deviceId);
@@ -360,29 +372,119 @@ void DeviceManager::removeDeviceAt(int index)
emit deviceRemoved(deviceId); emit deviceRemoved(deviceId);
} }
bool DeviceManager::sendProjectFile(const QString &deviceId, const QString &projectFile) void DeviceManager::handleError(const ErrTypes &errType, const QString &deviceId, const QString &error)
{ {
auto device = findDevice(deviceId); if (!m_processInterrupted) {
if (!device) if (errType != ErrTypes::NoError)
return false; qCWarning(deviceSharePluginLog) << "Handling error" << error << "for device" << deviceId;
QFile file(projectFile); switch (errType) {
if (!file.open(QIODevice::ReadOnly)) { case ErrTypes::InternalError:
qCWarning(deviceSharePluginLog) << "Failed to open project file" << projectFile; emit internalError(deviceId, error);
return false; break;
case ErrTypes::ProjectPackingError:
emit projectPackingError(deviceId, error);
break;
case ErrTypes::ProjectSendingError:
emit projectSendingError(deviceId, error);
break;
case ErrTypes::ProjectStartError:
emit projectStartingError(deviceId, error);
break;
case ErrTypes::NoError:
break;
}
} }
qCDebug(deviceSharePluginLog) << "Sending project file to device" << deviceId; m_processInterrupted = false;
return device->sendProjectData(file.readAll()); m_currentQtKitVersion.clear();
m_currentDeviceId.clear();
m_currentState = OpTypes::Stopped;
emit projectStopped(deviceId);
} }
bool DeviceManager::stopRunningProject(const QString &deviceId) void DeviceManager::runProject(const QString &deviceId)
{ {
auto device = findDevice(deviceId); auto device = findDevice(deviceId);
if (!device) if (!device) {
return false; handleError(ErrTypes::InternalError, deviceId, "Device not found");
return;
}
return device->sendProjectStopped(); if (m_currentState != OpTypes::Stopped) {
qCDebug(deviceSharePluginLog) << "Another operation is in progress";
return;
}
m_currentQtKitVersion.clear();
ProjectExplorer::Target *target = QmlDesignerPlugin::instance()->currentDesignDocument()->currentTarget();
if (target && target->kit()) {
if (QtSupport::QtVersion *qtVer = QtSupport::QtKitAspect::qtVersion(target->kit()))
m_currentQtKitVersion = qtVer->qtVersion().toString();
}
m_currentState = OpTypes::Packing;
m_currentDeviceId = deviceId;
m_resourceGenerator.createQmlrcAsyncWithName();
emit projectPacking(deviceId);
qCDebug(deviceSharePluginLog) << "Packing project for device" << deviceId;
}
void DeviceManager::projectPacked(const Utils::FilePath &filePath)
{
qCDebug(deviceSharePluginLog) << "Project packed" << filePath.toString();
emit projectSendingProgress(m_currentDeviceId, 0);
m_currentState = OpTypes::Sending;
qCDebug(deviceSharePluginLog) << "Sending project file to device" << m_currentDeviceId;
QFile file(filePath.toString());
if (!file.open(QIODevice::ReadOnly)) {
handleError(ErrTypes::ProjectSendingError, m_currentDeviceId, "Failed to open project file");
return;
}
ProjectExplorer::Target *target = QmlDesignerPlugin::instance()->currentDesignDocument()->currentTarget();
if (target && target->kit()) {
if (QtSupport::QtVersion *qtVer = QtSupport::QtKitAspect::qtVersion(target->kit()))
m_currentQtKitVersion = qtVer->qtVersion().toString();
}
if (!findDevice(m_currentDeviceId)->sendProjectData(file.readAll(), m_currentQtKitVersion)) {
handleError(ErrTypes::ProjectSendingError, m_currentDeviceId, "Failed to send project file");
return;
}
}
void DeviceManager::stopProject()
{
auto device = findDevice(m_currentDeviceId);
if (!device) {
handleError(ErrTypes::InternalError, m_currentDeviceId, "Device not found");
return;
}
m_processInterrupted = true;
switch (m_currentState) {
case OpTypes::Stopped:
qCWarning(deviceSharePluginLog) << "No project is running";
return;
case OpTypes::Packing:
qCDebug(deviceSharePluginLog) << "Canceling project packing";
m_resourceGenerator.cancel();
break;
case OpTypes::Sending:
qCDebug(deviceSharePluginLog) << "Cancelling project sending";
device->abortProjectTransmission();
break;
case OpTypes::Starting:
case OpTypes::Running:
qCDebug(deviceSharePluginLog) << "Stopping project on device" << m_currentDeviceId;
device->sendProjectStopped();
break;
}
emit projectStopping(m_currentDeviceId);
} }
DeviceManagerWidget *DeviceManager::widget() DeviceManagerWidget *DeviceManager::widget()

View File

@@ -3,14 +3,17 @@
#pragma once #pragma once
#include <qmldesigner/components/componentcore/resourcegenerator.h>
#include "deviceinfo.h"
#include <QPointer> #include <QPointer>
#include <QUdpSocket>
#include <QWebSocketServer>
#include "device.h"
QT_BEGIN_NAMESPACE
class QUdpSocket;
QT_END_NAMESPACE
namespace QmlDesigner::DeviceShare { namespace QmlDesigner::DeviceShare {
class Device;
class DeviceManagerWidget; class DeviceManagerWidget;
class DeviceManager : public QObject class DeviceManager : public QObject
@@ -36,8 +39,10 @@ public:
bool addDevice(const QString &ip); bool addDevice(const QString &ip);
void removeDevice(const QString &deviceId); void removeDevice(const QString &deviceId);
void removeDeviceAt(int index); void removeDeviceAt(int index);
bool sendProjectFile(const QString &deviceId, const QString &projectFile);
bool stopRunningProject(const QString &deviceId); // project management functions
void runProject(const QString &deviceId); // async
void stopProject(); // async
DeviceManagerWidget *widget(); DeviceManagerWidget *widget();
@@ -48,7 +53,22 @@ private:
// settings // settings
QString m_settingsPath; QString m_settingsPath;
QString m_uuid; QString m_designStudioId;
enum class ErrTypes {
NoError,
InternalError,
ProjectPackingError,
ProjectSendingError,
ProjectStartError
};
enum class OpTypes { Stopped, Packing, Sending, Starting, Running };
OpTypes m_currentState;
QString m_currentDeviceId;
QString m_currentQtKitVersion;
bool m_processInterrupted;
QmlDesigner::ResourceGenerator m_resourceGenerator;
QPointer<DeviceManagerWidget> m_widget; QPointer<DeviceManagerWidget> m_widget;
@@ -59,16 +79,21 @@ private:
void incomingConnection(); void incomingConnection();
void readSettings(); void readSettings();
void writeSettings(); void writeSettings();
QSharedPointer<Device> initDevice(const DeviceInfo &deviceInfo = DeviceInfo(), void initDevice(const DeviceInfo &deviceInfo = DeviceInfo(),
const DeviceSettings &deviceSettings = DeviceSettings()); const DeviceSettings &deviceSettings = DeviceSettings());
// device signals // device signals
void deviceInfoReceived(const QString &deviceIp, const QString &deviceId); void deviceInfoReceived(const QString &deviceId);
void deviceDisconnected(const QString &deviceId);
QSharedPointer<Device> findDevice(const QString &deviceId) const; QSharedPointer<Device> findDevice(const QString &deviceId) const;
QString generateDeviceAlias() const; QString generateDeviceAlias() const;
// internal functions
void projectPacked(const Utils::FilePath &filePath);
void handleError(const ErrTypes &errType,
const QString &deviceId,
const QString &error = QString());
signals: signals:
void deviceAdded(const QString &deviceId); void deviceAdded(const QString &deviceId);
void deviceRemoved(const QString &deviceId); void deviceRemoved(const QString &deviceId);
@@ -77,10 +102,19 @@ signals:
void deviceActivated(const QString &deviceId); void deviceActivated(const QString &deviceId);
void deviceDeactivated(const QString &deviceId); void deviceDeactivated(const QString &deviceId);
void deviceAliasChanged(const QString &deviceId); void deviceAliasChanged(const QString &deviceId);
void projectPacking(const QString &deviceId);
void projectPackingError(const QString &deviceId, const QString &error);
void projectSendingProgress(const QString &deviceId, const int percentage); void projectSendingProgress(const QString &deviceId, const int percentage);
void projectSendingError(const QString &deviceId, const QString &error);
void projectStarting(const QString &deviceId);
void projectStartingError(const QString &deviceId, const QString &error);
void projectStarted(const QString &deviceId); void projectStarted(const QString &deviceId);
void projectStopping(const QString &deviceId);
void projectStopped(const QString &deviceId); void projectStopped(const QString &deviceId);
void projectLogsReceived(const QString &deviceId, const QString &logs); void projectLogsReceived(const QString &deviceId, const QString &logs);
void internalError(const QString &deviceId, const QString &error);
}; };
} // namespace QmlDesigner::DeviceShare } // namespace QmlDesigner::DeviceShare

View File

@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "devicemanagermodel.h" #include "devicemanagermodel.h"
#include "device.h"
#include "devicemanager.h" #include "devicemanager.h"
namespace QmlDesigner::DeviceShare { namespace QmlDesigner::DeviceShare {

View File

@@ -0,0 +1,45 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#ifdef QT_WEBSOCKET_ENABLED
#include <QWebSocket>
#else
#include <QAbstractSocket>
// QWebSocket mock.
// It is used to avoid linking against QtWebSockets.
namespace QWebSocketProtocol {
enum CloseCode { CloseCodeNormal = 1000 };
enum Version { Unknown = 0, Version13 = 13 };
} // namespace QWebSocketProtocol
class QWebSocket : public QObject
{
Q_OBJECT
public:
QWebSocket() = default;
~QWebSocket() = default;
void setOutgoingFrameSize(int) {}
void setParent(QObject *) {}
void open(const QUrl &) {}
void close() {}
void close(QWebSocketProtocol::CloseCode, const QString &) {}
void abort() {}
void flush() {}
void ping() {}
bool isValid() {return true;}
QAbstractSocket::SocketState state() {return QAbstractSocket::ConnectedState;}
void sendTextMessage(const QString &){}
void sendBinaryMessage(const QByteArray &){}
signals:
void pong(quint64, const QByteArray &);
void textMessageReceived(const QString &);
void disconnected();
void connected();
};
#endif // QT_WEBSOCKETS_LIB

View File

@@ -8,9 +8,7 @@
#include <qmldesigner/qmldesignerplugin.h> #include <qmldesigner/qmldesignerplugin.h>
#ifdef DVCONNECTOR_ENABLED #include <devicesharing/device.h>
#include <resourcegeneratorproxy.h>
#endif
namespace QmlDesigner { namespace QmlDesigner {
@@ -61,7 +59,75 @@ RunManager::RunManager(DeviceShare::DeviceManager &deviceManager)
this, this,
&RunManager::udpateTargets); &RunManager::udpateTargets);
// TODO If device going offline is currently running force stop // If device going offline is currently running force stop
connect(&m_deviceManager,
&DeviceShare::DeviceManager::deviceOffline,
this,
[this](const QString &deviceId) {
qCDebug(runManagerLog) << "Device offline." << deviceId;
if (m_runningTargets.empty())
return;
auto findRunningTarget = [&](const auto &runningTarget) {
return std::visit(overloaded{[](const QPointer<ProjectExplorer::RunControl>) {
return false;
},
[&](const QString &arg) { return arg == deviceId; }},
runningTarget);
};
const auto it = std::ranges::find_if(m_runningTargets, findRunningTarget);
if (it != m_runningTargets.end()) {
std::visit(overloaded{[](const QPointer<ProjectExplorer::RunControl>) {},
[&](const QString &) { m_deviceManager.stopProject(); }},
*it);
}
});
// Packing
connect(&m_deviceManager,
&DeviceShare::DeviceManager::projectPacking,
this,
[this](const QString &deviceId) {
qCDebug(runManagerLog) << "Project packing." << deviceId;
setState(TargetState::Packing);
});
connect(&m_deviceManager,
&DeviceShare::DeviceManager::projectPackingError,
this,
&RunManager::handleError);
// Sending
connect(&m_deviceManager,
&DeviceShare::DeviceManager::projectSendingProgress,
this,
[this](const QString &deviceId, const int percentage) {
qCDebug(runManagerLog) << "Project sending." << deviceId << percentage;
setProgress(percentage);
setState(TargetState::Sending);
});
connect(&m_deviceManager,
&DeviceShare::DeviceManager::projectSendingError,
this,
&RunManager::handleError);
// Starting
connect(&m_deviceManager,
&DeviceShare::DeviceManager::projectStarting,
this,
[this](const QString &deviceId) {
qCDebug(runManagerLog) << "Project starting." << deviceId;
setState(TargetState::Starting);
});
connect(&m_deviceManager,
&DeviceShare::DeviceManager::projectStartingError,
this,
&RunManager::handleError);
connect(&m_deviceManager, &DeviceShare::DeviceManager::internalError, this, &RunManager::handleError);
// Connect Android/Device run/stop project signals // Connect Android/Device run/stop project signals
connect(&m_deviceManager, connect(&m_deviceManager,
@@ -72,8 +138,7 @@ RunManager::RunManager(DeviceShare::DeviceManager &deviceManager)
m_runningTargets.append(deviceId); m_runningTargets.append(deviceId);
m_state = TargetState::Running; setState(TargetState::Running);
emit stateChanged();
}); });
connect(&m_deviceManager, connect(&m_deviceManager,
&DeviceShare::DeviceManager::projectStopped, &DeviceShare::DeviceManager::projectStopped,
@@ -95,8 +160,7 @@ RunManager::RunManager(DeviceShare::DeviceManager &deviceManager)
if (!m_runningTargets.isEmpty()) if (!m_runningTargets.isEmpty())
return; return;
m_state = TargetState::NotRunning; setState(TargetState::NotRunning);
emit stateChanged();
}); });
// Connect Creator run/stop project signals // Connect Creator run/stop project signals
@@ -109,8 +173,7 @@ RunManager::RunManager(DeviceShare::DeviceManager &deviceManager)
m_runningTargets.append(QPointer(runControl)); m_runningTargets.append(QPointer(runControl));
m_state = TargetState::Running; setState(TargetState::Running);
emit stateChanged();
}); });
connect(projectExplorerPlugin, connect(projectExplorerPlugin,
&ProjectExplorer::ProjectExplorerPlugin::runControlStoped, &ProjectExplorer::ProjectExplorerPlugin::runControlStoped,
@@ -132,8 +195,7 @@ RunManager::RunManager(DeviceShare::DeviceManager &deviceManager)
if (!m_runningTargets.isEmpty()) if (!m_runningTargets.isEmpty())
return; return;
m_state = TargetState::NotRunning; setState(TargetState::NotRunning);
emit stateChanged();
}); });
udpateTargets(); udpateTargets();
@@ -180,10 +242,7 @@ void RunManager::toggleCurrentTarget()
if (!arg.isNull()) if (!arg.isNull())
arg.get()->initiateStop(); arg.get()->initiateStop();
}, },
[](const QString arg) { [](const QString &) { deviceManager()->stopProject(); }},
if (!arg.isEmpty())
deviceManager()->stopRunningProject(arg);
}},
runningTarget); runningTarget);
} }
return; return;
@@ -199,9 +258,16 @@ void RunManager::toggleCurrentTarget()
return; return;
} }
setError("");
std::visit([&](const auto &arg) { arg.run(); }, *target); std::visit([&](const auto &arg) { arg.run(); }, *target);
} }
void RunManager::cancelCurrentTarget()
{
deviceManager()->stopProject();
}
int RunManager::currentTargetIndex() const int RunManager::currentTargetIndex() const
{ {
return runTargetIndex(m_currentTargetId); return runTargetIndex(m_currentTargetId);
@@ -230,6 +296,45 @@ bool RunManager::selectRunTarget(const QString &targetName)
return selectRunTarget(Utils::Id::fromString(targetName)); return selectRunTarget(Utils::Id::fromString(targetName));
} }
void RunManager::setState(TargetState state)
{
if (state == m_state)
return;
m_state = state;
emit stateChanged();
}
RunManager::TargetState RunManager::state() const
{
return m_state;
}
void RunManager::setProgress(int progress)
{
m_progress = progress;
emit progressChanged();
}
int RunManager::progress() const
{
return m_progress;
}
void RunManager::setError(const QString &error)
{
if (error == m_error)
return;
m_error = error;
emit errorChanged();
}
const QString &RunManager::error() const
{
return m_error;
}
std::optional<Target> RunManager::runTarget(Utils::Id targetId) const std::optional<Target> RunManager::runTarget(Utils::Id targetId) const
{ {
auto find_id = [&](const auto &target) { auto find_id = [&](const auto &target) {
@@ -260,6 +365,13 @@ int RunManager::runTargetIndex(Utils::Id targetId) const
return -1; return -1;
} }
void RunManager::handleError(const QString &deviceId, const QString &error)
{
qCDebug(runManagerLog) << "Error" << deviceId << error;
setError(error);
setState(TargetState::NotRunning);
}
QString NormalTarget::name() const QString NormalTarget::name() const
{ {
return "Default"; return "Default";
@@ -306,8 +418,8 @@ AndroidTarget::AndroidTarget(const QString &deviceId)
QString AndroidTarget::name() const QString AndroidTarget::name() const
{ {
if (auto devcieSettings = deviceManager()->deviceSettings(m_deviceId)) if (auto deviceSettings = deviceManager()->deviceSettings(m_deviceId))
return devcieSettings->alias(); return deviceSettings->alias();
return {}; return {};
} }
@@ -329,10 +441,8 @@ void AndroidTarget::run() const
{ {
if (!ProjectExplorer::ProjectExplorerPlugin::saveModifiedFiles()) if (!ProjectExplorer::ProjectExplorerPlugin::saveModifiedFiles())
return; return;
#ifdef DVCONNECTOR_ENABLED
auto qmlrcPath = DesignViewer::ResourceGeneratorProxy().createResourceFileSync(); deviceManager()->runProject(m_deviceId);
deviceManager()->sendProjectFile(m_deviceId, qmlrcPath->toString());
#endif
} }
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -54,7 +54,7 @@ class RunManager : public QObject
public: public:
explicit RunManager(DeviceShare::DeviceManager &deviceManager); explicit RunManager(DeviceShare::DeviceManager &deviceManager);
enum TargetState { Running, NotRunning }; enum TargetState { Packing, Sending, Starting, Running, NotRunning };
Q_ENUM(TargetState) Q_ENUM(TargetState)
void udpateTargets(); void udpateTargets();
@@ -62,18 +62,28 @@ public:
const QList<Target> targets() const; const QList<Target> targets() const;
void toggleCurrentTarget(); void toggleCurrentTarget();
void cancelCurrentTarget();
int currentTargetIndex() const; int currentTargetIndex() const;
bool selectRunTarget(Utils::Id id); bool selectRunTarget(Utils::Id id);
bool selectRunTarget(const QString &targetName); bool selectRunTarget(const QString &targetName);
TargetState state() const { return m_state; } void setState(TargetState state);
TargetState state() const;
void setProgress(int progress);
int progress() const;
void setError(const QString &error);
const QString &error() const;
private: private:
std::optional<Target> runTarget(Utils::Id targetId) const; std::optional<Target> runTarget(Utils::Id targetId) const;
int runTargetIndex(Utils::Id targetId) const; int runTargetIndex(Utils::Id targetId) const;
void handleError(const QString &deviceId, const QString &error);
DeviceShare::DeviceManager &m_deviceManager; DeviceShare::DeviceManager &m_deviceManager;
QList<Target> m_targets; QList<Target> m_targets;
@@ -82,11 +92,15 @@ private:
QList<RunningTarget> m_runningTargets; QList<RunningTarget> m_runningTargets;
TargetState m_state = TargetState::NotRunning; TargetState m_state = TargetState::NotRunning;
int m_progress = 0;
QString m_error;
signals: signals:
void runTargetChanged(); void runTargetChanged();
void stateChanged(); void stateChanged();
void targetsChanged(); void targetsChanged();
void progressChanged();
void errorChanged();
}; };
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -489,6 +489,14 @@ ToolBarBackend::ToolBarBackend(QObject *parent)
&RunManager::stateChanged, &RunManager::stateChanged,
this, this,
&ToolBarBackend::runManagerStateChanged); &ToolBarBackend::runManagerStateChanged);
connect(&QmlDesignerPlugin::runManager(),
&RunManager::progressChanged,
this,
&ToolBarBackend::runManagerProgressChanged);
connect(&QmlDesignerPlugin::runManager(),
&RunManager::errorChanged,
this,
&ToolBarBackend::runManagerErrorChanged);
} }
void ToolBarBackend::registerDeclarativeType() void ToolBarBackend::registerDeclarativeType()
@@ -713,6 +721,11 @@ void ToolBarBackend::toggleRunning()
QmlDesignerPlugin::runManager().toggleCurrentTarget(); QmlDesignerPlugin::runManager().toggleCurrentTarget();
} }
void ToolBarBackend::cancelRunning()
{
QmlDesignerPlugin::runManager().cancelCurrentTarget();
}
bool ToolBarBackend::canGoBack() const bool ToolBarBackend::canGoBack() const
{ {
QTC_ASSERT(designModeWidget(), return false); QTC_ASSERT(designModeWidget(), return false);
@@ -901,6 +914,16 @@ int ToolBarBackend::runManagerState() const
return QmlDesignerPlugin::runManager().state(); return QmlDesignerPlugin::runManager().state();
} }
int ToolBarBackend::runManagerProgress() const
{
return QmlDesignerPlugin::runManager().progress();
}
QString ToolBarBackend::runManagerError() const
{
return QmlDesignerPlugin::runManager().error();
}
#ifdef DVCONNECTOR_ENABLED #ifdef DVCONNECTOR_ENABLED
DesignViewer::DVConnector *ToolBarBackend::designViewerConnector() DesignViewer::DVConnector *ToolBarBackend::designViewerConnector()
{ {

View File

@@ -128,6 +128,8 @@ class ToolBarBackend : public QObject
Q_PROPERTY(int runTargetIndex READ runTargetIndex NOTIFY runTargetIndexChanged) Q_PROPERTY(int runTargetIndex READ runTargetIndex NOTIFY runTargetIndexChanged)
Q_PROPERTY(int runManagerState READ runManagerState NOTIFY runManagerStateChanged) Q_PROPERTY(int runManagerState READ runManagerState NOTIFY runManagerStateChanged)
Q_PROPERTY(int runManagerProgress READ runManagerProgress NOTIFY runManagerProgressChanged)
Q_PROPERTY(QString runManagerError READ runManagerError NOTIFY runManagerErrorChanged)
#ifdef DVCONNECTOR_ENABLED #ifdef DVCONNECTOR_ENABLED
Q_PROPERTY(DesignViewer::DVConnector *designViewerConnector READ designViewerConnector CONSTANT) Q_PROPERTY(DesignViewer::DVConnector *designViewerConnector READ designViewerConnector CONSTANT)
@@ -155,6 +157,7 @@ public:
Q_INVOKABLE void openDeviceManager(); Q_INVOKABLE void openDeviceManager();
Q_INVOKABLE void selectRunTarget(const QString &targetName); Q_INVOKABLE void selectRunTarget(const QString &targetName);
Q_INVOKABLE void toggleRunning(); Q_INVOKABLE void toggleRunning();
Q_INVOKABLE void cancelRunning();
bool canGoBack() const; bool canGoBack() const;
bool canGoForward() const; bool canGoForward() const;
@@ -190,6 +193,8 @@ public:
int runTargetIndex() const; int runTargetIndex() const;
int runManagerState() const; int runManagerState() const;
int runManagerProgress() const;
QString runManagerError() const;
#ifdef DVCONNECTOR_ENABLED #ifdef DVCONNECTOR_ENABLED
DesignViewer::DVConnector *designViewerConnector(); DesignViewer::DVConnector *designViewerConnector();
@@ -217,6 +222,8 @@ signals:
void runTargetIndexChanged(); void runTargetIndexChanged();
void runManagerStateChanged(); void runManagerStateChanged();
void runManagerProgressChanged();
void runManagerErrorChanged();
private: private:
void setupWorkspaces(); void setupWorkspaces();

View File

@@ -15,12 +15,12 @@
namespace QmlDesigner { namespace QmlDesigner {
template<typename Timer> template<typename Timer>
class DirectoryPathCompressor class DirectoryPathCompressor final
{ {
public: public:
DirectoryPathCompressor() { m_timer.setSingleShot(true); } DirectoryPathCompressor() { m_timer.setSingleShot(true); }
virtual ~DirectoryPathCompressor() = default; ~DirectoryPathCompressor() = default;
void addSourceContextId(SourceContextId sourceContextId) void addSourceContextId(SourceContextId sourceContextId)
{ {
@@ -34,12 +34,18 @@ public:
restartTimer(); restartTimer();
} }
SourceContextIds takeSourceContextIds() { return std::move(m_sourceContextIds); } const SourceContextIds &sourceContextIds() { return m_sourceContextIds; }
virtual void setCallback(std::function<void(QmlDesigner::SourceContextIds &&)> &&callback) virtual void setCallback(std::function<void(const QmlDesigner::SourceContextIds &)> &&callback)
{ {
QObject::connect(&m_timer, &Timer::timeout, [this, callback = std::move(callback)] { if (connection)
callback(takeSourceContextIds()); QObject::disconnect(connection);
connection = QObject::connect(&m_timer, &Timer::timeout, [this, callback = std::move(callback)] {
try {
callback(m_sourceContextIds);
m_sourceContextIds.clear();
} catch (const std::exception &) {
}
}); });
} }
@@ -53,8 +59,22 @@ public:
return m_timer; return m_timer;
} }
private:
struct ConnectionGuard
{
~ConnectionGuard()
{
if (connection)
QObject::disconnect(connection);
}
QMetaObject::Connection &connection;
};
private: private:
SourceContextIds m_sourceContextIds; SourceContextIds m_sourceContextIds;
QMetaObject::Connection connection;
ConnectionGuard connectionGuard{connection};
Timer m_timer; Timer m_timer;
}; };

View File

@@ -49,14 +49,10 @@ public:
&FileSystemWatcher::directoryChanged, &FileSystemWatcher::directoryChanged,
[&](const QString &path) { compressChangedDirectoryPath(path); }); [&](const QString &path) { compressChangedDirectoryPath(path); });
m_directoryPathCompressor.setCallback([&](QmlDesigner::SourceContextIds &&sourceContextIds) { m_directoryPathCompressor.setCallback(
addChangedPathForFilePath(std::move(sourceContextIds)); [&](const QmlDesigner::SourceContextIds &sourceContextIds) {
}); addChangedPathForFilePath(sourceContextIds);
} });
~ProjectStoragePathWatcher()
{
m_directoryPathCompressor.setCallback([&](SourceContextIds &&) {});
} }
void updateIdPaths(const std::vector<IdPaths> &idPaths) override void updateIdPaths(const std::vector<IdPaths> &idPaths) override
@@ -324,7 +320,7 @@ public:
m_pathCache.sourceContextId(Utils::PathString{path})); m_pathCache.sourceContextId(Utils::PathString{path}));
} }
WatcherEntries watchedEntriesForPaths(QmlDesigner::SourceContextIds &&sourceContextIds) WatcherEntries watchedEntriesForPaths(const QmlDesigner::SourceContextIds &sourceContextIds)
{ {
WatcherEntries foundEntries; WatcherEntries foundEntries;
foundEntries.reserve(m_watchedEntries.size()); foundEntries.reserve(m_watchedEntries.size());
@@ -375,10 +371,10 @@ public:
return idPaths; return idPaths;
} }
void addChangedPathForFilePath(SourceContextIds &&sourceContextIds) void addChangedPathForFilePath(const SourceContextIds &sourceContextIds)
{ {
if (m_notifier) { if (m_notifier) {
WatcherEntries foundEntries = watchedEntriesForPaths(std::move(sourceContextIds)); WatcherEntries foundEntries = watchedEntriesForPaths(sourceContextIds);
SourceIds watchedSourceIds = watchedPaths(foundEntries); SourceIds watchedSourceIds = watchedPaths(foundEntries);

View File

@@ -248,6 +248,28 @@ QStringList DSStore::collectionNames() const
return names; return names;
} }
ThemeProperty DSStore::resolvedDSBinding(QStringView binding) const
{
const auto parts = binding.split('.', Qt::SkipEmptyParts);
if (parts.size() != 3)
return {};
const auto &collectionName = parts[0];
auto itr = m_collections.find(collectionName.toString());
if (itr == m_collections.end())
return {};
const DSThemeManager &boundCollection = itr->second;
const auto &propertyName = parts[2].toLatin1();
if (const auto group = boundCollection.groupType(propertyName)) {
auto property = boundCollection.property(boundCollection.activeTheme(), *group, propertyName);
if (property)
return property->isBinding ? resolvedDSBinding(property->value.toString()) : *property;
}
return {};
}
QString DSStore::uniqueCollectionName(const QString &hint) const QString DSStore::uniqueCollectionName(const QString &hint) const
{ {
return UniqueName::generateTypeName(hint, "Collection", [this](const QString &t) { return UniqueName::generateTypeName(hint, "Collection", [this](const QString &t) {

View File

@@ -38,6 +38,8 @@ public:
std::optional<Utils::FilePath> moduleDirPath() const; std::optional<Utils::FilePath> moduleDirPath() const;
QStringList collectionNames() const; QStringList collectionNames() const;
ThemeProperty resolvedDSBinding(QStringView binding) const;
private: private:
QString uniqueCollectionName(const QString &hint) const; QString uniqueCollectionName(const QString &hint) const;
std::optional<QString> loadCollection(const QString &typeName, const Utils::FilePath &qmlFilePath); std::optional<QString> loadCollection(const QString &typeName, const Utils::FilePath &qmlFilePath);

View File

@@ -199,7 +199,7 @@ void DSThemeGroup::decorate(ThemeId theme, ModelNode themeNode, bool wrapInGroup
for (auto &[propName, values] : m_values) { for (auto &[propName, values] : m_values) {
auto themeValue = values.find(theme); auto themeValue = values.find(theme);
if (themeValue != values.end()) if (themeValue != values.end())
addProperty(targetNode, propName, themeValue->second); addProperty(targetNode, propName, themeValue->second, wrapInGroups);
} }
} }
@@ -224,21 +224,18 @@ std::vector<PropertyName> DSThemeGroup::propertyNames() const
return names; return names;
} }
void DSThemeGroup::addProperty(ModelNode n, PropertyNameView propName, const PropertyData &data) const void DSThemeGroup::addProperty(ModelNode n, PropertyNameView propName, const PropertyData &data, bool createNewProperty) const
{ {
auto metaInfo = n.model()->metaInfo(n.type());
const bool propDefined = metaInfo.property(propName).isValid();
const auto typeName = groupTypeName(m_type); const auto typeName = groupTypeName(m_type);
if (data.isBinding) { if (data.isBinding) {
if (propDefined) if (!createNewProperty)
n.bindingProperty(propName).setExpression(data.value.toString()); n.bindingProperty(propName).setExpression(data.value.toString());
else if (auto bindingProp = n.bindingProperty(propName)) else if (auto bindingProp = n.bindingProperty(propName))
bindingProp.setDynamicTypeNameAndExpression(*typeName, data.value.toString()); bindingProp.setDynamicTypeNameAndExpression(*typeName, data.value.toString());
else else
qCDebug(dsLog) << "Assigning invalid binding" << propName << n.id(); qCDebug(dsLog) << "Assigning invalid binding" << propName << n.id();
} else { } else {
if (propDefined) if (!createNewProperty)
n.variantProperty(propName).setValue(data.value); n.variantProperty(propName).setValue(data.value);
else if (auto nodeProp = n.variantProperty(propName)) else if (auto nodeProp = n.variantProperty(propName))
nodeProp.setDynamicTypeNameAndValue(*typeName, data.value); nodeProp.setDynamicTypeNameAndValue(*typeName, data.value);

View File

@@ -54,7 +54,7 @@ public:
std::vector<PropertyName> propertyNames() const; std::vector<PropertyName> propertyNames() const;
private: private:
void addProperty(ModelNode n, PropertyNameView propName, const PropertyData &data) const; void addProperty(ModelNode n, PropertyNameView propName, const PropertyData &data, bool createNewProperty) const;
private: private:
const GroupType m_type; const GroupType m_type;

Some files were not shown because too many files have changed in this diff Show More