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

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

View File

@@ -19,7 +19,7 @@
The editor shows found issues as inline annotations if the project matches
the 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}

View File

@@ -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

View File

@@ -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

View File

@@ -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}.

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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}

View File

@@ -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

View File

@@ -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}

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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}

View File

@@ -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}

View File

@@ -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.
@@ -232,7 +232,7 @@
When you run a console application, you can view the output in the console
window of the calling application. If the
calling application is a GUI application (for example, a release-built
version of \QC), a new console window is opened. For this
version of \QC), a new console window is opened. For this
type of application, \c qDebug() and related functions use standard output
and error output.
@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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}

View File

@@ -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
*/

View File

@@ -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}.

View File

@@ -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

View File

@@ -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}.

View File

@@ -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

View File

@@ -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 >

View File

@@ -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).

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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"

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View 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

View File

@@ -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}.

View File

@@ -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),

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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"

View File

@@ -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.

View File

@@ -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.

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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.
*/

View File

@@ -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

View File

@@ -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
.

View File

@@ -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.

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -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
}
/*

View File

@@ -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()
}

View File

@@ -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,47 +174,95 @@ 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
font.family: StudioTheme.Constants.iconFont.family
font.pixelSize: primaryButton.style.baseIconFontSize
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: {
if (root.runManagerState === RunManager.NotRunning)
return primaryButton.press ? primaryButton.style.text.idle
: primaryButton.hover ? "#2eff68" // running green
: "#649a5d" // idle green
else
return primaryButton.press ? primaryButton.style.text.idle
: primaryButton.hover ? "#cc3c34" // recording red
: "#6a4242" // idle red
ProgressCircle {
anchors.centerIn: parent
visible: root.showProgress
indeterminate: root.runManagerState === RunManager.Packing
|| root.runManagerState === RunManager.Starting
value: root.runManagerProgress / 100
}
text: {
if (root.runManagerState === RunManager.NotRunning)
return StudioTheme.Constants.playOutline_medium
else
return StudioTheme.Constants.stop_medium
T.Label {
visible: !root.showProgress
height: primaryButton.height
font.family: StudioTheme.Constants.iconFont.family
font.pixelSize: primaryButton.style.baseIconFontSize
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: {
if (root.runManagerState === RunManager.NotRunning)
return primaryButton.press ? primaryButton.style.text.idle
: primaryButton.hover ? "#2eff68" // running green
: "#649a5d" // idle green
else
return primaryButton.press ? primaryButton.style.text.idle
: primaryButton.hover ? "#cc3c34" // recording red
: "#6a4242" // idle red
}
text: {
if (root.runManagerState === RunManager.NotRunning)
return StudioTheme.Constants.playOutline_medium
else
return StudioTheme.Constants.stop_medium
}
}
}
T.Label {
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) {

View File

@@ -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)

View File

@@ -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

View File

@@ -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;
}
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()));
return false;
if (!runAsync) {
QByteArray stdOut;
QByteArray stdErr;
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 (m_rccProcess.exitStatus() != QProcess::NormalExit || m_rccProcess.exitCode() != 0)
return false;
}
if (!stdOut.trimmed().isEmpty())
Core::MessageManager::writeFlashing(QString::fromLocal8Bit(stdOut));
if (!stdErr.trimmed().isEmpty())
Core::MessageManager::writeFlashing(QString::fromLocal8Bit(stdErr));
if (rccProcess.exitStatus() != QProcess::NormalExit) {
Core::MessageManager::writeDisrupting(Tr::tr("QmlDesigner::GenerateResource", "\"%1\" crashed.")
.arg(rccProcess.commandLine().toUserOutput()));
return false;
}
if (rccProcess.exitCode() != 0) {
Core::MessageManager::writeDisrupting(
Tr::tr("QmlDesigner::GenerateResource", "\"%1\" failed (exit code %2).")
.arg(rccProcess.commandLine().toUserOutput())
.arg(rccProcess.exitCode()));
return false;
}
return true;
}
} // namespace QmlDesigner::ResourceGenerator
void ResourceGenerator::cancel()
{
m_rccProcess.kill();
}
} // namespace QmlDesigner

View File

@@ -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

View File

@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#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) {

View File

@@ -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;

View File

@@ -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);

View File

@@ -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,12 +162,10 @@ DVConnector::DVConnector(QObject *parent)
uploadProject(projectName, resourcePath->toString());
});
connect(&m_resourceGenerator,
&ResourceGeneratorProxy::errorOccurred,
[this](const QString &errorString) {
qCWarning(deploymentPluginLog) << "Error occurred while packing the project";
emit projectPackingFailed(errorString);
});
connect(&m_resourceGenerator, &ResourceGenerator::errorOccurred, [this](const QString &errorString) {
qCWarning(deploymentPluginLog) << "Error occurred while packing the project";
emit projectPackingFailed(errorString);
});
fetchUserInfo();
}
@@ -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();
}

View File

@@ -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
{

View File

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

View File

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

View File

@@ -5,7 +5,8 @@
#include <QJsonDocument>
#include <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;
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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";
};

View File

@@ -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);
if (!retVal) {
qCWarning(deviceSharePluginLog)
<< "UDP:: Failed to bind to UDP port 53452 on" << entry.ip().toString()
<< "on interface" << interface.name()
<< ". Error:" << udpSocket->errorString();
continue;
}
qCDebug(deviceSharePluginLog) << "UDP:: Listening on" << entry.ip().toString()
<< "port" << udpSocket->localPort();
m_udpSockets.append(udpSocket);
}
}
}
QSharedPointer<QUdpSocket> udpSocket = QSharedPointer<QUdpSocket>::create();
bool retVal = udpSocket->bind(QHostAddress::AnyIPv4, 53452, QUdpSocket::ShareAddress);
if (!retVal) {
qCWarning(deviceSharePluginLog) << "UDP:: Failed to bind to UDP port 53452 on AnyIPv4"
<< ". Error:" << udpSocket->errorString();
return;
}
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()

View File

@@ -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(),
const DeviceSettings &deviceSettings = DeviceSettings());
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

View File

@@ -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 {

View File

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

View File

@@ -8,9 +8,7 @@
#include <qmldesigner/qmldesignerplugin.h>
#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

View File

@@ -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

View File

@@ -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()
{

View File

@@ -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();

View File

@@ -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;
};

View File

@@ -49,14 +49,10 @@ public:
&FileSystemWatcher::directoryChanged,
[&](const QString &path) { compressChangedDirectoryPath(path); });
m_directoryPathCompressor.setCallback([&](QmlDesigner::SourceContextIds &&sourceContextIds) {
addChangedPathForFilePath(std::move(sourceContextIds));
});
}
~ProjectStoragePathWatcher()
{
m_directoryPathCompressor.setCallback([&](SourceContextIds &&) {});
m_directoryPathCompressor.setCallback(
[&](const QmlDesigner::SourceContextIds &sourceContextIds) {
addChangedPathForFilePath(sourceContextIds);
});
}
void updateIdPaths(const std::vector<IdPaths> &idPaths) override
@@ -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);

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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);

View File

@@ -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