forked from qt-creator/qt-creator
Merge "Merge remote-tracking branch 'origin/qds/dev'"
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
\image qtcreator-axivion-annotation.webp {Annotation popup}
|
||||
|
@@ -140,7 +140,7 @@
|
||||
\li Select \uicontrol Projects > \uicontrol {Project Settings} >
|
||||
\uicontrol {Clang Tools}.
|
||||
\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 In \uicontrol {Suppressed diagnostics}, you can view the suppression
|
||||
list for a project and to remove diagnostics from it.
|
||||
@@ -196,7 +196,7 @@
|
||||
generated during the build. For big projects, not building the
|
||||
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.
|
||||
|
||||
\li In the \uicontrol {Parallel jobs} field, select the number of jobs
|
||||
|
@@ -122,7 +122,7 @@
|
||||
Memcheck also reports uses of uninitialised values, most commonly with the
|
||||
message \uicontrol {Conditional jump or move depends on uninitialised value(s).}
|
||||
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.
|
||||
|
||||
\section1 Viewing a Summary
|
||||
|
@@ -94,7 +94,7 @@
|
||||
source files are located. To hide the subfolder names and arrange the files
|
||||
only according to their source group, select \preferences >
|
||||
\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 >
|
||||
\uicontrol {Run CMake}.
|
||||
|
||||
|
@@ -1109,7 +1109,7 @@
|
||||
If you cannot debug Qt objects because their data is corrupted, you can
|
||||
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 Debugger > \uicontrol {Locals & Expressions}.
|
||||
|
||||
@@ -2166,7 +2166,7 @@
|
||||
\section1 Structure Members Are Not Sorted According to Structure Layout
|
||||
|
||||
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
|
||||
context menu in the \uicontrol Locals and \uicontrol Expressions views.
|
||||
|
||||
|
@@ -166,7 +166,7 @@
|
||||
|
||||
When completion is invoked manually, \QC completes the common prefix of the
|
||||
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.
|
||||
|
||||
Select the \uicontrol {Automatically split strings} check box to split
|
||||
|
@@ -68,7 +68,7 @@
|
||||
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}
|
||||
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.
|
||||
|
||||
To use a context-specific margin when available, select the
|
||||
|
@@ -34,14 +34,14 @@
|
||||
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
|
||||
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.
|
||||
|
||||
To improve the readability of text in the editor, adjust the line spacing in
|
||||
the \uicontrol {Line spacing} field.
|
||||
|
||||
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.
|
||||
|
||||
\sa {Behavior}, {Change editor colors}
|
||||
|
@@ -88,7 +88,7 @@
|
||||
\endlist
|
||||
|
||||
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
|
||||
services generally enabled, but switch them off for certain projects, or vice versa.
|
||||
|
||||
@@ -337,7 +337,7 @@
|
||||
\li Select \uicontrol Projects > \uicontrol {Project Settings} >
|
||||
\uicontrol {Clangd}.
|
||||
\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 Specify \uicontrol Clangd preferences for the project.
|
||||
\endlist
|
||||
|
@@ -230,7 +230,7 @@
|
||||
filter to locate the files.
|
||||
|
||||
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.
|
||||
|
||||
\image qtcreator-locator-filter-edit-md.webp {Filter Configuration dialog}
|
||||
|
@@ -81,8 +81,7 @@
|
||||
\uicontrol {Request Copilot Suggestion} in the \l{Navigate with locator}
|
||||
{locator}.
|
||||
|
||||
Hover the mouse over a suggestion to show a toolbar with
|
||||
\inlineimage icons/prev.png
|
||||
Hover over a suggestion to show a toolbar with \inlineimage icons/prev.png
|
||||
and \inlineimage icons/next.png
|
||||
buttons for cycling between Copilot suggestions.
|
||||
|
||||
|
@@ -47,8 +47,8 @@
|
||||
To use a locator filter:
|
||||
|
||||
\list
|
||||
\li Type the locator filter prefix followed by \key Space. The prefix
|
||||
is usually short, from one to three characters. Then type the search
|
||||
\li Enter the locator filter prefix followed by \key Space. The prefix
|
||||
is usually short, from one to three characters. Then enter the search
|
||||
string (for example, a filename or class name) or the command to
|
||||
execute.
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
selected filter.
|
||||
\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
|
||||
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
|
||||
|
@@ -68,7 +68,7 @@
|
||||
select \preferences > \uicontrol {Text Editor} >
|
||||
\uicontrol Behavior > \uicontrol Typing.
|
||||
|
||||
To disable automatic indentation, deselect the
|
||||
To disable automatic indentation, clear the
|
||||
\uicontrol {Enable automatic indentation} check box.
|
||||
|
||||
You can specify how the indentation is decreased when you press
|
||||
|
@@ -368,7 +368,7 @@
|
||||
|
||||
If a Qt Quick test case does not have a name, it is marked
|
||||
\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
|
||||
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 deselect all message types, select \uicontrol {Uncheck All Filters}.
|
||||
To clear all message types, select \uicontrol {Uncheck All Filters}.
|
||||
|
||||
\section1 Blacklisting Tests
|
||||
|
||||
|
@@ -15,7 +15,7 @@
|
||||
|
||||
To set preferences for documentation comments for a particular project,
|
||||
select \uicontrol Projects > \uicontrol {Project Settings} >
|
||||
\uicontrol {Documentation Comments}, and deselect the
|
||||
\uicontrol {Documentation Comments}, and clear the
|
||||
\uicontrol {Use global settings} check box.
|
||||
|
||||
\image qtcreator-preferences-documentation-comments.webp {Documentation Comments settings}
|
||||
|
@@ -137,7 +137,7 @@
|
||||
Increase the limit if frames are dropped during the recording.
|
||||
\row
|
||||
\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.
|
||||
\row
|
||||
\li \uicontrol {Write command line of FFmpeg calls to General Messages}
|
||||
|
@@ -149,7 +149,7 @@
|
||||
|
||||
To trigger the GDB command that generates a core file while debugging,
|
||||
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
|
||||
another location for the file, including a relative or absolute path, as an
|
||||
argument of the command.
|
||||
@@ -317,8 +317,8 @@
|
||||
in the tab bar, find our that the function is inline, fix the problem, and
|
||||
forget where they came from.
|
||||
|
||||
With \QC, developers can type \c {Ctrl+K m AFun} to find the function.
|
||||
Typically, they only need to type 3 to 4 characters of the function name.
|
||||
With \QC, developers can enter \c {Ctrl+K m AFun} to find the function.
|
||||
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 were.
|
||||
|
||||
|
@@ -100,7 +100,7 @@
|
||||
deployment time and only copies files that have changed since the last
|
||||
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
|
||||
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.
|
||||
|
||||
\section2 Creating a Tarball
|
||||
|
@@ -22,7 +22,7 @@
|
||||
By default, \QC builds qmake projects (that have .pro files) in a separate
|
||||
directory from the source directory, as \l{glossary-shadow-build}
|
||||
{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.
|
||||
|
||||
Select the build directory in the \uicontrol {Build Directory} field. You
|
||||
@@ -38,7 +38,7 @@
|
||||
\section1 Tooltips in Kit Selector
|
||||
|
||||
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}.
|
||||
|
||||
You can create separate versions of project files to keep platform-dependent
|
||||
|
@@ -60,7 +60,7 @@
|
||||
\li Enter code to see the resulting assembly code.
|
||||
\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.
|
||||
|
||||
You can also see the application status and output.
|
||||
|
@@ -85,7 +85,7 @@
|
||||
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
|
||||
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 more information about the project file settings, see
|
||||
|
@@ -66,7 +66,7 @@
|
||||
\list 1
|
||||
\li Select \preferences > \uicontrol Qbs.
|
||||
\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.
|
||||
\li In the \uicontrol {Path to qbs executable} field, you can view
|
||||
and change the path to the Qbs executable.
|
||||
|
@@ -21,7 +21,7 @@
|
||||
\uicontrol {Build Directory} field.
|
||||
|
||||
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}.
|
||||
|
||||
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
|
||||
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
|
||||
in the run settings that will be performed just before
|
||||
running the application.
|
||||
@@ -108,7 +108,7 @@
|
||||
starts.
|
||||
|
||||
\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
|
||||
\uicontrol {Installation directory} field.
|
||||
|
||||
|
@@ -26,7 +26,7 @@
|
||||
\li Select \uicontrol Projects > \uicontrol {Project Settings} >
|
||||
\uicontrol Editor.
|
||||
|
||||
\li Deselect \uicontrol {Use global settings}.
|
||||
\li Clear \uicontrol {Use global settings}.
|
||||
|
||||
\li Specify text editor settings for the project.
|
||||
|
||||
|
@@ -72,7 +72,7 @@
|
||||
\image qtcreator-settings-run-desktop.webp {Run Settings}
|
||||
|
||||
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.
|
||||
|
||||
\section1 Overriding Global Preferences
|
||||
|
@@ -41,7 +41,7 @@
|
||||
\li Select \uicontrol{Next} (on Windows and Linux) or \uicontrol Continue
|
||||
(on \macos) to open the \uicontrol {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.
|
||||
\li In \uicontrol {Base class}, select \b {QWidget} as the base class.
|
||||
\note The \uicontrol {Source file} field is automatically updated to
|
||||
|
@@ -24,7 +24,7 @@
|
||||
\endlist
|
||||
|
||||
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
|
||||
|
||||
|
@@ -10,7 +10,7 @@
|
||||
\title Show and hide the main menu
|
||||
|
||||
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.
|
||||
|
||||
\image qtcreator-without-menubar.webp {Qt Creator without the main menu}
|
||||
|
@@ -24,7 +24,7 @@
|
||||
\inlineimage icons/sort_alphabetically.png
|
||||
(\uicontrol {Sort Alphabetically}).
|
||||
\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}).
|
||||
\endlist
|
||||
*/
|
||||
|
@@ -42,7 +42,7 @@
|
||||
in the context menu or select \key {Ctrl+A}.
|
||||
|
||||
\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
|
||||
\key {Ctrl+F}.
|
||||
|
@@ -108,7 +108,7 @@
|
||||
\li Close all files in a project.
|
||||
\li Close the selected project or all projects except the selected
|
||||
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}
|
||||
check box.
|
||||
\endlist
|
||||
@@ -139,7 +139,7 @@
|
||||
\endlist
|
||||
|
||||
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}).
|
||||
|
||||
Some build systems support adding and removing files to a project in \QC
|
||||
|
@@ -57,7 +57,7 @@
|
||||
or select \key F6 and \key Shift+F6.
|
||||
|
||||
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 {Clear issues list on new build}.
|
||||
|
||||
|
@@ -54,7 +54,7 @@
|
||||
Unified Change Management (UCM) view, they are added to the change set of
|
||||
a UCM activity. By default, the activities are automatically assigned names.
|
||||
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.
|
||||
|
||||
To automatically check out files when you edit them, select the
|
||||
|
@@ -64,7 +64,7 @@
|
||||
within the client workspace. By default, files are automatically opened for
|
||||
editing. To disable this feature, select \preferences >
|
||||
\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.
|
||||
|
||||
To list files that are open for editing, select \uicontrol Tools >
|
||||
|
@@ -145,7 +145,7 @@
|
||||
\row
|
||||
\li Check box - \uicontrol {Clean whitespace}
|
||||
\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
|
||||
\li Combo box - \uicontrol Size
|
||||
\li The font size used in the terminal (in points).
|
||||
|
@@ -202,7 +202,7 @@
|
||||
select \imageplus
|
||||
.
|
||||
\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}.
|
||||
\endlist
|
||||
\image animation-tutorial-property.png
|
||||
|
@@ -116,7 +116,7 @@
|
||||
|
||||
\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.
|
||||
|
||||
For more information about using the timeline, see
|
||||
|
@@ -54,7 +54,7 @@
|
||||
to return to the Standard screen. Finally, at frame 4000, we set the X
|
||||
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.
|
||||
|
||||
When we select \e tripScreen in the \uicontrol Navigator, we can see the
|
||||
|
@@ -158,7 +158,7 @@
|
||||
(\uicontrol {Per Property Recording}) button for the
|
||||
\uicontrol opacity property of \e repeatPassword to start
|
||||
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.
|
||||
\li Move the playhead to frame \e 1000 and change the opacity value to \e 1
|
||||
to show the button.
|
||||
|
@@ -23,7 +23,7 @@
|
||||
\l {progress-bar-control}{Progress Bar} component available in
|
||||
\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 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
|
||||
@@ -31,12 +31,12 @@
|
||||
|
||||
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
|
||||
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
|
||||
to the \uicontrol {2D} view and change its ID to \e root in
|
||||
\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
|
||||
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
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
\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 Properties.
|
||||
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
|
||||
component file. To make sure that the component will contain the timeline,
|
||||
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
|
||||
component to \e root to have it moved as a part of the root component.
|
||||
|
||||
|
@@ -13,7 +13,7 @@
|
||||
|
||||
\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
|
||||
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
|
||||
\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
|
||||
\l {2D} view. The CustomButton component is listed in
|
||||
\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}
|
||||
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
|
||||
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
|
||||
unchecks the previously checked one. Therefore, we set the
|
||||
to be selected at any time, so that selecting another button automatically
|
||||
clears the previously selected one. Therefore, we set the
|
||||
\l {AbstractButton::}{autoExclusive} property to \c true for all
|
||||
button instances.
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
\section1 Creating a side menu
|
||||
|
||||
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 Basic and a \l {slider-control}{Slider} component from
|
||||
\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
|
||||
\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 Navigator to create the effect stack.
|
||||
|
||||
|
@@ -167,7 +167,7 @@
|
||||
|
||||
In our UI, we use connections and states to move between screens. First,
|
||||
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
|
||||
\uicontrol Navigator or the \uicontrol {2D} view: \e StartScreen,
|
||||
\e SettingsScreen, \e PresetsScreen, and \e RunningScreen.
|
||||
|
@@ -149,7 +149,7 @@
|
||||
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
|
||||
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"
|
||||
|
||||
|
@@ -13,7 +13,7 @@
|
||||
that is to be animated, and apply the animation depending on the type of
|
||||
behavior that is required.
|
||||
|
||||
You can drag-and-drop animation components from
|
||||
You can drag animation components from
|
||||
\uicontrol Components > \uicontrol {Default Components} >
|
||||
\uicontrol Animation to the \l Navigator or \l {2D} view to
|
||||
create instances of them.
|
||||
@@ -132,7 +132,7 @@
|
||||
|
||||
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
|
||||
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,
|
||||
one after the other, create an instance of a
|
||||
\uicontrol {Sequential Animation} instead.
|
||||
|
@@ -33,7 +33,7 @@
|
||||
(\uicontrol W) and height (\uicontrol H) of the button in
|
||||
\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
|
||||
in \uicontrol Navigator. This creates a nested component where the
|
||||
Item is the parent of the Rectangle. Components are positioned
|
||||
@@ -57,7 +57,7 @@
|
||||
|
||||
\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.
|
||||
|
||||
\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
|
||||
you plan to use. This specifies the initial size of the button
|
||||
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 Basic to the root component in \uicontrol Navigator.
|
||||
\li Drag-and-drop a \uicontrol Text component to the root component.
|
||||
\li Drag-and-drop a \uicontrol {Mouse Area} to the root component.
|
||||
\li Drag a \uicontrol Text component 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:
|
||||
\list 1
|
||||
\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
|
||||
or other components for the UI, the button component appears in
|
||||
\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
|
||||
of their properties to assign them useful IDs, change their appearance,
|
||||
and set the button text for each button instance, for example.
|
||||
|
@@ -16,7 +16,7 @@
|
||||
To create component instances and edit their properties:
|
||||
|
||||
\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).
|
||||
This creates instances of the components in the current
|
||||
component file.
|
||||
|
@@ -33,7 +33,7 @@
|
||||
capital letter.
|
||||
\li Click \uicontrol Design to open the file in the
|
||||
\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.
|
||||
\li Edit component properties in the \uicontrol Properties view.
|
||||
The available properties depend on the component type. You can
|
||||
|
@@ -124,7 +124,7 @@
|
||||
\section3 Flat buttons
|
||||
|
||||
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 following image shows an example of a flat button:
|
||||
@@ -134,7 +134,7 @@
|
||||
\section3 Icon buttons
|
||||
|
||||
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
|
||||
wizard template, see \l{Creating a Push Button}.
|
||||
|
||||
|
@@ -27,7 +27,7 @@
|
||||
|
||||
\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
|
||||
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
|
||||
@@ -181,7 +181,7 @@
|
||||
(\uicontrol Paused) check box.
|
||||
|
||||
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.
|
||||
|
||||
If the image data comes from a sequential device (such as a socket),
|
||||
|
@@ -26,7 +26,7 @@
|
||||
|
||||
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
|
||||
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}
|
||||
in the context menu.
|
||||
|
@@ -79,7 +79,7 @@
|
||||
pressed.
|
||||
|
||||
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
|
||||
mouse events.
|
||||
|
||||
|
@@ -45,7 +45,7 @@
|
||||
\list 1
|
||||
\li In the \uicontrol {Export path} field, specify the path where
|
||||
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.
|
||||
\li Select the \uicontrol {Export components separately} check box to
|
||||
generate separate metadata files for each component.
|
||||
|
@@ -30,7 +30,7 @@
|
||||
set of predefined properties, some of which control things that are
|
||||
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
|
||||
instances of them. You then change the instances to your liking by modifying
|
||||
their properties in the \l Properties view. The application code is
|
||||
|
@@ -11,12 +11,12 @@
|
||||
You can import 2D assets, such as images, fonts, and sound files, to \QDS to
|
||||
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},
|
||||
\uicontrol Navigator, or \uicontrol {Code} view. Alternatively, select
|
||||
\uicontrol Assets > \imageplus
|
||||
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.
|
||||
|
||||
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
|
||||
project as a text component. Other imported assets, such as sound files,
|
||||
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
|
||||
|
||||
@@ -74,9 +74,9 @@
|
||||
the image files to.
|
||||
\li Select the \uicontrol {Create sub directory} check box to import the
|
||||
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.
|
||||
\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.
|
||||
\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
|
||||
@@ -93,7 +93,7 @@
|
||||
The components that you specified in the design tool are displayed in
|
||||
\uicontrol Components > \uicontrol {My Components} as well as in the
|
||||
\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.
|
||||
|
||||
If asset importer conflicts, warnings, and errors are displayed in the
|
||||
|
@@ -111,7 +111,7 @@
|
||||
|
||||
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 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
|
||||
example speedProp, needs to match a \uicontrol signal or a \uicontrol
|
||||
property in \QDS.
|
||||
@@ -165,7 +165,7 @@
|
||||
bind the root item property. Select the \imageactionicon
|
||||
(\uicontrol Actions) menu next to a property, and then select
|
||||
\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}.
|
||||
|
||||
\image studio-binding-editor.png "The Binding Editor window"
|
||||
|
@@ -268,7 +268,7 @@
|
||||
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"
|
||||
|
||||
\li Drag-and-drop the QML stream component to MyOwnCluster in \uicontrol
|
||||
\li Drag the QML stream component to MyOwnCluster in \uicontrol
|
||||
Navigator.
|
||||
\image exporting-from-qt3ds/34-drag-to-myowncluster-in-navigator.png "Drag the QML stream component to MyOwnCluster"
|
||||
|
||||
|
@@ -9,7 +9,7 @@
|
||||
\title 3D effects
|
||||
|
||||
\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 {Qt Quick 3D Effects} to a \uicontrol SceneEnvironment component
|
||||
in \l Navigator.
|
||||
|
@@ -14,7 +14,7 @@
|
||||
the \l Properties view simultaneously.
|
||||
|
||||
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}
|
||||
view or to \l Navigator > \uicontrol View3D > \uicontrol {Scene Environment}
|
||||
> \uicontrol Scene.
|
||||
|
@@ -47,7 +47,7 @@
|
||||
\l{https://doc.qt.io/qt/qml-qtquick3d-fileinstancing.html}{FileInstancing}
|
||||
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.
|
||||
|
||||
\section2 Instancing properties
|
||||
@@ -84,11 +84,11 @@
|
||||
|
||||
To build an instance table:
|
||||
\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 {Qt Quick 3D} to \uicontrol Scene in
|
||||
\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.
|
||||
\image studio-3d-instancing-instance-list.png "Instance List and Instance Entries in Navigator"
|
||||
|
||||
|
@@ -23,7 +23,7 @@
|
||||
\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
|
||||
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 {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
|
||||
|
@@ -18,7 +18,7 @@
|
||||
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.
|
||||
|
||||
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 {Qt Quick 3D} > \uicontrol {Qt Quick 3D} to \uicontrol Scene in
|
||||
\l Navigator. Then select the model in \uicontrol Navigator, and in
|
||||
|
@@ -386,7 +386,7 @@
|
||||
when you select it again.
|
||||
|
||||
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.
|
||||
|
||||
Select \uicontrol Logging to collect particle system statistics, such as
|
||||
@@ -1131,7 +1131,7 @@
|
||||
them in \uicontrol Particles. Select \imageplus
|
||||
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.
|
||||
*/
|
||||
|
||||
|
@@ -22,7 +22,7 @@
|
||||
|
||||
Logic helpers are invisible components that you can use in connection with
|
||||
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
|
||||
find the logic helpers in \uicontrol {Components}, you need to add the
|
||||
\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.
|
||||
|
||||
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
|
||||
\uicontrol {And Operator} component to \uicontrol Navigator (1). Then, we
|
||||
select the \uicontrol {And Operator} component instance (2) and set its
|
||||
@@ -81,7 +81,7 @@
|
||||
condition is not met.
|
||||
|
||||
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 {Not Operator} component to \uicontrol Navigator. Then, we select
|
||||
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"
|
||||
|
||||
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.
|
||||
|
||||
\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
|
||||
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
|
||||
\uicontrol {Bi Direct. Binding} component to the same parent component in
|
||||
\uicontrol Navigator. Then, we select the bi-directional binding instance
|
||||
@@ -140,7 +140,7 @@
|
||||
the string.
|
||||
|
||||
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,
|
||||
we select the \uicontrol {String Mapper} instance in \uicontrol Navigator
|
||||
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,
|
||||
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.
|
||||
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
|
||||
|
@@ -47,11 +47,11 @@
|
||||
in \l Navigator, you can select \imagelockon
|
||||
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
|
||||
.
|
||||
|
||||
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
|
||||
.
|
||||
|
||||
|
@@ -125,7 +125,7 @@
|
||||
|
||||
\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.
|
||||
This might have surprise effects when using property bindings. In such
|
||||
cases, it may be better to use the \uicontrol Opacity property instead.
|
||||
|
@@ -147,7 +147,7 @@
|
||||
not part of a view.
|
||||
\li In \uicontrol States, select the \uicontrol + symbol to create
|
||||
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
|
||||
is not needed in this view. If you specify the setting for the
|
||||
parent component, all child components inherit it and are also
|
||||
|
@@ -121,13 +121,14 @@ Rectangle {
|
||||
}
|
||||
|
||||
component Cell: Rectangle {
|
||||
required property string display
|
||||
required property var display
|
||||
required property int row
|
||||
required property int column
|
||||
|
||||
required property bool editing
|
||||
|
||||
required property bool isBinding
|
||||
required property var propertyValue
|
||||
|
||||
color: root.backgroundColor
|
||||
implicitWidth: root.cellWidth
|
||||
@@ -227,7 +228,7 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
leftPadding: root.leftPadding
|
||||
|
||||
value: parseInt(numberDelegate.display)
|
||||
value: numberDelegate.display
|
||||
from: -1000 // TODO define min/max
|
||||
to: 1000
|
||||
editable: true
|
||||
@@ -261,7 +262,7 @@ Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: root.leftPadding
|
||||
|
||||
checked: flagDelegate.display === "true"
|
||||
checked: flagDelegate.display
|
||||
text: flagDelegate.display
|
||||
|
||||
onToggled: {
|
||||
@@ -328,7 +329,7 @@ Rectangle {
|
||||
height: parent.height
|
||||
verticalAlignment: Qt.AlignVCenter
|
||||
color: StudioTheme.Values.themeTextColor
|
||||
text: colorDelegate.display
|
||||
text: colorDelegate.propertyValue
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -748,6 +748,10 @@ Item {
|
||||
enabled: !root.propNameError && !root.uniNameError
|
||||
|
||||
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.visible = false
|
||||
}
|
||||
|
@@ -15,6 +15,7 @@ Section {
|
||||
anchors.right: parent.right
|
||||
|
||||
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() {
|
||||
return anchorBackend.isFilled || anchorBackend.isInLayout
|
||||
@@ -67,7 +68,7 @@ Section {
|
||||
|
||||
ControlLabel {
|
||||
text: "X"
|
||||
tooltip: xSpinBox.enabled ? qsTr("X-coordinate") : root.disabledTooltip
|
||||
tooltip: xSpinBox.enabled ? qsTr("X-coordinate") : root.disabledAnchoredTooltip
|
||||
enabled: xSpinBox.enabled
|
||||
}
|
||||
|
||||
@@ -88,7 +89,7 @@ Section {
|
||||
|
||||
ControlLabel {
|
||||
text: "Y"
|
||||
tooltip: xSpinBox.enabled ? qsTr("Y-coordinate") : root.disabledTooltip
|
||||
tooltip: ySpinBox.enabled ? qsTr("Y-coordinate") : root.disabledAnchoredTooltip
|
||||
enabled: ySpinBox.enabled
|
||||
}
|
||||
/*
|
||||
@@ -123,7 +124,7 @@ Section {
|
||||
ControlLabel {
|
||||
//: The width of the object
|
||||
text: qsTr("W", "width")
|
||||
tooltip: widthSpinBox.enabled ? qsTr("Width") : root.disabledTooltip
|
||||
tooltip: widthSpinBox.enabled ? qsTr("Width") : root.disabledAnchoredTooltip
|
||||
enabled: widthSpinBox.enabled
|
||||
}
|
||||
|
||||
@@ -145,7 +146,7 @@ Section {
|
||||
ControlLabel {
|
||||
//: The height of the object
|
||||
text: qsTr("H", "height")
|
||||
tooltip: heightSpinBox.enabled ? qsTr("Height") : root.disabledTooltip
|
||||
tooltip: heightSpinBox.enabled ? qsTr("Height") : root.disabledAnchoredTooltip
|
||||
enabled: heightSpinBox.enabled
|
||||
}
|
||||
/*
|
||||
|
@@ -118,12 +118,15 @@ Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: home.right
|
||||
anchors.leftMargin: 10
|
||||
width: 160
|
||||
width: 170
|
||||
|
||||
runTarget: backend?.runTargetIndex
|
||||
runManagerState: backend?.runManagerState
|
||||
runManagerProgress: backend?.runManagerProgress
|
||||
runManagerError: backend?.runManagerError
|
||||
|
||||
onClicked: backend.toggleRunning()
|
||||
onCancelClicked: backend.cancelRunning()
|
||||
onRunTargetSelected: function(targetName) { backend.selectRunTarget(targetName) }
|
||||
onOpenRunTargets: backend.openDeviceManager()
|
||||
}
|
||||
|
@@ -2,6 +2,8 @@
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Templates as T
|
||||
|
||||
import StudioTheme as StudioTheme
|
||||
@@ -17,11 +19,15 @@ Item {
|
||||
property bool hover: primaryButton.hover || menuButton.hover
|
||||
|
||||
signal clicked()
|
||||
signal cancelClicked()
|
||||
signal runTargetSelected(targetId: string)
|
||||
signal openRunTargets()
|
||||
|
||||
property int runTarget: 0
|
||||
property int runTarget: 0 // index
|
||||
property string runTargetName
|
||||
property int runManagerState: RunManager.NotRunning
|
||||
property int runManagerProgress: 0
|
||||
property string runManagerError
|
||||
|
||||
property int menuWidth: Math.max(160, root.width)
|
||||
|
||||
@@ -33,6 +39,11 @@ Item {
|
||||
return runManagerModel.data(modelIndex, RunManagerModel.Enabled)
|
||||
}
|
||||
|
||||
function getRunTargetName(index: int): string {
|
||||
let modelIndex = runManagerModel.index(index, 0)
|
||||
return runManagerModel.data(modelIndex, "targetName")
|
||||
}
|
||||
|
||||
ToolTipArea {
|
||||
id: toolTipArea
|
||||
anchors.fill: parent
|
||||
@@ -42,16 +53,85 @@ Item {
|
||||
}
|
||||
|
||||
onRunTargetChanged: {
|
||||
root.runTargetName = root.getRunTargetName(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 {
|
||||
target: runManagerModel
|
||||
|
||||
function onModelReset() {
|
||||
root.runTargetName = root.getRunTargetName(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 {
|
||||
T.AbstractButton {
|
||||
id: primaryButton
|
||||
@@ -94,14 +174,30 @@ Item {
|
||||
width: primaryButton.width
|
||||
height: primaryButton.height
|
||||
|
||||
Row {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
RowLayout {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: 8
|
||||
anchors.rightMargin: 8
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 8
|
||||
|
||||
T.Label {
|
||||
Item {
|
||||
width: 20
|
||||
height: primaryButton.height
|
||||
|
||||
ProgressCircle {
|
||||
anchors.centerIn: parent
|
||||
visible: root.showProgress
|
||||
indeterminate: root.runManagerState === RunManager.Packing
|
||||
|| root.runManagerState === RunManager.Starting
|
||||
value: root.runManagerProgress / 100
|
||||
}
|
||||
|
||||
T.Label {
|
||||
visible: !root.showProgress
|
||||
height: primaryButton.height
|
||||
|
||||
font.family: StudioTheme.Constants.iconFont.family
|
||||
font.pixelSize: primaryButton.style.baseIconFontSize
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
@@ -124,17 +220,49 @@ Item {
|
||||
return StudioTheme.Constants.stop_medium
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
T.Label {
|
||||
Layout.fillWidth: true
|
||||
height: primaryButton.height
|
||||
color: primaryButton.enabled ? primaryButton.style.text.idle
|
||||
: primaryButton.style.text.disabled
|
||||
font.pixelSize: primaryButton.style.baseFontSize
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
elide: Text.ElideMiddle
|
||||
text: {
|
||||
let index = runManagerModel.index(root.runTarget, 0)
|
||||
return runManagerModel.data(index, "targetName")
|
||||
if (root.runManagerState === RunManager.Packing)
|
||||
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
|
||||
checked: window.visible
|
||||
enabled: root.runManagerState === RunManager.NotRunning
|
||||
|
||||
onClicked: {
|
||||
if (window.visible) {
|
||||
|
@@ -77,10 +77,7 @@ public:
|
||||
template<typename String>
|
||||
friend void convertToString(String &string, BasicId id)
|
||||
{
|
||||
if (id.isNull())
|
||||
NanotraceHR::convertToString(string, "invalid null");
|
||||
else
|
||||
NanotraceHR::convertToString(string, id.internalId());
|
||||
NanotraceHR::convertToString(string, id.id);
|
||||
}
|
||||
|
||||
friend bool compareId(BasicId first, BasicId second) { return first.id == second.id; }
|
||||
@@ -146,8 +143,10 @@ public:
|
||||
template<typename String>
|
||||
friend void convertToString(String &string, CompoundBasicId id)
|
||||
{
|
||||
convertToString(string, id.mainId());
|
||||
convertToString(string, id.contextId());
|
||||
int mainId = id;
|
||||
int contextId = id >> 32;
|
||||
convertToString(string, mainId);
|
||||
convertToString(string, contextId);
|
||||
}
|
||||
|
||||
friend bool compareId(CompoundBasicId first, CompoundBasicId second)
|
||||
|
@@ -748,15 +748,22 @@ extend_qtc_plugin(QmlDesigner
|
||||
|
||||
extend_qtc_plugin(QmlDesigner
|
||||
CONDITION TARGET Qt::WebSockets
|
||||
DEFINES QT_WEBSOCKET_ENABLED
|
||||
DEPENDS
|
||||
Qt::WebSockets
|
||||
)
|
||||
|
||||
extend_qtc_plugin(QmlDesigner
|
||||
SOURCES_PREFIX components/devicesharing
|
||||
DEPENDS
|
||||
QtCreator::QrCodeGenerator Qt::WebSockets
|
||||
QtCreator::QrCodeGenerator
|
||||
SOURCES
|
||||
device.cpp device.h
|
||||
deviceinfo.cpp deviceinfo.h
|
||||
devicemanager.cpp devicemanager.h
|
||||
devicemanagermodel.cpp devicemanagermodel.h
|
||||
devicemanagerwidget.cpp devicemanagerwidget.h
|
||||
websocketmock.h
|
||||
)
|
||||
|
||||
extend_qtc_plugin(QmlDesigner
|
||||
@@ -774,7 +781,6 @@ extend_qtc_plugin(QmlDesigner
|
||||
PUBLIC_DEFINES DVCONNECTOR_ENABLED
|
||||
SOURCES
|
||||
dvconnector.cpp dvconnector.h
|
||||
resourcegeneratorproxy.cpp resourcegeneratorproxy.h
|
||||
)
|
||||
|
||||
add_qtc_plugin(assetexporterplugin
|
||||
|
@@ -19,127 +19,13 @@
|
||||
|
||||
#include <qtsupport/qtkitaspect.h>
|
||||
|
||||
#include <utils/qtcassert.h>
|
||||
#include <utils/qtcprocess.h>
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QProgressDialog>
|
||||
#include <QTemporaryFile>
|
||||
#include <QXmlStreamWriter>
|
||||
#include <QtConcurrent>
|
||||
|
||||
using namespace Utils;
|
||||
|
||||
namespace QmlDesigner::ResourceGenerator {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
namespace QmlDesigner {
|
||||
QStringList getProjectFileList()
|
||||
{
|
||||
const ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
|
||||
@@ -156,12 +42,163 @@ QStringList getProjectFileList()
|
||||
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());
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
QXmlStreamWriter writer(&qrcFile);
|
||||
writer.setAutoFormatting(true);
|
||||
@@ -180,12 +217,74 @@ bool createQrcFile(const FilePath &qrcFilePath)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool createQmlrcFile(const FilePath &qmlrcFilePath)
|
||||
void ResourceGenerator::createQmlrcAsyncWithName(const QString &projectName)
|
||||
{
|
||||
const FilePath tempQrcFile = qmlrcFilePath.parentDir().pathAppended("temp.qrc");
|
||||
if (!createQrcFile(tempQrcFile))
|
||||
if (m_rccProcess.state() != QProcess::NotRunning) {
|
||||
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;
|
||||
|
||||
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 QtSupport::QtVersion *qtVersion = QtSupport::QtKitAspect::qtVersion(
|
||||
project->activeTarget()->kit());
|
||||
@@ -193,8 +292,7 @@ bool createQmlrcFile(const FilePath &qmlrcFilePath)
|
||||
|
||||
const FilePath rccBinary = qtVersion->rccFilePath();
|
||||
|
||||
Utils::Process rccProcess;
|
||||
rccProcess.setWorkingDirectory(project->projectDirectory());
|
||||
m_rccProcess.setWorkingDirectory(project->projectDirectory());
|
||||
|
||||
const QStringList arguments = {"--binary",
|
||||
"--no-zstd",
|
||||
@@ -204,46 +302,36 @@ bool createQmlrcFile(const FilePath &qmlrcFilePath)
|
||||
"30",
|
||||
"--output",
|
||||
qmlrcFilePath.toUrlishString(),
|
||||
tempQrcFile.toUrlishString()};
|
||||
qrcFilePath.toUrlishString()};
|
||||
|
||||
rccProcess.setCommand({rccBinary, arguments});
|
||||
rccProcess.start();
|
||||
if (!rccProcess.waitForStarted()) {
|
||||
m_rccProcess.setCommand({rccBinary, arguments});
|
||||
m_rccProcess.start();
|
||||
if (!m_rccProcess.waitForStarted()) {
|
||||
Core::MessageManager::writeDisrupting(
|
||||
Tr::tr("QmlDesigner::GenerateResource", "Unable to generate resource file: %1")
|
||||
.arg(qmlrcFilePath.toUrlishString()));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!runAsync) {
|
||||
QByteArray stdOut;
|
||||
QByteArray stdErr;
|
||||
if (!rccProcess.readDataFromProcess(&stdOut, &stdErr)) {
|
||||
rccProcess.stop();
|
||||
Core::MessageManager::writeDisrupting(
|
||||
Tr::tr("QmlDesigner::GenerateResource", "A timeout occurred running \"%1\".")
|
||||
.arg(rccProcess.commandLine().toUserOutput()));
|
||||
if (!m_rccProcess.readDataFromProcess(&stdOut, &stdErr)) {
|
||||
m_rccProcess.stop();
|
||||
Core::MessageManager::writeDisrupting(Tr::tr("A timeout occurred running \"%1\".")
|
||||
.arg(m_rccProcess.commandLine().toUserOutput()));
|
||||
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()));
|
||||
if (m_rccProcess.exitStatus() != QProcess::NormalExit || m_rccProcess.exitCode() != 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace QmlDesigner::ResourceGenerator
|
||||
void ResourceGenerator::cancel()
|
||||
{
|
||||
m_rccProcess.kill();
|
||||
}
|
||||
} // namespace QmlDesigner
|
||||
|
@@ -3,15 +3,41 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utils/filepath.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);
|
||||
QMLDESIGNERCOMPONENTS_EXPORT QStringList getProjectFileList();
|
||||
QMLDESIGNERCOMPONENTS_EXPORT bool createQrcFile(const Utils::FilePath &qrcFilePath);
|
||||
QMLDESIGNERCOMPONENTS_EXPORT bool createQmlrcFile(const Utils::FilePath &qmlrcFilePath);
|
||||
Q_INVOKABLE bool createQrc(const Utils::FilePath &qrcFilePath);
|
||||
Q_INVOKABLE std::optional<Utils::FilePath> createQrc(const QString &projectName = "share");
|
||||
|
||||
} // 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
|
||||
|
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "collectionmodel.h"
|
||||
#include <designsystem/dsstore.h>
|
||||
#include <designsystem/dsthemegroup.h>
|
||||
#include <designsystem/dsthememanager.h>
|
||||
|
||||
@@ -9,12 +10,28 @@
|
||||
|
||||
namespace QmlDesigner {
|
||||
|
||||
CollectionModel::CollectionModel(DSThemeManager *collection)
|
||||
CollectionModel::CollectionModel(DSThemeManager *collection, const DSStore *store)
|
||||
: m_collection(collection)
|
||||
, m_store(store)
|
||||
{
|
||||
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
|
||||
{
|
||||
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)
|
||||
return {};
|
||||
|
||||
const QVariant propertyValue = property->value.toString();
|
||||
const QVariant displayValue = property->isBinding
|
||||
? m_store->resolvedDSBinding(propertyValue.toString()).value
|
||||
: property->value;
|
||||
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
return property->value.toString();
|
||||
case static_cast<int>(Roles::GroupRole):
|
||||
case Roles::ResolvedValueRole:
|
||||
return displayValue;
|
||||
case Roles::PropertyValueRole:
|
||||
return propertyValue;
|
||||
case Roles::GroupRole:
|
||||
return QVariant::fromValue<GroupType>(groupType);
|
||||
case static_cast<int>(Roles::BindingRole):
|
||||
case Roles::BindingRole:
|
||||
return property->isBinding;
|
||||
case Roles::ActiveThemeRole:
|
||||
return m_collection->activeTheme() == themeId;
|
||||
}
|
||||
|
||||
return {};
|
||||
@@ -64,18 +91,26 @@ int CollectionModel::rowCount(const QModelIndex &parent) const
|
||||
|
||||
QVariant CollectionModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
|
||||
return QString::fromLatin1(m_collection->themeName(findThemeId(section)));
|
||||
if (orientation == Qt::Horizontal) {
|
||||
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 (auto propInfo = findPropertyName(section)) {
|
||||
if (role == Qt::DisplayRole)
|
||||
return QString::fromLatin1(propInfo->second);
|
||||
if (role == static_cast<int>(Roles::GroupRole))
|
||||
if (role == Roles::GroupRole)
|
||||
return QVariant::fromValue<GroupType>(propInfo->first);
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -87,8 +122,11 @@ Qt::ItemFlags CollectionModel::flags(const QModelIndex &index) const
|
||||
QHash<int, QByteArray> CollectionModel::roleNames() const
|
||||
{
|
||||
auto roles = QAbstractItemModel::roleNames();
|
||||
roles.insert(static_cast<int>(Roles::GroupRole), "group");
|
||||
roles.insert(static_cast<int>(Roles::BindingRole), "isBinding");
|
||||
roles.insert(Roles::ResolvedValueRole, "resolvedValue");
|
||||
roles.insert(Roles::GroupRole, "group");
|
||||
roles.insert(Roles::BindingRole, "isBinding");
|
||||
roles.insert(Roles::ActiveThemeRole, "isActive");
|
||||
roles.insert(Roles::PropertyValueRole, "propertyValue");
|
||||
return roles;
|
||||
}
|
||||
|
||||
@@ -106,6 +144,7 @@ bool CollectionModel::insertColumns([[maybe_unused]] int column, int count, cons
|
||||
beginResetModel();
|
||||
updateCache();
|
||||
endResetModel();
|
||||
emit themeNameChanged();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -122,6 +161,7 @@ bool CollectionModel::removeColumns(int column, int count, const QModelIndex &pa
|
||||
|
||||
updateCache();
|
||||
endResetModel();
|
||||
emit themeNameChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -190,25 +230,26 @@ bool CollectionModel::setHeaderData(int section,
|
||||
const QVariant &value,
|
||||
int role)
|
||||
{
|
||||
if (role != Qt::EditRole)
|
||||
return false;
|
||||
|
||||
if (section < 0 || (orientation == Qt::Horizontal && section >= columnCount())
|
||||
if (role != Qt::EditRole || section < 0
|
||||
|| (orientation == Qt::Horizontal && section >= columnCount())
|
||||
|| (orientation == Qt::Vertical && section >= rowCount())) {
|
||||
return false; // Out of bounds
|
||||
}
|
||||
|
||||
const auto &newName = value.toString().toUtf8();
|
||||
bool success = false;
|
||||
if (orientation == Qt::Horizontal) {
|
||||
// Theme
|
||||
success = m_collection->renameTheme(findThemeId(section), newName);
|
||||
} else {
|
||||
if (orientation == Qt::Vertical) {
|
||||
// Property Name
|
||||
if (auto propInfo = findPropertyName(section)) {
|
||||
auto [groupType, propName] = *propInfo;
|
||||
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) {
|
||||
|
@@ -10,6 +10,7 @@
|
||||
|
||||
namespace QmlDesigner {
|
||||
class DSThemeManager;
|
||||
class DSStore;
|
||||
using PropInfo = std::pair<GroupType, PropertyName>;
|
||||
|
||||
class CollectionModel : public QAbstractItemModel
|
||||
@@ -17,9 +18,20 @@ class CollectionModel : public QAbstractItemModel
|
||||
Q_OBJECT
|
||||
|
||||
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
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
@@ -52,12 +64,16 @@ public:
|
||||
const QVariant &value,
|
||||
int role = Qt::EditRole) override;
|
||||
|
||||
signals:
|
||||
void themeNameChanged();
|
||||
|
||||
private:
|
||||
ThemeId findThemeId(int column) const;
|
||||
std::optional<PropInfo> findPropertyName(int row) const;
|
||||
|
||||
private:
|
||||
DSThemeManager *m_collection = nullptr;
|
||||
const DSStore *m_store;
|
||||
|
||||
// cache
|
||||
std::vector<ThemeId> m_themeIdList;
|
||||
|
@@ -74,7 +74,8 @@ QStringList DesignSystemInterface::collections() const
|
||||
CollectionModel *DesignSystemInterface::createModel(const QString &typeName, DSThemeManager *collection)
|
||||
{
|
||||
auto [newItr, success] = m_models.try_emplace(typeName,
|
||||
std::make_unique<CollectionModel>(collection));
|
||||
std::make_unique<CollectionModel>(collection,
|
||||
m_store));
|
||||
if (success) {
|
||||
// Otherwise the model will be deleted by the QML engine.
|
||||
QQmlEngine::setObjectOwnership(newItr->second.get(), QQmlEngine::CppOwnership);
|
||||
|
@@ -154,7 +154,7 @@ DVConnector::DVConnector(QObject *parent)
|
||||
});
|
||||
|
||||
connect(&m_resourceGenerator,
|
||||
&ResourceGeneratorProxy::resourceFileCreated,
|
||||
&ResourceGenerator::qmlrcCreated,
|
||||
this,
|
||||
[this](const std::optional<Utils::FilePath> &resourcePath) {
|
||||
emit projectIsUploading();
|
||||
@@ -162,9 +162,7 @@ DVConnector::DVConnector(QObject *parent)
|
||||
uploadProject(projectName, resourcePath->toString());
|
||||
});
|
||||
|
||||
connect(&m_resourceGenerator,
|
||||
&ResourceGeneratorProxy::errorOccurred,
|
||||
[this](const QString &errorString) {
|
||||
connect(&m_resourceGenerator, &ResourceGenerator::errorOccurred, [this](const QString &errorString) {
|
||||
qCWarning(deploymentPluginLog) << "Error occurred while packing the project";
|
||||
emit projectPackingFailed(errorString);
|
||||
});
|
||||
@@ -220,7 +218,7 @@ void DVConnector::projectList()
|
||||
void DVConnector::uploadCurrentProject()
|
||||
{
|
||||
QString projectName = ProjectExplorer::ProjectManager::startupProject()->displayName();
|
||||
m_resourceGenerator.createResourceFileAsync(projectName);
|
||||
m_resourceGenerator.createQmlrcAsyncWithName(projectName);
|
||||
emit projectIsPacking();
|
||||
}
|
||||
|
||||
|
@@ -9,7 +9,7 @@
|
||||
#include <QWebEngineProfile>
|
||||
#include <QWebEngineView>
|
||||
|
||||
#include "resourcegeneratorproxy.h"
|
||||
#include <qmldesigner/components/componentcore/resourcegenerator.h>
|
||||
|
||||
namespace QmlDesigner::DesignViewer {
|
||||
|
||||
@@ -99,7 +99,7 @@ private:
|
||||
QByteArray m_userInfo;
|
||||
|
||||
// other internals
|
||||
ResourceGeneratorProxy m_resourceGenerator;
|
||||
ResourceGenerator m_resourceGenerator;
|
||||
|
||||
struct ReplyEvaluatorData
|
||||
{
|
||||
|
@@ -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
|
@@ -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
|
@@ -5,7 +5,8 @@
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QLatin1String>
|
||||
#include <QThreadPool>
|
||||
|
||||
#include "websocketmock.h"
|
||||
|
||||
namespace QmlDesigner::DeviceShare {
|
||||
|
||||
@@ -20,17 +21,24 @@ constexpr auto stopRunningProject = "stopRunningProject"_L1;
|
||||
namespace PackageFromDevice {
|
||||
using namespace Qt::Literals;
|
||||
constexpr auto deviceInfo = "deviceInfo"_L1;
|
||||
constexpr auto projectReceivingProgress = "projectReceivingProgress"_L1;
|
||||
constexpr auto projectStarting = "projectStarting"_L1;
|
||||
constexpr auto projectRunning = "projectRunning"_L1;
|
||||
constexpr auto projectStopped = "projectStopped"_L1;
|
||||
constexpr auto projectLogs = "projectLogs"_L1;
|
||||
}; // 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)
|
||||
, m_deviceInfo(deviceInfo)
|
||||
, m_deviceSettings(deviceSettings)
|
||||
, m_socket(nullptr)
|
||||
, m_socketWasConnected(false)
|
||||
, m_socketManuallyClosed(false)
|
||||
, m_designStudioId(designStudioId)
|
||||
{
|
||||
qCDebug(deviceSharePluginLog) << "initial device info:" << m_deviceInfo;
|
||||
|
||||
@@ -38,40 +46,33 @@ Device::Device(const DeviceInfo &deviceInfo, const DeviceSettings &deviceSetting
|
||||
m_socket->setOutgoingFrameSize(128000);
|
||||
connect(m_socket.data(), &QWebSocket::textMessageReceived, this, &Device::processTextMessage);
|
||||
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)
|
||||
return;
|
||||
|
||||
m_socketWasConnected = false;
|
||||
m_pingTimer.stop();
|
||||
m_pongTimer.stop();
|
||||
stopPingPong();
|
||||
emit disconnected(m_deviceSettings.deviceId());
|
||||
});
|
||||
connect(m_socket.data(), &QWebSocket::connected, this, [this]() {
|
||||
m_socketWasConnected = true;
|
||||
m_reconnectTimer.stop();
|
||||
m_pingTimer.start();
|
||||
sendDesignStudioReady(m_deviceSettings.deviceId());
|
||||
restartPingPong();
|
||||
sendDesignStudioReady();
|
||||
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.setInterval(m_reconnectTimeout);
|
||||
connect(&m_reconnectTimer, &QTimer::timeout, this, &Device::reconnect);
|
||||
connect(&m_reconnectTimer, &QTimer::timeout, this, [this]() { reconnect(); });
|
||||
|
||||
m_sendTimer.setSingleShot(true);
|
||||
m_sendTimer.setInterval(10);
|
||||
connect(&m_sendTimer, &QTimer::timeout, this, &Device::sendProjectDataInternal, Qt::UniqueConnection);
|
||||
|
||||
initPingPong();
|
||||
reconnect();
|
||||
@@ -114,10 +115,23 @@ void Device::initPingPong()
|
||||
});
|
||||
}
|
||||
|
||||
void Device::reconnect()
|
||||
void Device::stopPingPong()
|
||||
{
|
||||
if (m_socket->state() == QAbstractSocket::ConnectedState)
|
||||
m_socket->close();
|
||||
m_pingTimer.stop();
|
||||
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));
|
||||
m_socket->open(url);
|
||||
@@ -146,19 +160,63 @@ void Device::setDeviceSettings(const DeviceSettings &deviceSettings)
|
||||
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()
|
||||
@@ -186,16 +244,12 @@ bool Device::sendTextMessage(const QLatin1String &dataType, const QJsonValue &da
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Device::sendBinaryMessage(const QByteArray &data)
|
||||
void Device::abortProjectTransmission()
|
||||
{
|
||||
if (!isConnected())
|
||||
return false;
|
||||
return;
|
||||
|
||||
m_lastProjectSize = data.size();
|
||||
m_lastProjectSentSize = 0;
|
||||
sendProjectNotification(m_lastProjectSize);
|
||||
m_socket->sendBinaryMessage(data);
|
||||
return true;
|
||||
m_sendProject = false;
|
||||
}
|
||||
|
||||
void Device::processTextMessage(const QString &data)
|
||||
@@ -213,7 +267,7 @@ void Device::processTextMessage(const QString &data)
|
||||
if (dataType == PackageFromDevice::deviceInfo) {
|
||||
QJsonObject deviceInfo = jsonObj.value("data").toObject();
|
||||
m_deviceInfo.setJsonObject(deviceInfo);
|
||||
emit deviceInfoReady(m_deviceSettings.ipAddress(), m_deviceSettings.deviceId());
|
||||
emit deviceInfoReady(m_deviceSettings.deviceId());
|
||||
} else if (dataType == PackageFromDevice::projectRunning) {
|
||||
qCDebug(deviceSharePluginLog) << "Project started on device" << 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();
|
||||
emit projectStopped(m_deviceSettings.deviceId());
|
||||
} 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 {
|
||||
qCDebug(deviceSharePluginLog) << "Invalid JSON message:" << jsonObj;
|
||||
}
|
||||
|
@@ -4,17 +4,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <QTimer>
|
||||
#include <QWebSocket>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "deviceinfo.h"
|
||||
|
||||
class QWebSocket;
|
||||
namespace QmlDesigner::DeviceShare {
|
||||
|
||||
class Device : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Device(const DeviceInfo &deviceInfo = {},
|
||||
Device(const QString designStudioId,
|
||||
const DeviceInfo &deviceInfo = {},
|
||||
const DeviceSettings &deviceSettings = {},
|
||||
QObject *parent = nullptr);
|
||||
~Device();
|
||||
@@ -26,13 +29,13 @@ public:
|
||||
void setDeviceSettings(const DeviceSettings &deviceSettings);
|
||||
|
||||
// device communication
|
||||
bool sendDesignStudioReady(const QString &uuid);
|
||||
bool sendProjectData(const QByteArray &data);
|
||||
bool sendProjectData(const QByteArray &data, const QString &qtVersion);
|
||||
bool sendProjectStopped();
|
||||
void abortProjectTransmission();
|
||||
|
||||
// socket
|
||||
bool isConnected() const;
|
||||
void reconnect();
|
||||
void reconnect(const QString &closeMessage = QString());
|
||||
|
||||
private slots:
|
||||
void processTextMessage(const QString &data);
|
||||
@@ -43,28 +46,39 @@ private:
|
||||
|
||||
QScopedPointer<QWebSocket> m_socket;
|
||||
bool m_socketWasConnected;
|
||||
bool m_socketManuallyClosed;
|
||||
|
||||
QTimer m_reconnectTimer;
|
||||
QTimer m_pingTimer;
|
||||
QTimer m_pongTimer;
|
||||
QTimer m_sendTimer;
|
||||
|
||||
int m_lastProjectSize;
|
||||
int m_lastProjectSentSize;
|
||||
std::atomic<bool> m_sendProject;
|
||||
QByteArray m_projectData;
|
||||
int m_totalSentSize;
|
||||
int m_lastSentProgress;
|
||||
|
||||
const QString m_designStudioId;
|
||||
|
||||
static constexpr int m_reconnectTimeout = 5000;
|
||||
static constexpr int m_pingTimeout = 10000;
|
||||
static constexpr int m_pongTimeout = 30000;
|
||||
|
||||
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 sendBinaryMessage(const QByteArray &data);
|
||||
|
||||
signals:
|
||||
void connected(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 projectStarting(const QString &deviceId);
|
||||
void projectStarted(const QString &deviceId);
|
||||
void projectStopped(const QString &deviceId);
|
||||
void projectLogsReceived(const QString &deviceId, const QString &logs);
|
||||
|
@@ -127,6 +127,11 @@ void DeviceInfo::setScreenHeight(const int &screenHeight)
|
||||
m_data[keyScreenHeight] = screenHeight;
|
||||
}
|
||||
|
||||
void DeviceInfo::setSelfId(const QString &selfId)
|
||||
{
|
||||
m_data[keySelfId] = selfId;
|
||||
}
|
||||
|
||||
void DeviceInfo::setAppVersion(const QString &appVersion)
|
||||
{
|
||||
m_data[keyAppVersion] = appVersion;
|
||||
|
@@ -27,7 +27,7 @@ protected:
|
||||
class DeviceSettings : public IDeviceData
|
||||
{
|
||||
public:
|
||||
DeviceSettings() = default;
|
||||
using IDeviceData::IDeviceData;
|
||||
|
||||
// Getters
|
||||
bool active() const;
|
||||
@@ -51,7 +51,7 @@ private:
|
||||
class DeviceInfo : public IDeviceData
|
||||
{
|
||||
public:
|
||||
DeviceInfo() = default;
|
||||
using IDeviceData::IDeviceData;
|
||||
|
||||
// Getters
|
||||
QString os() const;
|
||||
@@ -76,7 +76,7 @@ private:
|
||||
static constexpr char keyOsVersion[] = "osVersion";
|
||||
static constexpr char keyScreenWidth[] = "screenWidth";
|
||||
static constexpr char keyScreenHeight[] = "screenHeight";
|
||||
static constexpr char keySelfId[] = "selfId";
|
||||
static constexpr char keySelfId[] = "deviceId";
|
||||
static constexpr char keyArchitecture[] = "architecture";
|
||||
static constexpr char keyAppVersion[] = "appVersion";
|
||||
};
|
||||
|
@@ -2,65 +2,73 @@
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "devicemanager.h"
|
||||
|
||||
#include "device.h"
|
||||
#include "devicemanagerwidget.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QLatin1String>
|
||||
#include <QNetworkDatagram>
|
||||
#include <QNetworkInterface>
|
||||
#include <QUdpSocket>
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
#include <projectexplorer/kitaspect.h>
|
||||
#include <projectexplorer/target.h>
|
||||
#include <qmldesigner/qmldesignerplugin.h>
|
||||
#include <qtsupport/qtkitaspect.h>
|
||||
|
||||
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)
|
||||
: QObject(parent)
|
||||
, m_currentState(OpTypes::Stopped)
|
||||
, m_processInterrupted(false)
|
||||
{
|
||||
QFileInfo fileInfo(Core::ICore::settings()->fileName());
|
||||
m_settingsPath = fileInfo.absolutePath() + "/device_manager.json";
|
||||
readSettings();
|
||||
if (m_uuid.isEmpty()) {
|
||||
m_uuid = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
writeSettings();
|
||||
}
|
||||
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;
|
||||
|
||||
void DeviceManager::initUdpDiscovery()
|
||||
{
|
||||
const QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
|
||||
for (const QNetworkInterface &interface : interfaces) {
|
||||
if (interface.flags().testFlag(QNetworkInterface::IsUp)
|
||||
&& interface.flags().testFlag(QNetworkInterface::IsRunning)) {
|
||||
for (const QNetworkAddressEntry &entry : interface.addressEntries()) {
|
||||
if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
QSharedPointer<QUdpSocket> udpSocket = QSharedPointer<QUdpSocket>::create();
|
||||
connect(udpSocket.data(),
|
||||
&QUdpSocket::readyRead,
|
||||
this,
|
||||
&DeviceManager::incomingDatagram);
|
||||
|
||||
bool retVal = udpSocket->bind(entry.ip(), 53452, QUdpSocket::ShareAddress);
|
||||
|
||||
bool retVal = udpSocket->bind(QHostAddress::AnyIPv4, 53452, QUdpSocket::ShareAddress);
|
||||
if (!retVal) {
|
||||
qCWarning(deviceSharePluginLog)
|
||||
<< "UDP:: Failed to bind to UDP port 53452 on" << entry.ip().toString()
|
||||
<< "on interface" << interface.name()
|
||||
qCWarning(deviceSharePluginLog) << "UDP:: Failed to bind to UDP port 53452 on AnyIPv4"
|
||||
<< ". Error:" << udpSocket->errorString();
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(deviceSharePluginLog) << "UDP:: Listening on" << entry.ip().toString()
|
||||
<< "port" << udpSocket->localPort();
|
||||
connect(udpSocket.data(), &QUdpSocket::readyRead, this, &DeviceManager::incomingDatagram);
|
||||
|
||||
qCDebug(deviceSharePluginLog) << "UDP:: Listening on AnyIPv4 port" << udpSocket->localPort();
|
||||
m_udpSockets.append(udpSocket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceManager::incomingDatagram()
|
||||
@@ -80,16 +88,20 @@ void DeviceManager::incomingDatagram()
|
||||
|
||||
const QString id = message["id"].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) {
|
||||
if (device->deviceInfo().selfId() == id) {
|
||||
found = true;
|
||||
if (device->deviceSettings().ipAddress() != ip) {
|
||||
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;
|
||||
for (const auto &device : m_devices) {
|
||||
QJsonObject deviceInfo;
|
||||
deviceInfo.insert("deviceInfo", device->deviceInfo().jsonObject());
|
||||
deviceInfo.insert("deviceSettings", device->deviceSettings().jsonObject());
|
||||
deviceInfo.insert(JsonKeys::deviceInfo, device->deviceInfo().jsonObject());
|
||||
deviceInfo.insert(JsonKeys::deviceSettings, device->deviceSettings().jsonObject());
|
||||
devices.append(deviceInfo);
|
||||
}
|
||||
|
||||
root.insert("devices", devices);
|
||||
root.insert("uuid", m_uuid);
|
||||
root.insert(JsonKeys::devices, devices);
|
||||
root.insert(JsonKeys::designStudioId, m_designStudioId);
|
||||
|
||||
QJsonDocument doc(root);
|
||||
QFile file(m_settingsPath);
|
||||
@@ -127,15 +139,17 @@ void DeviceManager::readSettings()
|
||||
}
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
|
||||
m_uuid = doc.object()["uuid"].toString();
|
||||
QJsonArray devices = doc.object()["devices"].toArray();
|
||||
m_designStudioId = doc.object()[JsonKeys::designStudioId].toString();
|
||||
if (m_designStudioId.isEmpty()) {
|
||||
m_designStudioId = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
writeSettings();
|
||||
}
|
||||
|
||||
QJsonArray devices = doc.object()[JsonKeys::devices].toArray();
|
||||
for (const QJsonValue &deviceInfoJson : devices) {
|
||||
DeviceInfo deviceInfo;
|
||||
DeviceSettings deviceSettings;
|
||||
deviceInfo.setJsonObject(deviceInfoJson.toObject()["deviceInfo"].toObject());
|
||||
deviceSettings.setJsonObject(deviceInfoJson.toObject()["deviceSettings"].toObject());
|
||||
auto device = initDevice(deviceInfo, deviceSettings);
|
||||
m_devices.append(device);
|
||||
DeviceInfo deviceInfo{deviceInfoJson.toObject()[JsonKeys::deviceInfo].toObject()};
|
||||
DeviceSettings deviceSettings{deviceInfoJson.toObject()[JsonKeys::deviceSettings].toObject()};
|
||||
initDevice(deviceInfo, deviceSettings);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,60 +281,68 @@ bool DeviceManager::addDevice(const QString &ip)
|
||||
deviceSettings.setAlias(generateDeviceAlias());
|
||||
deviceSettings.setDeviceId(QUuid::createUuid().toString(QUuid::WithoutBraces));
|
||||
|
||||
auto device = initDevice({}, deviceSettings);
|
||||
m_devices.append(device);
|
||||
initDevice({}, deviceSettings);
|
||||
writeSettings();
|
||||
emit deviceAdded(deviceSettings.deviceId());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QSharedPointer<Device> DeviceManager::initDevice(const DeviceInfo &deviceInfo,
|
||||
const DeviceSettings &deviceSettings)
|
||||
void DeviceManager::initDevice(const DeviceInfo &deviceInfo, 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);
|
||||
QString deviceId = device->deviceSettings().deviceId();
|
||||
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::projectStarted, this, &DeviceManager::projectStarted);
|
||||
connect(device.data(), &Device::projectStopped, this, &DeviceManager::projectStopped);
|
||||
|
||||
connect(device.data(),
|
||||
&Device::projectLogsReceived,
|
||||
this,
|
||||
[this](const QString deviceId, const QString &logs) {
|
||||
qCDebug(deviceSharePluginLog) << "Log:" << deviceId << logs;
|
||||
emit projectLogsReceived(deviceId, logs);
|
||||
connect(device.data(), &Device::projectStarting, this, [this](const QString &deviceId) {
|
||||
m_currentState = OpTypes::Starting;
|
||||
emit projectStarting(deviceId);
|
||||
});
|
||||
|
||||
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(),
|
||||
m_devices.end(),
|
||||
[deviceId, deviceIp](const auto &device) {
|
||||
return device->deviceSettings().deviceId() == deviceId
|
||||
&& device->deviceSettings().ipAddress() == deviceIp;
|
||||
});
|
||||
auto newDevice = findDevice(deviceId);
|
||||
const QString selfId = newDevice->deviceInfo().selfId();
|
||||
const QString deviceIp = newDevice->deviceSettings().ipAddress();
|
||||
|
||||
auto oldDevIt = std::find_if(m_devices.begin(),
|
||||
m_devices.end(),
|
||||
[deviceId, deviceIp](const auto &device) {
|
||||
return device->deviceSettings().deviceId() == deviceId
|
||||
[selfId, deviceIp](const auto &device) {
|
||||
return device->deviceInfo().selfId() == selfId
|
||||
&& device->deviceSettings().ipAddress() != deviceIp;
|
||||
});
|
||||
|
||||
// if there are 2 devices with the same ID but different IPs, remove the old one
|
||||
// aka: merge devices with the same ID
|
||||
if (oldDevIt != m_devices.end()) {
|
||||
QSharedPointer<Device> oldDevice = *oldDevIt;
|
||||
QSharedPointer<Device> newDevice = *newDevIt;
|
||||
DeviceSettings deviceSettings = oldDevice->deviceSettings();
|
||||
deviceSettings.setIpAddress(newDevice->deviceSettings().ipAddress());
|
||||
newDevice->setDeviceSettings(deviceSettings);
|
||||
m_devices.removeOne(oldDevice);
|
||||
auto oldDevice = *oldDevIt;
|
||||
const QString alias = oldDevice->deviceSettings().alias();
|
||||
m_devices.removeOne(*oldDevIt);
|
||||
DeviceSettings settings = newDevice->deviceSettings();
|
||||
settings.setAlias(alias);
|
||||
newDevice->setDeviceSettings(settings);
|
||||
}
|
||||
|
||||
writeSettings();
|
||||
@@ -328,16 +350,6 @@ void DeviceManager::deviceInfoReceived(const QString &deviceIp, const QString &d
|
||||
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)
|
||||
{
|
||||
auto device = findDevice(deviceId);
|
||||
@@ -360,29 +372,119 @@ void DeviceManager::removeDeviceAt(int index)
|
||||
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 (!device)
|
||||
return false;
|
||||
if (!m_processInterrupted) {
|
||||
if (errType != ErrTypes::NoError)
|
||||
qCWarning(deviceSharePluginLog) << "Handling error" << error << "for device" << deviceId;
|
||||
|
||||
QFile file(projectFile);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCWarning(deviceSharePluginLog) << "Failed to open project file" << projectFile;
|
||||
return false;
|
||||
switch (errType) {
|
||||
case ErrTypes::InternalError:
|
||||
emit internalError(deviceId, error);
|
||||
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;
|
||||
return device->sendProjectData(file.readAll());
|
||||
m_processInterrupted = false;
|
||||
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);
|
||||
if (!device)
|
||||
return false;
|
||||
if (!device) {
|
||||
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()
|
||||
|
@@ -3,14 +3,17 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <qmldesigner/components/componentcore/resourcegenerator.h>
|
||||
|
||||
#include "deviceinfo.h"
|
||||
|
||||
#include <QPointer>
|
||||
#include <QUdpSocket>
|
||||
#include <QWebSocketServer>
|
||||
|
||||
#include "device.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QUdpSocket;
|
||||
QT_END_NAMESPACE
|
||||
namespace QmlDesigner::DeviceShare {
|
||||
|
||||
class Device;
|
||||
class DeviceManagerWidget;
|
||||
|
||||
class DeviceManager : public QObject
|
||||
@@ -36,8 +39,10 @@ public:
|
||||
bool addDevice(const QString &ip);
|
||||
void removeDevice(const QString &deviceId);
|
||||
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();
|
||||
|
||||
@@ -48,7 +53,22 @@ private:
|
||||
|
||||
// settings
|
||||
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;
|
||||
|
||||
@@ -59,16 +79,21 @@ private:
|
||||
void incomingConnection();
|
||||
void readSettings();
|
||||
void writeSettings();
|
||||
QSharedPointer<Device> initDevice(const DeviceInfo &deviceInfo = DeviceInfo(),
|
||||
void initDevice(const DeviceInfo &deviceInfo = DeviceInfo(),
|
||||
const DeviceSettings &deviceSettings = DeviceSettings());
|
||||
|
||||
// device signals
|
||||
void deviceInfoReceived(const QString &deviceIp, const QString &deviceId);
|
||||
void deviceDisconnected(const QString &deviceId);
|
||||
void deviceInfoReceived(const QString &deviceId);
|
||||
|
||||
QSharedPointer<Device> findDevice(const QString &deviceId) 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:
|
||||
void deviceAdded(const QString &deviceId);
|
||||
void deviceRemoved(const QString &deviceId);
|
||||
@@ -77,10 +102,19 @@ signals:
|
||||
void deviceActivated(const QString &deviceId);
|
||||
void deviceDeactivated(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 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 projectStopping(const QString &deviceId);
|
||||
void projectStopped(const QString &deviceId);
|
||||
void projectLogsReceived(const QString &deviceId, const QString &logs);
|
||||
|
||||
void internalError(const QString &deviceId, const QString &error);
|
||||
};
|
||||
|
||||
} // namespace QmlDesigner::DeviceShare
|
||||
|
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "devicemanagermodel.h"
|
||||
#include "device.h"
|
||||
#include "devicemanager.h"
|
||||
|
||||
namespace QmlDesigner::DeviceShare {
|
||||
|
@@ -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
|
@@ -8,9 +8,7 @@
|
||||
|
||||
#include <qmldesigner/qmldesignerplugin.h>
|
||||
|
||||
#ifdef DVCONNECTOR_ENABLED
|
||||
#include <resourcegeneratorproxy.h>
|
||||
#endif
|
||||
#include <devicesharing/device.h>
|
||||
|
||||
namespace QmlDesigner {
|
||||
|
||||
@@ -61,7 +59,75 @@ RunManager::RunManager(DeviceShare::DeviceManager &deviceManager)
|
||||
this,
|
||||
&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(&m_deviceManager,
|
||||
@@ -72,8 +138,7 @@ RunManager::RunManager(DeviceShare::DeviceManager &deviceManager)
|
||||
|
||||
m_runningTargets.append(deviceId);
|
||||
|
||||
m_state = TargetState::Running;
|
||||
emit stateChanged();
|
||||
setState(TargetState::Running);
|
||||
});
|
||||
connect(&m_deviceManager,
|
||||
&DeviceShare::DeviceManager::projectStopped,
|
||||
@@ -95,8 +160,7 @@ RunManager::RunManager(DeviceShare::DeviceManager &deviceManager)
|
||||
if (!m_runningTargets.isEmpty())
|
||||
return;
|
||||
|
||||
m_state = TargetState::NotRunning;
|
||||
emit stateChanged();
|
||||
setState(TargetState::NotRunning);
|
||||
});
|
||||
|
||||
// Connect Creator run/stop project signals
|
||||
@@ -109,8 +173,7 @@ RunManager::RunManager(DeviceShare::DeviceManager &deviceManager)
|
||||
|
||||
m_runningTargets.append(QPointer(runControl));
|
||||
|
||||
m_state = TargetState::Running;
|
||||
emit stateChanged();
|
||||
setState(TargetState::Running);
|
||||
});
|
||||
connect(projectExplorerPlugin,
|
||||
&ProjectExplorer::ProjectExplorerPlugin::runControlStoped,
|
||||
@@ -132,8 +195,7 @@ RunManager::RunManager(DeviceShare::DeviceManager &deviceManager)
|
||||
if (!m_runningTargets.isEmpty())
|
||||
return;
|
||||
|
||||
m_state = TargetState::NotRunning;
|
||||
emit stateChanged();
|
||||
setState(TargetState::NotRunning);
|
||||
});
|
||||
|
||||
udpateTargets();
|
||||
@@ -180,10 +242,7 @@ void RunManager::toggleCurrentTarget()
|
||||
if (!arg.isNull())
|
||||
arg.get()->initiateStop();
|
||||
},
|
||||
[](const QString arg) {
|
||||
if (!arg.isEmpty())
|
||||
deviceManager()->stopRunningProject(arg);
|
||||
}},
|
||||
[](const QString &) { deviceManager()->stopProject(); }},
|
||||
runningTarget);
|
||||
}
|
||||
return;
|
||||
@@ -199,9 +258,16 @@ void RunManager::toggleCurrentTarget()
|
||||
return;
|
||||
}
|
||||
|
||||
setError("");
|
||||
|
||||
std::visit([&](const auto &arg) { arg.run(); }, *target);
|
||||
}
|
||||
|
||||
void RunManager::cancelCurrentTarget()
|
||||
{
|
||||
deviceManager()->stopProject();
|
||||
}
|
||||
|
||||
int RunManager::currentTargetIndex() const
|
||||
{
|
||||
return runTargetIndex(m_currentTargetId);
|
||||
@@ -230,6 +296,45 @@ bool RunManager::selectRunTarget(const QString &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
|
||||
{
|
||||
auto find_id = [&](const auto &target) {
|
||||
@@ -260,6 +365,13 @@ int RunManager::runTargetIndex(Utils::Id targetId) const
|
||||
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
|
||||
{
|
||||
return "Default";
|
||||
@@ -306,8 +418,8 @@ AndroidTarget::AndroidTarget(const QString &deviceId)
|
||||
|
||||
QString AndroidTarget::name() const
|
||||
{
|
||||
if (auto devcieSettings = deviceManager()->deviceSettings(m_deviceId))
|
||||
return devcieSettings->alias();
|
||||
if (auto deviceSettings = deviceManager()->deviceSettings(m_deviceId))
|
||||
return deviceSettings->alias();
|
||||
|
||||
return {};
|
||||
}
|
||||
@@ -329,10 +441,8 @@ void AndroidTarget::run() const
|
||||
{
|
||||
if (!ProjectExplorer::ProjectExplorerPlugin::saveModifiedFiles())
|
||||
return;
|
||||
#ifdef DVCONNECTOR_ENABLED
|
||||
auto qmlrcPath = DesignViewer::ResourceGeneratorProxy().createResourceFileSync();
|
||||
deviceManager()->sendProjectFile(m_deviceId, qmlrcPath->toString());
|
||||
#endif
|
||||
|
||||
deviceManager()->runProject(m_deviceId);
|
||||
}
|
||||
|
||||
} // namespace QmlDesigner
|
||||
|
@@ -54,7 +54,7 @@ class RunManager : public QObject
|
||||
public:
|
||||
explicit RunManager(DeviceShare::DeviceManager &deviceManager);
|
||||
|
||||
enum TargetState { Running, NotRunning };
|
||||
enum TargetState { Packing, Sending, Starting, Running, NotRunning };
|
||||
Q_ENUM(TargetState)
|
||||
|
||||
void udpateTargets();
|
||||
@@ -62,18 +62,28 @@ public:
|
||||
const QList<Target> targets() const;
|
||||
|
||||
void toggleCurrentTarget();
|
||||
void cancelCurrentTarget();
|
||||
|
||||
int currentTargetIndex() const;
|
||||
|
||||
bool selectRunTarget(Utils::Id id);
|
||||
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:
|
||||
std::optional<Target> runTarget(Utils::Id targetId) const;
|
||||
int runTargetIndex(Utils::Id targetId) const;
|
||||
|
||||
void handleError(const QString &deviceId, const QString &error);
|
||||
|
||||
DeviceShare::DeviceManager &m_deviceManager;
|
||||
|
||||
QList<Target> m_targets;
|
||||
@@ -82,11 +92,15 @@ private:
|
||||
QList<RunningTarget> m_runningTargets;
|
||||
|
||||
TargetState m_state = TargetState::NotRunning;
|
||||
int m_progress = 0;
|
||||
QString m_error;
|
||||
|
||||
signals:
|
||||
void runTargetChanged();
|
||||
void stateChanged();
|
||||
void targetsChanged();
|
||||
void progressChanged();
|
||||
void errorChanged();
|
||||
};
|
||||
|
||||
} // namespace QmlDesigner
|
||||
|
@@ -489,6 +489,14 @@ ToolBarBackend::ToolBarBackend(QObject *parent)
|
||||
&RunManager::stateChanged,
|
||||
this,
|
||||
&ToolBarBackend::runManagerStateChanged);
|
||||
connect(&QmlDesignerPlugin::runManager(),
|
||||
&RunManager::progressChanged,
|
||||
this,
|
||||
&ToolBarBackend::runManagerProgressChanged);
|
||||
connect(&QmlDesignerPlugin::runManager(),
|
||||
&RunManager::errorChanged,
|
||||
this,
|
||||
&ToolBarBackend::runManagerErrorChanged);
|
||||
}
|
||||
|
||||
void ToolBarBackend::registerDeclarativeType()
|
||||
@@ -713,6 +721,11 @@ void ToolBarBackend::toggleRunning()
|
||||
QmlDesignerPlugin::runManager().toggleCurrentTarget();
|
||||
}
|
||||
|
||||
void ToolBarBackend::cancelRunning()
|
||||
{
|
||||
QmlDesignerPlugin::runManager().cancelCurrentTarget();
|
||||
}
|
||||
|
||||
bool ToolBarBackend::canGoBack() const
|
||||
{
|
||||
QTC_ASSERT(designModeWidget(), return false);
|
||||
@@ -901,6 +914,16 @@ int ToolBarBackend::runManagerState() const
|
||||
return QmlDesignerPlugin::runManager().state();
|
||||
}
|
||||
|
||||
int ToolBarBackend::runManagerProgress() const
|
||||
{
|
||||
return QmlDesignerPlugin::runManager().progress();
|
||||
}
|
||||
|
||||
QString ToolBarBackend::runManagerError() const
|
||||
{
|
||||
return QmlDesignerPlugin::runManager().error();
|
||||
}
|
||||
|
||||
#ifdef DVCONNECTOR_ENABLED
|
||||
DesignViewer::DVConnector *ToolBarBackend::designViewerConnector()
|
||||
{
|
||||
|
@@ -128,6 +128,8 @@ class ToolBarBackend : public QObject
|
||||
|
||||
Q_PROPERTY(int runTargetIndex READ runTargetIndex NOTIFY runTargetIndexChanged)
|
||||
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
|
||||
Q_PROPERTY(DesignViewer::DVConnector *designViewerConnector READ designViewerConnector CONSTANT)
|
||||
@@ -155,6 +157,7 @@ public:
|
||||
Q_INVOKABLE void openDeviceManager();
|
||||
Q_INVOKABLE void selectRunTarget(const QString &targetName);
|
||||
Q_INVOKABLE void toggleRunning();
|
||||
Q_INVOKABLE void cancelRunning();
|
||||
|
||||
bool canGoBack() const;
|
||||
bool canGoForward() const;
|
||||
@@ -190,6 +193,8 @@ public:
|
||||
|
||||
int runTargetIndex() const;
|
||||
int runManagerState() const;
|
||||
int runManagerProgress() const;
|
||||
QString runManagerError() const;
|
||||
|
||||
#ifdef DVCONNECTOR_ENABLED
|
||||
DesignViewer::DVConnector *designViewerConnector();
|
||||
@@ -217,6 +222,8 @@ signals:
|
||||
|
||||
void runTargetIndexChanged();
|
||||
void runManagerStateChanged();
|
||||
void runManagerProgressChanged();
|
||||
void runManagerErrorChanged();
|
||||
|
||||
private:
|
||||
void setupWorkspaces();
|
||||
|
@@ -15,12 +15,12 @@
|
||||
namespace QmlDesigner {
|
||||
|
||||
template<typename Timer>
|
||||
class DirectoryPathCompressor
|
||||
class DirectoryPathCompressor final
|
||||
{
|
||||
public:
|
||||
DirectoryPathCompressor() { m_timer.setSingleShot(true); }
|
||||
|
||||
virtual ~DirectoryPathCompressor() = default;
|
||||
~DirectoryPathCompressor() = default;
|
||||
|
||||
void addSourceContextId(SourceContextId sourceContextId)
|
||||
{
|
||||
@@ -34,12 +34,18 @@ public:
|
||||
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)] {
|
||||
callback(takeSourceContextIds());
|
||||
if (connection)
|
||||
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;
|
||||
}
|
||||
|
||||
private:
|
||||
struct ConnectionGuard
|
||||
{
|
||||
~ConnectionGuard()
|
||||
{
|
||||
if (connection)
|
||||
QObject::disconnect(connection);
|
||||
}
|
||||
|
||||
QMetaObject::Connection &connection;
|
||||
};
|
||||
|
||||
private:
|
||||
SourceContextIds m_sourceContextIds;
|
||||
QMetaObject::Connection connection;
|
||||
ConnectionGuard connectionGuard{connection};
|
||||
Timer m_timer;
|
||||
};
|
||||
|
||||
|
@@ -49,16 +49,12 @@ public:
|
||||
&FileSystemWatcher::directoryChanged,
|
||||
[&](const QString &path) { compressChangedDirectoryPath(path); });
|
||||
|
||||
m_directoryPathCompressor.setCallback([&](QmlDesigner::SourceContextIds &&sourceContextIds) {
|
||||
addChangedPathForFilePath(std::move(sourceContextIds));
|
||||
m_directoryPathCompressor.setCallback(
|
||||
[&](const QmlDesigner::SourceContextIds &sourceContextIds) {
|
||||
addChangedPathForFilePath(sourceContextIds);
|
||||
});
|
||||
}
|
||||
|
||||
~ProjectStoragePathWatcher()
|
||||
{
|
||||
m_directoryPathCompressor.setCallback([&](SourceContextIds &&) {});
|
||||
}
|
||||
|
||||
void updateIdPaths(const std::vector<IdPaths> &idPaths) override
|
||||
{
|
||||
const auto &[entires, ids] = convertIdPathsToWatcherEntriesAndIds(idPaths);
|
||||
@@ -324,7 +320,7 @@ public:
|
||||
m_pathCache.sourceContextId(Utils::PathString{path}));
|
||||
}
|
||||
|
||||
WatcherEntries watchedEntriesForPaths(QmlDesigner::SourceContextIds &&sourceContextIds)
|
||||
WatcherEntries watchedEntriesForPaths(const QmlDesigner::SourceContextIds &sourceContextIds)
|
||||
{
|
||||
WatcherEntries foundEntries;
|
||||
foundEntries.reserve(m_watchedEntries.size());
|
||||
@@ -375,10 +371,10 @@ public:
|
||||
return idPaths;
|
||||
}
|
||||
|
||||
void addChangedPathForFilePath(SourceContextIds &&sourceContextIds)
|
||||
void addChangedPathForFilePath(const SourceContextIds &sourceContextIds)
|
||||
{
|
||||
if (m_notifier) {
|
||||
WatcherEntries foundEntries = watchedEntriesForPaths(std::move(sourceContextIds));
|
||||
WatcherEntries foundEntries = watchedEntriesForPaths(sourceContextIds);
|
||||
|
||||
SourceIds watchedSourceIds = watchedPaths(foundEntries);
|
||||
|
||||
|
@@ -248,6 +248,28 @@ QStringList DSStore::collectionNames() const
|
||||
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
|
||||
{
|
||||
return UniqueName::generateTypeName(hint, "Collection", [this](const QString &t) {
|
||||
|
@@ -38,6 +38,8 @@ public:
|
||||
std::optional<Utils::FilePath> moduleDirPath() const;
|
||||
QStringList collectionNames() const;
|
||||
|
||||
ThemeProperty resolvedDSBinding(QStringView binding) const;
|
||||
|
||||
private:
|
||||
QString uniqueCollectionName(const QString &hint) const;
|
||||
std::optional<QString> loadCollection(const QString &typeName, const Utils::FilePath &qmlFilePath);
|
||||
|
@@ -199,7 +199,7 @@ void DSThemeGroup::decorate(ThemeId theme, ModelNode themeNode, bool wrapInGroup
|
||||
for (auto &[propName, values] : m_values) {
|
||||
auto themeValue = values.find(theme);
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
if (data.isBinding) {
|
||||
if (propDefined)
|
||||
if (!createNewProperty)
|
||||
n.bindingProperty(propName).setExpression(data.value.toString());
|
||||
else if (auto bindingProp = n.bindingProperty(propName))
|
||||
bindingProp.setDynamicTypeNameAndExpression(*typeName, data.value.toString());
|
||||
else
|
||||
qCDebug(dsLog) << "Assigning invalid binding" << propName << n.id();
|
||||
} else {
|
||||
if (propDefined)
|
||||
if (!createNewProperty)
|
||||
n.variantProperty(propName).setValue(data.value);
|
||||
else if (auto nodeProp = n.variantProperty(propName))
|
||||
nodeProp.setDynamicTypeNameAndValue(*typeName, data.value);
|
||||
|
@@ -54,7 +54,7 @@ public:
|
||||
std::vector<PropertyName> propertyNames() const;
|
||||
|
||||
private:
|
||||
void addProperty(ModelNode n, PropertyNameView propName, const PropertyData &data) const;
|
||||
void addProperty(ModelNode n, PropertyNameView propName, const PropertyData &data, bool createNewProperty) const;
|
||||
|
||||
private:
|
||||
const GroupType m_type;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user